gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit ae6453874de8412a487911f8f28ba22ea975fd07
parent a0d8f30701a876d569cea96ace37c50086cb648b
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Wed, 24 Jul 2024 23:51:35 +0200

optimizations: replace yield by condition variable / reduce thread contention in processAudio() / support latency

Diffstat:
Msource/nord/n2x/n2xLib/n2xdsp.cpp | 14++++++++------
Msource/nord/n2x/n2xLib/n2xdsp.h | 4++++
Msource/nord/n2x/n2xLib/n2xhardware.cpp | 25+++++++++++++++++++++----
Msource/nord/n2x/n2xLib/n2xhardware.h | 12+++++++++---
Msource/nord/n2x/n2xTestConsole/n2xTestConsole.cpp | 2+-
Msource/synthLib/CMakeLists.txt | 1+
Asource/synthLib/trigger.cpp | 1+
Asource/synthLib/trigger.h | 40++++++++++++++++++++++++++++++++++++++++
8 files changed, 85 insertions(+), 14 deletions(-)

diff --git a/source/nord/n2x/n2xLib/n2xdsp.cpp b/source/nord/n2x/n2xLib/n2xdsp.cpp @@ -176,6 +176,11 @@ namespace n2x m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str())); m_thread->setLogToStdout(false); + + m_vbaInterruptDone = dsp().registerInterruptFunc([this] + { + m_triggerInterruptDone.notify(); + }); } void DSP::advanceSamples(const uint32_t _samples, const uint32_t _latency) @@ -211,15 +216,12 @@ namespace n2x void DSP::hdiSendIrqToDSP(const uint8_t _irq) { // waitDspRxEmpty(); - const auto& rxData = hdi08().rxData(); - - assert(rxData.size() <= 1); - dsp().injectExternalInterrupt(_irq); - m_hardware.ucYieldLoop([&] + dsp().injectExternalInterrupt(m_vbaInterruptDone); + m_hardware.resumeDSPsForFunc([&] { - return dsp().hasPendingInterrupts() || hdi08().hasRXData(); + m_triggerInterruptDone.wait(); }); hdiTransferDSPtoUC(); diff --git a/source/nord/n2x/n2xLib/n2xdsp.h b/source/nord/n2x/n2xLib/n2xdsp.h @@ -5,6 +5,7 @@ #include "dsp56kEmu/dsp.h" #include "dsp56kEmu/dspthread.h" +#include "synthLib/trigger.h" namespace mc68k { @@ -80,5 +81,8 @@ namespace n2x std::condition_variable m_haltDSPcv; std::mutex m_haltDSPmutex; + + synthLib::Trigger<> m_triggerInterruptDone; + uint32_t m_vbaInterruptDone = 0; }; } diff --git a/source/nord/n2x/n2xLib/n2xhardware.cpp b/source/nord/n2x/n2xLib/n2xhardware.cpp @@ -67,7 +67,7 @@ namespace n2x haltDSP(); } - void Hardware::processAudio(uint32_t _frames, uint32_t _latency) + void Hardware::processAudio(uint32_t _frames, const uint32_t _latency) { ensureBufferSize(_frames); @@ -92,11 +92,11 @@ namespace n2x while (_frames) { - const auto processCount = std::min(_frames, static_cast<uint32_t>(16)); + const auto processCount = std::min(_frames, static_cast<uint32_t>(64)); _frames -= processCount; - m_dspA.advanceSamples(processCount, 0); - m_dspB.advanceSamples(processCount, 0); + m_dspA.advanceSamples(processCount, _latency); + m_dspB.advanceSamples(processCount, _latency); auto* buf = m_dspAtoBBuffer.data(); @@ -124,6 +124,23 @@ namespace n2x // esaiB.processAudioInput(m_dspAtoBBuffer.data(), processCount * 2, 4, 0); + const auto requiredSize = processCount > 8 ? processCount - 8 : 0; + + if(esaiB.getAudioOutputs().size() < requiredSize) + { + // reduce thread contention by waiting for output buffer to be full enough to let us grab the data without entering the read mutex too often + + std::unique_lock uLock(m_requestedFramesAvailableMutex); + m_requestedFrames = requiredSize; + m_requestedFramesAvailableCv.wait(uLock, [&]() + { + if(esaiB.getAudioOutputs().size() < requiredSize) + return false; + m_requestedFrames = 0; + return true; + }); + } + // read output of DSP B to regular audio output esaiB.processAudioOutputInterleaved(outputs, processCount); diff --git a/source/nord/n2x/n2xLib/n2xhardware.h b/source/nord/n2x/n2xLib/n2xhardware.h @@ -21,13 +21,19 @@ namespace n2x const auto& getAudioOutputs() const { return m_audioOutputs; } - void processAudio(const uint32_t _frames) + void processAudio(uint32_t _frames, uint32_t _latency); + + void resumeDSPsForFunc(const std::function<void()>& _callback) { - return processAudio(_frames, _frames); + const auto halted = m_haltDSP; + if(halted) + resumeDSP(); + _callback(); + if(halted) + haltDSP(); } private: - void processAudio(uint32_t _frames, uint32_t _latency); void ensureBufferSize(uint32_t _frames); void onEsaiCallback(); void syncUCtoDSP(); diff --git a/source/nord/n2x/n2xTestConsole/n2xTestConsole.cpp b/source/nord/n2x/n2xTestConsole/n2xTestConsole.cpp @@ -50,7 +50,7 @@ int main() while(true) { - hw->processAudio(blockSize); + hw->processAudio(blockSize, blockSize); auto& outs = hw->getAudioOutputs(); diff --git a/source/synthLib/CMakeLists.txt b/source/synthLib/CMakeLists.txt @@ -28,6 +28,7 @@ set(SOURCES resamplerInOut.cpp resamplerInOut.h romLoader.cpp romLoader.h sysexToMidi.cpp sysexToMidi.h + trigger.cpp trigger.h wavReader.cpp wavReader.h wavTypes.h wavWriter.cpp wavWriter.h diff --git a/source/synthLib/trigger.cpp b/source/synthLib/trigger.cpp @@ -0,0 +1 @@ +#include "trigger.h" diff --git a/source/synthLib/trigger.h b/source/synthLib/trigger.h @@ -0,0 +1,40 @@ +#pragma once + +#include <mutex> +#include <condition_variable> +#include <cstdint> + +namespace synthLib +{ + template<typename TCounter = uint32_t> + class Trigger + { + public: + void wait() + { + std::unique_lock uLock(m_haltMutex); + + m_haltcv.wait(uLock, [&] + { + return m_notifyCounter > m_waitCounter; + }); + + ++m_waitCounter; + } + + void notify() + { + { + std::lock_guard uLockHalt(m_haltMutex); + ++m_notifyCounter; + } + m_haltcv.notify_one(); + } + + private: + std::condition_variable m_haltcv; + std::mutex m_haltMutex; + TCounter m_waitCounter = 0; + TCounter m_notifyCounter = 0; + }; +}