Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct a bug with dynamic updates on offed EGs #1088

Merged
merged 1 commit into from
Jun 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/sfizz/ADSREnvelope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ Float ADSREnvelope::secondsToExpRate(Float timeInSeconds) const noexcept
if (timeInSeconds <= 0)
return Float(0.0);

timeInSeconds = std::max(Float(25e-3), timeInSeconds);
timeInSeconds = std::max(Float(Default::offTime), timeInSeconds);
return std::exp(Float(-9.0) / (timeInSeconds * sampleRate));
};

void ADSREnvelope::reset(const EGDescription& desc, const Region& region, int delay, float velocity, float sampleRate) noexcept
{
this->sampleRate = sampleRate;
desc_ = &desc;
dynamic_ = desc.dynamic;
triggerVelocity_ = velocity;
currentState = State::Delay; // Has to be before the update
updateValues(delay);
Expand All @@ -58,7 +59,6 @@ void ADSREnvelope::updateValues(int delay) noexcept
{
if (currentState == State::Delay)
this->delay = delay + secondsToSamples(desc_->getDelay(midiState_, triggerVelocity_, delay));

this->attackStep = secondsToLinRate(desc_->getAttack(midiState_, triggerVelocity_, delay));
this->decayRate = secondsToExpRate(desc_->getDecay(midiState_, triggerVelocity_, delay));
this->releaseRate = secondsToExpRate(desc_->getRelease(midiState_, triggerVelocity_, delay));
Expand All @@ -70,7 +70,7 @@ void ADSREnvelope::updateValues(int delay) noexcept

void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
{
if (desc_ && desc_->dynamic) {
if (dynamic_) {
int processed = 0;
int remaining = static_cast<int>(output.size());
while(remaining > 0) {
Expand Down
7 changes: 7 additions & 0 deletions src/sfizz/ADSREnvelope.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ class ADSREnvelope {
*/
int getRemainingDelay() const noexcept { return delay; }

/**
* @brief Stop dynamic updates of the EG values.
*
*/
void stopDynamicUpdates() { dynamic_ = false; }

private:
float sampleRate { config::defaultSampleRate };
int secondsToSamples(Float timeInSeconds) const noexcept;
Expand All @@ -99,6 +105,7 @@ class ADSREnvelope {
const EGDescription* desc_ { nullptr };
const MidiState& midiState_;
float triggerVelocity_ { 0.0f };
bool dynamic_ { false };
int delay { 0 };
Float attackStep { 0 };
Float decayRate { 0 };
Expand Down
13 changes: 4 additions & 9 deletions src/sfizz/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,12 @@ namespace config {
*/
static constexpr float overflowVoiceMultiplier { 1.5f };
static_assert(overflowVoiceMultiplier >= 1.0f, "This needs to add voices");

/**
* @brief Calculate the effective voice number for the polyphony setting,
* accounting for the overflow factor.
* @brief Minimum number of overflow voices to add
*
*/
inline constexpr int calculateActualVoices(int polyphony)
{
return
(int(polyphony * config::overflowVoiceMultiplier) < int(config::maxVoices)) ?
int(polyphony * config::overflowVoiceMultiplier) : int(config::maxVoices);
}
static constexpr int minOverflowVoices { 4 };

/**
* @brief The smoothing time constant per "smooth" steps
*/
Expand Down
7 changes: 6 additions & 1 deletion src/sfizz/Synth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,12 @@ int Synth::getNumActiveVoices() const noexcept
std::min(impl.numVoices_, activeVoices) : activeVoices;
}

std::vector<const Voice*> Synth::getActiveVoices() const noexcept
{
Impl& impl = *impl_;
return impl.voiceManager_.getActiveVoices();
}

void Synth::setSamplesPerBlock(int samplesPerBlock) noexcept
{
Impl& impl = *impl_;
Expand Down Expand Up @@ -1295,7 +1301,6 @@ void Synth::Impl::startVoice(Layer* layer, int delay, const TriggerEvent& trigge
if (selectedVoice == nullptr)
return;

ASSERT(selectedVoice->isFree());
if (selectedVoice->startVoice(layer, delay, triggerEvent))
ring.addVoiceToRing(selectedVoice);
}
Expand Down
8 changes: 8 additions & 0 deletions src/sfizz/Synth.h
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,14 @@ class Synth final {
* @return int
*/
int getNumActiveVoices() const noexcept;

/**
* @brief Get the active voices as a view
*
* @return std::vector<const Voice*>
*/
std::vector<const Voice*> getActiveVoices() const noexcept;

/**
* @brief Get the total number of voices in the synth (the polyphony)
*
Expand Down
1 change: 1 addition & 0 deletions src/sfizz/Voice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ void Voice::Impl::off(int delay, bool fast) noexcept
} else if (region_->offMode == OffMode::time) {
egAmplitude_.setReleaseTime(region_->offTime);
}
egAmplitude_.stopDynamicUpdates();
}
else {
// TODO(jpc): Flex AmpEG
Expand Down
32 changes: 25 additions & 7 deletions src/sfizz/VoiceManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ const PolyphonyGroup* VoiceManager::getPolyphonyGroupView(int idx) noexcept
return &polyphonyGroups_[idx];
}

std::vector<const Voice*> VoiceManager::getActiveVoices() const noexcept
{
std::vector<const Voice*> returned;
std::copy(activeVoices_.begin(), activeVoices_.end(), std::back_inserter(returned));
return returned;
}


void VoiceManager::clear()
{
for (auto& pg : polyphonyGroups_)
Expand Down Expand Up @@ -147,12 +155,21 @@ void VoiceManager::checkPolyphony(const Region* region, int delay, const Trigger

Voice* VoiceManager::findFreeVoice() noexcept
{
auto freeVoice = absl::c_find_if(list_, [](const Voice& voice) {
return voice.isFree();
});
Voice* freeVoice = nullptr;
for (auto& v: list_) {
if (v.isFree()) {
freeVoice = &v;
break;
}

if (freeVoice != list_.end())
return &*freeVoice;
if (v.offedOrFree()) {
if (freeVoice == nullptr || v.getAge() > freeVoice->getAge())
freeVoice = &v;
}
};

if (freeVoice != nullptr)
return freeVoice;

DBG("Engine hard polyphony reached");
return {};
Expand All @@ -161,7 +178,8 @@ Voice* VoiceManager::findFreeVoice() noexcept
void VoiceManager::requireNumVoices(int numVoices, Resources& resources)
{
numRequiredVoices_ = numVoices;
const int numEffectiveVoices = getNumEffectiveVoices();
const int numEffectiveVoices = std::min(int(config::maxVoices), numVoices +
std::max(int(numVoices * config::overflowVoiceMultiplier), config::minOverflowVoices));

clear();
list_.reserve(numEffectiveVoices);
Expand Down Expand Up @@ -249,7 +267,7 @@ void VoiceManager::checkEnginePolyphony(int delay) noexcept
{
Voice* candidate = stealer_->checkPolyphony(
absl::MakeSpan(activeVoices_), numRequiredVoices_);
SisterVoiceRing::offAllSisters(candidate, delay);
SisterVoiceRing::offAllSisters(candidate, delay, true);
}

} // namespace sfz
8 changes: 7 additions & 1 deletion src/sfizz/VoiceManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ struct VoiceManager final : public Voice::StateListener
*/
const PolyphonyGroup* getPolyphonyGroupView(int idx) noexcept;

/**
* @brief Get the actives voices as a view
*
* @return std::vector<const Voice*>
*/
std::vector<const Voice*> getActiveVoices() const noexcept;

/**
* @brief Clear all voices and polyphony groups.
* Also resets the stealing algorithm to default.
Expand Down Expand Up @@ -134,7 +141,6 @@ struct VoiceManager final : public Voice::StateListener

private:
int numRequiredVoices_ { config::numVoices };
int getNumEffectiveVoices() const noexcept { return config::calculateActualVoices(numRequiredVoices_); }
std::vector<Voice> list_;
std::vector<Voice*> activeVoices_;
std::vector<Voice*> temp_;
Expand Down
24 changes: 22 additions & 2 deletions tests/SynthT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -989,11 +989,11 @@ TEST_CASE("[Synth] Sustain threshold")
REQUIRE( playingSamples(synth) == std::vector<std::string> { "*sine", "*sine" } );
synth.noteOn(0, 62, 85);
synth.renderBlock(buffer);
REQUIRE( playingSamples(synth) == std::vector<std::string> { "*silence", "*sine", "*sine" } );
REQUIRE( playingSamples(synth) == std::vector<std::string> { "*sine", "*sine", "*silence" } );
synth.cc(0, 64, 64);
synth.noteOff(0, 62, 85);
synth.renderBlock(buffer);
REQUIRE( playingSamples(synth) == std::vector<std::string> { "*silence", "*sine", "*sine" } );
REQUIRE( playingSamples(synth) == std::vector<std::string> {"*sine", "*sine", "*silence" } );
}

TEST_CASE("[Synth] Sustain")
Expand Down Expand Up @@ -2140,3 +2140,23 @@ TEST_CASE("[Synth] Reloading a file ignores the `set_ccN` opcodes")

REQUIRE(messageList == expected);
}

TEST_CASE("[Synth] Reuse offed voices in the last case scenario for new notes")
{
sfz::Synth synth;
synth.setNumVoices(2);
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<region> sample=*sine ampeg_release=10
)");
synth.noteOn(0, 60, 63);
synth.renderBlock(buffer);
REQUIRE( playingNotes(synth) == std::vector<int> { 60 } );
for (int i = 61; i < 80; ++i) {
synth.noteOn(0, i, 63);
synth.renderBlock(buffer);
auto notes = playingNotes(synth);
std::sort(notes.begin(), notes.end());
REQUIRE( notes == std::vector<int> { i - 1, i } );
}
}
38 changes: 30 additions & 8 deletions tests/TestHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ unsigned numActiveVoices(const sfz::Synth& synth)
const std::vector<std::string> playingSamples(const sfz::Synth& synth)
{
std::vector<std::string> samples;
for (int i = 0; i < synth.getNumVoices(); ++i) {
const auto* voice = synth.getVoiceView(i);
auto activeVoices = synth.getActiveVoices();
for (const auto* voice: activeVoices) {
if (!voice->released()) {
if (auto region = voice->getRegion())
samples.push_back(region->sampleId->filename());
Expand All @@ -105,19 +105,30 @@ const std::vector<std::string> playingSamples(const sfz::Synth& synth)
const std::vector<float> playingVelocities(const sfz::Synth& synth)
{
std::vector<float> velocities;
for (int i = 0; i < synth.getNumVoices(); ++i) {
const auto* voice = synth.getVoiceView(i);
auto activeVoices = synth.getActiveVoices();
for (const auto* voice: activeVoices) {
if (!voice->released())
velocities.push_back(voice->getTriggerEvent().value);
}
return velocities;
}

const std::vector<int> playingNotes(const sfz::Synth& synth)
{
std::vector<int> notes;
auto activeVoices = synth.getActiveVoices();
for (const auto* voice: activeVoices) {
if (!voice->released())
notes.push_back(voice->getTriggerEvent().number);
}
return notes;
}

const std::vector<std::string> activeSamples(const sfz::Synth& synth)
{
std::vector<std::string> samples;
for (int i = 0; i < synth.getNumVoices(); ++i) {
const auto* voice = synth.getVoiceView(i);
auto activeVoices = synth.getActiveVoices();
for (const auto* voice: activeVoices) {
if (!voice->isFree()) {
const sfz::Region* region = voice->getRegion();
if (region)
Expand All @@ -130,14 +141,25 @@ const std::vector<std::string> activeSamples(const sfz::Synth& synth)
const std::vector<float> activeVelocities(const sfz::Synth& synth)
{
std::vector<float> velocities;
for (int i = 0; i < synth.getNumVoices(); ++i) {
const auto* voice = synth.getVoiceView(i);
auto activeVoices = synth.getActiveVoices();
for (const auto* voice: activeVoices) {
if (!voice->isFree())
velocities.push_back(voice->getTriggerEvent().value);
}
return velocities;
}

const std::vector<int> activeNotes(const sfz::Synth& synth)
{
std::vector<int> notes;
auto activeVoices = synth.getActiveVoices();
for (const auto* voice: activeVoices) {
if (!voice->isFree())
notes.push_back(voice->getTriggerEvent().number);
}
return notes;
}

std::string createDefaultGraph(std::vector<std::string> lines, int numRegions)
{
for (int regionIdx = 0; regionIdx < numRegions; ++regionIdx) {
Expand Down
24 changes: 20 additions & 4 deletions tests/TestHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,34 +90,50 @@ unsigned numActiveVoices(const sfz::Synth& synth);
* @brief Get the playing samples
*
* @param synth
* @return unsigned
* @return std::vector<std::string>
*/
const std::vector<std::string> playingSamples(const sfz::Synth& synth);

/**
* @brief Get the playing notes velocities
*
* @param synth
* @return unsigned
* @return std::vector<float>
*/
const std::vector<float> playingVelocities(const sfz::Synth& synth);

/**
* @brief Get the playing notes
*
* @param synth
* @return std::vector<int>
*/
const std::vector<int> playingNotes(const sfz::Synth& synth);

/**
* @brief Get the active samples
*
* @param synth
* @return unsigned
* @return std::vector<std::string>
*/
const std::vector<std::string> activeSamples(const sfz::Synth& synth);

/**
* @brief Get the active notes velocities
*
* @param synth
* @return unsigned
* @return std::vector<float>
*/
const std::vector<float> activeVelocities(const sfz::Synth& synth);

/**
* @brief Get the active notes
*
* @param synth
* @return std::vector<int>
*/
const std::vector<int> activeNotes(const sfz::Synth& synth);

/**
* @brief Create the default dot graph representation for standard regions
*
Expand Down