Use advanceTime to compute the note-on time

This commit is contained in:
Paul Ferrand 2020-03-28 23:41:15 +01:00
parent 45124cce53
commit 6a66077911
4 changed files with 63 additions and 14 deletions

View file

@ -20,7 +20,7 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex
if (noteNumber >= 0 && noteNumber < 128) { if (noteNumber >= 0 && noteNumber < 128) {
lastNoteVelocities[noteNumber] = velocity; lastNoteVelocities[noteNumber] = velocity;
noteOnTimes[noteNumber] = std::chrono::steady_clock::now(); noteOnTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
activeNotes++; 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 void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noexcept
{ {
ASSERT(delay >= 0);
ASSERT(noteNumber >= 0 && noteNumber <= 127); ASSERT(noteNumber >= 0 && noteNumber <= 127);
ASSERT(velocity >= 0.0 && velocity <= 1.0); ASSERT(velocity >= 0.0 && velocity <= 1.0);
UNUSED(velocity); 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) { if (noteNumber >= 0 && noteNumber < 128) {
const auto noteOffTime = std::chrono::steady_clock::now(); const unsigned timeInSamples = internalClock + static_cast<unsigned>(delay) - noteOnTimes[noteNumber];
const auto duration = std::chrono::duration_cast<std::chrono::duration<float>>(noteOffTime - noteOnTimes[noteNumber]); return static_cast<float>(timeInSamples) / sampleRate;
return duration.count();
} }
return 0.0f; return 0.0f;
@ -95,6 +112,8 @@ void sfz::MidiState::reset(int delay) noexcept
pitchBend = 0; pitchBend = 0;
activeNotes = 0; activeNotes = 0;
internalClock = 0;
absl::c_fill(noteOnTimes, 0);
} }
void sfz::MidiState::resetAllControllers(int delay) noexcept void sfz::MidiState::resetAllControllers(int delay) noexcept

View file

@ -5,7 +5,6 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#pragma once #pragma once
#include <chrono>
#include <array> #include <array>
#include "CCMap.h" #include "CCMap.h"
#include "Range.h" #include "Range.h"
@ -42,13 +41,29 @@ public:
int getActiveNotes() const noexcept { return activeNotes; } 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 noteNumber
* @param delay
* @return float * @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 * @brief Get the note on velocity for a given note
* *
@ -79,6 +94,13 @@ public:
*/ */
void ccEvent(int delay, int ccNumber, float ccValue) noexcept; 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 * @brief Get the CC value for CC number
* *
@ -121,13 +143,13 @@ public:
private: private:
template<class T> template<class T>
using MidiNoteArray = std::array<T, 128>; using MidiNoteArray = std::array<T, 128>;
using NoteOnTime = std::chrono::steady_clock::time_point;
int activeNotes { 0 }; int activeNotes { 0 };
/** /**
* @brief Stores the note on times. * @brief Stores the note on times.
* *
*/ */
MidiNoteArray<NoteOnTime> noteOnTimes; MidiNoteArray<unsigned> noteOnTimes {};
/** /**
* @brief Stores the velocity of the note ons for currently * @brief Stores the velocity of the note ons for currently
* depressed notes. * depressed notes.
@ -143,5 +165,8 @@ private:
* Pitch bend status * Pitch bend status
*/ */
int pitchBend { 0 }; int pitchBend { 0 };
double sampleRate { config::defaultSampleRate };
int samplesPerBlock { config::defaultSamplesPerBlock };
unsigned internalClock { 0 };
}; };
} }

View file

@ -483,6 +483,7 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept
this->tempMixNodeBuffer.resize(samplesPerBlock); this->tempMixNodeBuffer.resize(samplesPerBlock);
for (auto& voice : voices) for (auto& voice : voices)
voice->setSamplesPerBlock(samplesPerBlock); voice->setSamplesPerBlock(samplesPerBlock);
resources.midiState.setSamplesPerBlock(samplesPerBlock);
for (auto& bus : effectBuses) { for (auto& bus : effectBuses) {
if (bus) if (bus)
@ -503,6 +504,7 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept
resources.filterPool.setSampleRate(sampleRate); resources.filterPool.setSampleRate(sampleRate);
resources.eqPool.setSampleRate(sampleRate); resources.eqPool.setSampleRate(sampleRate);
resources.midiState.setSampleRate(sampleRate);
for (auto& bus : effectBuses) { for (auto& bus : effectBuses) {
if (bus) if (bus)
@ -525,6 +527,8 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
auto temp = AudioSpan<float>(tempBuffer).first(numFrames); auto temp = AudioSpan<float>(tempBuffer).first(numFrames);
auto tempMixNode = AudioSpan<float>(tempMixNodeBuffer).first(numFrames); auto tempMixNode = AudioSpan<float>(tempMixNodeBuffer).first(numFrames);
resources.midiState.advanceTime(buffer.getNumFrames());
CallbackBreakdown callbackBreakdown; CallbackBreakdown callbackBreakdown;
{ // Prepare the effect inputs. They are mixes of per-region outputs. { // Prepare the effect inputs. They are mixes of per-region outputs.

View file

@ -262,20 +262,21 @@ TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack")
TEST_CASE("[Region] rt_decay") TEST_CASE("[Region] rt_decay")
{ {
sfz::MidiState midiState; sfz::MidiState midiState;
midiState.setSampleRate(1000);
sfz::Region region { midiState }; sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "trigger", "release" }); region.parseOpcode({ "trigger", "release" });
region.parseOpcode({ "rt_decay", "10" }); region.parseOpcode({ "rt_decay", "10" });
midiState.noteOnEvent(0, 64, 64_norm); 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) ); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) );
region.parseOpcode({ "rt_decay", "20" }); region.parseOpcode({ "rt_decay", "20" });
midiState.noteOnEvent(0, 64, 64_norm); 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) ); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) );
region.parseOpcode({ "trigger", "attack" }); region.parseOpcode({ "trigger", "attack" });
midiState.noteOnEvent(0, 64, 64_norm); 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) ); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume).margin(0.1) );
} }