diff --git a/libs/sst/sst-voicemanager b/libs/sst/sst-voicemanager index b10309de..ecc54a61 160000 --- a/libs/sst/sst-voicemanager +++ b/libs/sst/sst-voicemanager @@ -1 +1 @@ -Subproject commit b10309de18814a6eb6f9f5b56bf3bbbaf66019a7 +Subproject commit ecc54a61e3ff558f3fb166fc350402ede3841fc9 diff --git a/src/configuration.h b/src/configuration.h index 4976ff73..eff53ee5 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -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}; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index f8a143a5..4495987d 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -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)) @@ -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 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) @@ -230,7 +253,7 @@ void Engine::releaseAllVoices() { for (auto &v : voices) { - if (v && v->isVoiceAssigned) + if (v && v->isVoiceAssigned && v->isGated) v->release(); } } @@ -238,19 +261,10 @@ void Engine::releaseAllVoices() void Engine::stopAllSounds() { std::array 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(); } } diff --git a/src/engine/engine.h b/src/engine/engine.h index 7772d0e8..89091818 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -207,6 +207,7 @@ struct Engine : MoveableOnly, 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") diff --git a/src/engine/engine_voice_responder.cpp b/src/engine/engine_voice_responder.cpp index dedf363d..494a6400 100644 --- a/src/engine/engine_voice_responder.cpp +++ b/src/engine/engine_voice_responder.cpp @@ -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]; @@ -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) { @@ -114,6 +117,7 @@ int32_t Engine::VoiceManagerResponder::initializeMultipleVoices( v->velocity = velocity; v->originalMidiKey = key; v->attack(); + actualCreated++; } voiceInitWorkingBuffer[idx] = v; SCLOG_IF(voiceResponder, @@ -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, @@ -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 diff --git a/src/voice/voice.cpp b/src/voice/voice.cpp index 631a43a3..c16a1ffd 100644 --- a/src/voice/voice.cpp +++ b/src/voice/voice.cpp @@ -494,6 +494,12 @@ template 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]); @@ -558,6 +564,10 @@ template bool Voice::processWithOS() isVoicePlaying = false; } + if (terminationSequence == 0) + { + isVoicePlaying = false; + } return true; } diff --git a/src/voice/voice.h b/src/voice/voice.h index 572ffc49..5895547b 100644 --- a/src/voice/voice.h +++ b/src/voice/voice.h @@ -186,6 +186,10 @@ struct alignas(16) Voice : MoveableOnly, 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; @@ -198,6 +202,7 @@ struct alignas(16) Voice : MoveableOnly, voiceStarted(); } void release() { isGated = false; } + void beginTerminationSequence() { terminationSequence = blocksToTerminate; } void cleanupVoice(); void onSampleRateChanged() override;