Skip to content

Commit

Permalink
Voice Stealing and Termination Improvements (#1414)
Browse files Browse the repository at this point in the history
* Voice Stealing and Termination Improvements

Handle a voice stealing API which, for now, steals only at
the hard limit, but has been tested at both hard and soft
limit at this commit with a different configuration. Closes

At the same time, add a voice termination fade for soft
terminates which keeps the voice alive for 8 blocks with
a fade during the period. Closes #801

* Closes #1392
  • Loading branch information
baconpaul authored Oct 9, 2024
1 parent 7c855ae commit 67900f7
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 20 deletions.
2 changes: 1 addition & 1 deletion libs/sst/sst-voicemanager
2 changes: 1 addition & 1 deletion src/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ static constexpr uint16_t numPluginOutputs{numNonMainPluginOutputs + 1};

static constexpr size_t numTransportPhasors{7}; // double whole -> 32

static constexpr uint16_t maxVoices{256};
static constexpr uint16_t maxVoices{512};

// some battles are not worth it
static constexpr uint16_t BLOCK_SIZE{blockSize};
Expand Down
44 changes: 29 additions & 15 deletions src/engine/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,7 @@ thread_local uint64_t Engine::fullEngineUnstreamStreamingVersion{0};

voice::Voice *Engine::initiateVoice(const pathToZone_t &path)
{
#if DEBUG_VOICE_LIFECYCLE
SCLOG("Initializing Voice at " << SCD((int)path.key));
#endif
SCLOG_IF(voiceResponder, "Initializing Voice at " << SCD((int)path.key));

assert(zoneByPath(path));
for (const auto &[idx, v] : sst::cpputils::enumerate(voices))
Expand Down Expand Up @@ -198,6 +196,31 @@ voice::Voice *Engine::initiateVoice(const pathToZone_t &path)
return voices[idx];
}
}

SCLOG_IF(voiceResponder, "Fallthrough - looking for early termination voices");
for (const auto &[idx, v] : sst::cpputils::enumerate(voices))
{
if (v && v->terminationSequence > 0)
{
voices[idx]->cleanupVoice();
std::unique_ptr<voice::modulation::MatrixEndpoints> mp;
mp = std::move(voices[idx]->endpoints);
voices[idx]->~Voice();

auto *dp = voiceInPlaceBuffer.get() + idx * sizeof(voice::Voice);
const auto &z = zoneByPath(path);
voices[idx] = new (dp) voice::Voice(this, z.get());
voices[idx]->zonePath = path;
voices[idx]->channel = path.channel;
voices[idx]->key = path.key;
voices[idx]->noteId = path.noteid;
voices[idx]->setSampleRate(sampleRate, sampleRateInv);
voices[idx]->endpoints = std::move(mp);
activeVoices++;
return voices[idx];
}
}

return nullptr;
}
void Engine::releaseVoice(int16_t channel, int16_t key, int32_t noteId, int32_t releaseVelocity)
Expand Down Expand Up @@ -230,27 +253,18 @@ void Engine::releaseAllVoices()
{
for (auto &v : voices)
{
if (v && v->isVoiceAssigned)
if (v && v->isVoiceAssigned && v->isGated)
v->release();
}
}

void Engine::stopAllSounds()
{
std::array<voice::Voice *, maxVoices> toCleanUp{};
size_t cleanupIdx{0};
for (auto &v : voices)
{
if (v && v->isVoiceAssigned)
{
v->release(); // dont call cleanup here since it will break the weak pointers and the
// voices array
toCleanUp[cleanupIdx++] = v;
}
}
for (int i = 0; i < cleanupIdx; ++i)
{
toCleanUp[i]->cleanupVoice();
if (v && v->isVoiceAssigned && v->isVoicePlaying)
v->beginTerminationSequence();
}
}

Expand Down
1 change: 1 addition & 0 deletions src/engine/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ struct Engine : MoveableOnly<Engine>, SampleRateSupport
int32_t noteId, float velocity);

