diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 6e8ccac3..a5b40576 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -20,7 +20,7 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex if (noteNumber >= 0 && noteNumber < 128) { lastNoteVelocities[noteNumber] = velocity; - noteOnTimes[noteNumber] = std::chrono::steady_clock::now(); + noteOnTimes[noteNumber] = internalClock + static_cast(delay); activeNotes++; } @@ -28,6 +28,7 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noexcept { + ASSERT(delay >= 0); ASSERT(noteNumber >= 0 && noteNumber <= 127); ASSERT(velocity >= 0.0 && velocity <= 1.0); UNUSED(velocity); @@ -38,14 +39,30 @@ void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noe } -float sfz::MidiState::getNoteDuration(int noteNumber) const +void sfz::MidiState::setSampleRate(float sampleRate) noexcept { - ASSERT(noteNumber >= 0 && noteNumber <= 127); + this->sampleRate = sampleRate; + internalClock = 0; + absl::c_fill(noteOnTimes, 0); +} + +void sfz::MidiState::advanceTime(int numSamples) noexcept +{ + internalClock += numSamples; +} + +void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept +{ + this->samplesPerBlock = samplesPerBlock; +} + +float sfz::MidiState::getNoteDuration(int noteNumber, int delay) const +{ + ASSERT(noteNumber >= 0 && noteNumber < 128); if (noteNumber >= 0 && noteNumber < 128) { - const auto noteOffTime = std::chrono::steady_clock::now(); - const auto duration = std::chrono::duration_cast>(noteOffTime - noteOnTimes[noteNumber]); - return duration.count(); + const unsigned timeInSamples = internalClock + static_cast(delay) - noteOnTimes[noteNumber]; + return static_cast(timeInSamples) / sampleRate; } return 0.0f; @@ -95,6 +112,8 @@ void sfz::MidiState::reset(int delay) noexcept pitchBend = 0; activeNotes = 0; + internalClock = 0; + absl::c_fill(noteOnTimes, 0); } void sfz::MidiState::resetAllControllers(int delay) noexcept diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 09c4ce78..d127d1de 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -5,7 +5,6 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #pragma once -#include #include #include "CCMap.h" #include "Range.h" @@ -42,13 +41,29 @@ public: int getActiveNotes() const noexcept { return activeNotes; } /** - * @brief Register a note off and get the note duration + * @brief Get the note duration since note on * * @param noteNumber + * @param delay * @return float */ - float getNoteDuration(int noteNumber) const; + float getNoteDuration(int noteNumber, int delay = 0) const; + /** + * @brief Set the maximum size of the blocks for the callback. The actual + * size can be lower in each callback but should not be larger + * than this value. + * + * @param samplesPerBlock + */ + void setSamplesPerBlock(int samplesPerBlock) noexcept; + /** + * @brief Set the sample rate. If you do not call it it is initialized + * to sfz::config::defaultSampleRate. + * + * @param sampleRate + */ + void setSampleRate(float sampleRate) noexcept; /** * @brief Get the note on velocity for a given note * @@ -79,6 +94,13 @@ public: */ void ccEvent(int delay, int ccNumber, float ccValue) noexcept; + /** + * @brief Advances the internal clock of a given amount of samples. + * You should call this at each callback. + * + * @param numSamples the number of samples of clock advance + */ + void advanceTime(int numSamples) noexcept; /** * @brief Get the CC value for CC number * @@ -121,13 +143,13 @@ public: private: template using MidiNoteArray = std::array; - using NoteOnTime = std::chrono::steady_clock::time_point; int activeNotes { 0 }; + /** * @brief Stores the note on times. * */ - MidiNoteArray noteOnTimes; + MidiNoteArray noteOnTimes {}; /** * @brief Stores the velocity of the note ons for currently * depressed notes. @@ -143,5 +165,8 @@ private: * Pitch bend status */ int pitchBend { 0 }; + double sampleRate { config::defaultSampleRate }; + int samplesPerBlock { config::defaultSamplesPerBlock }; + unsigned internalClock { 0 }; }; } diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index a3efca3e..6882030c 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -483,6 +483,7 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); + resources.midiState.setSamplesPerBlock(samplesPerBlock); for (auto& bus : effectBuses) { if (bus) @@ -503,6 +504,7 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept resources.filterPool.setSampleRate(sampleRate); resources.eqPool.setSampleRate(sampleRate); + resources.midiState.setSampleRate(sampleRate); for (auto& bus : effectBuses) { if (bus) @@ -525,6 +527,8 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept auto temp = AudioSpan(tempBuffer).first(numFrames); auto tempMixNode = AudioSpan(tempMixNodeBuffer).first(numFrames); + resources.midiState.advanceTime(buffer.getNumFrames()); + CallbackBreakdown callbackBreakdown; { // Prepare the effect inputs. They are mixes of per-region outputs. diff --git a/tests/RegionValueComputationsT.cpp b/tests/RegionValueComputationsT.cpp index 31566e76..0f760b06 100644 --- a/tests/RegionValueComputationsT.cpp +++ b/tests/RegionValueComputationsT.cpp @@ -262,20 +262,21 @@ TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack") TEST_CASE("[Region] rt_decay") { sfz::MidiState midiState; + midiState.setSampleRate(1000); sfz::Region region { midiState }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "trigger", "release" }); region.parseOpcode({ "rt_decay", "10" }); midiState.noteOnEvent(0, 64, 64_norm); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + midiState.advanceTime(100); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) ); region.parseOpcode({ "rt_decay", "20" }); midiState.noteOnEvent(0, 64, 64_norm); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + midiState.advanceTime(100); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) ); region.parseOpcode({ "trigger", "attack" }); midiState.noteOnEvent(0, 64, 64_norm); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + midiState.advanceTime(100); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume).margin(0.1) ); }