From 3214211a06571b98fc08adb2df0231db9363af37 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 30 Oct 2020 11:05:36 +0100 Subject: [PATCH] Refactor of Synth and Voice into a pimpl pattern --- src/sfizz/Synth.cpp | 1555 +++++++++++------ src/sfizz/Synth.h | 435 +---- src/sfizz/Voice.cpp | 1202 +++++++++---- src/sfizz/Voice.h | 276 +-- src/sfizz/VoiceList.h | 68 + .../modulations/sources/ADSREnvelope.cpp | 19 +- src/sfizz/modulations/sources/ADSREnvelope.h | 7 +- .../modulations/sources/FlexEnvelope.cpp | 13 +- src/sfizz/modulations/sources/FlexEnvelope.h | 5 +- src/sfizz/modulations/sources/LFO.cpp | 10 +- src/sfizz/modulations/sources/LFO.h | 6 +- tests/PolyphonyT.cpp | 4 + tests/SynthT.cpp | 13 +- 13 files changed, 1955 insertions(+), 1658 deletions(-) create mode 100644 src/sfizz/VoiceList.h diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 271e1e3e..43fac9bb 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -4,120 +4,469 @@ // license. You should have receive a LICENSE.md file along with the code. // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz -#include "Synth.h" -#include "Config.h" -#include "Debug.h" -#include "Macros.h" -#include "MidiState.h" -#include "TriggerEvent.h" -#include "ModifierHelpers.h" -#include "ScopedFTZ.h" -#include "StringViewHelpers.h" -#include "modulations/ModMatrix.h" -#include "modulations/ModKey.h" -#include "modulations/ModId.h" -#include "modulations/sources/Controller.h" -#include "modulations/sources/LFO.h" -#include "modulations/sources/FlexEnvelope.h" -#include "modulations/sources/ADSREnvelope.h" -#include "utility/XmlHelpers.h" -#include "pugixml.hpp" #include "absl/algorithm/container.h" #include "absl/memory/memory.h" #include "absl/strings/str_replace.h" +#include "Config.h" +#include "Debug.h" +#include "Effects.h" +#include "Macros.h" +#include "ModifierHelpers.h" +#include "modulations/ModId.h" +#include "modulations/ModKey.h" +#include "modulations/ModMatrix.h" +#include "modulations/sources/ADSREnvelope.h" +#include "modulations/sources/Controller.h" +#include "modulations/sources/FlexEnvelope.h" +#include "modulations/sources/LFO.h" +#include "PolyphonyGroup.h" +#include "pugixml.hpp" +#include "RegionSet.h" +#include "Resources.h" +#include "ScopedFTZ.h" #include "SisterVoiceRing.h" +#include "StringViewHelpers.h" +#include "Synth.h" +#include "TriggerEvent.h" +#include "utility/SpinMutex.h" +#include "utility/XmlHelpers.h" +#include "VoiceList.h" +#include "VoiceStealing.h" +#include +#include #include #include #include +#include #include -sfz::Synth::Synth() - : Synth(config::numVoices) +namespace sfz { + +struct Synth::Impl : public Voice::StateListener, public Parser::Listener { + Impl(); + ~Impl(); + + /** + * @brief The voice callback which is called during a change of state. + */ + void onVoiceStateChanged(NumericId idNumber, Voice::State state) final; + + /** + * @brief The parser callback; this is called by the parent object each time + * a new region, group, master, global, curve or control set of opcodes + * appears in the parser + * + * @param header the header for the set of opcodes + * @param members the opcode members + */ + void onParseFullBlock(const std::string& header, const std::vector& members) final; + + /** + * @brief The parser callback when an error occurs. + */ + void onParseError(const SourceRange& range, const std::string& message) final; + + /** + * @brief The parser callback when a warning occurs. + */ + void onParseWarning(const SourceRange& range, const std::string& message) final; + + + /** + * @brief change the group maximum polyphony + * + * @param groupIdx the group index + * @param polyphone the max polyphony + */ + void setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept; + + /** + * @brief Reset all CCs; to be used on CC 121 + * + * @param delay the delay for the controller reset + * + */ + void resetAllControllers(int delay) noexcept; + + /** + * @brief Remove all regions, resets all voices and clears everything + * to bring back the synth in its original state. + * + * The callback mutex should be taken to call this function. + */ + void clear(); + + /** + * @brief Helper function to dispatch opcodes + * + * @param members the opcodes of the block + */ + void handleGlobalOpcodes(const std::vector& members); + /** + * @brief Helper function to dispatch opcodes + * + * @param members the opcodes of the block + */ + void handleMasterOpcodes(const std::vector& members); + /** + * @brief Helper function to dispatch opcodes + * + * @param members the opcodes of the block + */ + void handleGroupOpcodes(const std::vector& members, const std::vector& masterMembers); + /** + * @brief Helper function to dispatch opcodes + * + * @param members the opcodes of the block + */ + void handleControlOpcodes(const std::vector& members); + /** + * @brief Helper function to dispatch opcodes + * + * @param members the opcodes of the block + */ + void handleEffectOpcodes(const std::vector& members); + /** + * @brief Helper function to merge all the currently active opcodes + * as set by the successive callbacks and create a new region to store + * in the synth. + * + * @param regionOpcodes the opcodes that are specific to the region + */ + void buildRegion(const std::vector& regionOpcodes); + /** + * @brief Resets and possibly changes the number of voices (polyphony) in + * the synth. + * + * @param numVoices + */ + void resetVoices(int numVoices); + /** + * @brief Make the stored settings take effect in all the voices + */ + void applySettingsPerVoice(); + + /** + * @brief Establish all connections of the modulation matrix. + */ + void setupModMatrix(); + + /** + * @brief Get the modification time of all included sfz files + * + * @return fs::file_time_type + */ + fs::file_time_type checkModificationTime(); + + /** + * @brief Check all regions and start voices for note on events + * + * @param delay + * @param noteNumber + * @param velocity + */ + void noteOnDispatch(int delay, int noteNumber, float velocity) noexcept; + + /** + * @brief Check all regions and start voices for note off events + * + * @param delay + * @param noteNumber + * @param velocity + */ + void noteOffDispatch(int delay, int noteNumber, float velocity) noexcept; + + /** + * @brief Check all regions and start voices for cc events + * + * @param delay + * @param ccNumber + * @param value + */ + void ccDispatch(int delay, int ccNumber, float value) noexcept; + + /** + * @brief Find a voice that is not currently playing + * + * @return Voice* + */ + + Voice* findFreeVoice() noexcept; + + /** + * @brief Check the region polyphony, releasing voices if necessary + * + * @param region + * @param delay + */ + void checkRegionPolyphony(const Region* region, int delay) noexcept; + + /** + * @brief Check the note polyphony, releasing voices if necessary + * + * @param region + * @param delay + * @param triggerEvent + */ + void checkNotePolyphony(const Region* region, int delay, const TriggerEvent& triggerEvent) noexcept; + + /** + * @brief Check the group polyphony, releasing voices if necessary + * + * @param region + * @param delay + */ + void checkGroupPolyphony(const Region* region, int delay) noexcept; + + /** + * @brief Check the region set polyphony at all levels, releasing voices if necessary + * + * @param region + * @param delay + */ + void checkSetPolyphony(const Region* region, int delay) noexcept; + + /** + * @brief Check the engine polyphony, fast releasing voices if necessary + * + * @param delay + */ + void checkEnginePolyphony(int delay) noexcept; + + /** + * @brief Start a voice for a specific region. + * This will do the needed polyphony checks and voice stealing. + * + * @param region + * @param delay + * @param triggerEvent + * @param ring + */ + void startVoice(Region* region, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept; + + /** + * @brief Start all delayed release voices of the region if necessary + * + * @param region + * @param delay + * @param ring + */ + void startDelayedReleaseVoices(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept; + + /** + * @brief Check if a playing voice matches the release region + * + * @param releaseRegion + * @return true + * @return false + */ + bool playingAttackVoice(const Region* releaseRegion) noexcept; + + /** + * @brief Finalize SFZ loading, following a successful execution of the + * parsing step. + */ + void finalizeSfzLoad(); + + template + static void updateUsedCCsFromCCMap(std::bitset& usedCCs, const CCMap map) noexcept + { + for (auto& mod : map) + usedCCs[mod.cc] = true; + } + + static void updateUsedCCsFromRegion(std::bitset& usedCCs, const Region& region); + static void updateUsedCCsFromModulations(std::bitset& usedCCs, const ModMatrix& mm); + + int numGroups_ { 0 }; + int numMasters_ { 0 }; + + // Opcode memory; these are used to build regions, as a new region + // will integrate opcodes from the group, master and global block + std::vector globalOpcodes_; + std::vector masterOpcodes_; + std::vector groupOpcodes_; + + // Names for the CC and notes as set by label_cc and label_key + std::vector ccLabels_; + std::vector keyLabels_; + std::vector keyswitchLabels_; + + // Set as sw_default if present in the file + absl::optional currentSwitch_; + std::vector unknownOpcodes_; + using RegionViewVector = std::vector; + using VoiceViewVector = std::vector; + using RegionPtr = std::unique_ptr; + using RegionSetPtr = std::unique_ptr; + std::vector regions_; + VoiceList voiceList_; + + // These are more general "groups" than sfz and encapsulates the full hierarchy + RegionSet* currentSet_ { nullptr }; + std::vector sets_; + // This region set holds the engine set of voices, which tries to respect the required + // engine polyphony + RegionSetPtr engineSet_; + + // These are the `group=` groups where you can off voices + std::vector polyphonyGroups_; + + // Views to speed up iteration over the regions and voices when events + // occur in the audio callback + VoiceViewVector tempPolyphonyArray_; + VoiceViewVector voiceViewArray_; + VoiceStealing stealer_; + + std::array lastKeyswitchLists_; + std::array downKeyswitchLists_; + std::array upKeyswitchLists_; + RegionViewVector previousKeyswitchLists_; + std::array noteActivationLists_; + std::array ccActivationLists_; + + // Effect factory and buses + EffectFactory effectFactory_; + typedef std::unique_ptr EffectBusPtr; + std::vector effectBuses_; // 0 is "main", 1-N are "fx1"-"fxN" + + int samplesPerBlock_ { config::defaultSamplesPerBlock }; + float sampleRate_ { config::defaultSampleRate }; + float volume_ { Default::globalVolume }; + int numRequiredVoices_ { config::numVoices }; + int numActualVoices_ { static_cast(config::numVoices * config::overflowVoiceMultiplier) }; + int activeVoices_ { 0 }; + Oversampling oversamplingFactor_ { config::defaultOversamplingFactor }; + + // Distribution used to generate random value for the *rand opcodes + std::uniform_real_distribution randNoteDistribution_ { 0, 1 }; + + SpinMutex callbackGuard_; + + // Singletons passed as references to the voices + Resources resources_; + + // Control opcodes + std::string defaultPath_ { "" }; + int noteOffset_ { 0 }; + int octaveOffset_ { 0 }; + + // Modulation source generators + std::unique_ptr genController_; + std::unique_ptr genLFO_; + std::unique_ptr genFlexEnvelope_; + std::unique_ptr genADSREnvelope_; + + // Settings per voice + struct SettingsPerVoice { + size_t maxFilters { 0 }; + size_t maxEQs { 0 }; + size_t maxLFOs { 0 }; + size_t maxFlexEGs { 0 }; + bool havePitchEG { false }; + bool haveFilterEG { false }; + }; + SettingsPerVoice settingsPerVoice_; + + Duration dispatchDuration_ { 0 }; + + std::chrono::time_point lastGarbageCollection_; + + Parser parser_; + fs::file_time_type modificationTime_ { }; +}; + +Synth::Synth() +: impl_(new Impl) { } -sfz::Synth::Synth(int numVoices) +// Need to define the dtor after Impl has been defined +Synth::~Synth() +{ + +} + +Synth::Impl::Impl() { initializeSIMDDispatchers(); - const std::lock_guard disableCallback { callbackGuard }; - engineSet = absl::make_unique(nullptr, OpcodeScope::kOpcodeScopeGeneric); - parser.setListener(this); - effectFactory.registerStandardEffectTypes(); - effectBuses.reserve(5); // sufficient room for main and fx1-4 - resetVoices(numVoices); + const std::lock_guard disableCallback { callbackGuard_ }; + engineSet_ = absl::make_unique(nullptr, OpcodeScope::kOpcodeScopeGeneric); + parser_.setListener(this); + effectFactory_.registerStandardEffectTypes(); + effectBuses_.reserve(5); // sufficient room for main and fx1-4 + resetVoices(config::numVoices); // modulation sources - genController.reset(new ControllerSource(resources)); - genLFO.reset(new LFOSource(*this)); - genFlexEnvelope.reset(new FlexEnvelopeSource(*this)); - genADSREnvelope.reset(new ADSREnvelopeSource(*this)); + genController_.reset(new ControllerSource(resources_)); + genLFO_.reset(new LFOSource(voiceList_)); + genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceList_)); + genADSREnvelope_.reset(new ADSREnvelopeSource(voiceList_, resources_.midiState)); } -sfz::Synth::~Synth() +Synth::Impl::~Impl() { - const std::lock_guard disableCallback { callbackGuard }; + const std::lock_guard disableCallback { callbackGuard_ }; - for (auto& voice : voices) - voice->reset(); - - resources.filePool.emptyFileLoadingQueues(); + voiceList_.reset(); + resources_.filePool.emptyFileLoadingQueues(); } -void sfz::Synth::onVoiceStateChanged(NumericId id, Voice::State state) +void Synth::Impl::onVoiceStateChanged(NumericId id, Voice::State state) { (void)id; (void)state; if (state == Voice::State::idle) { - auto voice = getVoiceById(id); + auto voice = voiceList_.getVoiceById(id); RegionSet::removeVoiceFromHierarchy(voice->getRegion(), voice); - engineSet->removeVoice(voice); - polyphonyGroups[voice->getRegion()->group].removeVoice(voice); + engineSet_->removeVoice(voice); + polyphonyGroups_[voice->getRegion()->group].removeVoice(voice); } } -void sfz::Synth::onParseFullBlock(const std::string& header, const std::vector& members) +void Synth::Impl::onParseFullBlock(const std::string& header, const std::vector& members) { const auto newRegionSet = [&](OpcodeScope level) { - auto parent = currentSet; + auto parent = currentSet_; while (parent && parent->getLevel() >= level) parent = parent->getParent(); - sets.emplace_back(new RegionSet(parent, level)); - currentSet = sets.back().get(); + sets_.emplace_back(new RegionSet(parent, level)); + currentSet_ = sets_.back().get(); }; switch (hash(header)) { case hash("global"): - globalOpcodes = members; + globalOpcodes_ = members; newRegionSet(OpcodeScope::kOpcodeScopeGlobal); - groupOpcodes.clear(); - masterOpcodes.clear(); + groupOpcodes_.clear(); + masterOpcodes_.clear(); handleGlobalOpcodes(members); break; case hash("control"): - defaultPath = ""; // Always reset on a new control header + defaultPath_ = ""; // Always reset on a new control header handleControlOpcodes(members); break; case hash("master"): - masterOpcodes = members; + masterOpcodes_ = members; newRegionSet(OpcodeScope::kOpcodeScopeMaster); - groupOpcodes.clear(); + groupOpcodes_.clear(); handleMasterOpcodes(members); - numMasters++; + numMasters_++; break; case hash("group"): - groupOpcodes = members; + groupOpcodes_ = members; newRegionSet(OpcodeScope::kOpcodeScopeGroup); - handleGroupOpcodes(members, masterOpcodes); - numGroups++; + handleGroupOpcodes(members, masterOpcodes_); + numGroups_++; break; case hash("region"): buildRegion(members); break; case hash("curve"): - resources.curves.addCurveFromHeader(members); + resources_.curves.addCurveFromHeader(members); break; case hash("effect"): handleEffectOpcodes(members); @@ -127,39 +476,39 @@ void sfz::Synth::onParseFullBlock(const std::string& header, const std::vectorlexically_relative(parser.originalDirectory()); + const auto relativePath = range.start.filePath->lexically_relative(parser_.originalDirectory()); std::cerr << "Parse error in " << relativePath << " at line " << range.start.lineNumber + 1 << ": " << message << '\n'; } -void sfz::Synth::onParseWarning(const SourceRange& range, const std::string& message) +void Synth::Impl::onParseWarning(const SourceRange& range, const std::string& message) { - const auto relativePath = range.start.filePath->lexically_relative(parser.originalDirectory()); + const auto relativePath = range.start.filePath->lexically_relative(parser_.originalDirectory()); std::cerr << "Parse warning in " << relativePath << " at line " << range.start.lineNumber + 1 << ": " << message << '\n'; } -void sfz::Synth::buildRegion(const std::vector& regionOpcodes) +void Synth::Impl::buildRegion(const std::vector& regionOpcodes) { - int regionNumber = static_cast(regions.size()); - auto lastRegion = absl::make_unique(regionNumber, resources.midiState, defaultPath); + int regionNumber = static_cast(regions_.size()); + auto lastRegion = absl::make_unique(regionNumber, resources_.midiState, defaultPath_); // auto parseOpcodes = [&](const std::vector& opcodes) { for (auto& opcode : opcodes) { - const auto unknown = absl::c_find_if(unknownOpcodes, [&](absl::string_view sv) { return sv.compare(opcode.opcode) == 0; }); - if (unknown != unknownOpcodes.end()) { + const auto unknown = absl::c_find_if(unknownOpcodes_, [&](absl::string_view sv) { return sv.compare(opcode.opcode) == 0; }); + if (unknown != unknownOpcodes_.end()) { continue; } if (!lastRegion->parseOpcode(opcode)) - unknownOpcodes.emplace_back(opcode.opcode); + unknownOpcodes_.emplace_back(opcode.opcode); } }; - parseOpcodes(globalOpcodes); - parseOpcodes(masterOpcodes); - parseOpcodes(groupOpcodes); + parseOpcodes(globalOpcodes_); + parseOpcodes(masterOpcodes_); + parseOpcodes(groupOpcodes_); parseOpcodes(regionOpcodes); // Create the amplitude envelope @@ -172,137 +521,135 @@ void sfz::Synth::buildRegion(const std::vector& regionOpcodes) ModKey::createNXYZ(ModId::Envelope, lastRegion->id, *lastRegion->flexAmpEG), ModKey::createNXYZ(ModId::MasterAmplitude, lastRegion->id)).sourceDepth = 1.0f; - if (octaveOffset != 0 || noteOffset != 0) - lastRegion->offsetAllKeys(octaveOffset * 12 + noteOffset); + if (octaveOffset_ != 0 || noteOffset_ != 0) + lastRegion->offsetAllKeys(octaveOffset_ * 12 + noteOffset_); if (lastRegion->lastKeyswitch) - lastKeyswitchLists[*lastRegion->lastKeyswitch].push_back(lastRegion.get()); + lastKeyswitchLists_[*lastRegion->lastKeyswitch].push_back(lastRegion.get()); if (lastRegion->lastKeyswitchRange) { auto& range = *lastRegion->lastKeyswitchRange; for (uint8_t note = range.getStart(), end = range.getEnd(); note <= end; note++) - lastKeyswitchLists[note].push_back(lastRegion.get()); + lastKeyswitchLists_[note].push_back(lastRegion.get()); } if (lastRegion->upKeyswitch) - upKeyswitchLists[*lastRegion->upKeyswitch].push_back(lastRegion.get()); + upKeyswitchLists_[*lastRegion->upKeyswitch].push_back(lastRegion.get()); if (lastRegion->downKeyswitch) - downKeyswitchLists[*lastRegion->downKeyswitch].push_back(lastRegion.get()); + downKeyswitchLists_[*lastRegion->downKeyswitch].push_back(lastRegion.get()); if (lastRegion->previousKeyswitch) - previousKeyswitchLists.push_back(lastRegion.get()); + previousKeyswitchLists_.push_back(lastRegion.get()); if (lastRegion->defaultSwitch) - currentSwitch = *lastRegion->defaultSwitch; + currentSwitch_ = *lastRegion->defaultSwitch; // There was a combination of group= and polyphony= on a region, so set the group polyphony if (lastRegion->group != Default::group && lastRegion->polyphony != config::maxVoices) setGroupPolyphony(lastRegion->group, lastRegion->polyphony); - if (currentSet != nullptr) { - lastRegion->parent = currentSet; - currentSet->addRegion(lastRegion.get()); + if (currentSet_ != nullptr) { + lastRegion->parent = currentSet_; + currentSet_->addRegion(lastRegion.get()); } // Adapt the size of the delayed releases to avoid allocating later on lastRegion->delayedReleases.reserve(lastRegion->keyRange.length()); - regions.push_back(std::move(lastRegion)); + regions_.push_back(std::move(lastRegion)); } -void sfz::Synth::clear() +void Synth::Impl::clear() { // Clear the background queues before removing everyone - resources.filePool.waitForBackgroundLoading(); + resources_.filePool.waitForBackgroundLoading(); - for (auto& voice : voices) - voice->reset(); - for (auto& list : lastKeyswitchLists) + voiceList_.reset(); + for (auto& list : lastKeyswitchLists_) list.clear(); - for (auto& list : downKeyswitchLists) + for (auto& list : downKeyswitchLists_) list.clear(); - for (auto& list : upKeyswitchLists) + for (auto& list : upKeyswitchLists_) list.clear(); - for (auto& list : noteActivationLists) + for (auto& list : noteActivationLists_) list.clear(); - for (auto& list : ccActivationLists) + for (auto& list : ccActivationLists_) list.clear(); - previousKeyswitchLists.clear(); + previousKeyswitchLists_.clear(); - currentSet = nullptr; - sets.clear(); - regions.clear(); - effectBuses.clear(); - effectBuses.emplace_back(new EffectBus); - effectBuses[0]->setGainToMain(1.0); - effectBuses[0]->setSamplesPerBlock(samplesPerBlock); - effectBuses[0]->setSampleRate(sampleRate); - effectBuses[0]->clearInputs(samplesPerBlock); - resources.clear(); - numGroups = 0; - numMasters = 0; - currentSwitch = absl::nullopt; - defaultPath = ""; - resources.midiState.reset(); - resources.filePool.clear(); - resources.filePool.setRamLoading(config::loadInRam); - stealer.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::Oldest); - ccLabels.clear(); - keyLabels.clear(); - keyswitchLabels.clear(); - globalOpcodes.clear(); - masterOpcodes.clear(); - groupOpcodes.clear(); - unknownOpcodes.clear(); - polyphonyGroups.clear(); - polyphonyGroups.emplace_back(); - polyphonyGroups.back().setPolyphonyLimit(config::maxVoices); - modificationTime = fs::file_time_type::min(); + currentSet_ = nullptr; + sets_.clear(); + regions_.clear(); + effectBuses_.clear(); + effectBuses_.emplace_back(new EffectBus); + effectBuses_[0]->setGainToMain(1.0); + effectBuses_[0]->setSamplesPerBlock(samplesPerBlock_); + effectBuses_[0]->setSampleRate(sampleRate_); + effectBuses_[0]->clearInputs(samplesPerBlock_); + resources_.clear(); + numGroups_ = 0; + numMasters_ = 0; + currentSwitch_ = absl::nullopt; + defaultPath_ = ""; + resources_.midiState.reset(); + resources_.filePool.clear(); + resources_.filePool.setRamLoading(config::loadInRam); + stealer_.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::Oldest); + ccLabels_.clear(); + keyLabels_.clear(); + keyswitchLabels_.clear(); + globalOpcodes_.clear(); + masterOpcodes_.clear(); + groupOpcodes_.clear(); + unknownOpcodes_.clear(); + polyphonyGroups_.clear(); + polyphonyGroups_.emplace_back(); + polyphonyGroups_.back().setPolyphonyLimit(config::maxVoices); + modificationTime_ = fs::file_time_type::min(); // set default controllers - fill(absl::MakeSpan(ccInitialValues), 0.0f); - initCc(7, 100); // volume - initHdcc(10, 0.5f); // pan - initHdcc(11, 1.0f); // expression + resources_.midiState.ccEvent(0, 7, normalizeCC(100)); // volume + resources_.midiState.ccEvent(0, 10, 0.5f); // pan + resources_.midiState.ccEvent(0, 11, 1.0f); // expression // set default controller labels - insertPairUniquely(ccLabels, 7, "Volume"); - insertPairUniquely(ccLabels, 10, "Pan"); - insertPairUniquely(ccLabels, 11, "Expression"); + insertPairUniquely(ccLabels_, 7, "Volume"); + insertPairUniquely(ccLabels_, 10, "Pan"); + insertPairUniquely(ccLabels_, 11, "Expression"); } -void sfz::Synth::handleMasterOpcodes(const std::vector& members) +void Synth::Impl::handleMasterOpcodes(const std::vector& members) { for (auto& rawMember : members) { const Opcode member = rawMember.cleanUp(kOpcodeScopeMaster); switch (member.lettersOnlyHash) { case hash("polyphony"): - ASSERT(currentSet != nullptr); + ASSERT(currentSet_ != nullptr); if (auto value = readOpcode(member.value, Default::polyphonyRange)) - currentSet->setPolyphonyLimit(*value); + currentSet_->setPolyphonyLimit(*value); break; case hash("sw_default"): - setValueFromOpcode(member, currentSwitch, Default::keyRange); + setValueFromOpcode(member, currentSwitch_, Default::keyRange); break; } } } -void sfz::Synth::handleGlobalOpcodes(const std::vector& members) +void Synth::Impl::handleGlobalOpcodes(const std::vector& members) { for (auto& rawMember : members) { const Opcode member = rawMember.cleanUp(kOpcodeScopeGlobal); switch (member.lettersOnlyHash) { case hash("polyphony"): - ASSERT(currentSet != nullptr); + ASSERT(currentSet_ != nullptr); if (auto value = readOpcode(member.value, Default::polyphonyRange)) - currentSet->setPolyphonyLimit(*value); + currentSet_->setPolyphonyLimit(*value); break; case hash("sw_default"): - setValueFromOpcode(member, currentSwitch, Default::keyRange); + setValueFromOpcode(member, currentSwitch_, Default::keyRange); break; case hash("volume"): // FIXME : Probably best not to mess with this and let the host control the volume @@ -312,7 +659,7 @@ void sfz::Synth::handleGlobalOpcodes(const std::vector& members) } } -void sfz::Synth::handleGroupOpcodes(const std::vector& members, const std::vector& masterMembers) +void Synth::Impl::handleGroupOpcodes(const std::vector& members, const std::vector& masterMembers) { absl::optional groupIdx; absl::optional maxPolyphony; @@ -328,7 +675,7 @@ void sfz::Synth::handleGroupOpcodes(const std::vector& members, const st setValueFromOpcode(member, maxPolyphony, Default::polyphonyRange); break; case hash("sw_default"): - setValueFromOpcode(member, currentSwitch, Default::keyRange); + setValueFromOpcode(member, currentSwitch_, Default::keyRange); break; } }; @@ -342,14 +689,14 @@ void sfz::Synth::handleGroupOpcodes(const std::vector& members, const st if (groupIdx && maxPolyphony) { setGroupPolyphony(*groupIdx, *maxPolyphony); } else if (maxPolyphony) { - ASSERT(currentSet != nullptr); - currentSet->setPolyphonyLimit(*maxPolyphony); - } else if (groupIdx && *groupIdx > polyphonyGroups.size()) { + ASSERT(currentSet_ != nullptr); + currentSet_->setPolyphonyLimit(*maxPolyphony); + } else if (groupIdx && *groupIdx > polyphonyGroups_.size()) { setGroupPolyphony(*groupIdx, config::maxVoices); } } -void sfz::Synth::handleControlOpcodes(const std::vector& members) +void Synth::Impl::handleControlOpcodes(const std::vector& members) { for (auto& rawMember : members) { const Opcode member = rawMember.cleanUp(kOpcodeScopeControl); @@ -359,63 +706,65 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) { const auto ccValue = readOpcode(member.value, Default::midi7Range); if (ccValue) - initCc(member.parameters.back(), *ccValue); + resources_.midiState.ccEvent( + 0, member.parameters.back(), normalizeCC(*ccValue)); } break; case hash("set_hdcc&"): if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) { const auto ccValue = readOpcode(member.value, Default::normalizedRange); if (ccValue) - initHdcc(member.parameters.back(), *ccValue); + resources_.midiState.ccEvent( + 0, member.parameters.back(), *ccValue); } break; case hash("label_cc&"): if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) - insertPairUniquely(ccLabels, member.parameters.back(), std::string(member.value)); + insertPairUniquely(ccLabels_, member.parameters.back(), std::string(member.value)); break; case hash("label_key&"): if (member.parameters.back() <= Default::keyRange.getEnd()) { const auto noteNumber = static_cast(member.parameters.back()); - insertPairUniquely(keyLabels, noteNumber, std::string(member.value)); + insertPairUniquely(keyLabels_, noteNumber, std::string(member.value)); } break; case hash("default_path"): - defaultPath = absl::StrReplaceAll(trim(member.value), { { "\\", "/" } }); - DBG("Changing default sample path to " << defaultPath); + defaultPath_ = absl::StrReplaceAll(trim(member.value), { { "\\", "/" } }); + DBG("Changing default sample path to " << defaultPath_); break; case hash("note_offset"): - setValueFromOpcode(member, noteOffset, Default::noteOffsetRange); + setValueFromOpcode(member, noteOffset_, Default::noteOffsetRange); break; case hash("octave_offset"): - setValueFromOpcode(member, octaveOffset, Default::octaveOffsetRange); + setValueFromOpcode(member, octaveOffset_, Default::octaveOffsetRange); break; case hash("hint_ram_based"): if (member.value == "1") - resources.filePool.setRamLoading(true); + resources_.filePool.setRamLoading(true); else if (member.value == "0") - resources.filePool.setRamLoading(false); + resources_.filePool.setRamLoading(false); else DBG("Unsupported value for hint_ram_based: " << member.value); break; case hash("hint_stealing"): switch(hash(member.value)) { case hash("first"): - for (auto& voice : voices) - voice->disablePowerFollower(); + for (auto& voice : voiceList_) + voice.disablePowerFollower(); - stealer.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::First); + stealer_.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::First); break; case hash("oldest"): - for (auto& voice : voices) - voice->disablePowerFollower(); + for (auto& voice : voiceList_) + voice.disablePowerFollower(); - stealer.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::Oldest); + stealer_.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::Oldest); break; case hash("envelope_and_age"): - for (auto& voice : voices) - voice->enablePowerFollower(); + for (auto& voice : voiceList_) + voice.enablePowerFollower(); - stealer.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::EnvelopeAndAge); + stealer_.setStealingAlgorithm(VoiceStealing::StealingAlgorithm::EnvelopeAndAge); break; default: DBG("Unsupported value for hint_stealing: " << member.value); @@ -428,19 +777,19 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) } } -void sfz::Synth::handleEffectOpcodes(const std::vector& rawMembers) +void Synth::Impl::handleEffectOpcodes(const std::vector& rawMembers) { absl::string_view busName = "main"; auto getOrCreateBus = [this](unsigned index) -> EffectBus& { - if (index + 1 > effectBuses.size()) - effectBuses.resize(index + 1); - EffectBusPtr& bus = effectBuses[index]; + if (index + 1 > effectBuses_.size()) + effectBuses_.resize(index + 1); + EffectBusPtr& bus = effectBuses_[index]; if (!bus) { bus.reset(new EffectBus); - bus->setSampleRate(sampleRate); - bus->setSamplesPerBlock(samplesPerBlock); - bus->clearInputs(samplesPerBlock); + bus->setSampleRate(sampleRate_); + bus->setSamplesPerBlock(samplesPerBlock_); + bus->clearInputs(samplesPerBlock_); } return *bus; }; @@ -491,61 +840,63 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& rawMembers) // create the effect and add it EffectBus& bus = getOrCreateBus(busIndex); - auto fx = effectFactory.makeEffect(members); - fx->setSampleRate(sampleRate); - fx->setSamplesPerBlock(samplesPerBlock); + auto fx = effectFactory_.makeEffect(members); + fx->setSampleRate(sampleRate_); + fx->setSamplesPerBlock(samplesPerBlock_); bus.addEffect(std::move(fx)); } -bool sfz::Synth::loadSfzFile(const fs::path& file) +bool Synth::loadSfzFile(const fs::path& file) { - const std::lock_guard disableCallback { callbackGuard }; + Impl& impl = *impl_; + const std::lock_guard disableCallback { impl.callbackGuard_ }; - clear(); + impl.clear(); std::error_code ec; fs::path realFile = fs::canonical(file, ec); - parser.parseFile(ec ? file : realFile); - if (parser.getErrorCount() > 0) + impl.parser_.parseFile(ec ? file : realFile); + if (impl.parser_.getErrorCount() > 0) return false; - if (regions.empty()) + if (impl.regions_.empty()) return false; - finalizeSfzLoad(); + impl.finalizeSfzLoad(); return true; } -bool sfz::Synth::loadSfzString(const fs::path& path, absl::string_view text) +bool Synth::loadSfzString(const fs::path& path, absl::string_view text) { - const std::lock_guard disableCallback { callbackGuard }; + Impl& impl = *impl_; + const std::lock_guard disableCallback { impl.callbackGuard_ }; - clear(); + impl.clear(); - parser.parseString(path, text); - if (parser.getErrorCount() > 0) + impl.parser_.parseString(path, text); + if (impl.parser_.getErrorCount() > 0) return false; - if (regions.empty()) + if (impl.regions_.empty()) return false; - finalizeSfzLoad(); + impl.finalizeSfzLoad(); return true; } -void sfz::Synth::finalizeSfzLoad() +void Synth::Impl::finalizeSfzLoad() { - resources.filePool.setRootDirectory(parser.originalDirectory()); + resources_.filePool.setRootDirectory(parser_.originalDirectory()); size_t currentRegionIndex = 0; - size_t currentRegionCount = regions.size(); + size_t currentRegionCount = regions_.size(); auto removeCurrentRegion = [this, ¤tRegionIndex, ¤tRegionCount]() { - DBG("Removing the region with sample " << *regions[currentRegionIndex]->sampleId); - regions.erase(regions.begin() + currentRegionIndex); + DBG("Removing the region with sample " << *regions_[currentRegionIndex]->sampleId); + regions_.erase(regions_.begin() + currentRegionIndex); --currentRegionCount; }; @@ -559,17 +910,17 @@ void sfz::Synth::finalizeSfzLoad() FlexEGs::clearUnusedCurves(); while (currentRegionIndex < currentRegionCount) { - auto region = regions[currentRegionIndex].get(); + auto region = regions_[currentRegionIndex].get(); absl::optional fileInformation; if (!region->isGenerator()) { - if (!resources.filePool.checkSampleId(*region->sampleId)) { + if (!resources_.filePool.checkSampleId(*region->sampleId)) { removeCurrentRegion(); continue; } - fileInformation = resources.filePool.getFileInformation(*region->sampleId); + fileInformation = resources_.filePool.getFileInformation(*region->sampleId); if (!fileInformation) { removeCurrentRegion(); continue; @@ -613,56 +964,56 @@ void sfz::Synth::finalizeSfzLoad() return Default::offsetCCRange.clamp(sumOffsetCC); }(); - if (!resources.filePool.preloadFile(*region->sampleId, maxOffset)) + if (!resources_.filePool.preloadFile(*region->sampleId, maxOffset)) removeCurrentRegion(); } else if (!region->isGenerator()) { - if (!resources.wavePool.createFileWave(resources.filePool, std::string(region->sampleId->filename()))) { + if (!resources_.wavePool.createFileWave(resources_.filePool, std::string(region->sampleId->filename()))) { removeCurrentRegion(); continue; } } if (region->lastKeyswitch) { - if (currentSwitch) - region->keySwitched = (*currentSwitch == *region->lastKeyswitch); + if (currentSwitch_) + region->keySwitched = (*currentSwitch_ == *region->lastKeyswitch); if (region->keyswitchLabel) - insertPairUniquely(keyswitchLabels, *region->lastKeyswitch, *region->keyswitchLabel); + insertPairUniquely(keyswitchLabels_, *region->lastKeyswitch, *region->keyswitchLabel); } if (region->lastKeyswitchRange) { auto& range = *region->lastKeyswitchRange; - if (currentSwitch) - region->keySwitched = range.containsWithEnd(*currentSwitch); + if (currentSwitch_) + region->keySwitched = range.containsWithEnd(*currentSwitch_); if (region->keyswitchLabel) { for (uint8_t note = range.getStart(), end = range.getEnd(); note <= end; note++) - insertPairUniquely(keyswitchLabels, note, *region->keyswitchLabel); + insertPairUniquely(keyswitchLabels_, note, *region->keyswitchLabel); } } // Some regions had group number but no "group-level" opcodes handled the polyphony - while (polyphonyGroups.size() <= region->group) { - polyphonyGroups.emplace_back(); - polyphonyGroups.back().setPolyphonyLimit(config::maxVoices); + while (polyphonyGroups_.size() <= region->group) { + polyphonyGroups_.emplace_back(); + polyphonyGroups_.back().setPolyphonyLimit(config::maxVoices); } for (auto note = 0; note < 128; note++) { if (region->keyRange.containsWithEnd(note)) - noteActivationLists[note].push_back(region); + noteActivationLists_[note].push_back(region); } for (int cc = 0; cc < config::numCCs; cc++) { if (region->ccTriggers.contains(cc) || region->ccConditions.contains(cc) || (cc == region->sustainCC && region->trigger == SfzTrigger::release)) - ccActivationLists[cc].push_back(region); + ccActivationLists_[cc].push_back(region); } // Defaults for (int cc = 0; cc < config::numCCs; cc++) { - region->registerCC(cc, resources.midiState.getCCValue(cc)); + region->registerCC(cc, resources_.midiState.getCCValue(cc)); } @@ -696,12 +1047,12 @@ void sfz::Synth::finalizeSfzLoad() ++currentRegionIndex; } - DBG("Removing " << (regions.size() - currentRegionCount) << " out of " << regions.size() << " regions"); - regions.resize(currentRegionCount); + DBG("Removing " << (regions_.size() - currentRegionCount) << " out of " << regions_.size() << " regions"); + regions_.resize(currentRegionCount); // collect all CCs used in regions, with matrix not yet connected std::bitset usedCCs; - for (const RegionPtr& regionPtr : regions) { + for (const RegionPtr& regionPtr : regions_) { const Region& region = *regionPtr; updateUsedCCsFromRegion(usedCCs, region); for (const Region::Connection& connection : region.connections) { @@ -710,7 +1061,7 @@ void sfz::Synth::finalizeSfzLoad() } } // connect default controllers, except if these CC are already used - for (const RegionPtr& regionPtr : regions) { + for (const RegionPtr& regionPtr : regions_) { Region& region = *regionPtr; constexpr unsigned defaultSmoothness = 10; if (!usedCCs.test(7)) { @@ -730,128 +1081,135 @@ void sfz::Synth::finalizeSfzLoad() } } - modificationTime = checkModificationTime(); + modificationTime_ = checkModificationTime(); - settingsPerVoice.maxFilters = maxFilters; - settingsPerVoice.maxEQs = maxEQs; - settingsPerVoice.maxLFOs = maxLFOs; - settingsPerVoice.maxFlexEGs = maxFlexEGs; - settingsPerVoice.havePitchEG = havePitchEG; - settingsPerVoice.haveFilterEG = haveFilterEG; + settingsPerVoice_.maxFilters = maxFilters; + settingsPerVoice_.maxEQs = maxEQs; + settingsPerVoice_.maxLFOs = maxLFOs; + settingsPerVoice_.maxFlexEGs = maxFlexEGs; + settingsPerVoice_.havePitchEG = havePitchEG; + settingsPerVoice_.haveFilterEG = haveFilterEG; applySettingsPerVoice(); setupModMatrix(); } -bool sfz::Synth::loadScalaFile(const fs::path& path) +bool Synth::loadScalaFile(const fs::path& path) { - return resources.tuning.loadScalaFile(path); + Impl& impl = *impl_; + return impl.resources_.tuning.loadScalaFile(path); } -bool sfz::Synth::loadScalaString(const std::string& text) +bool Synth::loadScalaString(const std::string& text) { - return resources.tuning.loadScalaString(text); + Impl& impl = *impl_; + return impl.resources_.tuning.loadScalaString(text); } -void sfz::Synth::setScalaRootKey(int rootKey) +void Synth::setScalaRootKey(int rootKey) { - resources.tuning.setScalaRootKey(rootKey); + Impl& impl = *impl_; + impl.resources_.tuning.setScalaRootKey(rootKey); } -int sfz::Synth::getScalaRootKey() const +int Synth::getScalaRootKey() const { - return resources.tuning.getScalaRootKey(); + Impl& impl = *impl_; + return impl.resources_.tuning.getScalaRootKey(); } -void sfz::Synth::setTuningFrequency(float frequency) +void Synth::setTuningFrequency(float frequency) { - resources.tuning.setTuningFrequency(frequency); + Impl& impl = *impl_; + impl.resources_.tuning.setTuningFrequency(frequency); } -float sfz::Synth::getTuningFrequency() const +float Synth::getTuningFrequency() const { - return resources.tuning.getTuningFrequency(); + Impl& impl = *impl_; + return impl.resources_.tuning.getTuningFrequency(); } -void sfz::Synth::loadStretchTuningByRatio(float ratio) +void Synth::loadStretchTuningByRatio(float ratio) { + Impl& impl = *impl_; SFIZZ_CHECK(ratio >= 0.0f && ratio <= 1.0f); ratio = clamp(ratio, 0.0f, 1.0f); if (ratio > 0.0f) - resources.stretch = StretchTuning::createRailsbackFromRatio(ratio); + impl.resources_.stretch = StretchTuning::createRailsbackFromRatio(ratio); else - resources.stretch.reset(); + impl.resources_.stretch.reset(); } -sfz::Voice* sfz::Synth::findFreeVoice() noexcept +Voice* Synth::Impl::findFreeVoice() noexcept { - auto freeVoice = absl::c_find_if(voices, [](const std::unique_ptr& voice) { - return voice->isFree(); + auto freeVoice = absl::c_find_if(voiceList_, [](const Voice& voice) { + return voice.isFree(); }); - if (freeVoice != voices.end()) - return freeVoice->get(); + if (freeVoice != voiceList_.end()) + return &*freeVoice; DBG("Engine hard polyphony reached"); return {}; } -int sfz::Synth::getNumActiveVoices(bool recompute) const noexcept +int Synth::getNumActiveVoices(bool recompute) const noexcept { + Impl& impl = *impl_; if (!recompute) - return activeVoices; + return impl.activeVoices_; int active { 0 }; - for (auto& voice: voices) { - if (!voice->isFree()) + for (auto& voice: impl.voiceList_) { + if (!voice.isFree()) active++; } return active; } -void sfz::Synth::garbageCollect() noexcept -{ -} - -void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept +void Synth::setSamplesPerBlock(int samplesPerBlock) noexcept { + Impl& impl = *impl_; ASSERT(samplesPerBlock <= config::maxBlockSize); - const std::lock_guard disableCallback { callbackGuard }; + const std::lock_guard disableCallback { impl.callbackGuard_ }; - this->samplesPerBlock = samplesPerBlock; - for (auto& voice : voices) - voice->setSamplesPerBlock(samplesPerBlock); + impl.samplesPerBlock_ = samplesPerBlock; + for (auto& voice : impl.voiceList_) + voice.setSamplesPerBlock(samplesPerBlock); - resources.setSamplesPerBlock(samplesPerBlock); + impl.resources_.setSamplesPerBlock(samplesPerBlock); - for (auto& bus : effectBuses) { + for (auto& bus : impl.effectBuses_) { if (bus) bus->setSamplesPerBlock(samplesPerBlock); } } -void sfz::Synth::setSampleRate(float sampleRate) noexcept +void Synth::setSampleRate(float sampleRate) noexcept { - const std::lock_guard disableCallback { callbackGuard }; + Impl& impl = *impl_; + const std::lock_guard disableCallback { impl.callbackGuard_ }; - this->sampleRate = sampleRate; - for (auto& voice : voices) - voice->setSampleRate(sampleRate); + impl.sampleRate_ = sampleRate; + for (auto& voice : impl.voiceList_) + voice.setSampleRate(sampleRate); - resources.setSampleRate(sampleRate); + impl.resources_.setSampleRate(sampleRate); - for (auto& bus : effectBuses) { + for (auto& bus : impl.effectBuses_) { if (bus) bus->setSampleRate(sampleRate); } } -void sfz::Synth::renderBlock(AudioSpan buffer) noexcept +void Synth::renderBlock(AudioSpan buffer) noexcept { + Impl& impl = *impl_; ScopedFTZ ftz; CallbackBreakdown callbackBreakdown; @@ -860,74 +1218,74 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept buffer.fill(0.0f); } - if (resources.synthConfig.freeWheeling) - resources.filePool.waitForBackgroundLoading(); + if (impl.resources_.synthConfig.freeWheeling) + impl.resources_.filePool.waitForBackgroundLoading(); const auto now = std::chrono::high_resolution_clock::now(); const auto timeSinceLastCollection = - std::chrono::duration_cast(now - lastGarbageCollection); + std::chrono::duration_cast(now - impl.lastGarbageCollection_); if (timeSinceLastCollection.count() > config::fileClearingPeriod) { - lastGarbageCollection = now; - resources.filePool.triggerGarbageCollection(); + impl.lastGarbageCollection_ = now; + impl.resources_.filePool.triggerGarbageCollection(); } - const std::unique_lock lock { callbackGuard, std::try_to_lock }; + const std::unique_lock lock { impl.callbackGuard_, std::try_to_lock }; if (!lock.owns_lock()) return; size_t numFrames = buffer.getNumFrames(); - auto tempSpan = resources.bufferPool.getStereoBuffer(numFrames); - auto tempMixSpan = resources.bufferPool.getStereoBuffer(numFrames); - auto rampSpan = resources.bufferPool.getBuffer(numFrames); + auto tempSpan = impl.resources_.bufferPool.getStereoBuffer(numFrames); + auto tempMixSpan = impl.resources_.bufferPool.getStereoBuffer(numFrames); + auto rampSpan = impl.resources_.bufferPool.getBuffer(numFrames); if (!tempSpan || !tempMixSpan || !rampSpan) { DBG("[sfizz] Could not get a temporary buffer; exiting callback... "); return; } - ModMatrix& mm = resources.modMatrix; + ModMatrix& mm = impl.resources_.modMatrix; mm.beginCycle(numFrames); { // Clear effect busses ScopedTiming logger { callbackBreakdown.effects }; - for (auto& bus : effectBuses) { + for (auto& bus : impl.effectBuses_) { if (bus) bus->clearInputs(numFrames); } } - activeVoices = 0; + impl.activeVoices_ = 0; { // Main render block ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration }; tempMixSpan->fill(0.0f); - for (auto& voice : voices) { - if (voice->isFree()) + for (auto& voice : impl.voiceList_) { + if (voice.isFree()) continue; - mm.beginVoice(voice->getId(), voice->getRegion()->getId(), voice->getTriggerEvent().value); + mm.beginVoice(voice.getId(), voice.getRegion()->getId(), voice.getTriggerEvent().value); - activeVoices++; + impl.activeVoices_++; - const Region* region = voice->getRegion(); + const Region* region = voice.getRegion(); ASSERT(region != nullptr); - voice->renderBlock(*tempSpan); - for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { - if (auto& bus = effectBuses[i]) { + voice.renderBlock(*tempSpan); + for (size_t i = 0, n = impl.effectBuses_.size(); i < n; ++i) { + if (auto& bus = impl.effectBuses_[i]) { float addGain = region->getGainToEffectBus(i); bus->addToInputs(*tempSpan, addGain, numFrames); } } - callbackBreakdown.data += voice->getLastDataDuration(); - callbackBreakdown.amplitude += voice->getLastAmplitudeDuration(); - callbackBreakdown.filters += voice->getLastFilterDuration(); - callbackBreakdown.panning += voice->getLastPanningDuration(); + callbackBreakdown.data += voice.getLastDataDuration(); + callbackBreakdown.amplitude += voice.getLastAmplitudeDuration(); + callbackBreakdown.filters += voice.getLastFilterDuration(); + callbackBreakdown.panning += voice.getLastPanningDuration(); mm.endVoice(); - if (voice->toBeCleanedUp()) - voice->reset(); + if (voice.toBeCleanedUp()) + voice.reset(); } } @@ -936,7 +1294,7 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept // without any , the signal is just going to flow through it. ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration }; - for (auto& bus : effectBuses) { + for (auto& bus : impl.effectBuses_) { if (bus) { bus->process(numFrames); bus->mixOutputsTo(buffer, *tempMixSpan, numFrames); @@ -951,21 +1309,21 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept buffer.add(*tempMixSpan); // Apply the master volume - buffer.applyGain(db2mag(volume)); + buffer.applyGain(db2mag(impl.volume_)); // Perform any remaining modulators mm.endCycle(); { // Clear events and advance midi time - ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - resources.midiState.advanceTime(buffer.getNumFrames()); + ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; + impl.resources_.midiState.advanceTime(buffer.getNumFrames()); } - callbackBreakdown.dispatch = dispatchDuration; - resources.logger.logCallbackTime(callbackBreakdown, activeVoices, numFrames); + callbackBreakdown.dispatch = impl.dispatchDuration_; + impl.resources_.logger.logCallbackTime(callbackBreakdown, impl.activeVoices_, numFrames); // Reset the dispatch counter - dispatchDuration = Duration(0); + impl.dispatchDuration_ = Duration(0); ASSERT(!hasNanInf(buffer.getConstSpan(0))); ASSERT(!hasNanInf(buffer.getConstSpan(1))); @@ -973,46 +1331,48 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept SFIZZ_CHECK(isReasonableAudio(buffer.getConstSpan(1))); } -void sfz::Synth::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept +void Synth::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept { ASSERT(noteNumber < 128); ASSERT(noteNumber >= 0); + Impl& impl = *impl_; const auto normalizedVelocity = normalizeVelocity(velocity); - ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - resources.midiState.noteOnEvent(delay, noteNumber, normalizedVelocity); + ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; + impl.resources_.midiState.noteOnEvent(delay, noteNumber, normalizedVelocity); - const std::unique_lock lock { callbackGuard, std::try_to_lock }; + const std::unique_lock lock { impl.callbackGuard_, std::try_to_lock }; if (!lock.owns_lock()) return; - noteOnDispatch(delay, noteNumber, normalizedVelocity); + impl.noteOnDispatch(delay, noteNumber, normalizedVelocity); } -void sfz::Synth::noteOff(int delay, int noteNumber, uint8_t velocity) noexcept +void Synth::noteOff(int delay, int noteNumber, uint8_t velocity) noexcept { ASSERT(noteNumber < 128); ASSERT(noteNumber >= 0); UNUSED(velocity); + Impl& impl = *impl_; const auto normalizedVelocity = normalizeVelocity(velocity); - ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - resources.midiState.noteOffEvent(delay, noteNumber, normalizedVelocity); + ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; + impl.resources_.midiState.noteOffEvent(delay, noteNumber, normalizedVelocity); - const std::unique_lock lock { callbackGuard, std::try_to_lock }; + const std::unique_lock lock { impl.callbackGuard_, std::try_to_lock }; if (!lock.owns_lock()) return; // FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a // way in sfz to specify that a release trigger should NOT use the note-on velocity? - // auto replacedVelocity = (velocity == 0 ? sfz::getNoteVelocity(noteNumber) : velocity); - const auto replacedVelocity = resources.midiState.getNoteVelocity(noteNumber); + // auto replacedVelocity = (velocity == 0 ? getNoteVelocity(noteNumber) : velocity); + const auto replacedVelocity = impl.resources_.midiState.getNoteVelocity(noteNumber); - for (auto& voice : voices) - voice->registerNoteOff(delay, noteNumber, replacedVelocity); + for (auto& voice : impl.voiceList_) + voice.registerNoteOff(delay, noteNumber, replacedVelocity); - noteOffDispatch(delay, noteNumber, replacedVelocity); + impl.noteOffDispatch(delay, noteNumber, replacedVelocity); } -void sfz::Synth::startVoice(Region* region, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept +void Synth::Impl::startVoice(Region* region, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept { checkNotePolyphony(region, delay, triggerEvent); checkRegionPolyphony(region, delay); @@ -1027,42 +1387,42 @@ void sfz::Synth::startVoice(Region* region, int delay, const TriggerEvent& trigg ASSERT(selectedVoice->isFree()); selectedVoice->startVoice(region, delay, triggerEvent); ring.addVoiceToRing(selectedVoice); - engineSet->registerVoice(selectedVoice); + engineSet_->registerVoice(selectedVoice); RegionSet::registerVoiceInHierarchy(region, selectedVoice); - polyphonyGroups[region->group].registerVoice(selectedVoice); + polyphonyGroups_[region->group].registerVoice(selectedVoice); } -bool sfz::Synth::playingAttackVoice(const Region* releaseRegion) noexcept +bool Synth::Impl::playingAttackVoice(const Region* releaseRegion) noexcept { const auto compatibleVoice = [releaseRegion](const Voice* v) -> bool { - const sfz::TriggerEvent& event = v->getTriggerEvent(); + const TriggerEvent& event = v->getTriggerEvent(); return ( !v->isFree() - && event.type == sfz::TriggerEventType::NoteOn + && event.type == TriggerEventType::NoteOn && releaseRegion->keyRange.containsWithEnd(event.number) && releaseRegion->velocityRange.containsWithEnd(event.value) ); }; - if (absl::c_find_if(voiceViewArray, compatibleVoice) == voiceViewArray.end()) + if (absl::c_find_if(voiceViewArray_, compatibleVoice) == voiceViewArray_.end()) return false; else return true; } -void sfz::Synth::noteOffDispatch(int delay, int noteNumber, float velocity) noexcept +void Synth::Impl::noteOffDispatch(int delay, int noteNumber, float velocity) noexcept { - const auto randValue = randNoteDistribution(Random::randomGenerator); + const auto randValue = randNoteDistribution_(Random::randomGenerator); SisterVoiceRingBuilder ring; const TriggerEvent triggerEvent { TriggerEventType::NoteOff, noteNumber, velocity }; - for (auto& region : upKeyswitchLists[noteNumber]) + for (auto& region : upKeyswitchLists_[noteNumber]) region->keySwitched = true; - for (auto& region : downKeyswitchLists[noteNumber]) + for (auto& region : downKeyswitchLists_[noteNumber]) region->keySwitched = false; - for (auto& region : noteActivationLists[noteNumber]) { + for (auto& region : noteActivationLists_[noteNumber]) { if (region->registerNoteOff(noteNumber, velocity, randValue)) { if (region->trigger == SfzTrigger::release && !region->rtDead && !playingAttackVoice(region)) continue; @@ -1072,20 +1432,20 @@ void sfz::Synth::noteOffDispatch(int delay, int noteNumber, float velocity) noex } } -void sfz::Synth::checkRegionPolyphony(const Region* region, int delay) noexcept +void Synth::Impl::checkRegionPolyphony(const Region* region, int delay) noexcept { - tempPolyphonyArray.clear(); - absl::c_copy_if(voiceViewArray, - std::back_inserter(tempPolyphonyArray), + tempPolyphonyArray_.clear(); + absl::c_copy_if(voiceViewArray_, + std::back_inserter(tempPolyphonyArray_), [region](Voice* v) { return v->getRegion() == region && !v->releasedOrFree(); }); - if (tempPolyphonyArray.size() >= region->polyphony) { - const auto voiceToSteal = stealer.steal(absl::MakeSpan(tempPolyphonyArray)); + if (tempPolyphonyArray_.size() >= region->polyphony) { + const auto voiceToSteal = stealer_.steal(absl::MakeSpan(tempPolyphonyArray_)); SisterVoiceRing::offAllSisters(voiceToSteal, delay); } } -void sfz::Synth::checkNotePolyphony(const Region* region, int delay, const TriggerEvent& triggerEvent) noexcept +void Synth::Impl::checkNotePolyphony(const Region* region, int delay, const TriggerEvent& triggerEvent) noexcept { if (!region->notePolyphony) return; @@ -1093,8 +1453,8 @@ void sfz::Synth::checkNotePolyphony(const Region* region, int delay, const Trigg unsigned notePolyphonyCounter { 0 }; Voice* selfMaskCandidate { nullptr }; - for (Voice* voice : voiceViewArray) { - const sfz::TriggerEvent& voiceTriggerEvent = voice->getTriggerEvent(); + for (Voice* voice : voiceViewArray_) { + const TriggerEvent& voiceTriggerEvent = voice->getTriggerEvent(); const bool skipVoice = (triggerEvent.type == TriggerEventType::NoteOn && voice->releasedOrFree()) || voice->isFree(); if (!skipVoice && voice->getRegion()->group == region->group @@ -1122,30 +1482,30 @@ void sfz::Synth::checkNotePolyphony(const Region* region, int delay, const Trigg } } -void sfz::Synth::checkGroupPolyphony(const Region* region, int delay) noexcept +void Synth::Impl::checkGroupPolyphony(const Region* region, int delay) noexcept { - const auto& activeVoices = polyphonyGroups[region->group].getActiveVoices(); - tempPolyphonyArray.clear(); + const auto& activeVoices = polyphonyGroups_[region->group].getActiveVoices(); + tempPolyphonyArray_.clear(); absl::c_copy_if(activeVoices, - std::back_inserter(tempPolyphonyArray), [](Voice* v) { return !v->releasedOrFree(); }); + std::back_inserter(tempPolyphonyArray_), [](Voice* v) { return !v->releasedOrFree(); }); - if (tempPolyphonyArray.size() >= polyphonyGroups[region->group].getPolyphonyLimit()) { - const auto voiceToSteal = stealer.steal(absl::MakeSpan(tempPolyphonyArray)); + if (tempPolyphonyArray_.size() >= polyphonyGroups_[region->group].getPolyphonyLimit()) { + const auto voiceToSteal = stealer_.steal(absl::MakeSpan(tempPolyphonyArray_)); SisterVoiceRing::offAllSisters(voiceToSteal, delay); } } -void sfz::Synth::checkSetPolyphony(const Region* region, int delay) noexcept +void Synth::Impl::checkSetPolyphony(const Region* region, int delay) noexcept { auto parent = region->parent; while (parent != nullptr) { const auto& activeVoices = parent->getActiveVoices(); - tempPolyphonyArray.clear(); + tempPolyphonyArray_.clear(); absl::c_copy_if(activeVoices, - std::back_inserter(tempPolyphonyArray), [](Voice* v) { return !v->releasedOrFree(); }); + std::back_inserter(tempPolyphonyArray_), [](Voice* v) { return !v->releasedOrFree(); }); - if (tempPolyphonyArray.size() >= parent->getPolyphonyLimit()) { - const auto voiceToSteal = stealer.steal(absl::MakeSpan(tempPolyphonyArray)); + if (tempPolyphonyArray_.size() >= parent->getPolyphonyLimit()) { + const auto voiceToSteal = stealer_.steal(absl::MakeSpan(tempPolyphonyArray_)); SisterVoiceRing::offAllSisters(voiceToSteal, delay); } @@ -1153,47 +1513,47 @@ void sfz::Synth::checkSetPolyphony(const Region* region, int delay) noexcept } } -void sfz::Synth::checkEnginePolyphony(int delay) noexcept +void Synth::Impl::checkEnginePolyphony(int delay) noexcept { - auto& activeVoices = engineSet->getActiveVoices(); + auto& activeVoices = engineSet_->getActiveVoices(); - if (activeVoices.size() >= static_cast(numRequiredVoices)) { - tempPolyphonyArray.clear(); + if (activeVoices.size() >= static_cast(numRequiredVoices_)) { + tempPolyphonyArray_.clear(); absl::c_copy_if(activeVoices, - std::back_inserter(tempPolyphonyArray), [](Voice* v) { return !v->releasedOrFree(); }); - const auto voiceToSteal = stealer.steal(absl::MakeSpan(tempPolyphonyArray)); + std::back_inserter(tempPolyphonyArray_), [](Voice* v) { return !v->releasedOrFree(); }); + const auto voiceToSteal = stealer_.steal(absl::MakeSpan(tempPolyphonyArray_)); SisterVoiceRing::offAllSisters(voiceToSteal, delay, true); } } -void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexcept +void Synth::Impl::noteOnDispatch(int delay, int noteNumber, float velocity) noexcept { - const auto randValue = randNoteDistribution(Random::randomGenerator); + const auto randValue = randNoteDistribution_(Random::randomGenerator); SisterVoiceRingBuilder ring; const TriggerEvent triggerEvent { TriggerEventType::NoteOn, noteNumber, velocity }; - if (!lastKeyswitchLists[noteNumber].empty()) { - if (currentSwitch && *currentSwitch != noteNumber) { - for (auto& region : lastKeyswitchLists[*currentSwitch]) + if (!lastKeyswitchLists_[noteNumber].empty()) { + if (currentSwitch_ && *currentSwitch_ != noteNumber) { + for (auto& region : lastKeyswitchLists_[*currentSwitch_]) region->keySwitched = false; } - currentSwitch = noteNumber; + currentSwitch_ = noteNumber; } - for (auto& region : lastKeyswitchLists[noteNumber]) + for (auto& region : lastKeyswitchLists_[noteNumber]) region->keySwitched = true; - for (auto& region : upKeyswitchLists[noteNumber]) + for (auto& region : upKeyswitchLists_[noteNumber]) region->keySwitched = false; - for (auto& region : downKeyswitchLists[noteNumber]) + for (auto& region : downKeyswitchLists_[noteNumber]) region->keySwitched = true; - for (auto& region : noteActivationLists[noteNumber]) { + for (auto& region : noteActivationLists_[noteNumber]) { if (region->registerNoteOn(noteNumber, velocity, randValue)) { - for (auto& voice : voices) { - if (voice->checkOffGroup(region, delay, noteNumber)) { - const TriggerEvent& event = voice->getTriggerEvent(); + for (auto& voice : voiceList_) { + if (voice.checkOffGroup(region, delay, noteNumber)) { + const TriggerEvent& event = voice.getTriggerEvent(); noteOffDispatch(delay, event.number, event.value); } } @@ -1202,11 +1562,11 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc } } - for (auto& region : previousKeyswitchLists) + for (auto& region : previousKeyswitchLists_) region->previousKeySwitched = (*region->previousKeyswitch == noteNumber); } -void sfz::Synth::startDelayedReleaseVoices(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept +void Synth::Impl::startDelayedReleaseVoices(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept { if (!region->rtDead && !playingAttackVoice(region)) { region->delayedReleases.clear(); @@ -1222,17 +1582,17 @@ void sfz::Synth::startDelayedReleaseVoices(Region* region, int delay, SisterVoic } -void sfz::Synth::cc(int delay, int ccNumber, uint8_t ccValue) noexcept +void Synth::cc(int delay, int ccNumber, uint8_t ccValue) noexcept { const auto normalizedCC = normalizeCC(ccValue); hdcc(delay, ccNumber, normalizedCC); } -void sfz::Synth::ccDispatch(int delay, int ccNumber, float value) noexcept +void Synth::Impl::ccDispatch(int delay, int ccNumber, float value) noexcept { SisterVoiceRingBuilder ring; const TriggerEvent triggerEvent { TriggerEventType::CC, ccNumber, value }; - for (auto& region : ccActivationLists[ccNumber]) { + for (auto& region : ccActivationLists_[ccNumber]) { if (ccNumber == region->sustainCC) startDelayedReleaseVoices(region, delay, ring); @@ -1241,125 +1601,124 @@ void sfz::Synth::ccDispatch(int delay, int ccNumber, float value) noexcept } } -void sfz::Synth::hdcc(int delay, int ccNumber, float normValue) noexcept +void Synth::hdcc(int delay, int ccNumber, float normValue) noexcept { ASSERT(ccNumber < config::numCCs); ASSERT(ccNumber >= 0); - ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - resources.midiState.ccEvent(delay, ccNumber, normValue); + Impl& impl = *impl_; + ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; + impl.resources_.midiState.ccEvent(delay, ccNumber, normValue); - const std::unique_lock lock { callbackGuard, std::try_to_lock }; + const std::unique_lock lock { impl.callbackGuard_, std::try_to_lock }; if (!lock.owns_lock()) return; if (ccNumber == config::resetCC) { - resetAllControllers(delay); + impl.resetAllControllers(delay); return; } if (ccNumber == config::allNotesOffCC || ccNumber == config::allSoundOffCC) { - for (auto& voice : voices) - voice->reset(); - resources.midiState.allNotesOff(delay); + for (auto& voice : impl.voiceList_) + voice.reset(); + impl.resources_.midiState.allNotesOff(delay); return; } - for (auto& voice : voices) - voice->registerCC(delay, ccNumber, normValue); + for (auto& voice : impl.voiceList_) + voice.registerCC(delay, ccNumber, normValue); - ccDispatch(delay, ccNumber, normValue); + impl.ccDispatch(delay, ccNumber, normValue); } -void sfz::Synth::initCc(int ccNumber, uint8_t ccValue) noexcept -{ - const float normValue = normalizeCC(ccValue); - initHdcc(ccNumber, normValue); -} - -void sfz::Synth::initHdcc(int ccNumber, float normValue) noexcept +float Synth::getHdcc(int ccNumber) { ASSERT(ccNumber >= 0); ASSERT(ccNumber < config::numCCs); - ccInitialValues[ccNumber] = normValue; - resources.midiState.ccEvent(0, ccNumber, normValue); + Impl& impl = *impl_; + return impl.resources_.midiState.getCCValue(ccNumber); } -float sfz::Synth::getHdccInit(int ccNumber) -{ - ASSERT(ccNumber >= 0); - ASSERT(ccNumber < config::numCCs); - return ccInitialValues[ccNumber]; -} - -void sfz::Synth::pitchWheel(int delay, int pitch) noexcept +void Synth::pitchWheel(int delay, int pitch) noexcept { ASSERT(pitch <= 8192); ASSERT(pitch >= -8192); + Impl& impl = *impl_; const auto normalizedPitch = normalizeBend(float(pitch)); - ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - resources.midiState.pitchBendEvent(delay, normalizedPitch); + ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; + impl.resources_.midiState.pitchBendEvent(delay, normalizedPitch); - for (auto& region : regions) { + for (auto& region : impl.regions_) { region->registerPitchWheel(normalizedPitch); } - for (auto& voice : voices) { - voice->registerPitchWheel(delay, normalizedPitch); + for (auto& voice : impl.voiceList_) { + voice.registerPitchWheel(delay, normalizedPitch); } } -void sfz::Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept +void Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept { - ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; + Impl& impl = *impl_; + ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; } -void sfz::Synth::tempo(int /* delay */, float /* secondsPerQuarter */) noexcept +void Synth::tempo(int /* delay */, float /* secondsPerQuarter */) noexcept { - ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; + Impl& impl = *impl_; + ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; } -void sfz::Synth::timeSignature(int delay, int beatsPerBar, int beatUnit) +void Synth::timeSignature(int delay, int beatsPerBar, int beatUnit) { - ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; + Impl& impl = *impl_; + ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; (void)delay; (void)beatsPerBar; (void)beatUnit; } -void sfz::Synth::timePosition(int delay, int bar, float barBeat) +void Synth::timePosition(int delay, int bar, float barBeat) { - ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; + Impl& impl = *impl_; + ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; (void)delay; (void)bar; (void)barBeat; } -void sfz::Synth::playbackState(int delay, int playbackState) +void Synth::playbackState(int delay, int playbackState) { - ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; + Impl& impl = *impl_; + ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; (void)delay; (void)playbackState; } -int sfz::Synth::getNumRegions() const noexcept +int Synth::getNumRegions() const noexcept { - return static_cast(regions.size()); + Impl& impl = *impl_; + return static_cast(impl.regions_.size()); } -int sfz::Synth::getNumGroups() const noexcept +int Synth::getNumGroups() const noexcept { - return numGroups; + Impl& impl = *impl_; + return impl.numGroups_; } -int sfz::Synth::getNumMasters() const noexcept +int Synth::getNumMasters() const noexcept { - return numMasters; + Impl& impl = *impl_; + return impl.numMasters_; } -int sfz::Synth::getNumCurves() const noexcept +int Synth::getNumCurves() const noexcept { - return static_cast(resources.curves.getNumCurves()); + Impl& impl = *impl_; + return static_cast(impl.resources_.curves.getNumCurves()); } -std::string sfz::Synth::exportMidnam(absl::string_view model) const +std::string Synth::exportMidnam(absl::string_view model) const { + Impl& impl = *impl_; pugi::xml_document doc; absl::string_view manufacturer = config::midnamManufacturer; @@ -1421,7 +1780,7 @@ std::string sfz::Synth::exportMidnam(absl::string_view model) const pugi::xml_node cns = device.append_child("ControlNameList"); cns.append_attribute("Name").set_value("Controls"); - for (const auto& pair : ccLabels) { + for (const auto& pair : impl.ccLabels_) { anonymousCCs.set(pair.first, false); if (pair.first < 128) { pugi::xml_node cn = cns.append_child("Control"); @@ -1444,12 +1803,12 @@ std::string sfz::Synth::exportMidnam(absl::string_view model) const { pugi::xml_node nnl = device.append_child("NoteNameList"); nnl.append_attribute("Name").set_value("Notes"); - for (const auto& pair : keyswitchLabels) { + for (const auto& pair : impl.keyswitchLabels_) { pugi::xml_node nn = nnl.append_child("Note"); nn.append_attribute("Number").set_value(std::to_string(pair.first).c_str()); nn.append_attribute("Name").set_value(pair.second.c_str()); } - for (const auto& pair : keyLabels) { + for (const auto& pair : impl.keyLabels_) { pugi::xml_node nn = nnl.append_child("Note"); nn.append_attribute("Number").set_value(std::to_string(pair.first).c_str()); nn.append_attribute("Name").set_value(pair.second.c_str()); @@ -1461,29 +1820,34 @@ std::string sfz::Synth::exportMidnam(absl::string_view model) const return std::move(writer.str()); } -const sfz::Region* sfz::Synth::getRegionView(int idx) const noexcept +const Region* Synth::getRegionView(int idx) const noexcept { - return (size_t)idx < regions.size() ? regions[idx].get() : nullptr; + Impl& impl = *impl_; + return (size_t)idx < impl.regions_.size() ? impl.regions_[idx].get() : nullptr; } -const sfz::EffectBus* sfz::Synth::getEffectBusView(int idx) const noexcept +const EffectBus* Synth::getEffectBusView(int idx) const noexcept { - return (size_t)idx < effectBuses.size() ? effectBuses[idx].get() : nullptr; + Impl& impl = *impl_; + return (size_t)idx < impl.effectBuses_.size() ? impl.effectBuses_[idx].get() : nullptr; } -const sfz::RegionSet* sfz::Synth::getRegionSetView(int idx) const noexcept +const RegionSet* Synth::getRegionSetView(int idx) const noexcept { - return (size_t)idx < sets.size() ? sets[idx].get() : nullptr; + Impl& impl = *impl_; + return (size_t)idx < impl.sets_.size() ? impl.sets_[idx].get() : nullptr; } -const sfz::PolyphonyGroup* sfz::Synth::getPolyphonyGroupView(int idx) const noexcept +const PolyphonyGroup* Synth::getPolyphonyGroupView(int idx) const noexcept { - return (size_t)idx < polyphonyGroups.size() ? &polyphonyGroups[idx] : nullptr; + Impl& impl = *impl_; + return (size_t)idx < impl.polyphonyGroups_.size() ? &impl.polyphonyGroups_[idx] : nullptr; } -const sfz::Region* sfz::Synth::getRegionById(NumericId id) const noexcept +const Region* Synth::getRegionById(NumericId id) const noexcept { - const size_t size = regions.size(); + Impl& impl = *impl_; + const size_t size = impl.regions_.size(); if (size == 0 || !id.valid()) return nullptr; @@ -1492,72 +1856,61 @@ const sfz::Region* sfz::Synth::getRegionById(NumericId id) const noexcep size_t index = static_cast(id.number()); index = std::min(index, size - 1); - while (index > 0 && regions[index]->getId().number() > id.number()) + while (index > 0 && impl.regions_[index]->getId().number() > id.number()) --index; - return (regions[index]->getId() == id) ? regions[index].get() : nullptr; + return (impl.regions_[index]->getId() == id) ? impl.regions_[index].get() : nullptr; } -const sfz::Voice* sfz::Synth::getVoiceById(NumericId id) const noexcept +const Voice* Synth::getVoiceView(int idx) const noexcept { - const size_t size = voices.size(); - - if (size == 0 || !id.valid()) - return nullptr; - - // search a sequence of ordered identifiers with potential gaps - size_t index = static_cast(id.number()); - index = std::min(index, size - 1); - - while (index > 0 && voices[index]->getId().number() > id.number()) - --index; - - return (voices[index]->getId() == id) ? voices[index].get() : nullptr; + Impl& impl = *impl_; + return (size_t)idx < impl.voiceList_.size() ? &impl.voiceList_[idx] : nullptr; } -const sfz::Voice* sfz::Synth::getVoiceView(int idx) const noexcept +unsigned Synth::getNumPolyphonyGroups() const noexcept { - return (size_t)idx < voices.size() ? voices[idx].get() : nullptr; + Impl& impl = *impl_; + return impl.polyphonyGroups_.size(); } -unsigned sfz::Synth::getNumPolyphonyGroups() const noexcept +const std::vector& Synth::getUnknownOpcodes() const noexcept { - return polyphonyGroups.size(); + Impl& impl = *impl_; + return impl.unknownOpcodes_; +} +size_t Synth::getNumPreloadedSamples() const noexcept +{ + Impl& impl = *impl_; + return impl.resources_.filePool.getNumPreloadedSamples(); } -const std::vector& sfz::Synth::getUnknownOpcodes() const noexcept -{ - return unknownOpcodes; -} -size_t sfz::Synth::getNumPreloadedSamples() const noexcept -{ - return resources.filePool.getNumPreloadedSamples(); -} - -int sfz::Synth::getSampleQuality(ProcessMode mode) +int Synth::getSampleQuality(ProcessMode mode) { + Impl& impl = *impl_; switch (mode) { case ProcessLive: - return resources.synthConfig.liveSampleQuality; + return impl.resources_.synthConfig.liveSampleQuality; case ProcessFreewheeling: - return resources.synthConfig.freeWheelingSampleQuality; + return impl.resources_.synthConfig.freeWheelingSampleQuality; default: SFIZZ_CHECK(false); return 0; } } -void sfz::Synth::setSampleQuality(ProcessMode mode, int quality) +void Synth::setSampleQuality(ProcessMode mode, int quality) { SFIZZ_CHECK(quality >= 1 && quality <= 10); + Impl& impl = *impl_; quality = clamp(quality, 1, 10); switch (mode) { case ProcessLive: - resources.synthConfig.liveSampleQuality = quality; + impl.resources_.synthConfig.liveSampleQuality = quality; break; case ProcessFreewheeling: - resources.synthConfig.freeWheelingSampleQuality = quality; + impl.resources_.synthConfig.freeWheelingSampleQuality = quality; break; default: SFIZZ_CHECK(false); @@ -1565,93 +1918,94 @@ void sfz::Synth::setSampleQuality(ProcessMode mode, int quality) } } -float sfz::Synth::getVolume() const noexcept +float Synth::getVolume() const noexcept { - return volume; + Impl& impl = *impl_; + return impl.volume_; } -void sfz::Synth::setVolume(float volume) noexcept +void Synth::setVolume(float volume) noexcept { - this->volume = Default::volumeRange.clamp(volume); + Impl& impl = *impl_; + impl.volume_ = Default::volumeRange.clamp(volume); } -int sfz::Synth::getNumVoices() const noexcept +int Synth::getNumVoices() const noexcept { - return numRequiredVoices; + Impl& impl = *impl_; + return impl.numRequiredVoices_; } -void sfz::Synth::setNumVoices(int numVoices) noexcept +void Synth::setNumVoices(int numVoices) noexcept { ASSERT(numVoices > 0); - const std::lock_guard disableCallback { callbackGuard }; + Impl& impl = *impl_; + const std::lock_guard disableCallback { impl.callbackGuard_ }; // fast path - if (numVoices == this->numRequiredVoices) + if (numVoices == impl.numRequiredVoices_) return; - resetVoices(numVoices); + impl.resetVoices(numVoices); } -void sfz::Synth::resetVoices(int numVoices) +void Synth::Impl::resetVoices(int numVoices) { - numActualVoices = + numActualVoices_ = static_cast(config::overflowVoiceMultiplier * numVoices); - numRequiredVoices = numVoices; + numRequiredVoices_ = numVoices; - for (auto& set : sets) + for (auto& set : sets_) set->removeAllVoices(); - engineSet->removeAllVoices(); - engineSet->setPolyphonyLimit(numRequiredVoices); + engineSet_->removeAllVoices(); + engineSet_->setPolyphonyLimit(numRequiredVoices_); - voices.clear(); - voices.reserve(numActualVoices); + voiceList_.clear(); + voiceList_.reserve(numActualVoices_); - voiceViewArray.clear(); - voiceViewArray.reserve(numActualVoices); + voiceViewArray_.clear(); + voiceViewArray_.reserve(numActualVoices_); - tempPolyphonyArray.clear(); - tempPolyphonyArray.reserve(numActualVoices); + tempPolyphonyArray_.clear(); + tempPolyphonyArray_.reserve(numActualVoices_); - for (int i = 0; i < numActualVoices; ++i) { - auto voice = absl::make_unique(i, resources); - voice->setStateListener(this); - voiceViewArray.push_back(voice.get()); - voices.emplace_back(std::move(voice)); - } - - for (auto& voice : voices) { - voice->setSampleRate(this->sampleRate); - voice->setSamplesPerBlock(this->samplesPerBlock); + for (int i = 0; i < numActualVoices_; ++i) { + voiceList_.emplace_back(i, resources_); + Voice& lastVoice = voiceList_.back(); + lastVoice.setSampleRate(this->sampleRate_); + lastVoice.setSamplesPerBlock(this->samplesPerBlock_); + lastVoice.setStateListener(this); + voiceViewArray_.push_back(&lastVoice); } applySettingsPerVoice(); } -void sfz::Synth::applySettingsPerVoice() +void Synth::Impl::applySettingsPerVoice() { - for (auto& voice : voices) { - voice->setMaxFiltersPerVoice(settingsPerVoice.maxFilters); - voice->setMaxEQsPerVoice(settingsPerVoice.maxEQs); - voice->setMaxLFOsPerVoice(settingsPerVoice.maxLFOs); - voice->setMaxFlexEGsPerVoice(settingsPerVoice.maxFlexEGs); - voice->setPitchEGEnabledPerVoice(settingsPerVoice.havePitchEG); - voice->setFilterEGEnabledPerVoice(settingsPerVoice.haveFilterEG); + for (auto& voice : voiceList_) { + voice.setMaxFiltersPerVoice(settingsPerVoice_.maxFilters); + voice.setMaxEQsPerVoice(settingsPerVoice_.maxEQs); + voice.setMaxLFOsPerVoice(settingsPerVoice_.maxLFOs); + voice.setMaxFlexEGsPerVoice(settingsPerVoice_.maxFlexEGs); + voice.setPitchEGEnabledPerVoice(settingsPerVoice_.havePitchEG); + voice.setFilterEGEnabledPerVoice(settingsPerVoice_.haveFilterEG); } - if (stealer.getStealingAlgorithm() == + if (stealer_.getStealingAlgorithm() == VoiceStealing::StealingAlgorithm::EnvelopeAndAge) { - for (auto& voice : voices) - voice->enablePowerFollower(); + for (auto& voice : voiceList_) + voice.enablePowerFollower(); } else { - for (auto& voice : voices) - voice->disablePowerFollower(); + for (auto& voice : voiceList_) + voice.disablePowerFollower(); } } -void sfz::Synth::setupModMatrix() +void Synth::Impl::setupModMatrix() { - ModMatrix& mm = resources.modMatrix; + ModMatrix& mm = resources_.modMatrix; - for (const RegionPtr& region : regions) { + for (const RegionPtr& region : regions_) { for (const Region::Connection& conn : region->connections) { ModGenerator* gen = nullptr; @@ -1668,18 +2022,18 @@ void sfz::Synth::setupModMatrix() switch (sourceKey.id()) { case ModId::Controller: - gen = genController.get(); + gen = genController_.get(); break; case ModId::LFO: - gen = genLFO.get(); + gen = genLFO_.get(); break; case ModId::Envelope: - gen = genFlexEnvelope.get(); + gen = genFlexEnvelope_.get(); break; case ModId::AmpEG: case ModId::PitchEG: case ModId::FilEG: - gen = genADSREnvelope.get(); + gen = genADSREnvelope_.get(); break; default: DBG("[sfizz] Have unknown type of source generator"); @@ -1715,84 +2069,87 @@ void sfz::Synth::setupModMatrix() mm.init(); } -void sfz::Synth::setOversamplingFactor(sfz::Oversampling factor) noexcept +void Synth::setOversamplingFactor(Oversampling factor) noexcept { - const std::lock_guard disableCallback { callbackGuard }; + Impl& impl = *impl_; + const std::lock_guard disableCallback { impl.callbackGuard_ }; // fast path - if (factor == oversamplingFactor) + if (factor == impl.oversamplingFactor_) return; - for (auto& voice : voices) { + impl.voiceList_.reset(); - voice->reset(); - } - - resources.filePool.emptyFileLoadingQueues(); - resources.filePool.setOversamplingFactor(factor); - oversamplingFactor = factor; + impl.resources_.filePool.emptyFileLoadingQueues(); + impl.resources_.filePool.setOversamplingFactor(factor); + impl.oversamplingFactor_ = factor; } -sfz::Oversampling sfz::Synth::getOversamplingFactor() const noexcept +Oversampling Synth::getOversamplingFactor() const noexcept { - return oversamplingFactor; + Impl& impl = *impl_; + return impl.oversamplingFactor_; } -void sfz::Synth::setPreloadSize(uint32_t preloadSize) noexcept +void Synth::setPreloadSize(uint32_t preloadSize) noexcept { - const std::lock_guard disableCallback { callbackGuard }; + Impl& impl = *impl_; + const std::lock_guard disableCallback { impl.callbackGuard_ }; // fast path - if (preloadSize == resources.filePool.getPreloadSize()) + if (preloadSize == impl.resources_.filePool.getPreloadSize()) return; - resources.filePool.setPreloadSize(preloadSize); + impl.resources_.filePool.setPreloadSize(preloadSize); } -uint32_t sfz::Synth::getPreloadSize() const noexcept +uint32_t Synth::getPreloadSize() const noexcept { - return resources.filePool.getPreloadSize(); + Impl& impl = *impl_; + return impl.resources_.filePool.getPreloadSize(); } -void sfz::Synth::enableFreeWheeling() noexcept +void Synth::enableFreeWheeling() noexcept { - if (!resources.synthConfig.freeWheeling) { - resources.synthConfig.freeWheeling = true; + Impl& impl = *impl_; + if (!impl.resources_.synthConfig.freeWheeling) { + impl.resources_.synthConfig.freeWheeling = true; DBG("Enabling freewheeling"); } } -void sfz::Synth::disableFreeWheeling() noexcept +void Synth::disableFreeWheeling() noexcept { - if (resources.synthConfig.freeWheeling) { - resources.synthConfig.freeWheeling = false; + Impl& impl = *impl_; + if (impl.resources_.synthConfig.freeWheeling) { + impl.resources_.synthConfig.freeWheeling = false; DBG("Disabling freewheeling"); } } -void sfz::Synth::resetAllControllers(int delay) noexcept +void Synth::Impl::resetAllControllers(int delay) noexcept { - resources.midiState.resetAllControllers(delay); + resources_.midiState.resetAllControllers(delay); - const std::unique_lock lock { callbackGuard, std::try_to_lock }; + const std::unique_lock lock { callbackGuard_, std::try_to_lock }; if (!lock.owns_lock()) return; - for (auto& voice : voices) { - voice->registerPitchWheel(delay, 0); + for (auto& voice : voiceList_) { + voice.registerPitchWheel(delay, 0); for (int cc = 0; cc < config::numCCs; ++cc) - voice->registerCC(delay, cc, 0.0f); + voice.registerCC(delay, cc, 0.0f); } - for (auto& region : regions) { + for (auto& region : regions_) { for (int cc = 0; cc < config::numCCs; ++cc) region->registerCC(cc, 0.0f); } } -fs::file_time_type sfz::Synth::checkModificationTime() +fs::file_time_type Synth::Impl::checkModificationTime() { - auto returnedTime = modificationTime; - for (const auto& file : parser.getIncludedFiles()) { + auto returnedTime = modificationTime_; + for (const auto& file : parser_.getIncludedFiles()) { std::error_code ec; const auto fileTime = fs::last_write_time(file, ec); if (!ec && returnedTime < fileTime) @@ -1801,59 +2158,65 @@ fs::file_time_type sfz::Synth::checkModificationTime() return returnedTime; } -bool sfz::Synth::shouldReloadFile() +bool Synth::shouldReloadFile() { - return (checkModificationTime() > modificationTime); + Impl& impl = *impl_; + return (impl.checkModificationTime() > impl.modificationTime_); } -bool sfz::Synth::shouldReloadScala() +bool Synth::shouldReloadScala() { - return resources.tuning.shouldReloadScala(); + Impl& impl = *impl_; + return impl.resources_.tuning.shouldReloadScala(); } -void sfz::Synth::enableLogging(absl::string_view prefix) noexcept +void Synth::enableLogging(absl::string_view prefix) noexcept { - resources.logger.enableLogging(prefix); + Impl& impl = *impl_; + impl.resources_.logger.enableLogging(prefix); } -void sfz::Synth::setLoggingPrefix(absl::string_view prefix) noexcept +void Synth::setLoggingPrefix(absl::string_view prefix) noexcept { - resources.logger.setPrefix(prefix); + Impl& impl = *impl_; + impl.resources_.logger.setPrefix(prefix); } -void sfz::Synth::disableLogging() noexcept +void Synth::disableLogging() noexcept { - resources.logger.disableLogging(); + Impl& impl = *impl_; + impl.resources_.logger.disableLogging(); } -void sfz::Synth::allSoundOff() noexcept +void Synth::allSoundOff() noexcept { - const std::lock_guard disableCallback { callbackGuard }; + Impl& impl = *impl_; + const std::lock_guard disableCallback { impl.callbackGuard_ }; - for (auto& voice : voices) - voice->reset(); - for (auto& effectBus : effectBuses) + impl.voiceList_.reset(); + for (auto& effectBus : impl.effectBuses_) effectBus->clear(); } -void sfz::Synth::setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept +void Synth::Impl::setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept { - while (polyphonyGroups.size() <= groupIdx) - polyphonyGroups.emplace_back(); + while (polyphonyGroups_.size() <= groupIdx) + polyphonyGroups_.emplace_back(); - polyphonyGroups[groupIdx].setPolyphonyLimit(polyphony); + polyphonyGroups_[groupIdx].setPolyphonyLimit(polyphony); } -std::bitset sfz::Synth::getUsedCCs() const noexcept +std::bitset Synth::getUsedCCs() const noexcept { - std::bitset used; - for (const RegionPtr& region : regions) - updateUsedCCsFromRegion(used, *region); - updateUsedCCsFromModulations(used, resources.modMatrix); + Impl& impl = *impl_; + std::bitset used; + for (const Impl::RegionPtr& region : impl.regions_) + impl.updateUsedCCsFromRegion(used, *region); + impl.updateUsedCCsFromModulations(used, impl.resources_.modMatrix); return used; } -void sfz::Synth::updateUsedCCsFromRegion(std::bitset& usedCCs, const Region& region) +void Synth::Impl::updateUsedCCsFromRegion(std::bitset& usedCCs, const Region& region) { updateUsedCCsFromCCMap(usedCCs, region.offsetCC); updateUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccAttack); @@ -1887,11 +2250,11 @@ void sfz::Synth::updateUsedCCsFromRegion(std::bitset& usedC updateUsedCCsFromCCMap(usedCCs, region.crossfadeCCOutRange); } -void sfz::Synth::updateUsedCCsFromModulations(std::bitset& usedCCs, const ModMatrix& mm) +void Synth::Impl::updateUsedCCsFromModulations(std::bitset& usedCCs, const ModMatrix& mm) { class CCSourceCollector : public ModMatrix::KeyVisitor { public: - explicit CCSourceCollector(std::bitset& used) + explicit CCSourceCollector(std::bitset& used) : used_(used) { } @@ -1902,9 +2265,47 @@ void sfz::Synth::updateUsedCCsFromModulations(std::bitset& used_.set(key.parameters().cc); return true; } - std::bitset& used_; + std::bitset& used_; }; CCSourceCollector vtor(usedCCs); mm.visitSources(vtor); } + +Parser& Synth::getParser() noexcept +{ + Impl& impl = *impl_; + return impl.parser_; +} + +const Parser& Synth::getParser() const noexcept +{ + Impl& impl = *impl_; + return impl.parser_; +} + +const std::vector& Synth::getKeyLabels() const noexcept +{ + Impl& impl = *impl_; + return impl.keyLabels_; +} + +const std::vector& Synth::getCCLabels() const noexcept +{ + Impl& impl = *impl_; + return impl.ccLabels_; +} + +Resources& Synth::getResources() noexcept +{ + Impl& impl = *impl_; + return impl.resources_; +} + +const Resources& Synth::getResources() const noexcept +{ + Impl& impl = *impl_; + return impl.resources_; +} + +} // namespace sfz diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 40b5012e..37e095d2 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -5,31 +5,22 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #pragma once -#include "Resources.h" -#include "Parser.h" -#include "Voice.h" #include "Region.h" -#include "RegionSet.h" -#include "PolyphonyGroup.h" -#include "Effects.h" +#include "Voice.h" #include "LeakDetector.h" -#include "MidiState.h" #include "AudioSpan.h" #include "parser/Parser.h" -#include "VoiceStealing.h" -#include "utility/SpinMutex.h" -#include -#include #include -#include +#include #include #include namespace sfz { -class ControllerSource; -class LFOSource; -class FlexEnvelopeSource; -class ADSREnvelopeSource; + +// Forward declarations for the introspection methods +class RegionSet; +class PolyphonyGroup; +class EffectBus; /** * @brief This class is the core of the sfizz library. In C++ it is the main point @@ -68,11 +59,10 @@ class ADSREnvelopeSource; * The jack_client.cpp file contains examples of the most classical usage of the * synth and can be used as a reference. */ -class Synth final : public Voice::StateListener, public Parser::Listener { +class Synth final { public: /** - * @brief Construct a new Synth object with no voices. If you want sound - * you will need to call setNumVoices() before playing. + * @brief Construct a new Synth object with a default number of voices. * */ Synth(); @@ -80,13 +70,10 @@ public: * @brief Destructor */ ~Synth(); - /** - * @brief Construct a new Synth object with a specified number of voices. - * - * @param numVoices - */ - Synth(int numVoices); - + Synth(const Synth& other) = delete; + Synth& operator=(const Synth& other) = delete; + Synth(Synth&& other) = delete; + Synth& operator=(Synth&& other) = delete; /** * @brief Processing mode */ @@ -122,11 +109,6 @@ public: * @true otherwise. */ bool loadSfzString(const fs::path& path, absl::string_view text); - /** - * @brief Finalize SFZ loading, following a successful execution of the - * parsing step. - */ - void finalizeSfzLoad(); /** * @brief Sets the tuning from a Scala file loaded from the file system. * @@ -208,24 +190,6 @@ public: * @return const Region* */ const Region* getRegionById(NumericId id) const noexcept; - /** - * @brief Find the voice which is associated with the given identifier. - * - * @param id - * @return const Voice* - */ - const Voice* getVoiceById(NumericId id) const noexcept; - /** - * @brief Find the voice which is associated with the given identifier. - * - * @param id - * @return Voice* - */ - Voice* getVoiceById(NumericId id) noexcept - { - return const_cast( - const_cast(this)->getVoiceById(id)); - } /** * @brief Get a raw view into a specific region. This is mostly used * for testing. @@ -245,6 +209,7 @@ public: /** * @brief Get a raw view into a specific effect bus. This is mostly used * for testing. + * You'll need to include "Effects.h" to resolve the forward declaration. * * @param idx * @return const EffectBus* @@ -253,6 +218,7 @@ public: /** * @brief Get a raw view into a specific set of regions. This is mostly used * for testing. + * You'll need to include "RegionSet.h" to resolve the forward declaration. * * @param idx * @return const RegionSet* @@ -261,6 +227,7 @@ public: /** * @brief Get a raw view into a specific polyphony group. This is mostly used * for testing. + * You'll need to include "PolyphonyGroup.h" to resolve the forward declaration. * * @param idx * @return const PolyphonyGroup* @@ -370,29 +337,13 @@ public: * @param normValue the normalized cc value, in domain 0 to 1 */ void hdcc(int delay, int ccNumber, float normValue) noexcept; -private: /** - * @brief Set the initial value of a controller and send it to the synth + * @brief Get the current value of a controller under the current instrument * * @param ccNumber the cc number - * @param ccValue the cc value + * @return the current value */ - void initCc(int ccNumber, uint8_t ccValue) noexcept; - /** - * @brief Set the initial value of a controller and send it to the synth - * - * @param ccNumber the cc number - * @param normValue the normalized cc value, in domain 0 to 1 - */ - void initHdcc(int ccNumber, float normValue) noexcept; -public: - /** - * @brief Get the initial value of a controller under the current instrument - * - * @param ccNumber the cc number - * @return the initial value - */ - float getHdccInit(int ccNumber); + float getHdcc(int ccNumber); /** * @brief Send a pitch bend event to the synth * @@ -475,15 +426,6 @@ public: * @param numVoices */ void setNumVoices(int numVoices) noexcept; - /** - * @brief Trigger a garbage collection, which removes the samples that are - * loaded by the FilePool after being requested by the voices. This does - * not concern the preloaded samples, only the samples loaded to be played - * fully. This function is run regularly in a background thread so normally - * you should not need to call it explicitely. - * - */ - void garbageCollect() noexcept; /** * @brief Set the oversampling factor to a new value. @@ -551,8 +493,8 @@ public: */ void disableFreeWheeling() noexcept; - Resources& getResources() noexcept { return resources; } - const Resources& getResources() const noexcept { return resources; } + Resources& getResources() noexcept; + const Resources& getResources() const noexcept; /** * @brief Check if the SFZ should be reloaded. @@ -604,26 +546,26 @@ public: * * @return A reference to the parser. */ - Parser& getParser() noexcept { return parser; } + Parser& getParser() noexcept; /** * @brief Get the parser. * * @return A reference to the parser. */ - const Parser& getParser() const noexcept { return parser; } + const Parser& getParser() const noexcept; /** * @brief Get the key labels, if any * * @return const std::vector& */ - const std::vector& getKeyLabels() const noexcept { return keyLabels; } + const std::vector& getKeyLabels() const noexcept; /** * @brief Get the CC labels, if any * * @return const std::vector& */ - const std::vector& getCCLabels() const noexcept { return ccLabels; } + const std::vector& getCCLabels() const noexcept; /** * @brief Get the used CCs @@ -632,332 +574,9 @@ public: */ std::bitset getUsedCCs() const noexcept; -protected: - /** - * @brief The voice callback which is called during a change of state. - */ - void onVoiceStateChanged(NumericId idNumber, Voice::State state) override; - -protected: - /** - * @brief The parser callback; this is called by the parent object each time - * a new region, group, master, global, curve or control set of opcodes - * appears in the parser - * - * @param header the header for the set of opcodes - * @param members the opcode members - */ - void onParseFullBlock(const std::string& header, const std::vector& members) override; - - /** - * @brief The parser callback when an error occurs. - */ - void onParseError(const SourceRange& range, const std::string& message) override; - - /** - * @brief The parser callback when a warning occurs. - */ - void onParseWarning(const SourceRange& range, const std::string& message) override; - private: - /** - * @brief change the group maximum polyphony - * - * @param groupIdx the group index - * @param polyphone the max polyphony - */ - void setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept; - - /** - * @brief Reset all CCs; to be used on CC 121 - * - * @param delay the delay for the controller reset - * - */ - void resetAllControllers(int delay) noexcept; - - int numGroups { 0 }; - int numMasters { 0 }; - - /** - * @brief Remove all regions, resets all voices and clears everything - * to bring back the synth in its original state. - * - * The callback mutex should be taken to call this function. - */ - void clear(); - - /** - * @brief Helper function to dispatch opcodes - * - * @param members the opcodes of the block - */ - void handleGlobalOpcodes(const std::vector& members); - /** - * @brief Helper function to dispatch opcodes - * - * @param members the opcodes of the block - */ - void handleMasterOpcodes(const std::vector& members); - /** - * @brief Helper function to dispatch opcodes - * - * @param members the opcodes of the block - */ - void handleGroupOpcodes(const std::vector& members, const std::vector& masterMembers); - /** - * @brief Helper function to dispatch opcodes - * - * @param members the opcodes of the block - */ - void handleControlOpcodes(const std::vector& members); - /** - * @brief Helper function to dispatch opcodes - * - * @param members the opcodes of the block - */ - void handleEffectOpcodes(const std::vector& members); - /** - * @brief Helper function to merge all the currently active opcodes - * as set by the successive callbacks and create a new region to store - * in the synth. - * - * @param regionOpcodes the opcodes that are specific to the region - */ - void buildRegion(const std::vector& regionOpcodes); - /** - * @brief Resets and possibly changes the number of voices (polyphony) in - * the synth. - * - * @param numVoices - */ - void resetVoices(int numVoices); - /** - * @brief Make the stored settings take effect in all the voices - */ - void applySettingsPerVoice(); - - /** - * @brief Establish all connections of the modulation matrix. - */ - void setupModMatrix(); - - /** - * @brief Get the modification time of all included sfz files - * - * @return fs::file_time_type - */ - fs::file_time_type checkModificationTime(); - - /** - * @brief Check all regions and start voices for note on events - * - * @param delay - * @param noteNumber - * @param velocity - */ - void noteOnDispatch(int delay, int noteNumber, float velocity) noexcept; - - /** - * @brief Check all regions and start voices for note off events - * - * @param delay - * @param noteNumber - * @param velocity - */ - void noteOffDispatch(int delay, int noteNumber, float velocity) noexcept; - - /** - * @brief Check all regions and start voices for cc events - * - * @param delay - * @param ccNumber - * @param value - */ - void ccDispatch(int delay, int ccNumber, float value) noexcept; - - template - static void updateUsedCCsFromCCMap(std::bitset& usedCCs, const CCMap map) - { - for (auto& mod : map) - usedCCs[mod.cc] = true; - } - static void updateUsedCCsFromRegion(std::bitset& usedCCs, const Region& region); - static void updateUsedCCsFromModulations(std::bitset& usedCCs, const ModMatrix& mm); - - // Opcode memory; these are used to build regions, as a new region - // will integrate opcodes from the group, master and global block - std::vector globalOpcodes; - std::vector masterOpcodes; - std::vector groupOpcodes; - - /** - * @brief Find a voice that is not currently playing - * - * @return Voice* - */ - Voice* findFreeVoice() noexcept; - - // Names for the CC and notes as set by label_cc and label_key - std::vector ccLabels; - std::vector keyLabels; - std::vector keyswitchLabels; - - // Set as sw_default if present in the file - absl::optional currentSwitch; - std::vector unknownOpcodes; - using RegionViewVector = std::vector; - using VoiceViewVector = std::vector; - using VoicePtr = std::unique_ptr; - using RegionPtr = std::unique_ptr; - using RegionSetPtr = std::unique_ptr; - std::vector regions; - std::vector voices; - - // These are more general "groups" than sfz and encapsulates the full hierarchy - RegionSet* currentSet { nullptr }; - std::vector sets; - // This region set holds the engine set of voices, which tries to respect the required - // engine polyphony - RegionSetPtr engineSet; - - // These are the `group=` groups where you can off voices - std::vector polyphonyGroups; - - // Views to speed up iteration over the regions and voices when events - // occur in the audio callback - VoiceViewVector tempPolyphonyArray; - VoiceViewVector voiceViewArray; - VoiceStealing stealer; - - /** - * @brief Check the region polyphony, releasing voices if necessary - * - * @param region - * @param delay - */ - void checkRegionPolyphony(const Region* region, int delay) noexcept; - - /** - * @brief Check the note polyphony, releasing voices if necessary - * - * @param region - * @param delay - * @param triggerEvent - */ - void checkNotePolyphony(const Region* region, int delay, const TriggerEvent& triggerEvent) noexcept; - - /** - * @brief Check the group polyphony, releasing voices if necessary - * - * @param region - * @param delay - */ - void checkGroupPolyphony(const Region* region, int delay) noexcept; - - /** - * @brief Check the region set polyphony at all levels, releasing voices if necessary - * - * @param region - * @param delay - */ - void checkSetPolyphony(const Region* region, int delay) noexcept; - - /** - * @brief Check the engine polyphony, fast releasing voices if necessary - * - * @param delay - */ - void checkEnginePolyphony(int delay) noexcept; - - /** - * @brief Start a voice for a specific region. - * This will do the needed polyphony checks and voice stealing. - * - * @param region - * @param delay - * @param triggerEvent - * @param ring - */ - void startVoice(Region* region, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept; - - /** - * @brief Start all delayed release voices of the region if necessary - * - * @param region - * @param delay - * @param ring - */ - void startDelayedReleaseVoices(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept; - - /** - * @brief Check if a playing voice matches the release region - * - * @param releaseRegion - * @return true - * @return false - */ - bool playingAttackVoice(const Region* releaseRegion) noexcept; - - std::array lastKeyswitchLists; - std::array downKeyswitchLists; - std::array upKeyswitchLists; - RegionViewVector previousKeyswitchLists; - std::array noteActivationLists; - std::array ccActivationLists; - - // Effect factory and buses - EffectFactory effectFactory; - typedef std::unique_ptr EffectBusPtr; - std::vector effectBuses; // 0 is "main", 1-N are "fx1"-"fxN" - - int samplesPerBlock { config::defaultSamplesPerBlock }; - float sampleRate { config::defaultSampleRate }; - float volume { Default::globalVolume }; - int numRequiredVoices { config::numVoices }; - int numActualVoices { static_cast(config::numVoices * config::overflowVoiceMultiplier) }; - int activeVoices { 0 }; - Oversampling oversamplingFactor { config::defaultOversamplingFactor }; - - // Distribution used to generate random value for the *rand opcodes - std::uniform_real_distribution randNoteDistribution { 0, 1 }; - - SpinMutex callbackGuard; - - // Singletons passed as references to the voices - Resources resources; - - // Control opcodes - std::string defaultPath { "" }; - int noteOffset { 0 }; - int octaveOffset { 0 }; - - // Modulation source generators - std::unique_ptr genController; - std::unique_ptr genLFO; - std::unique_ptr genFlexEnvelope; - std::unique_ptr genADSREnvelope; - - // Settings per voice - struct SettingsPerVoice { - size_t maxFilters { 0 }; - size_t maxEQs { 0 }; - size_t maxLFOs { 0 }; - size_t maxFlexEGs { 0 }; - bool havePitchEG { false }; - bool haveFilterEG { false }; - }; - SettingsPerVoice settingsPerVoice; - - // Controller initial values - std::array ccInitialValues; - - Duration dispatchDuration { 0 }; - - std::chrono::time_point lastGarbageCollection; - - Parser parser; - fs::file_time_type modificationTime { }; + struct Impl; + std::unique_ptr impl_; LEAK_DETECTOR(Synth); }; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 3b2ee301..7f5faadc 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -4,58 +4,345 @@ // license. You should have receive a LICENSE.md file along with the code. // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz -#include "Voice.h" -#include "Macros.h" +#include "absl/algorithm/container.h" +#include "absl/types/span.h" +#include "AudioBuffer.h" +#include "Config.h" #include "Defaults.h" -#include "ModifierHelpers.h" -#include "MathHelpers.h" -#include "SIMDHelpers.h" -#include "Panning.h" -#include "SfzHelpers.h" -#include "LFO.h" +#include "EQPool.h" +#include "FilterPool.h" #include "FlexEnvelope.h" +#include "HistoricalBuffer.h" +#include "Interpolators.h" +#include "LFO.h" +#include "Macros.h" +#include "MathHelpers.h" +#include "ModifierHelpers.h" #include "modulations/ModId.h" #include "modulations/ModKey.h" #include "modulations/ModMatrix.h" -#include "Interpolators.h" -#include "absl/algorithm/container.h" +#include "OnePoleFilter.h" +#include "Panning.h" +#include "PowerFollower.h" +#include "SfzHelpers.h" +#include "SIMDHelpers.h" +#include "Smoothers.h" +#include "Voice.h" +#include -sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources) -: id{voiceNumber}, stateListener(nullptr), resources(resources) +namespace sfz { + +struct Voice::Impl +{ + Impl() = delete; + Impl(int voiceNumber, Resources& resources); + /** + * @brief Fill a span with data from a file source. This is the first step + * in rendering each block of data. + * + * @param buffer + */ + void fillWithData(AudioSpan buffer) noexcept; + /** + * @brief Fill a span with data from a generator source. This is the first step + * in rendering each block of data. + * + * @param buffer + */ + void fillWithGenerator(AudioSpan buffer) noexcept; + + /** + * @brief Fill a destination with an interpolated source. + * + * @param source the source sample + * @param dest the destination buffer + * @param indices the integral parts of the source positions + * @param coeffs the fractional parts of the source positions + */ + template + static void fillInterpolated( + const AudioSpan& source, const AudioSpan& dest, + absl::Span indices, absl::Span coeffs, + absl::Span addingGains); + + /** + * @brief Fill a destination with an interpolated source, selecting + * interpolation type dynamically by quality level. + * + * @param source the source sample + * @param dest the destination buffer + * @param indices the integral parts of the source positions + * @param coeffs the fractional parts of the source positions + * @param quality the quality level 1-10 + */ + template + static void fillInterpolatedWithQuality( + const AudioSpan& source, const AudioSpan& dest, + absl::Span indices, absl::Span coeffs, + absl::Span addingGains, int quality); + + /** + * @brief Get a S-shaped curve that is applicable to loop crossfading. + */ + static const Curve& getSCurve(); + + /** + * @brief Compute the amplitude envelope, applied as a gain to a mono + * or stereo buffer + * + * @param modulationSpan + */ + void amplitudeEnvelope(absl::Span modulationSpan) noexcept; + + /** + * @brief Apply the crossfade envelope to a span. + * + * @param modulationSpan + */ + void applyCrossfades(absl::Span modulationSpan) noexcept; + void resetCrossfades() noexcept; + + /** + * @brief Amplitude stage for a mono source + * + * @param buffer + */ + void ampStageMono(AudioSpan buffer) noexcept; + /** + * @brief Amplitude stage for a stereo source + * + * @param buffer + */ + void ampStageStereo(AudioSpan buffer) noexcept; + /** + * @brief Amplitude stage for a mono source + * + * @param buffer + */ + void panStageMono(AudioSpan buffer) noexcept; + void panStageStereo(AudioSpan buffer) noexcept; + /** + * @brief Amplitude stage for a mono source + * + * @param buffer + */ + void filterStageMono(AudioSpan buffer) noexcept; + void filterStageStereo(AudioSpan buffer) noexcept; + /** + * @brief Compute the pitch envelope. This envelope is meant to multiply + * the frequency parameter for each sample (which translates to floating + * point intervals for sample-based voices, or phases for generators) + * + * @param pitchSpan + */ + void pitchEnvelope(absl::Span pitchSpan) noexcept; + + /** + * @brief Initialize frequency and gain coefficients for the oscillators. + */ + void setupOscillatorUnison(); + void updateChannelPowers(AudioSpan buffer); + + /** + * @brief Modify the voice state and notify any listeners. + */ + void switchState(State s); + + /** + * @brief Save the modulation targets to avoid recomputing them in every callback. + * Must be called during startVoice() ideally. + */ + void saveModulationTargets(const Region* region) noexcept; + + /** + * @brief Get the sample quality determined by the active region. + * + * @return int + */ + int getCurrentSampleQuality() const noexcept; + /** + * @brief Reset the loop information + * + */ + void resetLoopInformation() noexcept; + /** + * @brief Read the loop information data from the region. + * This requires that the region and promise is properly set. + * + */ + void updateLoopInformation() noexcept; + + const NumericId id_; + StateListener* stateListener_ = nullptr; + + Region* region_ { nullptr }; + + State state_ { State::idle }; + bool noteIsOff_ { false }; + + TriggerEvent triggerEvent_; + absl::optional triggerDelay_; + + float speedRatio_ { 1.0 }; + float pitchRatio_ { 1.0 }; + float baseVolumedB_ { 0.0 }; + float baseGain_ { 1.0 }; + float baseFrequency_ { 440.0 }; + + float floatPositionOffset_ { 0.0f }; + int sourcePosition_ { 0 }; + int initialDelay_ { 0 }; + int age_ { 0 }; + struct { + int start { 0 }; + int end { 0 }; + int size { 0 }; + int xfSize { 0 }; + int xfOutStart { 0 }; + int xfInStart { 0 }; + } loop_; + + FileDataHolder currentPromise_; + + int samplesPerBlock_ { config::defaultSamplesPerBlock }; + float sampleRate_ { config::defaultSampleRate }; + + Resources& resources_; + + std::vector filters_; + std::vector equalizers_; + std::vector> lfos_; + std::vector> flexEGs_; + + ADSREnvelope egAmplitude_; + std::unique_ptr> egPitch_; + std::unique_ptr> egFilter_; + float bendStepFactor_ { centsFactor(1) }; + + WavetableOscillator waveOscillators_[config::oscillatorsPerVoice]; + + // unison of oscillators + unsigned waveUnisonSize_ { 0 }; + float waveDetuneRatio_[config::oscillatorsPerVoice] {}; + float waveLeftGain_[config::oscillatorsPerVoice] {}; + float waveRightGain_[config::oscillatorsPerVoice] {}; + + Duration dataDuration_; + Duration amplitudeDuration_; + Duration panningDuration_; + Duration filterDuration_; + + fast_real_distribution uniformNoiseDist_ { -config::uniformNoiseBounds, config::uniformNoiseBounds }; + fast_gaussian_generator gaussianNoiseDist_ { 0.0f, config::noiseVariance }; + + Smoother gainSmoother_; + Smoother bendSmoother_; + Smoother xfadeSmoother_; + void resetSmoothers() noexcept; + + ModMatrix::TargetId masterAmplitudeTarget_; + ModMatrix::TargetId amplitudeTarget_; + ModMatrix::TargetId volumeTarget_; + ModMatrix::TargetId panTarget_; + ModMatrix::TargetId positionTarget_; + ModMatrix::TargetId widthTarget_; + ModMatrix::TargetId pitchTarget_; + ModMatrix::TargetId oscillatorDetuneTarget_; + ModMatrix::TargetId oscillatorModDepthTarget_; + + bool followPower_ { false }; + PowerFollower powerFollower_; +}; + +Voice::Voice(int voiceNumber, Resources& resources) +: impl_(new Impl(voiceNumber, resources)) +{ + +} + +// Need to define the dtor after Impl has been defined +Voice::~Voice() +{ + +} + +Voice::Voice(Voice&& other) { + ASSERT(other.impl_); + impl_ = std::move(other.impl_); + + if (other.nextSisterVoice_ != &other) { + nextSisterVoice_ = other.nextSisterVoice_; + other.nextSisterVoice_ = &other; + nextSisterVoice_->setPreviousSisterVoice(this); + } else { + nextSisterVoice_ = this; + } + + if (other.previousSisterVoice_ != &other) { + previousSisterVoice_ = other.previousSisterVoice_; + other.previousSisterVoice_ = &other; + previousSisterVoice_->setNextSisterVoice(this); + } else { + previousSisterVoice_ = this; + } +} + +Voice& Voice::operator=(Voice&& other) { + ASSERT(other.impl_); + impl_ = std::move(other.impl_); + + if (other.nextSisterVoice_ != &other) { + nextSisterVoice_ = other.nextSisterVoice_; + other.nextSisterVoice_ = &other; + nextSisterVoice_->setPreviousSisterVoice(this); + } else { + nextSisterVoice_ = this; + } + + if (other.previousSisterVoice_ != &other) { + previousSisterVoice_ = other.previousSisterVoice_; + other.previousSisterVoice_ = &other; + previousSisterVoice_->setNextSisterVoice(this); + } else { + previousSisterVoice_ = this; + } + + return *this; +} + +Voice::Impl::Impl(int voiceNumber, Resources& resources) +: id_ { voiceNumber }, stateListener_(nullptr), resources_(resources) { for (unsigned i = 0; i < config::filtersPerVoice; ++i) - filters.emplace_back(resources); + filters_.emplace_back(resources); for (unsigned i = 0; i < config::eqsPerVoice; ++i) - equalizers.emplace_back(resources); + equalizers_.emplace_back(resources); - for (WavetableOscillator& osc : waveOscillators) - osc.init(sampleRate); + for (WavetableOscillator& osc : waveOscillators_) + osc.init(sampleRate_); - gainSmoother.setSmoothing(config::gainSmoothing, sampleRate); - xfadeSmoother.setSmoothing(config::xfadeSmoothing, sampleRate); + gainSmoother_.setSmoothing(config::gainSmoothing, sampleRate_); + xfadeSmoother_.setSmoothing(config::xfadeSmoothing, sampleRate_); // prepare curves getSCurve(); } -sfz::Voice::~Voice() -{ -} - -void sfz::Voice::startVoice(Region* region, int delay, const TriggerEvent& event) noexcept +void Voice::startVoice(Region* region, int delay, const TriggerEvent& event) noexcept { + Impl& impl = *impl_; ASSERT(event.value >= 0.0f && event.value <= 1.0f); - this->region = region; + impl.region_ = region; if (region->disabled()) return; - triggerEvent = event; - if (triggerEvent.type == TriggerEventType::CC) - triggerEvent.number = region->pitchKeycenter; + impl.triggerEvent_ = event; + if (impl.triggerEvent_.type == TriggerEventType::CC) + impl.triggerEvent_.number = region->pitchKeycenter; - switchState(State::playing); + impl.switchState(State::playing); ASSERT(delay >= 0); if (delay < 0) @@ -64,116 +351,125 @@ void sfz::Voice::startVoice(Region* region, int delay, const TriggerEvent& event if (region->isOscillator()) { const WavetableMulti* wave = nullptr; if (!region->isGenerator()) - wave = resources.wavePool.getFileWave(region->sampleId->filename()); + wave = impl.resources_.wavePool.getFileWave(region->sampleId->filename()); else { switch (hash(region->sampleId->filename())) { default: case hash("*silence"): break; case hash("*sine"): - wave = resources.wavePool.getWaveSin(); + wave = impl.resources_.wavePool.getWaveSin(); break; case hash("*triangle"): // fallthrough case hash("*tri"): - wave = resources.wavePool.getWaveTriangle(); + wave = impl.resources_.wavePool.getWaveTriangle(); break; case hash("*square"): - wave = resources.wavePool.getWaveSquare(); + wave = impl.resources_.wavePool.getWaveSquare(); break; case hash("*saw"): - wave = resources.wavePool.getWaveSaw(); + wave = impl.resources_.wavePool.getWaveSaw(); break; } } const float phase = region->getPhase(); const int quality = region->oscillatorQuality.value_or(Default::oscillatorQuality); - for (WavetableOscillator& osc : waveOscillators) { + for (WavetableOscillator& osc : impl.waveOscillators_) { osc.setWavetable(wave); osc.setPhase(phase); osc.setQuality(quality); } - setupOscillatorUnison(); + impl.setupOscillatorUnison(); } else { - currentPromise = resources.filePool.getFilePromise(region->sampleId); - if (!currentPromise) { - switchState(State::cleanMeUp); + impl.currentPromise_ = impl.resources_.filePool.getFilePromise(region->sampleId); + if (!impl.currentPromise_) { + impl.switchState(State::cleanMeUp); return; } - updateLoopInformation(); - speedRatio = static_cast(currentPromise->information.sampleRate / this->sampleRate); - sourcePosition = region->getOffset(resources.filePool.getOversamplingFactor()); + impl.updateLoopInformation(); + impl.speedRatio_ = static_cast(impl.currentPromise_->information.sampleRate / impl.sampleRate_); + impl.sourcePosition_ = region->getOffset(impl.resources_.filePool.getOversamplingFactor()); } // do Scala retuning and reconvert the frequency into a 12TET key number - const float numberRetuned = resources.tuning.getKeyFractional12TET(triggerEvent.number); + const float numberRetuned = impl.resources_.tuning.getKeyFractional12TET(impl.triggerEvent_.number); - pitchRatio = region->getBasePitchVariation(numberRetuned, triggerEvent.value); + impl.pitchRatio_ = region->getBasePitchVariation(numberRetuned, impl.triggerEvent_.value); // apply stretch tuning if set - if (resources.stretch) - pitchRatio *= resources.stretch->getRatioForFractionalKey(numberRetuned); + if (impl.resources_.stretch) + impl.pitchRatio_ *= impl.resources_.stretch->getRatioForFractionalKey(numberRetuned); - baseVolumedB = region->getBaseVolumedB(triggerEvent.number); - baseGain = region->getBaseGain(); - if (triggerEvent.type != TriggerEventType::CC) - baseGain *= region->getNoteGain(triggerEvent.number, triggerEvent.value); - gainSmoother.reset(); - resetCrossfades(); + impl.baseVolumedB_ = region->getBaseVolumedB(impl.triggerEvent_.number); + impl.baseGain_ = region->getBaseGain(); + if (impl.triggerEvent_.type != TriggerEventType::CC) + impl.baseGain_ *= region->getNoteGain(impl.triggerEvent_.number, impl.triggerEvent_.value); + impl.gainSmoother_.reset(); + impl.resetCrossfades(); for (unsigned i = 0; i < region->filters.size(); ++i) { - filters[i].setup(*region, i, triggerEvent.number, triggerEvent.value); + impl.filters_[i].setup(*region, i, impl.triggerEvent_.number, impl.triggerEvent_.value); } for (unsigned i = 0; i < region->equalizers.size(); ++i) { - equalizers[i].setup(*region, i, triggerEvent.value); + impl.equalizers_[i].setup(*region, i, impl.triggerEvent_.value); } - triggerDelay = delay; - initialDelay = delay + static_cast(region->getDelay() * sampleRate); - baseFrequency = resources.tuning.getFrequencyOfKey(triggerEvent.number); - bendStepFactor = centsFactor(region->bendStep); - bendSmoother.setSmoothing(region->bendSmooth, sampleRate); - bendSmoother.reset(centsFactor(region->getBendInCents(resources.midiState.getPitchBend()))); + impl.triggerDelay_ = delay; + impl.initialDelay_ = delay + static_cast(region->getDelay() * impl.sampleRate_); + impl.baseFrequency_ = impl.resources_.tuning.getFrequencyOfKey(impl.triggerEvent_.number); + impl.bendStepFactor_ = centsFactor(region->bendStep); + impl.bendSmoother_.setSmoothing(region->bendSmooth, impl.sampleRate_); + impl.bendSmoother_.reset(centsFactor(region->getBendInCents(impl.resources_.midiState.getPitchBend()))); - resources.modMatrix.initVoice(id, region->getId(), delay); - saveModulationTargets(region); + impl.resources_.modMatrix.initVoice(impl.id_, region->getId(), delay); + impl.saveModulationTargets(region); } -int sfz::Voice::getCurrentSampleQuality() const noexcept +int Voice::Impl::getCurrentSampleQuality() const noexcept { - return (region && region->sampleQuality) ? - *region->sampleQuality : resources.synthConfig.currentSampleQuality(); + return (region_ && region_->sampleQuality) ? + *region_->sampleQuality : resources_.synthConfig.currentSampleQuality(); } -bool sfz::Voice::isFree() const noexcept +int Voice::getCurrentSampleQuality() const noexcept { - return (state == State::idle); + Impl& impl = *impl_; + return impl.getCurrentSampleQuality(); } -void sfz::Voice::release(int delay) noexcept +bool Voice::isFree() const noexcept { - if (state != State::playing) + Impl& impl = *impl_; + return (impl.state_ == State::idle); +} + +void Voice::release(int delay) noexcept +{ + Impl& impl = *impl_; + if (impl.state_ != State::playing) return; - if (!region->flexAmpEG) { - if (egAmplitude.getRemainingDelay() > delay) - switchState(State::cleanMeUp); + if (!impl.region_->flexAmpEG) { + if (impl.egAmplitude_.getRemainingDelay() > delay) + impl.switchState(State::cleanMeUp); } else { - if (flexEGs[*region->flexAmpEG]->getRemainingDelay() > static_cast(delay)) - switchState(State::cleanMeUp); + if (impl.flexEGs_[*impl.region_->flexAmpEG]->getRemainingDelay() > static_cast(delay)) + impl.switchState(State::cleanMeUp); } - resources.modMatrix.releaseVoice(id, region->getId(), delay); + impl.resources_.modMatrix.releaseVoice(impl.id_, impl.region_->getId(), delay); } -void sfz::Voice::off(int delay, bool fast) noexcept +void Voice::off(int delay, bool fast) noexcept { - if (!region->flexAmpEG) { - if (region->offMode == SfzOffMode::fast || fast) { - egAmplitude.setReleaseTime(Default::offTime); - } else if (region->offMode == SfzOffMode::time) { - egAmplitude.setReleaseTime(region->offTime); + Impl& impl = *impl_; + if (!impl.region_->flexAmpEG) { + if (impl.region_->offMode == SfzOffMode::fast || fast) { + impl.egAmplitude_.setReleaseTime(Default::offTime); + } else if (impl.region_->offMode == SfzOffMode::time) { + impl.egAmplitude_.setReleaseTime(impl.region_->offTime); } } else { @@ -183,136 +479,146 @@ void sfz::Voice::off(int delay, bool fast) noexcept release(delay); } -void sfz::Voice::registerNoteOff(int delay, int noteNumber, float velocity) noexcept +void Voice::registerNoteOff(int delay, int noteNumber, float velocity) noexcept { ASSERT(velocity >= 0.0 && velocity <= 1.0); UNUSED(velocity); + Impl& impl = *impl_; - if (region == nullptr) + if (impl.region_ == nullptr) return; - if (state != State::playing) + if (impl.state_ != State::playing) return; - if (triggerEvent.number == noteNumber && triggerEvent.type == TriggerEventType::NoteOn) { - noteIsOff = true; + if (impl.triggerEvent_.number == noteNumber && impl.triggerEvent_.type == TriggerEventType::NoteOn) { + impl.noteIsOff_ = true; - if (region->loopMode == SfzLoopMode::one_shot) + if (impl.region_->loopMode == SfzLoopMode::one_shot) return; - if (!region->checkSustain || resources.midiState.getCCValue(region->sustainCC) < region->sustainThreshold) + if (!impl.region_->checkSustain + || impl.resources_.midiState.getCCValue(impl.region_->sustainCC) < impl.region_->sustainThreshold) release(delay); } } -void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept +void Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept { ASSERT(ccValue >= 0.0 && ccValue <= 1.0); - if (region == nullptr) + Impl& impl = *impl_; + if (impl.region_ == nullptr) return; - if (state != State::playing) + if (impl.state_ != State::playing) return; - if (region->checkSustain && noteIsOff && ccNumber == region->sustainCC && ccValue < region->sustainThreshold) + if (impl.region_->checkSustain + && impl.noteIsOff_ + && ccNumber == impl.region_->sustainCC + && ccValue < impl.region_->sustainThreshold) release(delay); } -void sfz::Voice::registerPitchWheel(int delay, float pitch) noexcept +void Voice::registerPitchWheel(int delay, float pitch) noexcept { - if (state != State::playing) + Impl& impl = *impl_; + if (impl.state_ != State::playing) return; UNUSED(delay); UNUSED(pitch); } -void sfz::Voice::registerAftertouch(int delay, uint8_t aftertouch) noexcept +void Voice::registerAftertouch(int delay, uint8_t aftertouch) noexcept { // TODO UNUSED(delay); UNUSED(aftertouch); } -void sfz::Voice::registerTempo(int delay, float secondsPerQuarter) noexcept +void Voice::registerTempo(int delay, float secondsPerQuarter) noexcept { // TODO UNUSED(delay); UNUSED(secondsPerQuarter); } -void sfz::Voice::setSampleRate(float sampleRate) noexcept +void Voice::setSampleRate(float sampleRate) noexcept { - this->sampleRate = sampleRate; - gainSmoother.setSmoothing(config::gainSmoothing, sampleRate); - xfadeSmoother.setSmoothing(config::xfadeSmoothing, sampleRate); + Impl& impl = *impl_; + impl.sampleRate_ = sampleRate; + impl.gainSmoother_.setSmoothing(config::gainSmoothing, sampleRate); + impl.xfadeSmoother_.setSmoothing(config::xfadeSmoothing, sampleRate); - for (WavetableOscillator& osc : waveOscillators) + for (WavetableOscillator& osc : impl.waveOscillators_) osc.init(sampleRate); - for (auto& lfo : lfos) + for (auto& lfo : impl.lfos_) lfo->setSampleRate(sampleRate); - for (auto& filter : filters) + for (auto& filter : impl.filters_) filter.setSampleRate(sampleRate); - for (auto& eq : equalizers) + for (auto& eq : impl.equalizers_) eq.setSampleRate(sampleRate); - powerFollower.setSampleRate(sampleRate); + impl.powerFollower_.setSampleRate(sampleRate); } -void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept +void Voice::setSamplesPerBlock(int samplesPerBlock) noexcept { - this->samplesPerBlock = samplesPerBlock; - powerFollower.setSamplesPerBlock(samplesPerBlock); + Impl& impl = *impl_; + impl.samplesPerBlock_ = samplesPerBlock; + impl.powerFollower_.setSamplesPerBlock(samplesPerBlock); } -void sfz::Voice::renderBlock(AudioSpan buffer) noexcept +void Voice::renderBlock(AudioSpan buffer) noexcept { - ASSERT(static_cast(buffer.getNumFrames()) <= samplesPerBlock); + Impl& impl = *impl_; + ASSERT(static_cast(buffer.getNumFrames()) <= impl.samplesPerBlock_); buffer.fill(0.0f); - if (region == nullptr) + if (impl.region_ == nullptr) return; - const auto delay = min(static_cast(initialDelay), buffer.getNumFrames()); + const auto delay = min(static_cast(impl.initialDelay_), buffer.getNumFrames()); auto delayed_buffer = buffer.subspan(delay); - initialDelay -= static_cast(delay); + impl.initialDelay_ -= static_cast(delay); { // Fill buffer with raw data - ScopedTiming logger { dataDuration }; - if (region->isOscillator()) - fillWithGenerator(delayed_buffer); + ScopedTiming logger { impl.dataDuration_ }; + if (impl.region_->isOscillator()) + impl.fillWithGenerator(delayed_buffer); else - fillWithData(delayed_buffer); + impl.fillWithData(delayed_buffer); } - if (region->isStereo()) { - ampStageStereo(buffer); - panStageStereo(buffer); - filterStageStereo(buffer); + if (impl.region_->isStereo()) { + impl.ampStageStereo(buffer); + impl.panStageStereo(buffer); + impl.filterStageStereo(buffer); } else { - ampStageMono(buffer); - filterStageMono(buffer); - panStageMono(buffer); + impl.ampStageMono(buffer); + impl.filterStageMono(buffer); + impl.panStageMono(buffer); } - if (!region->flexAmpEG) { - if (!egAmplitude.isSmoothing()) - switchState(State::cleanMeUp); + if (!impl.region_->flexAmpEG) { + if (!impl.egAmplitude_.isSmoothing()) + impl.switchState(State::cleanMeUp); } else { - if (flexEGs[*region->flexAmpEG]->isFinished()) - switchState(State::cleanMeUp); + if (impl.flexEGs_[*impl.region_->flexAmpEG]->isFinished()) + impl.switchState(State::cleanMeUp); } - powerFollower.process(buffer); + impl.powerFollower_.process(buffer); - age += buffer.getNumFrames(); - if (triggerDelay) { + impl.age_ += buffer.getNumFrames(); + if (impl.triggerDelay_) { // Should be OK but just in case; - age = min(age - *triggerDelay, 0); - triggerDelay = absl::nullopt; + impl.age_ = min(impl.age_ - *impl.triggerDelay_, 0); + impl.triggerDelay_ = absl::nullopt; } #if 0 @@ -323,31 +629,31 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept #endif } -void sfz::Voice::resetCrossfades() noexcept +void Voice::Impl::resetCrossfades() noexcept { float xfadeValue { 1.0f }; - const auto xfCurve = region->crossfadeCCCurve; + const auto xfCurve = region_->crossfadeCCCurve; - for (const auto& mod : region->crossfadeCCInRange) { - const auto value = resources.midiState.getCCValue(mod.cc); + for (const auto& mod : region_->crossfadeCCInRange) { + const auto value = resources_.midiState.getCCValue(mod.cc); xfadeValue *= crossfadeIn(mod.data, value, xfCurve); } - for (const auto& mod : region->crossfadeCCOutRange) { - const auto value = resources.midiState.getCCValue(mod.cc); + for (const auto& mod : region_->crossfadeCCOutRange) { + const auto value = resources_.midiState.getCCValue(mod.cc); xfadeValue *= crossfadeOut(mod.data, value, xfCurve); } - xfadeSmoother.reset(xfadeValue); + xfadeSmoother_.reset(xfadeValue); } -void sfz::Voice::applyCrossfades(absl::Span modulationSpan) noexcept +void Voice::Impl::applyCrossfades(absl::Span modulationSpan) noexcept { const auto numSamples = modulationSpan.size(); - const auto xfCurve = region->crossfadeCCCurve; + const auto xfCurve = region_->crossfadeCCCurve; - auto tempSpan = resources.bufferPool.getBuffer(numSamples); - auto xfadeSpan = resources.bufferPool.getBuffer(numSamples); + auto tempSpan = resources_.bufferPool.getBuffer(numSamples); + auto xfadeSpan = resources_.bufferPool.getBuffer(numSamples); if (!tempSpan || !xfadeSpan) return; @@ -355,8 +661,8 @@ void sfz::Voice::applyCrossfades(absl::Span modulationSpan) noexcept fill(*xfadeSpan, 1.0f); bool canShortcut = true; - for (const auto& mod : region->crossfadeCCInRange) { - const auto& events = resources.midiState.getCCEvents(mod.cc); + for (const auto& mod : region_->crossfadeCCInRange) { + const auto& events = resources_.midiState.getCCEvents(mod.cc); canShortcut &= (events.size() == 1); linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.data, x, xfCurve); @@ -364,8 +670,8 @@ void sfz::Voice::applyCrossfades(absl::Span modulationSpan) noexcept applyGain(*tempSpan, *xfadeSpan); } - for (const auto& mod : region->crossfadeCCOutRange) { - const auto& events = resources.midiState.getCCEvents(mod.cc); + for (const auto& mod : region_->crossfadeCCOutRange) { + const auto& events = resources_.midiState.getCCEvents(mod.cc); canShortcut &= (events.size() == 1); linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.data, x, xfCurve); @@ -373,48 +679,48 @@ void sfz::Voice::applyCrossfades(absl::Span modulationSpan) noexcept applyGain(*tempSpan, *xfadeSpan); } - xfadeSmoother.process(*xfadeSpan, *xfadeSpan, canShortcut); + xfadeSmoother_.process(*xfadeSpan, *xfadeSpan, canShortcut); applyGain(*xfadeSpan, modulationSpan); } -void sfz::Voice::amplitudeEnvelope(absl::Span modulationSpan) noexcept +void Voice::Impl::amplitudeEnvelope(absl::Span modulationSpan) noexcept { const auto numSamples = modulationSpan.size(); - ModMatrix& mm = resources.modMatrix; + ModMatrix& mm = resources_.modMatrix; // Amplitude EG - absl::Span ampegOut(mm.getModulation(masterAmplitudeTarget), numSamples); + absl::Span ampegOut(mm.getModulation(masterAmplitudeTarget_), numSamples); ASSERT(ampegOut.data()); copy(ampegOut, modulationSpan); // Amplitude envelope - applyGain1(baseGain, modulationSpan); - if (float* mod = mm.getModulation(amplitudeTarget)) { + applyGain1(baseGain_, modulationSpan); + if (float* mod = mm.getModulation(amplitudeTarget_)) { for (size_t i = 0; i < numSamples; ++i) modulationSpan[i] *= normalizePercents(mod[i]); } // Volume envelope - applyGain1(db2mag(baseVolumedB), modulationSpan); - if (float* mod = mm.getModulation(volumeTarget)) { + applyGain1(db2mag(baseVolumedB_), modulationSpan); + if (float* mod = mm.getModulation(volumeTarget_)) { for (size_t i = 0; i < numSamples; ++i) modulationSpan[i] *= db2mag(mod[i]); } // Smooth the gain transitions - gainSmoother.process(modulationSpan, modulationSpan); + gainSmoother_.process(modulationSpan, modulationSpan); } -void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept +void Voice::Impl::ampStageMono(AudioSpan buffer) noexcept { - ScopedTiming logger { amplitudeDuration }; + ScopedTiming logger { amplitudeDuration_ }; const auto numSamples = buffer.getNumFrames(); const auto leftBuffer = buffer.getSpan(0); - auto modulationSpan = resources.bufferPool.getBuffer(numSamples); + auto modulationSpan = resources_.bufferPool.getBuffer(numSamples); if (!modulationSpan) return; @@ -423,12 +729,12 @@ void sfz::Voice::ampStageMono(AudioSpan buffer) noexcept applyGain(*modulationSpan, leftBuffer); } -void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept +void Voice::Impl::ampStageStereo(AudioSpan buffer) noexcept { - ScopedTiming logger { amplitudeDuration }; + ScopedTiming logger { amplitudeDuration_ }; const auto numSamples = buffer.getNumFrames(); - auto modulationSpan = resources.bufferPool.getBuffer(numSamples); + auto modulationSpan = resources_.bufferPool.getBuffer(numSamples); if (!modulationSpan) return; @@ -437,63 +743,63 @@ void sfz::Voice::ampStageStereo(AudioSpan buffer) noexcept buffer.applyGain(*modulationSpan); } -void sfz::Voice::panStageMono(AudioSpan buffer) noexcept +void Voice::Impl::panStageMono(AudioSpan buffer) noexcept { - ScopedTiming logger { panningDuration }; + ScopedTiming logger { panningDuration_ }; const auto numSamples = buffer.getNumFrames(); const auto leftBuffer = buffer.getSpan(0); const auto rightBuffer = buffer.getSpan(1); - auto modulationSpan = resources.bufferPool.getBuffer(numSamples); + auto modulationSpan = resources_.bufferPool.getBuffer(numSamples); if (!modulationSpan) return; - ModMatrix& mm = resources.modMatrix; + ModMatrix& mm = resources_.modMatrix; // Prepare for stereo output copy(leftBuffer, rightBuffer); // Apply panning - fill(*modulationSpan, region->pan); - if (float* mod = mm.getModulation(panTarget)) { + fill(*modulationSpan, region_->pan); + if (float* mod = mm.getModulation(panTarget_)) { for (size_t i = 0; i < numSamples; ++i) (*modulationSpan)[i] += normalizePercents(mod[i]); } pan(*modulationSpan, leftBuffer, rightBuffer); } -void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept +void Voice::Impl::panStageStereo(AudioSpan buffer) noexcept { - ScopedTiming logger { panningDuration }; + ScopedTiming logger { panningDuration_ }; const auto numSamples = buffer.getNumFrames(); const auto leftBuffer = buffer.getSpan(0); const auto rightBuffer = buffer.getSpan(1); - auto modulationSpan = resources.bufferPool.getBuffer(numSamples); + auto modulationSpan = resources_.bufferPool.getBuffer(numSamples); if (!modulationSpan) return; - ModMatrix& mm = resources.modMatrix; + ModMatrix& mm = resources_.modMatrix; // Apply panning - fill(*modulationSpan, region->pan); - if (float* mod = mm.getModulation(panTarget)) { + fill(*modulationSpan, region_->pan); + if (float* mod = mm.getModulation(panTarget_)) { for (size_t i = 0; i < numSamples; ++i) (*modulationSpan)[i] += normalizePercents(mod[i]); } pan(*modulationSpan, leftBuffer, rightBuffer); // Apply the width/position process - fill(*modulationSpan, region->width); - if (float* mod = mm.getModulation(widthTarget)) { + fill(*modulationSpan, region_->width); + if (float* mod = mm.getModulation(widthTarget_)) { for (size_t i = 0; i < numSamples; ++i) (*modulationSpan)[i] += normalizePercents(mod[i]); } width(*modulationSpan, leftBuffer, rightBuffer); - fill(*modulationSpan, region->position); - if (float* mod = mm.getModulation(positionTarget)) { + fill(*modulationSpan, region_->position); + if (float* mod = mm.getModulation(positionTarget_)) { for (size_t i = 0; i < numSamples; ++i) (*modulationSpan)[i] += normalizePercents(mod[i]); } @@ -504,25 +810,25 @@ void sfz::Voice::panStageStereo(AudioSpan buffer) noexcept applyGain1(1.4125375446227544f, rightBuffer); } -void sfz::Voice::filterStageMono(AudioSpan buffer) noexcept +void Voice::Impl::filterStageMono(AudioSpan buffer) noexcept { - ScopedTiming logger { filterDuration }; + ScopedTiming logger { filterDuration_ }; const auto numSamples = buffer.getNumFrames(); const auto leftBuffer = buffer.getSpan(0); const float* inputChannel[1] { leftBuffer.data() }; float* outputChannel[1] { leftBuffer.data() }; - for (unsigned i = 0; i < region->filters.size(); ++i) { - filters[i].process(inputChannel, outputChannel, numSamples); + for (unsigned i = 0; i < region_->filters.size(); ++i) { + filters_[i].process(inputChannel, outputChannel, numSamples); } - for (unsigned i = 0; i < region->equalizers.size(); ++i) { - equalizers[i].process(inputChannel, outputChannel, numSamples); + for (unsigned i = 0; i < region_->equalizers.size(); ++i) { + equalizers_[i].process(inputChannel, outputChannel, numSamples); } } -void sfz::Voice::filterStageStereo(AudioSpan buffer) noexcept +void Voice::Impl::filterStageStereo(AudioSpan buffer) noexcept { - ScopedTiming logger { filterDuration }; + ScopedTiming logger { filterDuration_ }; const auto numSamples = buffer.getNumFrames(); const auto leftBuffer = buffer.getSpan(0); const auto rightBuffer = buffer.getSpan(1); @@ -530,52 +836,52 @@ void sfz::Voice::filterStageStereo(AudioSpan buffer) noexcept const float* inputChannels[2] { leftBuffer.data(), rightBuffer.data() }; float* outputChannels[2] { leftBuffer.data(), rightBuffer.data() }; - for (unsigned i = 0; i < region->filters.size(); ++i) { - filters[i].process(inputChannels, outputChannels, numSamples); + for (unsigned i = 0; i < region_->filters.size(); ++i) { + filters_[i].process(inputChannels, outputChannels, numSamples); } - for (unsigned i = 0; i < region->equalizers.size(); ++i) { - equalizers[i].process(inputChannels, outputChannels, numSamples); + for (unsigned i = 0; i < region_->equalizers.size(); ++i) { + equalizers_[i].process(inputChannels, outputChannels, numSamples); } } -void sfz::Voice::fillWithData(AudioSpan buffer) noexcept +void Voice::Impl::fillWithData(AudioSpan buffer) noexcept { const auto numSamples = buffer.getNumFrames(); if (numSamples == 0) return; - if (!currentPromise) { + if (!currentPromise_) { DBG("[Voice] Missing promise during fillWithData"); return; } - auto source = currentPromise->getData(); + auto source = currentPromise_->getData(); // calculate interpolation data // indices: integral position in the source audio // coeffs: fractional position normalized 0-1 - auto coeffs = resources.bufferPool.getBuffer(numSamples); - auto indices = resources.bufferPool.getIndexBuffer(numSamples); + auto coeffs = resources_.bufferPool.getBuffer(numSamples); + auto indices = resources_.bufferPool.getIndexBuffer(numSamples); if (!indices || !coeffs) return; { - auto jumps = resources.bufferPool.getBuffer(numSamples); + auto jumps = resources_.bufferPool.getBuffer(numSamples); if (!jumps) return; - fill(*jumps, pitchRatio * speedRatio); + fill(*jumps, pitchRatio_ * speedRatio_); pitchEnvelope(*jumps); - jumps->front() += floatPositionOffset; + jumps->front() += floatPositionOffset_; cumsum(*jumps, *jumps); sfzInterpolationCast(*jumps, *indices, *coeffs); - add1(sourcePosition, *indices); + add1(sourcePosition_, *indices); } // calculate loop characteristics - const auto loop = this->loop; - const bool isLooping = region->shouldLoop() + const auto loop = this->loop_; + const bool isLooping = region_->shouldLoop() && (static_cast(loop.end) < source.getNumFrames()); /* @@ -606,7 +912,7 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept numPartitions = 1; } else { for (auto& buf : partitionBuffers) { - buf = resources.bufferPool.getIndexBuffer(numSamples); + buf = resources_.bufferPool.getIndexBuffer(numSamples); if (!buf) return; } @@ -647,27 +953,27 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept else { // cut short the voice at the instant of reaching end of sample const auto sampleEnd = min( - static_cast(currentPromise->information.end), + static_cast(currentPromise_->information.end), static_cast(source.getNumFrames()) ) - 1; for (unsigned i = 0; i < numSamples; ++i) { if ((*indices)[i] >= sampleEnd) { #ifndef NDEBUG // Check for underflow - if (source.getNumFrames() - 1 < currentPromise->information.end) { + if (source.getNumFrames() - 1 < currentPromise_->information.end) { DBG("[sfizz] Underflow: source available samples " << source.getNumFrames() << "/" - << currentPromise->information.end - << " for sample " << *region->sampleId); + << currentPromise_->information.end + << " for sample " << *region_->sampleId); } #endif - if (!region->flexAmpEG) { - egAmplitude.setReleaseTime(0.0f); - egAmplitude.startRelease(i); + if (!region_->flexAmpEG) { + egAmplitude_.setReleaseTime(0.0f); + egAmplitude_.startRelease(i); } else { // TODO(jpc): Flex AmpEG - flexEGs[*region->flexAmpEG]->release(i); + flexEGs_[*region_->flexAmpEG]->release(i); } fill(indices->subspan(i), sampleEnd); fill(coeffs->subspan(i), 1.0f); @@ -695,9 +1001,9 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept source, ptBuffer, ptIndices, ptCoeffs, {}, quality); if (ptType == kPartitionLoopXfade) { - auto xfTemp1 = resources.bufferPool.getBuffer(numSamples); - auto xfTemp2 = resources.bufferPool.getBuffer(numSamples); - auto xfIndicesTemp = resources.bufferPool.getIndexBuffer(numSamples); + auto xfTemp1 = resources_.bufferPool.getBuffer(numSamples); + auto xfTemp2 = resources_.bufferPool.getBuffer(numSamples); + auto xfIndicesTemp = resources_.bufferPool.getIndexBuffer(numSamples); if (!xfTemp1 || !xfTemp2 || !xfIndicesTemp) return; @@ -721,7 +1027,7 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept xfCurve[i] = xfIn.evalNormalized(1.0f - xfCurvePos[i]); } else IF_CONSTEXPR (config::loopXfadeCurve == 1) { - const Curve& xfOut = resources.curves.getCurve(6); + const Curve& xfOut = resources_.curves.getCurve(6); for (unsigned i = 0; i < ptSize; ++i) xfCurve[i] = xfOut.evalNormalized(xfCurvePos[i]); } @@ -771,7 +1077,7 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept xfCurve[i] = xfIn.evalNormalized(xfInCurvePos[i]); } else IF_CONSTEXPR (config::loopXfadeCurve == 1) { - const Curve& xfIn = resources.curves.getCurve(5); + const Curve& xfIn = resources_.curves.getCurve(5); for (unsigned i = 0; i < applySize; ++i) xfCurve[i] = xfIn.evalNormalized(xfInCurvePos[i]); } @@ -787,8 +1093,8 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept } } - sourcePosition = indices->back(); - floatPositionOffset = coeffs->back(); + sourcePosition_ = indices->back(); + floatPositionOffset_ = coeffs->back(); #if 1 ASSERT(!hasNanInf(buffer.getConstSpan(0))); @@ -798,9 +1104,9 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept #endif } -template -void sfz::Voice::fillInterpolated( - const sfz::AudioSpan& source, const sfz::AudioSpan& dest, +template +void Voice::Impl::fillInterpolated( + const AudioSpan& source, const AudioSpan& dest, absl::Span indices, absl::Span coeffs, absl::Span addingGains) { @@ -811,7 +1117,7 @@ void sfz::Voice::fillInterpolated( auto left = dest.getChannel(0); if (source.getNumChannels() == 1) { while (ind < indices.end()) { - auto output = sfz::interpolate(&leftSource[*ind], *coeff); + auto output = interpolate(&leftSource[*ind], *coeff); IF_CONSTEXPR(Adding) { float g = *addingGain++; *left += g * output; @@ -824,8 +1130,8 @@ void sfz::Voice::fillInterpolated( auto right = dest.getChannel(1); auto rightSource = source.getConstSpan(1); while (ind < indices.end()) { - auto leftOutput = sfz::interpolate(&leftSource[*ind], *coeff); - auto rightOutput = sfz::interpolate(&rightSource[*ind], *coeff); + auto leftOutput = interpolate(&leftSource[*ind], *coeff); + auto rightOutput = interpolate(&rightSource[*ind], *coeff); IF_CONSTEXPR(Adding) { float g = *addingGain++; *left += g * leftOutput; @@ -841,8 +1147,8 @@ void sfz::Voice::fillInterpolated( } template -void sfz::Voice::fillInterpolatedWithQuality( - const sfz::AudioSpan& source, const sfz::AudioSpan& dest, +void Voice::Impl::fillInterpolatedWithQuality( + const AudioSpan& source, const AudioSpan& dest, absl::Span indices, absl::Span coeffs, absl::Span addingGains, int quality) { @@ -872,7 +1178,7 @@ void sfz::Voice::fillInterpolatedWithQuality( } } -const sfz::Curve& sfz::Voice::getSCurve() +const Curve& Voice::Impl::getSCurve() { static const Curve curve = []() -> Curve { constexpr unsigned N = Curve::NumValues; @@ -886,51 +1192,51 @@ const sfz::Curve& sfz::Voice::getSCurve() return curve; } -void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept +void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept { const auto leftSpan = buffer.getSpan(0); const auto rightSpan = buffer.getSpan(1); - if (region->sampleId->filename() == "*noise") { + if (region_->sampleId->filename() == "*noise") { auto gen = [&]() { - return uniformNoiseDist(Random::randomGenerator); + return uniformNoiseDist_(Random::randomGenerator); }; absl::c_generate(leftSpan, gen); absl::c_generate(rightSpan, gen); - } else if (region->sampleId->filename() == "*gnoise") { + } else if (region_->sampleId->filename() == "*gnoise") { // You need to wrap in a lambda, otherwise generate will // make a copy of the gaussian distribution *along with its state* // leading to periodic behavior.... auto gen = [&]() { - return gaussianNoiseDist(); + return gaussianNoiseDist_(); }; absl::c_generate(leftSpan, gen); absl::c_generate(rightSpan, gen); } else { const auto numFrames = buffer.getNumFrames(); - auto frequencies = resources.bufferPool.getBuffer(numFrames); + auto frequencies = resources_.bufferPool.getBuffer(numFrames); if (!frequencies) return; - float keycenterFrequency = midiNoteFrequency(region->pitchKeycenter); - fill(*frequencies, pitchRatio * keycenterFrequency); + float keycenterFrequency = midiNoteFrequency(region_->pitchKeycenter); + fill(*frequencies, pitchRatio_ * keycenterFrequency); pitchEnvelope(*frequencies); - auto detuneSpan = resources.bufferPool.getBuffer(numFrames); + auto detuneSpan = resources_.bufferPool.getBuffer(numFrames); if (!detuneSpan) return; - const int oscillatorMode = region->oscillatorMode; - const int oscillatorMulti = region->oscillatorMulti; + const int oscillatorMode = region_->oscillatorMode; + const int oscillatorMulti = region_->oscillatorMulti; if (oscillatorMode <= 0 && oscillatorMulti < 2) { // single oscillator - auto tempSpan = resources.bufferPool.getBuffer(numFrames); + auto tempSpan = resources_.bufferPool.getBuffer(numFrames); if (!tempSpan) return; - WavetableOscillator& osc = waveOscillators[0]; + WavetableOscillator& osc = waveOscillators_[0]; fill(*detuneSpan, 1.0f); osc.processModulated(frequencies->data(), detuneSpan->data(), tempSpan->data(), buffer.getNumFrames()); copy(*tempSpan, leftSpan); @@ -938,30 +1244,30 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept } else if (oscillatorMode <= 0 && oscillatorMulti >= 3) { // unison oscillator - auto tempSpan = resources.bufferPool.getBuffer(numFrames); - auto tempLeftSpan = resources.bufferPool.getBuffer(numFrames); - auto tempRightSpan = resources.bufferPool.getBuffer(numFrames); + auto tempSpan = resources_.bufferPool.getBuffer(numFrames); + auto tempLeftSpan = resources_.bufferPool.getBuffer(numFrames); + auto tempRightSpan = resources_.bufferPool.getBuffer(numFrames); if (!tempSpan || !tempLeftSpan || !tempRightSpan) return; - const float* detuneMod = resources.modMatrix.getModulation(oscillatorDetuneTarget); - for (unsigned u = 0, uSize = waveUnisonSize; u < uSize; ++u) { - WavetableOscillator& osc = waveOscillators[u]; + const float* detuneMod = resources_.modMatrix.getModulation(oscillatorDetuneTarget_); + for (unsigned u = 0, uSize = waveUnisonSize_; u < uSize; ++u) { + WavetableOscillator& osc = waveOscillators_[u]; if (!detuneMod) - fill(*detuneSpan, waveDetuneRatio[u]); + fill(*detuneSpan, waveDetuneRatio_[u]); else { for (size_t i = 0; i < numFrames; ++i) (*detuneSpan)[i] = centsFactor(detuneMod[i]); - applyGain1(waveDetuneRatio[u], *detuneSpan); + applyGain1(waveDetuneRatio_[u], *detuneSpan); } osc.processModulated(frequencies->data(), detuneSpan->data(), tempSpan->data(), numFrames); if (u == 0) { - applyGain1(waveLeftGain[u], *tempSpan, *tempLeftSpan); - applyGain1(waveRightGain[u], *tempSpan, *tempRightSpan); + applyGain1(waveLeftGain_[u], *tempSpan, *tempLeftSpan); + applyGain1(waveRightGain_[u], *tempSpan, *tempRightSpan); } else { - multiplyAdd1(waveLeftGain[u], *tempSpan, *tempLeftSpan); - multiplyAdd1(waveRightGain[u], *tempSpan, *tempRightSpan); + multiplyAdd1(waveLeftGain_[u], *tempSpan, *tempLeftSpan); + multiplyAdd1(waveRightGain_[u], *tempSpan, *tempRightSpan); } } @@ -970,39 +1276,39 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept } else { // modulated oscillator - auto tempSpan = resources.bufferPool.getBuffer(numFrames); + auto tempSpan = resources_.bufferPool.getBuffer(numFrames); if (!tempSpan) return; - WavetableOscillator& oscCar = waveOscillators[0]; - WavetableOscillator& oscMod = waveOscillators[1]; + WavetableOscillator& oscCar = waveOscillators_[0]; + WavetableOscillator& oscMod = waveOscillators_[1]; // compute the modulator - auto modulatorSpan = resources.bufferPool.getBuffer(numFrames); + auto modulatorSpan = resources_.bufferPool.getBuffer(numFrames); if (!modulatorSpan) return; - const float* detuneMod = resources.modMatrix.getModulation(oscillatorDetuneTarget); + const float* detuneMod = resources_.modMatrix.getModulation(oscillatorDetuneTarget_); if (!detuneMod) - fill(*detuneSpan, waveDetuneRatio[1]); + fill(*detuneSpan, waveDetuneRatio_[1]); else { for (size_t i = 0; i < numFrames; ++i) (*detuneSpan)[i] = centsFactor(detuneMod[i]); - applyGain1(waveDetuneRatio[1], *detuneSpan); + applyGain1(waveDetuneRatio_[1], *detuneSpan); } oscMod.processModulated(frequencies->data(), detuneSpan->data(), modulatorSpan->data(), numFrames); // scale the modulator - const float oscillatorModDepth = region->oscillatorModDepth; + const float oscillatorModDepth = region_->oscillatorModDepth; if (oscillatorModDepth != 1.0f) applyGain1(oscillatorModDepth, *modulatorSpan); - const float* modDepthMod = resources.modMatrix.getModulation(oscillatorModDepthTarget); + const float* modDepthMod = resources_.modMatrix.getModulation(oscillatorModDepthTarget_); if (modDepthMod) multiplyMul1(0.01f, absl::MakeConstSpan(modDepthMod, numFrames), *modulatorSpan); // compute carrierĂ—modulator - switch (region->oscillatorMode) { + switch (region_->oscillatorMode) { case 0: // RM synthesis default: fill(*detuneSpan, 1.0f); @@ -1036,14 +1342,15 @@ void sfz::Voice::fillWithGenerator(AudioSpan buffer) noexcept #endif } -bool sfz::Voice::checkOffGroup(const Region* other, int delay, int noteNumber) noexcept +bool Voice::checkOffGroup(const Region* other, int delay, int noteNumber) noexcept { - if (region == nullptr || other == nullptr) + Impl& impl = *impl_; + if (impl.region_ == nullptr || other == nullptr) return false; - if (triggerEvent.type == TriggerEventType::NoteOn - && region->offBy == other->group - && (region->group != other->group || noteNumber != triggerEvent.number)) { + if (impl.triggerEvent_.type == TriggerEventType::NoteOn + && impl.region_->offBy == other->group + && (impl.region_->group != other->group || noteNumber != impl.triggerEvent_.number)) { off(delay); return true; } @@ -1051,178 +1358,187 @@ bool sfz::Voice::checkOffGroup(const Region* other, int delay, int noteNumber) n return false; } -void sfz::Voice::reset() noexcept +void Voice::reset() noexcept { - switchState(State::idle); - region = nullptr; - currentPromise.reset(); - sourcePosition = 0; - age = 0; - floatPositionOffset = 0.0f; - noteIsOff = false; + Impl& impl = *impl_; + impl.switchState(State::idle); + impl.region_ = nullptr; + impl.currentPromise_.reset(); + impl.sourcePosition_ = 0; + impl.age_ = 0; + impl.floatPositionOffset_ = 0.0f; + impl.noteIsOff_ = false; - resetLoopInformation(); + impl.resetLoopInformation(); - powerFollower.clear(); + impl.powerFollower_.clear(); - for (auto& filter : filters) + for (auto& filter : impl.filters_) filter.reset(); - for (auto& eq : equalizers) + for (auto& eq : impl.equalizers_) eq.reset(); removeVoiceFromRing(); } -void sfz::Voice::resetLoopInformation() noexcept +void Voice::Impl::resetLoopInformation() noexcept { - loop.start = 0; - loop.end = 0; - loop.size = 0; - loop.xfSize = 0; - loop.xfOutStart = 0; - loop.xfInStart = 0; + loop_.start = 0; + loop_.end = 0; + loop_.size = 0; + loop_.xfSize = 0; + loop_.xfOutStart = 0; + loop_.xfInStart = 0; } -void sfz::Voice::updateLoopInformation() noexcept +void Voice::Impl::updateLoopInformation() noexcept { - if (!region || !currentPromise) + if (!region_ || !currentPromise_) return; - if (!region->shouldLoop()) + if (!region_->shouldLoop()) return; - const auto& info = currentPromise->information; - const auto factor = resources.filePool.getOversamplingFactor(); + const auto& info = currentPromise_->information; + const auto factor = resources_.filePool.getOversamplingFactor(); const auto rate = info.sampleRate; - loop.end = static_cast(region->loopEnd(factor)); - loop.start = static_cast(region->loopStart(factor)); - loop.size = loop.end + 1 - loop.start; - loop.xfSize = static_cast(lroundPositive(region->loopCrossfade * rate)); - loop.xfOutStart = loop.end + 1 - loop.xfSize; - loop.xfInStart = loop.start - loop.xfSize; + loop_.end = static_cast(region_->loopEnd(factor)); + loop_.start = static_cast(region_->loopStart(factor)); + loop_.size = loop_.end + 1 - loop_.start; + loop_.xfSize = static_cast(lroundPositive(region_->loopCrossfade * rate)); + loop_.xfOutStart = loop_.end + 1 - loop_.xfSize; + loop_.xfInStart = loop_.start - loop_.xfSize; } -void sfz::Voice::setNextSisterVoice(Voice* voice) noexcept +void Voice::setNextSisterVoice(Voice* voice) noexcept { // Should never be null ASSERT(voice); - nextSisterVoice = voice; + nextSisterVoice_ = voice; } -void sfz::Voice::setPreviousSisterVoice(Voice* voice) noexcept +void Voice::setPreviousSisterVoice(Voice* voice) noexcept { // Should never be null ASSERT(voice); - previousSisterVoice = voice; + previousSisterVoice_ = voice; } -void sfz::Voice::removeVoiceFromRing() noexcept +void Voice::removeVoiceFromRing() noexcept { - previousSisterVoice->setNextSisterVoice(nextSisterVoice); - nextSisterVoice->setPreviousSisterVoice(previousSisterVoice); - previousSisterVoice = this; - nextSisterVoice = this; + previousSisterVoice_->setNextSisterVoice(nextSisterVoice_); + nextSisterVoice_->setPreviousSisterVoice(previousSisterVoice_); + previousSisterVoice_ = this; + nextSisterVoice_ = this; } -float sfz::Voice::getAveragePower() const noexcept +float Voice::getAveragePower() const noexcept { - if (followPower) - return powerFollower.getAveragePower(); + Impl& impl = *impl_; + if (impl.followPower_) + return impl.powerFollower_.getAveragePower(); else return 0.0f; } -bool sfz::Voice::releasedOrFree() const noexcept +bool Voice::releasedOrFree() const noexcept { - if (state != State::playing) + Impl& impl = *impl_; + if (impl.state_ != State::playing) return true; - if (!region->flexAmpEG) - return egAmplitude.isReleased(); + if (!impl.region_->flexAmpEG) + return impl.egAmplitude_.isReleased(); else - return flexEGs[*region->flexAmpEG]->isReleased(); + return impl.flexEGs_[*impl.region_->flexAmpEG]->isReleased(); } -void sfz::Voice::setMaxFiltersPerVoice(size_t numFilters) +void Voice::setMaxFiltersPerVoice(size_t numFilters) { - if (numFilters == filters.size()) + Impl& impl = *impl_; + if (numFilters == impl.filters_.size()) return; - filters.clear(); + impl.filters_.clear(); for (unsigned i = 0; i < numFilters; ++i) - filters.emplace_back(resources); + impl.filters_.emplace_back(impl.resources_); } -void sfz::Voice::setMaxEQsPerVoice(size_t numFilters) +void Voice::setMaxEQsPerVoice(size_t numFilters) { - if (numFilters == equalizers.size()) + Impl& impl = *impl_; + if (numFilters == impl.equalizers_.size()) return; - equalizers.clear(); + impl.equalizers_.clear(); for (unsigned i = 0; i < numFilters; ++i) - equalizers.emplace_back(resources); + impl.equalizers_.emplace_back(impl.resources_); } -void sfz::Voice::setMaxLFOsPerVoice(size_t numLFOs) +void Voice::setMaxLFOsPerVoice(size_t numLFOs) { - lfos.resize(numLFOs); + Impl& impl = *impl_; + impl.lfos_.resize(numLFOs); for (size_t i = 0; i < numLFOs; ++i) { auto lfo = absl::make_unique(); - lfo->setSampleRate(sampleRate); - lfos[i] = std::move(lfo); + lfo->setSampleRate(impl.sampleRate_); + impl.lfos_[i] = std::move(lfo); } } -void sfz::Voice::setMaxFlexEGsPerVoice(size_t numFlexEGs) +void Voice::setMaxFlexEGsPerVoice(size_t numFlexEGs) { - flexEGs.resize(numFlexEGs); + Impl& impl = *impl_; + impl.flexEGs_.resize(numFlexEGs); for (size_t i = 0; i < numFlexEGs; ++i) { auto eg = absl::make_unique(); - eg->setSampleRate(sampleRate); - flexEGs[i] = std::move(eg); + eg->setSampleRate(impl.sampleRate_); + impl.flexEGs_[i] = std::move(eg); } } -void sfz::Voice::setPitchEGEnabledPerVoice(bool havePitchEG) +void Voice::setPitchEGEnabledPerVoice(bool havePitchEG) { + Impl& impl = *impl_; if (havePitchEG) - egPitch.reset(new ADSREnvelope); + impl.egPitch_.reset(new ADSREnvelope); else - egPitch.reset(); + impl.egPitch_.reset(); } -void sfz::Voice::setFilterEGEnabledPerVoice(bool haveFilterEG) +void Voice::setFilterEGEnabledPerVoice(bool haveFilterEG) { + Impl& impl = *impl_; if (haveFilterEG) - egFilter.reset(new ADSREnvelope); + impl.egFilter_.reset(new ADSREnvelope); else - egFilter.reset(); + impl.egFilter_.reset(); } -void sfz::Voice::setupOscillatorUnison() +void Voice::Impl::setupOscillatorUnison() { - const int m = region->oscillatorMulti; - const float d = region->oscillatorDetune; + const int m = region_->oscillatorMulti; + const float d = region_->oscillatorDetune; // 3-9: unison mode, 1: normal/RM, 2: PM/FM - if (m < 3 || region->oscillatorMode > 0) { - waveUnisonSize = 1; + if (m < 3 || region_->oscillatorMode > 0) { + waveUnisonSize_ = 1; // carrier - waveDetuneRatio[0] = 1.0; - waveLeftGain[0] = 1.0; - waveRightGain[0] = 1.0; + waveDetuneRatio_[0] = 1.0; + waveLeftGain_[0] = 1.0; + waveRightGain_[0] = 1.0; // modulator - const float modDepth = region->oscillatorModDepth; - waveDetuneRatio[1] = centsFactor(d); - waveLeftGain[1] = modDepth; - waveRightGain[1] = modDepth; + const float modDepth = region_->oscillatorModDepth; + waveDetuneRatio_[1] = centsFactor(d); + waveLeftGain_[1] = modDepth; + waveRightGain_[1] = modDepth; return; } // oscillator count, aka. unison size - waveUnisonSize = m; + waveUnisonSize_ = m; // detune (cents) float detunes[config::oscillatorsPerVoice]; @@ -1236,15 +1552,15 @@ void sfz::Voice::setupOscillatorUnison() // detune (ratio) for (int i = 0; i < m; ++i) - waveDetuneRatio[i] = centsFactor(detunes[i]); + waveDetuneRatio_[i] = centsFactor(detunes[i]); // gains - waveLeftGain[0] = 0.0; - waveRightGain[m - 1] = 0.0; + waveLeftGain_[0] = 0.0; + waveRightGain_[m - 1] = 0.0; for (int i = 0; i < m - 1; ++i) { float g = 1.0f - float(i) / float(m - 1); - waveLeftGain[m - 1 - i] = g; - waveRightGain[i] = g; + waveLeftGain_[m - 1 - i] = g; + waveRightGain_[i] = g; } #if 0 @@ -1263,69 +1579,181 @@ void sfz::Voice::setupOscillatorUnison() #endif } -void sfz::Voice::switchState(State s) +void Voice::Impl::switchState(State s) { - if (s != state) { - state = s; - if (stateListener) - stateListener->onVoiceStateChanged(id, s); + if (s != state_) { + state_ = s; + if (stateListener_) + stateListener_->onVoiceStateChanged(id_, s); } } -void sfz::Voice::pitchEnvelope(absl::Span pitchSpan) noexcept +void Voice::Impl::pitchEnvelope(absl::Span pitchSpan) noexcept { const auto numFrames = pitchSpan.size(); - auto bends = resources.bufferPool.getBuffer(numFrames); + auto bends = resources_.bufferPool.getBuffer(numFrames); if (!bends) return; - const auto events = resources.midiState.getPitchEvents(); + const auto events = resources_.midiState.getPitchEvents(); const auto bendLambda = [this](float bend) { - return centsFactor(region->getBendInCents(bend)); + return centsFactor(region_->getBendInCents(bend)); }; - if (region->bendStep > 1) - pitchBendEnvelope(events, *bends, bendLambda, bendStepFactor); + if (region_->bendStep > 1) + pitchBendEnvelope(events, *bends, bendLambda, bendStepFactor_); else pitchBendEnvelope(events, *bends, bendLambda); - bendSmoother.process(*bends, *bends); + bendSmoother_.process(*bends, *bends); applyGain(*bends, pitchSpan); - ModMatrix& mm = resources.modMatrix; + ModMatrix& mm = resources_.modMatrix; - if (float* mod = mm.getModulation(pitchTarget)) { + if (float* mod = mm.getModulation(pitchTarget_)) { for (size_t i = 0; i < numFrames; ++i) pitchSpan[i] *= centsFactor(mod[i]); } } -void sfz::Voice::resetSmoothers() noexcept +void Voice::Impl::resetSmoothers() noexcept { - bendSmoother.reset(1.0f); - gainSmoother.reset(0.0f); + bendSmoother_.reset(1.0f); + gainSmoother_.reset(0.0f); } -void sfz::Voice::saveModulationTargets(const Region* region) noexcept +void Voice::Impl::saveModulationTargets(const Region* region) noexcept { - ModMatrix& mm = resources.modMatrix; - masterAmplitudeTarget = mm.findTarget(ModKey::createNXYZ(ModId::MasterAmplitude, region->getId())); - amplitudeTarget = mm.findTarget(ModKey::createNXYZ(ModId::Amplitude, region->getId())); - volumeTarget = mm.findTarget(ModKey::createNXYZ(ModId::Volume, region->getId())); - panTarget = mm.findTarget(ModKey::createNXYZ(ModId::Pan, region->getId())); - positionTarget = mm.findTarget(ModKey::createNXYZ(ModId::Position, region->getId())); - widthTarget = mm.findTarget(ModKey::createNXYZ(ModId::Width, region->getId())); - pitchTarget = mm.findTarget(ModKey::createNXYZ(ModId::Pitch, region->getId())); - oscillatorDetuneTarget = mm.findTarget(ModKey::createNXYZ(ModId::OscillatorDetune, region->getId())); - oscillatorModDepthTarget = mm.findTarget(ModKey::createNXYZ(ModId::OscillatorModDepth, region->getId())); + ModMatrix& mm = resources_.modMatrix; + masterAmplitudeTarget_ = mm.findTarget(ModKey::createNXYZ(ModId::MasterAmplitude, region->getId())); + amplitudeTarget_ = mm.findTarget(ModKey::createNXYZ(ModId::Amplitude, region->getId())); + volumeTarget_ = mm.findTarget(ModKey::createNXYZ(ModId::Volume, region->getId())); + panTarget_ = mm.findTarget(ModKey::createNXYZ(ModId::Pan, region->getId())); + positionTarget_ = mm.findTarget(ModKey::createNXYZ(ModId::Position, region->getId())); + widthTarget_ = mm.findTarget(ModKey::createNXYZ(ModId::Width, region->getId())); + pitchTarget_ = mm.findTarget(ModKey::createNXYZ(ModId::Pitch, region->getId())); + oscillatorDetuneTarget_ = mm.findTarget(ModKey::createNXYZ(ModId::OscillatorDetune, region->getId())); + oscillatorModDepthTarget_ = mm.findTarget(ModKey::createNXYZ(ModId::OscillatorModDepth, region->getId())); } -void sfz::Voice::enablePowerFollower() noexcept +void Voice::enablePowerFollower() noexcept { - followPower = true; - powerFollower.clear(); + Impl& impl = *impl_; + impl.followPower_ = true; + impl.powerFollower_.clear(); } -void sfz::Voice::disablePowerFollower() noexcept +void Voice::disablePowerFollower() noexcept { - followPower = false; + Impl& impl = *impl_; + impl.followPower_ = false; } + +float Voice::getSampleRate() const noexcept +{ + Impl& impl = *impl_; + return impl.sampleRate_; +} + +int Voice::getSamplesPerBlock() const noexcept +{ + Impl& impl = *impl_; + return impl.samplesPerBlock_; +} + +bool Voice::toBeCleanedUp() const +{ + Impl& impl = *impl_; + return impl.state_ == State::cleanMeUp; +} + +void Voice::setStateListener(StateListener *l) noexcept +{ + Impl& impl = *impl_; + impl.stateListener_ = l; +} + +NumericId Voice::getId() const noexcept +{ + Impl& impl = *impl_; + return impl.id_; +} + +const TriggerEvent& Voice::getTriggerEvent() const noexcept +{ + Impl& impl = *impl_; + return impl.triggerEvent_; +} + +const Region* Voice::getRegion() const noexcept +{ + Impl& impl = *impl_; + return impl.region_; +} + +LFO* Voice::getLFO(size_t index) +{ + Impl& impl = *impl_; + return impl.lfos_[index].get(); +} + +FlexEnvelope* Voice::getFlexEG(size_t index) +{ + Impl& impl = *impl_; + return impl.flexEGs_[index].get(); +} + +int Voice::getAge() const noexcept +{ + Impl& impl = *impl_; + return impl.age_; +} + +Duration Voice::getLastDataDuration() const noexcept +{ + Impl& impl = *impl_; + return impl.dataDuration_; +} + +Duration Voice::getLastAmplitudeDuration() const noexcept +{ + Impl& impl = *impl_; + return impl.amplitudeDuration_; +} + +Duration Voice::getLastFilterDuration() const noexcept +{ + Impl& impl = *impl_; + return impl.filterDuration_; +} + +Duration Voice::getLastPanningDuration() const noexcept +{ + Impl& impl = *impl_; + return impl.panningDuration_; +} + +ADSREnvelope* Voice::getAmplitudeEG() +{ + Impl& impl = *impl_; + return &impl.egAmplitude_; +} + +ADSREnvelope* Voice::getPitchEG() +{ + Impl& impl = *impl_; + return impl.egPitch_.get(); +} + +ADSREnvelope* Voice::getFilterEG() +{ + Impl& impl = *impl_; + return impl.egFilter_.get(); +} + +const TriggerEvent& Voice::getTriggerEvent() +{ + Impl& impl = *impl_; + return impl.triggerEvent_; +} + +} // namespace sfz diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 0503b6d7..c5e55bcb 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -5,24 +5,14 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #pragma once -#include "TriggerEvent.h" -#include "Config.h" #include "ADSREnvelope.h" -#include "HistoricalBuffer.h" +#include "TriggerEvent.h" #include "Region.h" -#include "AudioBuffer.h" #include "Resources.h" -#include "FilterPool.h" -#include "EQPool.h" -#include "Smoothers.h" #include "AudioSpan.h" #include "LeakDetector.h" -#include "OnePoleFilter.h" -#include "PowerFollower.h" #include "utility/NumericId.h" -#include "absl/types/span.h" #include -#include namespace sfz { enum InterpolatorModel : int; @@ -44,16 +34,16 @@ public: * @param midiState */ Voice(int voiceNumber, Resources& resources); - ~Voice(); + Voice(const Voice& other) = delete; + Voice& operator=(const Voice& other) = delete; + Voice(Voice&& other); + Voice& operator=(Voice&& other); /** * @brief Get the unique identifier of this voice in a synth */ - NumericId getId() const noexcept - { - return id; - } + NumericId getId() const noexcept; enum class State { idle, @@ -69,12 +59,12 @@ public: /** * @brief Return true if the voice is to be cleaned up (zombie state) */ - bool toBeCleanedUp() const { return state == State::cleanMeUp; } + bool toBeCleanedUp() const; /** * @brief Sets the listener which is called when the voice state changes. */ - void setStateListener(StateListener *l) noexcept { stateListener = l; } + void setStateListener(StateListener *l) noexcept; /** * @brief Change the sample rate of the voice. This is used to compute all @@ -98,13 +88,13 @@ public: * * @return float */ - float getSampleRate() const noexcept { return sampleRate; } + float getSampleRate() const noexcept; /** * @brief Get the expected block size. * * @return int */ - int getSamplesPerBlock() const noexcept { return samplesPerBlock; } + int getSamplesPerBlock() const noexcept; /** * @brief Start playing a region after a short delay for different triggers (note on, off, cc) @@ -199,7 +189,7 @@ public: * * @return int */ - const TriggerEvent& getTriggerEvent() const noexcept { return triggerEvent; } + const TriggerEvent& getTriggerEvent() const noexcept; /** * @brief Reset the voice to its initial values @@ -232,14 +222,14 @@ public: * * @return Voice* */ - Voice* getNextSisterVoice() const noexcept { return nextSisterVoice; }; + Voice* getNextSisterVoice() const noexcept { return nextSisterVoice_; }; /** * @brief Get the previous sister voice in the ring * * @return Voice* */ - Voice* getPreviousSisterVoice() const noexcept { return previousSisterVoice; }; + Voice* getPreviousSisterVoice() const noexcept { return previousSisterVoice_; }; /** * @brief Get the mean squared power of the last rendered block. This is used @@ -266,19 +256,19 @@ public: * * @return */ - const Region* getRegion() const noexcept { return region; } + const Region* getRegion() const noexcept; /** * @brief Get the LFO designated by the given index * * @param index */ - LFO* getLFO(size_t index) { return lfos[index].get(); } + LFO* getLFO(size_t index); /** * @brief Get the Flex EG designated by the given index * * @param index */ - FlexEnvelope* getFlexEG(size_t index) { return flexEGs[index].get(); } + FlexEnvelope* getFlexEG(size_t index); /** * @brief Set the max number of filters per voice * @@ -337,250 +327,42 @@ public: * * @return */ - int getAge() const noexcept { return age; } + int getAge() const noexcept; - Duration getLastDataDuration() const noexcept { return dataDuration; } - Duration getLastAmplitudeDuration() const noexcept { return amplitudeDuration; } - Duration getLastFilterDuration() const noexcept { return filterDuration; } - Duration getLastPanningDuration() const noexcept { return panningDuration; } + Duration getLastDataDuration() const noexcept; + Duration getLastAmplitudeDuration() const noexcept; + Duration getLastFilterDuration() const noexcept; + Duration getLastPanningDuration() const noexcept; /** * @brief Get the SFZv1 amplitude EG, if existing */ - ADSREnvelope* getAmplitudeEG() { return &egAmplitude; } + ADSREnvelope* getAmplitudeEG(); /** * @brief Get the SFZv1 pitch EG, if existing */ - ADSREnvelope* getPitchEG() { return egPitch.get(); } + ADSREnvelope* getPitchEG(); /** * @brief Get the SFZv1 filter EG, if existing */ - ADSREnvelope* getFilterEG() { return egFilter.get(); } + ADSREnvelope* getFilterEG(); /** * @brief Get the trigger event */ - const TriggerEvent& getTriggerEvent() { return triggerEvent; } + const TriggerEvent& getTriggerEvent(); private: - /** - * @brief Fill a span with data from a file source. This is the first step - * in rendering each block of data. - * - * @param buffer - */ - void fillWithData(AudioSpan buffer) noexcept; - /** - * @brief Fill a span with data from a generator source. This is the first step - * in rendering each block of data. - * - * @param buffer - */ - void fillWithGenerator(AudioSpan buffer) noexcept; - - /** - * @brief Fill a destination with an interpolated source. - * - * @param source the source sample - * @param dest the destination buffer - * @param indices the integral parts of the source positions - * @param coeffs the fractional parts of the source positions - */ - template - static void fillInterpolated( - const AudioSpan& source, const AudioSpan& dest, - absl::Span indices, absl::Span coeffs, - absl::Span addingGains); - - /** - * @brief Fill a destination with an interpolated source, selecting - * interpolation type dynamically by quality level. - * - * @param source the source sample - * @param dest the destination buffer - * @param indices the integral parts of the source positions - * @param coeffs the fractional parts of the source positions - * @param quality the quality level 1-10 - */ - template - static void fillInterpolatedWithQuality( - const AudioSpan& source, const AudioSpan& dest, - absl::Span indices, absl::Span coeffs, - absl::Span addingGains, int quality); - - /** - * @brief Get a S-shaped curve that is applicable to loop crossfading. - */ - static const Curve& getSCurve(); - - /** - * @brief Compute the amplitude envelope, applied as a gain to a mono - * or stereo buffer - * - * @param modulationSpan - */ - void amplitudeEnvelope(absl::Span modulationSpan) noexcept; - - /** - * @brief Apply the crossfade envelope to a span. - * - * @param modulationSpan - */ - void applyCrossfades(absl::Span modulationSpan) noexcept; - void resetCrossfades() noexcept; - - /** - * @brief Amplitude stage for a mono source - * - * @param buffer - */ - void ampStageMono(AudioSpan buffer) noexcept; - /** - * @brief Amplitude stage for a stereo source - * - * @param buffer - */ - void ampStageStereo(AudioSpan buffer) noexcept; - /** - * @brief Amplitude stage for a mono source - * - * @param buffer - */ - void panStageMono(AudioSpan buffer) noexcept; - void panStageStereo(AudioSpan buffer) noexcept; - /** - * @brief Amplitude stage for a mono source - * - * @param buffer - */ - void filterStageMono(AudioSpan buffer) noexcept; - void filterStageStereo(AudioSpan buffer) noexcept; - /** - * @brief Compute the pitch envelope. This envelope is meant to multiply - * the frequency parameter for each sample (which translates to floating - * point intervals for sample-based voices, or phases for generators) - * - * @param pitchSpan - */ - void pitchEnvelope(absl::Span pitchSpan) noexcept; + struct Impl; + std::unique_ptr impl_; /** * @brief Remove the voice from the sister ring * */ void removeVoiceFromRing() noexcept; - - /** - * @brief Initialize frequency and gain coefficients for the oscillators. - */ - void setupOscillatorUnison(); - void updateChannelPowers(AudioSpan buffer); - - /** - * @brief Modify the voice state and notify any listeners. - */ - void switchState(State s); - - /** - * @brief Save the modulation targets to avoid recomputing them in every callback. - * Must be called during startVoice() ideally. - */ - void saveModulationTargets(const Region* region) noexcept; - - const NumericId id; - StateListener* stateListener = nullptr; - - Region* region { nullptr }; - - State state { State::idle }; - bool noteIsOff { false }; - - TriggerEvent triggerEvent; - absl::optional triggerDelay; - - float speedRatio { 1.0 }; - float pitchRatio { 1.0 }; - float baseVolumedB { 0.0 }; - float baseGain { 1.0 }; - float baseFrequency { 440.0 }; - - float floatPositionOffset { 0.0f }; - int sourcePosition { 0 }; - int initialDelay { 0 }; - int age { 0 }; - struct { - int start { 0 }; - int end { 0 }; - int size { 0 }; - int xfSize { 0 }; - int xfOutStart { 0 }; - int xfInStart { 0 }; - } loop; - /** - * @brief Reset the loop information - * - */ - void resetLoopInformation() noexcept; - /** - * @brief Read the loop information data from the region. - * This requires that the region and promise is properly set. - * - */ - void updateLoopInformation() noexcept; - - FileDataHolder currentPromise; - - int samplesPerBlock { config::defaultSamplesPerBlock }; - float sampleRate { config::defaultSampleRate }; - - Resources& resources; - - std::vector filters; - std::vector equalizers; - std::vector> lfos; - std::vector> flexEGs; - - ADSREnvelope egAmplitude; - std::unique_ptr> egPitch; - std::unique_ptr> egFilter; - float bendStepFactor { centsFactor(1) }; - - WavetableOscillator waveOscillators[config::oscillatorsPerVoice]; - - // unison of oscillators - unsigned waveUnisonSize { 0 }; - float waveDetuneRatio[config::oscillatorsPerVoice] {}; - float waveLeftGain[config::oscillatorsPerVoice] {}; - float waveRightGain[config::oscillatorsPerVoice] {}; - - Duration dataDuration; - Duration amplitudeDuration; - Duration panningDuration; - Duration filterDuration; - - Voice* nextSisterVoice { this }; - Voice* previousSisterVoice { this }; - - fast_real_distribution uniformNoiseDist { -config::uniformNoiseBounds, config::uniformNoiseBounds }; - fast_gaussian_generator gaussianNoiseDist { 0.0f, config::noiseVariance }; - - Smoother gainSmoother; - Smoother bendSmoother; - Smoother xfadeSmoother; - void resetSmoothers() noexcept; - - ModMatrix::TargetId masterAmplitudeTarget; - ModMatrix::TargetId amplitudeTarget; - ModMatrix::TargetId volumeTarget; - ModMatrix::TargetId panTarget; - ModMatrix::TargetId positionTarget; - ModMatrix::TargetId widthTarget; - ModMatrix::TargetId pitchTarget; - ModMatrix::TargetId oscillatorDetuneTarget; - ModMatrix::TargetId oscillatorModDepthTarget; - - bool followPower { false }; - PowerFollower powerFollower; + Voice* nextSisterVoice_ { this }; + Voice* previousSisterVoice_ { this }; LEAK_DETECTOR(Voice); }; diff --git a/src/sfizz/VoiceList.h b/src/sfizz/VoiceList.h new file mode 100644 index 00000000..aae6f44f --- /dev/null +++ b/src/sfizz/VoiceList.h @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once + +#include "Voice.h" +#include + +namespace sfz { + +struct VoiceList +{ + /** + * @brief Find the voice which is associated with the given identifier. + * + * @param id + * @return const Voice* + */ + const Voice* getVoiceById(NumericId id) const noexcept + { + const size_t size = list.size(); + + if (size == 0 || !id.valid()) + return nullptr; + + // search a sequence of ordered identifiers with potential gaps + size_t index = static_cast(id.number()); + index = std::min(index, size - 1); + + while (index > 0 && list[index].getId().number() > id.number()) + --index; + + return (list[index].getId() == id) ? &list[index] : nullptr; + } + + Voice* getVoiceById(NumericId id) noexcept + { + return const_cast( + const_cast(this)->getVoiceById(id)); + } + + void reset() + { + for (auto& voice : list) + voice.reset(); + } + + typename std::vector::iterator begin() { return list.begin(); } + typename std::vector::const_iterator cbegin() const { return list.cbegin(); } + typename std::vector::iterator end() { return list.end(); } + typename std::vector::const_iterator cend() const { return list.cend(); } + typename std::vector::reference operator[] (size_t n) { return list[n]; } + typename std::vector::const_reference operator[] (size_t n) const { return list[n]; } + typename std::vector::reference back() { return list.back(); } + typename std::vector::const_reference back() const { return list.back(); } + size_t size() const { return list.size(); } + void clear() { list.clear(); } + void reserve(size_t n) { list.reserve(n); } + template< class... Args > + void emplace_back(Args&&... args) { list.emplace_back(std::forward(args)...); } +private: + std::vector list; +}; + +} // namespace sfz diff --git a/src/sfizz/modulations/sources/ADSREnvelope.cpp b/src/sfizz/modulations/sources/ADSREnvelope.cpp index 57f8ba7d..dfe67317 100644 --- a/src/sfizz/modulations/sources/ADSREnvelope.cpp +++ b/src/sfizz/modulations/sources/ADSREnvelope.cpp @@ -15,16 +15,14 @@ namespace sfz { -ADSREnvelopeSource::ADSREnvelopeSource(Synth &synth) - : synth_(&synth) +ADSREnvelopeSource::ADSREnvelopeSource(VoiceList& list, MidiState& state) + : voiceList_(list), midiState_(state) { } void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) { - Synth& synth = *synth_; - - Voice* voice = synth.getVoiceById(voiceId); + Voice* voice = voiceList_.getVoiceById(voiceId); if (!voice) { ASSERTFALSE; return; @@ -55,17 +53,14 @@ void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId voiceId, return; } - Resources& resources = synth.getResources(); const TriggerEvent& triggerEvent = voice->getTriggerEvent(); const float sampleRate = voice->getSampleRate(); - eg->reset(*desc, *region, resources.midiState, delay, triggerEvent.value, sampleRate); + eg->reset(*desc, *region, midiState_, delay, triggerEvent.value, sampleRate); } void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) { - Synth& synth = *synth_; - - Voice* voice = synth.getVoiceById(voiceId); + Voice* voice = voiceList_.getVoiceById(voiceId); if (!voice) { ASSERTFALSE; return; @@ -96,9 +91,7 @@ void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId voice void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) { - Synth& synth = *synth_; - - Voice* voice = synth.getVoiceById(voiceId); + Voice* voice = voiceList_.getVoiceById(voiceId); if (!voice) { ASSERTFALSE; return; diff --git a/src/sfizz/modulations/sources/ADSREnvelope.h b/src/sfizz/modulations/sources/ADSREnvelope.h index b2644df9..a8835467 100644 --- a/src/sfizz/modulations/sources/ADSREnvelope.h +++ b/src/sfizz/modulations/sources/ADSREnvelope.h @@ -6,19 +6,22 @@ #pragma once #include "../ModGenerator.h" +#include "../../VoiceList.h" +#include "../../MidiState.h" namespace sfz { class Synth; class ADSREnvelopeSource : public ModGenerator { public: - explicit ADSREnvelopeSource(Synth &synth); + explicit ADSREnvelopeSource(VoiceList &synth, MidiState& state); void init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) override; private: - Synth* synth_ = nullptr; + VoiceList& voiceList_; + MidiState& midiState_; }; } // namespace sfz diff --git a/src/sfizz/modulations/sources/FlexEnvelope.cpp b/src/sfizz/modulations/sources/FlexEnvelope.cpp index 13965373..db471ed1 100644 --- a/src/sfizz/modulations/sources/FlexEnvelope.cpp +++ b/src/sfizz/modulations/sources/FlexEnvelope.cpp @@ -14,17 +14,16 @@ namespace sfz { -FlexEnvelopeSource::FlexEnvelopeSource(Synth &synth) - : synth_(&synth) +FlexEnvelopeSource::FlexEnvelopeSource(VoiceList& list) + : voiceList_(list) { } void FlexEnvelopeSource::init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) { - Synth& synth = *synth_; unsigned egIndex = sourceKey.parameters().N; - Voice* voice = synth.getVoiceById(voiceId); + Voice* voice = voiceList_.getVoiceById(voiceId); if (!voice) { ASSERTFALSE; return; @@ -49,10 +48,9 @@ void FlexEnvelopeSource::init(const ModKey& sourceKey, NumericId voiceId, void FlexEnvelopeSource::release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) { - Synth& synth = *synth_; unsigned egIndex = sourceKey.parameters().N; - Voice* voice = synth.getVoiceById(voiceId); + Voice* voice = voiceList_.getVoiceById(voiceId); if (!voice) { ASSERTFALSE; return; @@ -70,10 +68,9 @@ void FlexEnvelopeSource::release(const ModKey& sourceKey, NumericId voice void FlexEnvelopeSource::generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) { - Synth& synth = *synth_; unsigned egIndex = sourceKey.parameters().N; - Voice* voice = synth.getVoiceById(voiceId); + Voice* voice = voiceList_.getVoiceById(voiceId); if (!voice) { ASSERTFALSE; return; diff --git a/src/sfizz/modulations/sources/FlexEnvelope.h b/src/sfizz/modulations/sources/FlexEnvelope.h index c4e50fc4..487efdba 100644 --- a/src/sfizz/modulations/sources/FlexEnvelope.h +++ b/src/sfizz/modulations/sources/FlexEnvelope.h @@ -6,19 +6,20 @@ #pragma once #include "../ModGenerator.h" +#include "../../VoiceList.h" namespace sfz { class Synth; class FlexEnvelopeSource : public ModGenerator { public: - explicit FlexEnvelopeSource(Synth &synth); + explicit FlexEnvelopeSource(VoiceList& list); void init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) override; private: - Synth* synth_ = nullptr; + VoiceList& voiceList_; }; } // namespace sfz diff --git a/src/sfizz/modulations/sources/LFO.cpp b/src/sfizz/modulations/sources/LFO.cpp index 2f06c34f..8a603e43 100644 --- a/src/sfizz/modulations/sources/LFO.cpp +++ b/src/sfizz/modulations/sources/LFO.cpp @@ -14,17 +14,16 @@ namespace sfz { -LFOSource::LFOSource(Synth &synth) - : synth_(&synth) +LFOSource::LFOSource(VoiceList& list) + : voiceList_(list) { } void LFOSource::init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) { - Synth& synth = *synth_; unsigned lfoIndex = sourceKey.parameters().N; - Voice* voice = synth.getVoiceById(voiceId); + Voice* voice = voiceList_.getVoiceById(voiceId); if (!voice) { ASSERTFALSE; return; @@ -43,10 +42,9 @@ void LFOSource::init(const ModKey& sourceKey, NumericId voiceId, unsigned void LFOSource::generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) { - Synth& synth = *synth_; const unsigned lfoIndex = sourceKey.parameters().N; - Voice* voice = synth.getVoiceById(voiceId); + Voice* voice = voiceList_.getVoiceById(voiceId); if (!voice) { ASSERTFALSE; fill(buffer, 0.0f); diff --git a/src/sfizz/modulations/sources/LFO.h b/src/sfizz/modulations/sources/LFO.h index cf7e702f..02c696c9 100644 --- a/src/sfizz/modulations/sources/LFO.h +++ b/src/sfizz/modulations/sources/LFO.h @@ -6,18 +6,18 @@ #pragma once #include "../ModGenerator.h" - +#include "../../VoiceList.h" namespace sfz { class Synth; class LFOSource : public ModGenerator { public: - explicit LFOSource(Synth &synth); + explicit LFOSource(VoiceList &list); void init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) override; private: - Synth* synth_ = nullptr; + VoiceList& voiceList_; }; } // namespace sfz diff --git a/tests/PolyphonyT.cpp b/tests/PolyphonyT.cpp index c1d891d4..c7541391 100644 --- a/tests/PolyphonyT.cpp +++ b/tests/PolyphonyT.cpp @@ -10,6 +10,10 @@ #include #include "catch2/catch.hpp" +// Need these for the introspection of Synth +#include "sfizz/PolyphonyGroup.h" +#include "sfizz/RegionSet.h" + using namespace Catch::literals; using namespace sfz::literals; diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 868cd7ca..f03efda0 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -14,6 +14,9 @@ using namespace Catch::literals; using namespace sfz::literals; +// Need these for the introspection of Synth +#include "sfizz/Effects.h" + constexpr int blockSize { 256 }; TEST_CASE("[Synth] Play and check active voices") @@ -1357,17 +1360,17 @@ TEST_CASE("[Synth] Initial values of CC") sample=*sine )"); - REQUIRE(synth.getHdccInit(111) == 0.0f); - REQUIRE(synth.getHdccInit(7) == Approx(100.0f / 127)); // default volume - REQUIRE(synth.getHdccInit(10) == 0.5f); // default pan + REQUIRE(synth.getHdcc(111) == 0.0f); + REQUIRE(synth.getHdcc(7) == Approx(100.0f / 127)); // default volume + REQUIRE(synth.getHdcc(10) == 0.5f); // default pan synth.loadSfzString(fs::current_path() / "init_cc.sfz", R"( set_hdcc111=0.1234 set_cc112=77 sample=*sine )"); - REQUIRE(synth.getHdccInit(111) == Approx(0.1234f)); - REQUIRE(synth.getHdccInit(112) == Approx(77.0f / 127)); + REQUIRE(synth.getHdcc(111) == Approx(0.1234f)); + REQUIRE(synth.getHdcc(112) == Approx(77.0f / 127)); } TEST_CASE("[Synth] Default ampeg_release")