diff --git a/plugins/common/plugin/InstrumentDescription.cpp b/plugins/common/plugin/InstrumentDescription.cpp index 071655b1..8e396c0d 100644 --- a/plugins/common/plugin/InstrumentDescription.cpp +++ b/plugins/common/plugin/InstrumentDescription.cpp @@ -88,6 +88,8 @@ std::string getDescriptionBlob(sfizz_synth_t* handle) cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr); bufferedStrCat(cdata.pathbuf, "/cc", cc, "/default"); cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr); + bufferedStrCat(cdata.pathbuf, "/cc", cc, "/value"); + cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr); } } } @@ -163,6 +165,8 @@ InstrumentDescription parseDescriptionBlob(absl::string_view blob) desc.ccLabel[indices[0]] = args[0].s; else if (Messages::matchOSC("/cc&/default", path, indices) && !strcmp(sig, "f")) desc.ccDefault[indices[0]] = args[0].f; + else if (Messages::matchOSC("/cc&/value", path, indices) && !strcmp(sig, "f")) + desc.ccValue[indices[0]] = args[0].f; src += byteCount; srcSize -= byteCount; diff --git a/plugins/common/plugin/InstrumentDescription.h b/plugins/common/plugin/InstrumentDescription.h index 094d06b3..a78988cd 100644 --- a/plugins/common/plugin/InstrumentDescription.h +++ b/plugins/common/plugin/InstrumentDescription.h @@ -32,6 +32,7 @@ struct InstrumentDescription { std::array keyswitchLabel {}; std::array ccLabel {}; std::array ccDefault {}; + std::array ccValue {}; }; /** diff --git a/plugins/lv2/sfizz.cpp b/plugins/lv2/sfizz.cpp index 8b8a60d8..a4f097e5 100644 --- a/plugins/lv2/sfizz.cpp +++ b/plugins/lv2/sfizz.cpp @@ -1388,10 +1388,10 @@ sfizz_lv2_update_sfz_info(sfizz_plugin_t *self) for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) { if (desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc)) { // Mark all the used CCs for automation with default values - self->ccauto[cc] = desc.ccDefault[cc]; + self->ccauto[cc] = desc.ccValue[cc]; self->have_ccauto = true; // Update the current CCs - self->cc_current[cc] = desc.ccDefault[cc]; + self->cc_current[cc] = desc.ccValue[cc]; } } @@ -1476,7 +1476,7 @@ restore(LV2_Handle instance, if (path) { - strncpy(self->sfz_file_path, path, MAX_PATH_SIZE); + strncpy(self->sfz_file_path, path, MAX_PATH_SIZE - 1); self->sfz_file_path[MAX_PATH_SIZE - 1] = '\0'; if (map_path) @@ -1497,7 +1497,7 @@ restore(LV2_Handle instance, if (path) { - strncpy(self->scala_file_path, path, MAX_PATH_SIZE); + strncpy(self->scala_file_path, path, MAX_PATH_SIZE - 1); self->scala_file_path[MAX_PATH_SIZE - 1] = '\0'; if (map_path) diff --git a/plugins/vst/SfizzVstProcessor.cpp b/plugins/vst/SfizzVstProcessor.cpp index 4a78bc46..3457fcac 100644 --- a/plugins/vst/SfizzVstProcessor.cpp +++ b/plugins/vst/SfizzVstProcessor.cpp @@ -113,7 +113,7 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context) _synth->setBroadcastCallback(onMessage, this); _currentStretchedTuning = 0.0; - loadSfzFileOrDefault({}, false); + loadSfzFileOrDefault({}); _synth->bpmTempo(0, 120); _timeSigNumerator = 4; @@ -220,7 +220,7 @@ void SfizzVstProcessor::syncStateToSynth() if (!synth) return; - loadSfzFileOrDefault(_state.sfzFile, true); + loadSfzFileOrDefault(_state.sfzFile); synth->setVolume(_state.volume); synth->setNumVoices(_state.numVoices); synth->setOversamplingFactor(1 << _state.oversamplingLog2); @@ -590,7 +590,7 @@ tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message) std::unique_lock lock(_processMutex); _state.sfzFile.assign(static_cast(data), size); - loadSfzFileOrDefault(_state.sfzFile, false); + loadSfzFileOrDefault(_state.sfzFile); lock.unlock(); } else if (!std::strcmp(id, "LoadScala")) { @@ -705,7 +705,7 @@ void SfizzVstProcessor::receiveOSC(int delay, const char* path, const char* sig, } } -void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool initParametersFromState) +void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath) { sfz::Sfizz& synth = *_synth; @@ -725,16 +725,7 @@ void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool i const InstrumentDescription desc = parseDescriptionBlob(descBlob); for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) { if (desc.ccUsed.test(cc)) - newControllers[cc] = desc.ccDefault[cc]; - } - // set CC from existing state - if (initParametersFromState) { - for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) { - if (absl::optional value = oldControllers[cc]) { - newControllers[cc] = *value; - synth.automateHdcc(0, int(cc), *value); - } - } + newControllers[cc] = desc.ccValue[cc]; } _state.controllers = std::move(newControllers); } @@ -838,7 +829,7 @@ void SfizzVstProcessor::doBackgroundIdle(size_t idleCounter) if (_synth->shouldReloadFile()) { fprintf(stderr, "[Sfizz] sfz file has changed, reloading\n"); std::lock_guard lock(_processMutex); - loadSfzFileOrDefault(_state.sfzFile, false); + loadSfzFileOrDefault(_state.sfzFile); } if (_synth->shouldReloadScala()) { fprintf(stderr, "[Sfizz] scala file has changed, reloading\n"); diff --git a/plugins/vst/SfizzVstProcessor.h b/plugins/vst/SfizzVstProcessor.h index 0f983036..63cfabe4 100644 --- a/plugins/vst/SfizzVstProcessor.h +++ b/plugins/vst/SfizzVstProcessor.h @@ -86,7 +86,7 @@ private: void receiveOSC(int delay, const char* path, const char* sig, const sfizz_arg_t* args); // misc - void loadSfzFileOrDefault(const std::string& filePath, bool initParametersFromState); + void loadSfzFileOrDefault(const std::string& filePath); // note event tracking std::array _noteEventsCurrentCycle; // 0: off, >0: on, <0: no change diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index cce2941f..35dd61b9 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -320,6 +320,7 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce preloadedFiles[fileId].information.maxOffset = maxOffset; preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad); } + existingFile->second.preloadCallCount++; } else { fileInformation->sampleRate = static_cast(reader->sampleRate()); auto insertedPair = preloadedFiles.insert_or_assign(fileId, { @@ -327,14 +328,30 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce *fileInformation }); - if (!insertedPair.second) - return false; - insertedPair.first->second.status = FileData::Status::Preloaded; + insertedPair.first->second.preloadCallCount++; } + return true; } +void sfz::FilePool::resetPreloadCallCounts() noexcept +{ + for (auto& preloadedFile: preloadedFiles) + preloadedFile.second.preloadCallCount = 0; +} + +void sfz::FilePool::removeUnusedPreloadedData() noexcept +{ + for (auto it = preloadedFiles.begin(), end = preloadedFiles.end(); it != end; ) { + auto copyIt = it++; + if (copyIt->second.preloadCallCount == 0) { + DBG("[sfizz] Removing unused preloaded data: " << copyIt->first.filename()); + preloadedFiles.erase(copyIt); + } + } +} + sfz::FileDataHolder sfz::FilePool::loadFile(const FileId& fileId) noexcept { auto fileInformation = getFileInformation(fileId); @@ -363,7 +380,7 @@ sfz::FileDataHolder sfz::FilePool::getFilePromise(const std::shared_ptr& { const auto preloaded = preloadedFiles.find(*fileId); if (preloaded == preloadedFiles.end()) { - DBG("[sfizz] File not found in the preloaded files: " << fileId); + DBG("[sfizz] File not found in the preloaded files: " << fileId->filename()); return {}; } QueuedFileData queuedData { fileId, &preloaded->second, std::chrono::high_resolution_clock::now() }; diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index a219b46c..48a75f97 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -109,6 +109,7 @@ struct FileData FileAudioBuffer preloadedData; FileInformation information; FileAudioBuffer fileData {}; + int preloadCallCount { 0 }; std::atomic status { Status::Invalid }; std::atomic availableFrames { 0 }; std::atomic readerCount { 0 }; @@ -258,6 +259,17 @@ public: * */ void clear(); + + /** + * @brief Reset the number of preloadFile counts for each sample. + */ + void resetPreloadCallCounts() noexcept; + + /** + * @brief Clear all files with a preload call count of 0. + */ + void removeUnusedPreloadedData() noexcept; + /** * @brief Get a handle on a file, which triggers background loading * diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 4d0d45eb..53419911 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -10,7 +10,8 @@ sfz::MidiState::MidiState() { - reset(); + resetEventStates(); + resetNoteStates(); } void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noexcept @@ -213,11 +214,36 @@ float sfz::MidiState::getCCValueAt(int ccNumber, int delay) const noexcept return ccEvents[ccNumber].back().value; } -void sfz::MidiState::reset() noexcept +void sfz::MidiState::resetNoteStates() noexcept { for (auto& velocity: lastNoteVelocities) velocity = 0.0f; + velocityOverride = 0.0f; + activeNotes = 0; + internalClock = 0; + lastNotePlayed = 0; + alternate = 0.0f; + + auto setEvents = [] (EventVector& events, float value) { + events.clear(); + events.push_back({ 0, value }); + }; + + setEvents(ccEvents[ExtendedCCs::noteOnVelocity], 0.0f); + setEvents(ccEvents[ExtendedCCs::keyboardNoteNumber], 0.0f); + setEvents(ccEvents[ExtendedCCs::unipolarRandom], 0.0f); + setEvents(ccEvents[ExtendedCCs::bipolarRandom], 0.0f); + setEvents(ccEvents[ExtendedCCs::keyboardNoteGate], 0.0f); + setEvents(ccEvents[ExtendedCCs::alternate], 0.0f); + + noteStates.reset(); + absl::c_fill(noteOnTimes, 0); + absl::c_fill(noteOffTimes, 0); +} + +void sfz::MidiState::resetEventStates() noexcept +{ auto clearEvents = [] (EventVector& events) { events.clear(); events.push_back({ 0, 0.0f }); @@ -231,14 +257,6 @@ void sfz::MidiState::reset() noexcept clearEvents(pitchEvents); clearEvents(channelAftertouchEvents); - - velocityOverride = 0.0f; - activeNotes = 0; - internalClock = 0; - lastNotePlayed = 0; - noteStates.reset(); - absl::c_fill(noteOnTimes, 0); - absl::c_fill(noteOffTimes, 0); } const sfz::EventVector& sfz::MidiState::getCCEvents(int ccIdx) const noexcept diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index eaf947c6..023ad800 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -184,15 +184,20 @@ public: float getCCValueAt(int ccNumber, int delay) const noexcept; /** - * @brief Reset the midi state (does not impact the last note on time) + * @brief Reset the midi note states * */ - void reset() noexcept; + void resetNoteStates() noexcept; const EventVector& getCCEvents(int ccIdx) const noexcept; const EventVector& getPolyAftertouchEvents(int noteNumber) const noexcept; const EventVector& getPitchEvents() const noexcept; const EventVector& getChannelAftertouchEvents() const noexcept; + /** + * @brief Reset the midi event states (CC, AT, and pitch bend) + * + */ + void resetEventStates() noexcept; private: diff --git a/src/sfizz/Resources.cpp b/src/sfizz/Resources.cpp index 8770e60f..1e52ccc7 100644 --- a/src/sfizz/Resources.cpp +++ b/src/sfizz/Resources.cpp @@ -61,19 +61,25 @@ void Resources::setSamplesPerBlock(int samplesPerBlock) impl.beatClock.setSamplesPerBlock(samplesPerBlock); } -void Resources::clear() +void Resources::clearNonState() { Impl& impl = *impl_; impl.curves = CurveSet::createPredefined(); impl.filePool.clear(); impl.wavePool.clearFileWaves(); impl.logger.clear(); - impl.midiState.reset(); impl.modMatrix.clear(); - impl.beatClock.clear(); impl.metronome.clear(); } +void Resources::clearState() +{ + Impl& impl = *impl_; + impl.midiState.resetNoteStates(); + impl.midiState.resetEventStates(); + impl.beatClock.clear(); +} + const SynthConfig& Resources::getSynthConfig() const noexcept { return impl_->synthConfig; diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h index b1928a8c..86cfe8ee 100644 --- a/src/sfizz/Resources.h +++ b/src/sfizz/Resources.h @@ -31,7 +31,17 @@ public: void setSampleRate(float samplerate); void setSamplesPerBlock(int samplesPerBlock); - void clear(); + /** + * @brief Clear resources that are related to a currently loaded SFZ file + * + */ + void clearNonState(); + /** + * @brief Clear resources that are unrelated to the currently loaded SFZ file, + * i.e. midi state and beat clock. + * + */ + void clearState(); #define ACCESSOR_RW(Accessor, RetTy) \ RetTy const& Accessor() const noexcept; \ diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 86fb4093..d7e51ddd 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -65,6 +65,8 @@ Synth::Impl::Impl() effectFactory_.registerStandardEffectTypes(); initEffectBuses(); resetVoices(config::numVoices); + resetDefaultCCValues(); + resetAllControllers(0); // modulation sources MidiState& midiState = resources_.getMidiState(); @@ -274,7 +276,7 @@ void Synth::Impl::clear() currentSet_ = nullptr; sets_.clear(); layers_.clear(); - resources_.clear(); + resources_.clearNonState(); rootPath_.clear(); numGroups_ = 0; numMasters_ = 0; @@ -284,8 +286,8 @@ void Synth::Impl::clear() currentSwitch_ = absl::nullopt; defaultPath_ = ""; image_ = ""; - midiState.reset(); - filePool.clear(); + midiState.resetNoteStates(); + midiState.flushEvents(); filePool.setRamLoading(config::loadInRam); clearCCLabels(); currentUsedCCs_.clear(); @@ -303,18 +305,6 @@ void Synth::Impl::clear() modificationTime_ = absl::nullopt; playheadMoved_ = false; - // set default controllers - // midistate is reset above - fill(absl::MakeSpan(defaultCCValues_), 0.0f); - setDefaultHdcc(7, normalizeCC(100)); - setDefaultHdcc(10, 0.5f); - setDefaultHdcc(11, 1.0f); - - // set default controller labels - setCCLabel(7, "Volume"); - setCCLabel(10, "Pan"); - setCCLabel(11, "Expression"); - initEffectBuses(); } @@ -401,12 +391,20 @@ void Synth::Impl::handleControlOpcodes(const std::vector& members) switch (member.lettersOnlyHash) { case hash("set_cc&"): if (Default::ccNumber.bounds.containsWithEnd(member.parameters.back())) { - setDefaultHdcc(member.parameters.back(), member.read(Default::loCC)); + const auto ccNumber = member.parameters.back(); + const auto value = member.read(Default::loCC); + setDefaultHdcc(ccNumber, value); + if (!reloading) + resources_.getMidiState().ccEvent(0, ccNumber, value); } break; case hash("set_hdcc&"): if (Default::ccNumber.bounds.containsWithEnd(member.parameters.back())) { - setDefaultHdcc(member.parameters.back(), member.read(Default::loNormalized)); + const auto ccNumber = member.parameters.back(); + const auto value = member.read(Default::loNormalized); + setDefaultHdcc(ccNumber, value); + if (!reloading) + resources_.getMidiState().ccEvent(0, ccNumber, value); } break; case hash("label_cc&"): @@ -550,15 +548,53 @@ void Synth::Impl::handleEffectOpcodes(const std::vector& rawMembers) getOrCreateBus(busIndex).addEffect(std::move(fx)); } +void Synth::Impl::resetDefaultCCValues() noexcept +{ + fill(absl::MakeSpan(defaultCCValues_), 0.0f); + setDefaultHdcc(7, normalizeCC(100)); + setDefaultHdcc(10, 0.5f); + setDefaultHdcc(11, 1.0f); + + setCCLabel(7, "Volume"); + setCCLabel(10, "Pan"); + setCCLabel(11, "Expression"); +} + +void Synth::Impl::prepareSfzLoad(const fs::path& path) +{ + auto newPath_ = path.string(); + reloading = (lastPath_ == newPath_); + + clear(); + +#ifndef NDEBUG + if (reloading) { + DBG("[sfizz] Reloading the current file"); + } +#endif + + if (!reloading) { + + // Clear the background queues and clear the filePool + auto& filePool = resources_.getFilePool(); + filePool.waitForBackgroundLoading(); + filePool.clear(); + + // Set the default hdcc to their default + resetDefaultCCValues(); + + // Store the new path + lastPath_ = std::move(newPath_); + } +} + bool Synth::loadSfzFile(const fs::path& file) { Impl& impl = *impl_; - - impl.clear(); + impl.prepareSfzLoad(file); std::error_code ec; fs::path realFile = fs::canonical(file, ec); - bool success = true; Parser& parser = impl.parser_; parser.parseFile(ec ? file : realFile); @@ -570,7 +606,10 @@ bool Synth::loadSfzFile(const fs::path& file) success = success && !impl.layers_.empty(); if (!success) { + DBG("[sfizz] Loading failed"); + auto& filePool = impl.resources_.getFilePool(); parser.clear(); + filePool.clear(); return false; } @@ -581,8 +620,7 @@ bool Synth::loadSfzFile(const fs::path& file) bool Synth::loadSfzString(const fs::path& path, absl::string_view text) { Impl& impl = *impl_; - - impl.clear(); + impl.prepareSfzLoad(path); bool success = true; Parser& parser = impl.parser_; @@ -595,7 +633,10 @@ bool Synth::loadSfzString(const fs::path& path, absl::string_view text) success = success && !impl.layers_.empty(); if (!success) { + auto& filePool = impl.resources_.getFilePool(); + DBG("[sfizz] Loading failed"); parser.clear(); + filePool.clear(); return false; } @@ -605,8 +646,10 @@ bool Synth::loadSfzString(const fs::path& path, absl::string_view text) void Synth::Impl::finalizeSfzLoad() { - const fs::path& rootDirectory = parser_.originalDirectory(); FilePool& filePool = resources_.getFilePool(); + WavetablePool& wavePool = resources_.getWavePool(); + + const fs::path& rootDirectory = parser_.originalDirectory(); filePool.setRootDirectory(rootDirectory); // a string representation used for OSC purposes @@ -642,9 +685,6 @@ void Synth::Impl::finalizeSfzLoad() absl::optional fileInformation; - FilePool& filePool = resources_.getFilePool(); - WavetablePool& wavePool = resources_.getWavePool(); - if (!region.isGenerator()) { if (!filePool.checkSampleId(*region.sampleId)) { removeCurrentRegion(); @@ -797,10 +837,19 @@ void Synth::Impl::finalizeSfzLoad() ++currentRegionIndex; } - for (const auto& toLoad: filesToLoad) { - filePool.preloadFile(toLoad.first, toLoad.second); - } + // Reset the preload call count to check for unused preloaded samples + // when reloading + if (reloading) + filePool.resetPreloadCallCounts(); + for (const auto& toLoad: filesToLoad) + filePool.preloadFile(toLoad.first, toLoad.second); + + // Remove preloaded data with no linked regions + if (reloading) + filePool.removeUnusedPreloadedData(); + + // Remove bad regions with unknown files if (currentRegionCount < layers_.size()) { DBG("Removing " << (layers_.size() - currentRegionCount) << " out of " << layers_.size() << " regions"); @@ -1393,7 +1442,6 @@ void Synth::Impl::setDefaultHdcc(int ccNumber, float value) ASSERT(ccNumber >= 0); ASSERT(ccNumber < config::numCCs); defaultCCValues_[ccNumber] = value; - resources_.getMidiState().ccEvent(0, ccNumber, value); } float Synth::getHdcc(int ccNumber) diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 67a03c02..d57a70fc 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -199,6 +199,22 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co client.receive<'b'>(delay, path, &blob); } break; + MATCH("/aftertouch", "") { + client.receive<'f'>(delay, path, impl.resources_.getMidiState().getChannelAftertouch()); + } break; + + MATCH("/poly_aftertouch/&", "") { + if (indices[0] > 127) + break; + // Note: result value is not frame-exact + client.receive<'f'>(delay, path, impl.resources_.getMidiState().getPolyAftertouch(indices[0])); + } break; + + MATCH("/pitch_bend", "") { + // Note: result value is not frame-exact + client.receive<'f'>(delay, path, impl.resources_.getMidiState().getPitchBend()); + } break; + //---------------------------------------------------------------------- MATCH("/mem/buffers", "") { diff --git a/src/sfizz/SynthPrivate.h b/src/sfizz/SynthPrivate.h index b1e1e9ea..2fd072d9 100644 --- a/src/sfizz/SynthPrivate.h +++ b/src/sfizz/SynthPrivate.h @@ -176,9 +176,24 @@ struct Synth::Impl final: public Parser::Listener { */ void startDelayedSostenutoReleases(Layer* layer, int delay, SisterVoiceRingBuilder& ring) noexcept; + /** + * @brief Reset the default CCs + * + */ + void resetDefaultCCValues() noexcept; + + /** + * @brief Prepare before loading a new SFZ file. The behavior of this function + * is changed by the reloading state. + * + * @param path + */ + void prepareSfzLoad(const fs::path& path); + /** * @brief Finalize SFZ loading, following a successful execution of the - * parsing step. + * parsing step. The behavior of this function is changed by the reloading + * state. */ void finalizeSfzLoad(); @@ -330,7 +345,9 @@ struct Synth::Impl final: public Parser::Listener { std::chrono::time_point lastGarbageCollection_; Parser parser_; + std::string lastPath_; absl::optional modificationTime_ { }; + bool reloading { false }; std::array defaultCCValues_ { }; BitArray currentUsedCCs_; diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index b65b9d8b..d66649f9 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -699,3 +699,19 @@ TEST_CASE("[Files] Key center from audio file") REQUIRE(synth.getRegionView(4)->pitchKeycenter == 10); REQUIRE(synth.getRegionView(5)->pitchKeycenter == 62); } + +TEST_CASE("[Files] Unused samples are cleared on reloading") +{ + sfz::Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/unused_samples.sfz", R"( + sample=*sine + sample=kick.wav + )"); + REQUIRE(synth.getNumPreloadedSamples() == 1); + + // Same file path to reload + synth.loadSfzString(fs::current_path() / "tests/TestFiles/unused_samples.sfz", R"( + sample=*sine + )"); + REQUIRE(synth.getNumPreloadedSamples() == 0); +} diff --git a/tests/MidiStateT.cpp b/tests/MidiStateT.cpp index f29c7198..64d45d4c 100644 --- a/tests/MidiStateT.cpp +++ b/tests/MidiStateT.cpp @@ -45,16 +45,67 @@ TEST_CASE("[MidiState] Set and get pitch bends") REQUIRE(state.getPitchBend() == 0.0f); } -TEST_CASE("[MidiState] Reset") +TEST_CASE("[MidiState] Resetting things") { sfz::MidiState state; state.pitchBendEvent(0, 0.7f); state.noteOnEvent(0, 64, 24_norm); state.ccEvent(0, 123, 124_norm); - state.reset(); - REQUIRE(state.getPitchBend() == 0.0f); + state.channelAftertouchEvent(0, 56_norm); + state.polyAftertouchEvent(0, 64, 43_norm); + state.advanceTime(1024); + + // Only reset note stuff + state.resetNoteStates(); REQUIRE(state.getNoteVelocity(64) == 0_norm); + REQUIRE(state.getNoteDuration(64) == 0_norm); + REQUIRE(state.getActiveNotes() == 0); + + // Extended CCs too + REQUIRE(state.getCCValue(131) == 0.0f); + REQUIRE(state.getCCValue(132) == 0.0f); + REQUIRE(state.getCCValue(133) == 0.0f); + REQUIRE(state.getCCValue(134) == 0.0f); + REQUIRE(state.getCCValue(135) == 0.0f); + REQUIRE(state.getCCValue(136) == 0.0f); + REQUIRE(state.getCCValue(137) == 0.0f); + + // State isn't reset + REQUIRE(state.getPitchBend() != 0.0f); + REQUIRE(state.getCCValue(123) != 0_norm); + REQUIRE(state.getChannelAftertouch() != 0_norm); + REQUIRE(state.getPolyAftertouch(64) != 0_norm); + + state.resetEventStates(); // But now it is + REQUIRE(state.getPitchBend() == 0.0f); REQUIRE(state.getCCValue(123) == 0_norm); + REQUIRE(state.getChannelAftertouch() == 0_norm); + REQUIRE(state.getPolyAftertouch(64) == 0_norm); +} + +TEST_CASE("[MidiState] Flushing state") +{ + sfz::MidiState state; + state.pitchBendEvent(40, 0.7f); + state.ccEvent(100, 123, 124_norm); + state.channelAftertouchEvent(20, 56_norm); + state.polyAftertouchEvent(80, 64, 43_norm); + + REQUIRE(state.getCCEvents(123).size() > 1); + REQUIRE(state.getChannelAftertouchEvents().size() > 1); + REQUIRE(state.getPolyAftertouchEvents(64).size() > 1); + REQUIRE(state.getPitchEvents().size() > 1); + + state.flushEvents(); + REQUIRE(state.getCCEvents(123).size() == 1); + REQUIRE(state.getChannelAftertouchEvents().size() == 1); + REQUIRE(state.getPolyAftertouchEvents(64).size() == 1); + REQUIRE(state.getPitchEvents().size() == 1); + + REQUIRE(state.getCCValue(123) == 124_norm); + REQUIRE(state.getChannelAftertouch() == 56_norm); + REQUIRE(state.getPolyAftertouch(64) == 43_norm); + REQUIRE(state.getPitchBend() == 0.7f); } TEST_CASE("[MidiState] Set and get note velocities") diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index e92fd343..4974be3d 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -1758,7 +1758,7 @@ TEST_CASE("[Synth] Initial values of CC") REQUIRE(synth.getHdcc(10) == 0.7f); REQUIRE(synth.getDefaultHdcc(10) == 0.5f); - synth.loadSfzString(fs::current_path() / "init_cc.sfz", R"( + synth.loadSfzString(fs::current_path() / "init_cc_new_file.sfz", R"( set_hdcc111=0.1234 set_cc112=77 sample=*sine )"); @@ -2017,3 +2017,126 @@ TEST_CASE("[Synth] Loading resets note and octave offsets") }; REQUIRE(messageList == expected); } + + +TEST_CASE("[Synth] Default CC values") +{ + sfz::Synth synth; + std::vector messageList; + sfz::Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/default.sfz", R"( + sample=*sine + )"); + synth.renderBlock(buffer); + synth.dispatchMessage(client, 0, "/cc7/value", "", nullptr); + synth.dispatchMessage(client, 0, "/cc10/value", "", nullptr); + synth.dispatchMessage(client, 0, "/cc11/value", "", nullptr); + + std::vector expected { + "/cc7/value,f : { 0.787402 }", + "/cc10/value,f : { 0.5 }", + "/cc11/value,f : { 1 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Synth] Loading a new file doesn't reset the midi state") +{ + sfz::Synth synth; + std::vector messageList; + sfz::Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sine.sfz", R"( + sample=*sine + )"); + + synth.hdcc(10, 63, 0.4f); + synth.hdcc(10, 7, 0.41f); + synth.hdcc(10, 10, 0.42f); + synth.hdcc(10, 11, 0.43f); + synth.hdChannelAftertouch(20, 0.1f); + synth.hdPolyAftertouch(30, 64, 0.2f); + synth.hdPitchWheel(40, 0.3f); + synth.renderBlock(buffer); + synth.dispatchMessage(client, 0, "/cc63/value", "", nullptr); + synth.dispatchMessage(client, 0, "/cc7/value", "", nullptr); + synth.dispatchMessage(client, 0, "/cc10/value", "", nullptr); + synth.dispatchMessage(client, 0, "/cc11/value", "", nullptr); + synth.dispatchMessage(client, 0, "/aftertouch", "", nullptr); + synth.dispatchMessage(client, 0, "/poly_aftertouch/64", "", nullptr); + synth.dispatchMessage(client, 0, "/pitch_bend", "", nullptr); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/saw.sfz", R"( + sample=*saw + )"); + synth.renderBlock(buffer); + synth.dispatchMessage(client, 0, "/cc63/value", "", nullptr); + synth.dispatchMessage(client, 0, "/cc7/value", "", nullptr); + synth.dispatchMessage(client, 0, "/cc10/value", "", nullptr); + synth.dispatchMessage(client, 0, "/cc11/value", "", nullptr); + synth.dispatchMessage(client, 0, "/aftertouch", "", nullptr); + synth.dispatchMessage(client, 0, "/poly_aftertouch/64", "", nullptr); + synth.dispatchMessage(client, 0, "/pitch_bend", "", nullptr); + + std::vector expected { + "/cc63/value,f : { 0.4 }", + "/cc7/value,f : { 0.41 }", + "/cc10/value,f : { 0.42 }", + "/cc11/value,f : { 0.43 }", + "/aftertouch,f : { 0.1 }", + "/poly_aftertouch/64,f : { 0.2 }", + "/pitch_bend,f : { 0.3 }", + "/cc63/value,f : { 0.4 }", + "/cc7/value,f : { 0.41 }", + "/cc10/value,f : { 0.42 }", + "/cc11/value,f : { 0.43 }", + "/aftertouch,f : { 0.1 }", + "/poly_aftertouch/64,f : { 0.2 }", + "/pitch_bend,f : { 0.3 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Synth] Reloading a file ignores the `set_ccN` opcodes") +{ + sfz::Synth synth; + std::vector messageList; + sfz::Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sine.sfz", R"( + set_cc1=0 + sample=*sine + )"); + + synth.hdcc(10, 1, 0.4f); + synth.renderBlock(buffer); + synth.dispatchMessage(client, 0, "/cc1/value", "", nullptr); + + // Same file + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sine.sfz", R"( + set_cc1=0 + sample=*sine + )"); + synth.renderBlock(buffer); + synth.dispatchMessage(client, 0, "/cc1/value", "", nullptr); + + // Different file + synth.loadSfzString(fs::current_path() / "tests/TestFiles/saw.sfz", R"( + set_cc1=0 + sample=*saw + )"); + synth.renderBlock(buffer); + synth.dispatchMessage(client, 0, "/cc1/value", "", nullptr); + + std::vector expected { + "/cc1/value,f : { 0.4 }", + "/cc1/value,f : { 0.4 }", + "/cc1/value,f : { 0 }", + }; + + REQUIRE(messageList == expected); +}