void releaseVoice(voice::Voice *v, float velocity);
void terminateVoice(voice::Voice *v);
void retriggerVoiceWithNewNoteID(voice::Voice *v, int32_t noteid, float velocity)
{
SCLOG("Retrigger Voice Unimplemented")
Expand Down
20 changes: 17 additions & 3 deletions src/engine/engine_voice_responder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ int32_t Engine::VoiceManagerResponder::initializeMultipleVoices(
auto useKey = engine.midikeyRetuner.remapKeyTo(channel, key);
auto nts = transactionVoiceCount;
SCLOG_IF(voiceResponder, "voice initiation of " << nts << " voices");
int32_t actualCreated{0};
for (auto idx = 0; idx < nts; ++idx)
{
const auto &[path, variantIndex] = voiceCreationWorkingBuffer[idx];
Expand All @@ -100,9 +101,11 @@ int32_t Engine::VoiceManagerResponder::initializeMultipleVoices(
v->velocity = velocity;
v->originalMidiKey = key;
v->attack();
actualCreated++;
}
voiceInitWorkingBuffer[idx] = v;
SCLOG_IF(voiceResponder, "-- Created single voice for single zone");
SCLOG_IF(voiceResponder, "-- Created single voice for single zone ("
<< std::hex << v << std::dec << ")");
}
else if (z->variantData.variantPlaybackMode == Zone::UNISON)
{
Expand All @@ -114,6 +117,7 @@ int32_t Engine::VoiceManagerResponder::initializeMultipleVoices(
v->velocity = velocity;
v->originalMidiKey = key;
v->attack();
actualCreated++;
}
voiceInitWorkingBuffer[idx] = v;
SCLOG_IF(voiceResponder,
Expand Down Expand Up @@ -190,14 +194,15 @@ int32_t Engine::VoiceManagerResponder::initializeMultipleVoices(

v->originalMidiKey = key;
v->attack();
actualCreated++;
}
voiceInitWorkingBuffer[idx] = v;
}
}
}
engine.midiNoteStateCounter++;
SCLOG_IF(voiceResponder, "Completed voice initiation");
return nts;
SCLOG_IF(voiceResponder, "Completed voice initiation " << actualCreated << " of " << nts);
return actualCreated;
}

void Engine::VoiceManagerResponder::endVoiceCreationTransaction(uint16_t port, uint16_t channel,
Expand Down Expand Up @@ -231,4 +236,13 @@ void Engine::VoiceManagerResponder::setPolyphonicAftertouch(voice::Voice *v, int
v->polyAT = pat * 1.0 / 127.0;
}

void Engine::VoiceManagerResponder::terminateVoice(voice::Voice *v)
{
if (!v->isVoicePlaying)
return;
if (v->isGated)
v->release();
v->beginTerminationSequence();
}

} // namespace scxt::engine
10 changes: 10 additions & 0 deletions src/voice/voice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,12 @@ template <bool OS> bool Voice::processWithOS()

pao *= velKeyFade;

if (terminationSequence > 0)
{
terminationSequence--;
pao *= terminationSequence / blocksToTerminate;
}

if constexpr (OS)
{
outputAmpOS.set_target(pao * pao * pao * noteExpressions[(int)ExpressionIDs::VOLUME]);
Expand Down Expand Up @@ -558,6 +564,10 @@ template <bool OS> bool Voice::processWithOS()
isVoicePlaying = false;
}

if (terminationSequence == 0)
{
isVoicePlaying = false;
}
return true;
}

Expand Down
5 changes: 5 additions & 0 deletions src/voice/voice.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ struct alignas(16) Voice : MoveableOnly<Voice>,
bool isVoicePlaying{false};
bool isVoiceAssigned{false};

int16_t terminationSequence{-1};
// how many blocks is the early-terminate/steal fade
static constexpr int blocksToTerminate{8};

void attack()
{
isGated = true;
Expand All @@ -198,6 +202,7 @@ struct alignas(16) Voice : MoveableOnly<Voice>,
voiceStarted();
}
void release() { isGated = false; }
void beginTerminationSequence() { terminationSequence = blocksToTerminate; }
void cleanupVoice();

void onSampleRateChanged() override;
Expand Down

0 comments on commit 67900f7

Please sign in to comment.