diff --git a/artifacts/Pi-Icon-Inkscape.svg b/artifacts/Pi-Icon-Inkscape.svg index 4315f57..1925dbd 100644 --- a/artifacts/Pi-Icon-Inkscape.svg +++ b/artifacts/Pi-Icon-Inkscape.svg @@ -7,9 +7,9 @@ viewBox="0 0 16.933332 16.933334" version="1.1" id="svg1" - inkscape:export-filename="logo192.png" - inkscape:export-xdpi="288" - inkscape:export-ydpi="288" + inkscape:export-filename="../vite/public/logo192.png" + inkscape:export-xdpi="288.00006" + inkscape:export-ydpi="288.00006" inkscape:version="1.4.3 (1:1.4.3+202512261035+0d15f75042)" sodipodi:docname="Pi-Icon-Inkscape.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" @@ -25,18 +25,18 @@ inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" - inkscape:document-units="mm" - showgrid="true" + inkscape:document-units="px" + showgrid="false" inkscape:lockguides="true" - inkscape:zoom="14.609375" - inkscape:cx="32" - inkscape:cy="32" - inkscape:window-width="2141" - inkscape:window-height="1161" + inkscape:zoom="3.1914063" + inkscape:cx="61.101591" + inkscape:cy="46.844553" + inkscape:window-width="1920" + inkscape:window-height="1043" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" - inkscape:current-layer="layer1"> + inkscape:current-layer="layer2"> + id="defs1"> + + + + + + + + + style="display:none"> + + + + diff --git a/artifacts/Pi-Icon-Svg.svg b/artifacts/Pi-Icon-Svg.svg index 0fb2d6f..1925dbd 100644 --- a/artifacts/Pi-Icon-Svg.svg +++ b/artifacts/Pi-Icon-Svg.svg @@ -7,9 +7,9 @@ viewBox="0 0 16.933332 16.933334" version="1.1" id="svg1" - inkscape:export-filename="../vite/public/logo512.png" - inkscape:export-xdpi="768" - inkscape:export-ydpi="768" + inkscape:export-filename="../vite/public/logo192.png" + inkscape:export-xdpi="288.00006" + inkscape:export-ydpi="288.00006" inkscape:version="1.4.3 (1:1.4.3+202512261035+0d15f75042)" sodipodi:docname="Pi-Icon-Inkscape.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" @@ -25,18 +25,18 @@ inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" - inkscape:document-units="mm" - showgrid="true" + inkscape:document-units="px" + showgrid="false" inkscape:lockguides="true" - inkscape:zoom="14.609375" - inkscape:cx="32" - inkscape:cy="32" - inkscape:window-width="2141" - inkscape:window-height="1161" + inkscape:zoom="3.1914063" + inkscape:cx="61.101591" + inkscape:cy="46.844553" + inkscape:window-width="1920" + inkscape:window-height="1043" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" - inkscape:current-layer="layer1"> + inkscape:current-layer="layer2"> + id="defs1"> + + + + + + + + + style="display:none"> + r="8.4666653" /> + transform="matrix(1.1241207,0,0,1.1241207,-1.0580763,-1.0508884)"> + + + + diff --git a/artifacts/logo192.png b/artifacts/logo192.png new file mode 100644 index 0000000..2bb4cca Binary files /dev/null and b/artifacts/logo192.png differ diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index 7e999bc..06733c8 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -273,7 +273,7 @@ namespace pipedal auto inAvail = snd_pcm_avail_update(this->captureHandle); auto outAvail = snd_pcm_avail_update(this->playbackHandle); - auto total = (inAvail >= 0? inAvail: 0) + (outAvail >= 0 ? outAvail: 0) + framesInBuffer; + auto total = (inAvail >= 0 ? inAvail : 0) + (outAvail >= 0 ? outAvail : 0) + framesInBuffer; bufferTraces[bufferTraceIndex++] = { time, inAvail, @@ -332,23 +332,26 @@ namespace pipedal std::vector> allocatedBuffers; - std::set usedOutputChannels; - std::vector deviceCaptureBuffers; - std::vector devicePlaybackBuffers; + std::vector deviceCaptureBuffers; + std::vector devicePlaybackBuffers; float *zeroInputBuffer = nullptr; float *discardOutputBuffer = nullptr; std::vector mainCaptureBuffers; std::vector mainPlaybackBuffers; + std::vector mainInsertPlaybackBuffers; + std::vector mainVuPlaybackBuffers; std::vector auxCaptureBuffers; std::vector auxPlaybackBuffers; + std::vector auxVuPlaybackBuffers; std::vector sendCaptureBuffers; std::vector sendPlaybackBuffers; - struct OutputBufferMix { + struct OutputBufferMix + { size_t outputChannel = 0; - std::vector inputBuffers; + std::vector inputBuffers; float *outputBuffer = nullptr; }; @@ -362,7 +365,9 @@ namespace pipedal void validate_capture_handle() { // leftover debugging for a buffer overrun :-/ #ifdef DEBUG - if (snd_pcm_type(captureHandle) != SND_PCM_TYPE_HW) + auto pcmType = snd_pcm_type(captureHandle); + + if (pcmType != SND_PCM_TYPE_HW && pcmType != SND_PCM_TYPE_NULL) { throw std::runtime_error("Capture handle has been overwritten"); } @@ -1130,27 +1135,9 @@ namespace pipedal } } - public: - void TestFormatEncodeDecode(snd_pcm_format_t captureFormat); - - private: - void AllocateBuffers(std::vector &buffers, size_t n) - { - buffers.resize(n); - for (size_t i = 0; i < n; ++i) - { - buffers[i] = new float[this->bufferSize]; - for (size_t j = 0; j < this->bufferSize; ++j) - { - buffers[i][j] = 0; - } - } - } - - ChannelSelection channelSelection; - bool open = false; - virtual void Open(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection) + virtual void Open(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection) override { + this->isDummyDriver = jackServerSettings.IsDummyAudioDevice(); terminateAudio_ = false; if (open) { @@ -1174,6 +1161,27 @@ namespace pipedal } } + public: + void TestFormatEncodeDecode(snd_pcm_format_t captureFormat); + + private: + void AllocateBuffers(std::vector &buffers, size_t n) + { + buffers.resize(n); + for (size_t i = 0; i < n; ++i) + { + buffers[i] = new float[this->bufferSize]; + for (size_t j = 0; j < this->bufferSize; ++j) + { + buffers[i][j] = 0; + } + } + } + + ChannelSelection channelSelection; + + bool open = false; + void RestartAlsa() { std::lock_guard lock{restartMutex}; @@ -1200,13 +1208,13 @@ namespace pipedal throw std::runtime_error("Unable to restart the audio stream."); } int err; - + if ((err = snd_pcm_start(captureHandle)) < 0) { Lv2Log::error(SS("Unable to restart ALSA capture: " << snd_strerror(err))); throw PiPedalStateException("Unable to restart ALSA capture."); } - TraceBufferPositions(0,'+'); + TraceBufferPositions(0, '+'); audioRunning = true; } @@ -1280,15 +1288,11 @@ namespace pipedal << ", " << GetAlsaFormatDescription(this->captureFormat) << ", " << this->sampleRate << ", " << this->bufferSize << "x" << this->numberOfBuffers - << ", " << "device in: " << this->DeviceInputBufferCount() - << ", " << "device out: " << this->DeviceOutputBufferCount() - << ", main in: " << this->MainInputBufferCount() - << ", main out: " << this->MainOutputBufferCount() - << ", aux in: " << this->AuxInputBufferCount() - << ", aux out: " << this->AuxOutputBufferCount() - << ", send in: " << this->SendInputBufferCount() - << ", send out: " << this->SendOutputBufferCount() - ); + << ", " << "device: " << this->DeviceInputBufferCount() << "/" << this->DeviceOutputBufferCount() + << ", main: " << this->MainInputBufferCount() << "/" << this->MainOutputBufferCount() + << ", main ins: " << this->MainOutputBufferCount() << "/" << this->MainOutputBufferCount() + << ", aux: " << this->AuxInputBufferCount() << "/" << this->AuxOutputBufferCount() + << ", send: " << this->SendOutputBufferCount() << "/" << this->SendInputBufferCount()); return result; } void PreparePlaybackFunctions(snd_pcm_format_t playbackFormat) @@ -1357,10 +1361,9 @@ namespace pipedal int err; - std::string inputName = jackServerSettings.GetAlsaInputDevice(); + std::string inputName = jackServerSettings.GetAlsaInputDevice(); std::string outputName = jackServerSettings.GetAlsaOutputDevice(); - this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers(); this->bufferSize = jackServerSettings.GetBufferSize(); this->user_threshold = jackServerSettings.GetBufferSize(); @@ -1403,7 +1406,7 @@ namespace pipedal { snd_pcm_nonblock(playbackHandle, 0); } - + this->alsa_device_name = inputName; err = snd_pcm_open(&captureHandle, inputName.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); @@ -1494,6 +1497,10 @@ namespace pipedal memset(rawPlaybackBuffer.data(), 0, rawPlaybackBuffer.size()); int retry = 0; + if (this->isDummyDriver) + { + return; // dummy driver is insatiable. + } while (true) { auto avail = snd_pcm_avail(this->playbackHandle); @@ -1513,7 +1520,7 @@ namespace pipedal { throw PiPedalStateException(SS("Audio playback failed. " << snd_strerror(err))); } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } if (avail == 0) @@ -1658,7 +1665,7 @@ namespace pipedal auto frame_bytes = this->captureFrameSize; do { - TraceBufferPositions(framesRead,'1'); + TraceBufferPositions(framesRead, '1'); framesRead = snd_pcm_readi(handle, buffer, frames); if (framesRead < 0) @@ -1676,7 +1683,7 @@ namespace pipedal } } while (frames > 0); - TraceBufferPositions(framesRead,'2'); + TraceBufferPositions(framesRead, '2'); return framesRead; } @@ -1727,6 +1734,7 @@ namespace pipedal } private: + bool isDummyDriver = false; long WriteBuffer(snd_pcm_t *handle, uint8_t *buf, size_t frames) { long framesRead; @@ -1827,17 +1835,30 @@ namespace pipedal // Perform any neccessary mixing of outputs. for (auto &bufferMix : outputBufferMixes) { - float *outputBuffer = bufferMix.outputBuffer; - for (auto &mixBuffer : bufferMix.inputBuffers) + float * PIPEDAL_RESTRICT outputBuffer = bufferMix.outputBuffer; + float * PIPEDAL_RESTRICT inputBuffer = bufferMix.inputBuffers[0]; + for (size_t i = 0; i < framesRead; ++i) { + outputBuffer[i] = inputBuffer[i]; + } + for (size_t nInput = 1; nInput < bufferMix.inputBuffers.size(); ++nInput) + { + float * PIPEDAL_RESTRICT inputBuffer = bufferMix.inputBuffers[nInput]; for (size_t i = 0; i < framesRead; ++i) { - outputBuffer[i] += mixBuffer[i]; + outputBuffer[i] += inputBuffer[i]; } } } + // final downmix. (this->*copyOutputFn)(framesRead); + + if (this->driverHost) + { + driverHost->OnRealtimeUpdateDeviceVus(framesRead); + } + cpuUse.AddSample(ProfileCategory::Driver); // process. @@ -1850,6 +1871,10 @@ namespace pipedal recover_from_output_underrun(captureHandle, playbackHandle, err, framesRead); framesRead = 0; } + if (isDummyDriver) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 30)); + } cpuUse.AddSample(ProfileCategory::Write); } } @@ -1889,41 +1914,39 @@ namespace pipedal bool alsaActive = false; - PIPEDAL_NON_INLINE void AllocateInputChannels( const std::vector &channelSelection, - std::vector &channelBuffers - ) { + std::vector &channelBuffers) + { size_t nChannels = channelSelection.size(); if (nChannels == 0) { - channelBuffers.resize(1); - channelBuffers[0] = zeroInputBuffer; + channelBuffers.resize(0); return; } channelBuffers.resize(nChannels); - for (size_t i = 0; i < nChannels; ++i) + for (size_t i = 0; i < nChannels; ++i) { int64_t deviceChannel = channelSelection[i]; - if (deviceChannel == -1 || deviceChannel >= captureChannels) + if (deviceChannel == -1 || deviceChannel >= captureChannels) { channelBuffers[i] = zeroInputBuffer; - } - else + } + else { - channelBuffers[i] = deviceCaptureBuffers[deviceChannel]; + channelBuffers[i] = deviceCaptureBuffers[deviceChannel]; } } } - PIPEDAL_NON_INLINE void AddMixBuffer(int64_t outputChannel, float *mixBuffer) + PIPEDAL_NON_INLINE float *AddMixBuffer(int64_t outputChannel, float *mixBuffer) { - for (auto&bufferMix: outputBufferMixes) + for (auto &bufferMix : outputBufferMixes) { if (bufferMix.outputChannel == outputChannel) { bufferMix.inputBuffers.push_back(mixBuffer); - return; + return bufferMix.outputBuffer; } } OutputBufferMix newMix; @@ -1931,53 +1954,103 @@ namespace pipedal newMix.outputBuffer = devicePlaybackBuffers[outputChannel]; newMix.inputBuffers.push_back(mixBuffer); outputBufferMixes.push_back(std::move(newMix)); + return devicePlaybackBuffers[outputChannel]; } PIPEDAL_NON_INLINE void AllocateOutputChannels( const std::vector &channelSelection, - std::vector &channelBuffers - ) { + std::vector &channelBuffers) + { size_t nChannels = channelSelection.size(); if (nChannels == 0) { - channelBuffers.resize(1); - if (discardOutputBuffer != nullptr) - { - discardOutputBuffer = AllocateAudioBuffer(); - } - channelBuffers[0] = discardOutputBuffer; + channelBuffers.resize(0); return; } channelBuffers.resize(nChannels); - for (size_t i = 0; i < nChannels; ++i) + for (size_t i = 0; i < nChannels; ++i) { int64_t deviceChannel = channelSelection[i]; - if (deviceChannel == -1 || deviceChannel >= playbackChannels) + if (deviceChannel == -1) { - if (discardOutputBuffer == nullptr) + channelBuffers[i] = this->GetDiscardOutputBuffer(); + ; + } + else + { + float *mixBuffer = AllocateAudioBuffer(); + channelBuffers[i] = mixBuffer; + } + } + } + PIPEDAL_NON_INLINE void AllocateOutputChannels( + const std::vector &channelSelection, + std::vector &channelBuffers, + std::vector &vuBuffers, + const std::set &summedChannels) + { + size_t nChannels = channelSelection.size(); + + if (nChannels == 0) + { + channelBuffers.resize(0); + vuBuffers.resize(0); + return; + } + channelBuffers.resize(nChannels); + vuBuffers.resize(nChannels); + for (size_t i = 0; i < nChannels; ++i) + { + int64_t deviceChannel = channelSelection[i]; + if (deviceChannel == -1 || deviceChannel >= playbackChannels) + { + channelBuffers[i] = GetDiscardOutputBuffer(); + } + else + { + if (!summedChannels.contains(deviceChannel)) { - discardOutputBuffer = AllocateAudioBuffer(); + vuBuffers[i] = channelBuffers[i] = this->devicePlaybackBuffers[deviceChannel]; } - channelBuffers[i] = discardOutputBuffer; - } - else - { - if (!usedOutputChannels.contains(deviceChannel)) + else { - usedOutputChannels.insert(deviceChannel); - channelBuffers[i] = deviceCaptureBuffers[deviceChannel]; - - } else { // Used once already. We need to mix down. float *mixBuffer = AllocateAudioBuffer(); channelBuffers[i] = mixBuffer; - AddMixBuffer(deviceChannel, mixBuffer); + vuBuffers[i] = AddMixBuffer(deviceChannel, mixBuffer); } } } } - + std::set ComputeSummedChannels() + { + std::set usedOutputChannels; + std::set result; + for (auto ch : channelSelection.mainOutputChannels()) + { + if (usedOutputChannels.contains(ch)) + { + result.insert(ch); + } + else + { + usedOutputChannels.insert(ch); + } + } + for (auto ch : channelSelection.auxOutputChannels()) + { + if (usedOutputChannels.contains(ch)) + { + result.insert(ch); + } + else + { + usedOutputChannels.insert(ch); + } + } + return result; + } bool activated = false; PIPEDAL_NON_INLINE virtual void Activate() @@ -1990,10 +2063,8 @@ namespace pipedal activated = true; // Reset previously allocated buffers. - usedOutputChannels.clear(); allocatedBuffers.resize(0); - // Allocate device capture buffers. zeroInputBuffer = AllocateAudioBuffer(); deviceCaptureBuffers.resize(captureChannels); @@ -2007,27 +2078,33 @@ namespace pipedal devicePlaybackBuffers[i] = AllocateAudioBuffer(); } + std::set summedOutputChannels = ComputeSummedChannels(); + AllocateInputChannels( channelSelection.mainInputChannels(), this->mainCaptureBuffers); AllocateOutputChannels( channelSelection.mainOutputChannels(), - this->mainPlaybackBuffers - ); + this->mainPlaybackBuffers); + AllocateOutputChannels( + channelSelection.mainOutputChannels(), + this->mainInsertPlaybackBuffers, + this->mainVuPlaybackBuffers, + summedOutputChannels); AllocateInputChannels( channelSelection.auxInputChannels(), this->auxCaptureBuffers); AllocateOutputChannels( channelSelection.auxOutputChannels(), - this->auxPlaybackBuffers - ); + this->auxPlaybackBuffers, + this->auxVuPlaybackBuffers, + summedOutputChannels); AllocateInputChannels( channelSelection.sendInputChannels(), this->sendCaptureBuffers); AllocateOutputChannels( channelSelection.sendOutputChannels(), - this->sendPlaybackBuffers - ); + this->sendPlaybackBuffers); audioThread = std::make_unique([this]() { AudioThread(); }); @@ -2058,23 +2135,74 @@ namespace pipedal AlsaSequencer::ptr alsaSequencer; public: + virtual const ChannelSelection &GetChannelSelection() const override + { + return channelSelection; + } + virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) override { this->alsaSequencer = alsaSequencer; } - virtual size_t DeviceInputBufferCount() const override { + virtual size_t DeviceInputBufferCount() const override + { return deviceCaptureBuffers.size(); } - virtual size_t DeviceOutputBufferCount() const override { + virtual size_t DeviceOutputBufferCount() const override + { return devicePlaybackBuffers.size(); } + virtual float *GetDeviceInputBuffer(size_t channel) const override + { + if (channel >= deviceCaptureBuffers.size()) + return nullptr; + return deviceCaptureBuffers[channel]; + } + virtual float *GetDeviceOutputBuffer(size_t channel) const override + { + if (channel >= devicePlaybackBuffers.size()) + return nullptr; + return devicePlaybackBuffers[channel]; + } + + virtual float *GetZeroInputBuffer() + { + if (zeroInputBuffer == nullptr) + { + zeroInputBuffer = AllocateAudioBuffer(); + } + return zeroInputBuffer; + } + virtual float *GetDiscardOutputBuffer() + { + if (discardOutputBuffer == nullptr) + { + discardOutputBuffer = AllocateAudioBuffer(); + } + return discardOutputBuffer; + } + + virtual std::vector &DeviceInputBuffers() override { return this->deviceCaptureBuffers; } + virtual std::vector &DeviceOutputBuffers() override { return this->devicePlaybackBuffers; } + + virtual std::vector &MainInputBuffers() override { return this->mainCaptureBuffers; } + virtual std::vector &MainOutputBuffers() override { return this->mainPlaybackBuffers; } + virtual std::vector &MainInsertOutputBuffers() override { return this->mainInsertPlaybackBuffers; } + virtual std::vector &MainVuOutputBuffers() override { return this->mainVuPlaybackBuffers; } + + virtual std::vector &AuxInputBuffers() override { return this->auxCaptureBuffers; } + virtual std::vector &AuxOutputBuffers() override { return this->auxPlaybackBuffers; } + virtual std::vector &AuxVuOutputBuffers() override { return this->auxVuPlaybackBuffers; } + + virtual const std::vector &SendInputBuffers() const override { return this->sendCaptureBuffers; } + virtual const std::vector &SendOutputBuffers() const override { return this->sendPlaybackBuffers; } virtual size_t MainInputBufferCount() const { return mainCaptureBuffers.size(); } virtual float *GetMainInputBuffer(size_t channel) override { - if (channel >= (int64_t)mainCaptureBuffers.size()) + if (channel >= (int64_t)mainCaptureBuffers.size()) { throw std::runtime_error("Argument out of range."); } @@ -2085,8 +2213,10 @@ namespace pipedal { return auxCaptureBuffers[channel]; } - virtual size_t AuxOutputBufferCount() const { - return auxPlaybackBuffers.size(); } + virtual size_t AuxOutputBufferCount() const + { + return auxPlaybackBuffers.size(); + } virtual float *GetAuxOutputBuffer(size_t channel) override { return auxPlaybackBuffers[channel]; @@ -2116,11 +2246,25 @@ namespace pipedal { return mainPlaybackBuffers[channel]; } + virtual size_t MainInsertOutputBufferCount() const override + { + return mainInsertPlaybackBuffers.size(); + } + virtual float *GetMainInsertOutputBuffer(size_t channel) const + { + if (channel >= (int64_t)mainCaptureBuffers.size()) + { + throw std::runtime_error("GetMainInsertOutputBuffer: Argument out of range."); + } - float* AllocateAudioBuffer() { + return mainInsertPlaybackBuffers[channel]; + } + + float *AllocateAudioBuffer() + { std::vector buffer; buffer.resize(this->bufferSize); - float*pBuffer = buffer.data(); + float *pBuffer = buffer.data(); allocatedBuffers.push_back(std::move(buffer)); return pBuffer; } @@ -2173,19 +2317,19 @@ namespace pipedal std::vector &inputAudioPorts, std::vector &outputAudioPorts) { - if (jackServerSettings.IsDummyAudioDevice()) - { - auto nChannels = GetDummyAudioChannels(jackServerSettings.GetAlsaInputDevice()); + // if (jackServerSettings.IsDummyAudioDevice()) + // { + // auto nChannels = GetDummyAudioChannels(jackServerSettings.GetAlsaInputDevice()); - inputAudioPorts.clear(); - outputAudioPorts.clear(); - for (uint32_t i = 0; i < nChannels; ++i) - { - inputAudioPorts.push_back(std::string(SS("system::capture_" << i))); - outputAudioPorts.push_back(std::string(SS("system::playback_" << i))); - } - return true; - } + // inputAudioPorts.clear(); + // outputAudioPorts.clear(); + // for (uint32_t i = 0; i < nChannels; ++i) + // { + // inputAudioPorts.push_back(std::string(SS("system::capture_" << i))); + // outputAudioPorts.push_back(std::string(SS("system::playback_" << i))); + // } + // return true; + // } snd_pcm_t *playbackHandle = nullptr; snd_pcm_t *captureHandle = nullptr; @@ -2422,7 +2566,7 @@ namespace pipedal } } - void AlsaFormatEncodeDecodeTest(AudioDriverHost *testDriverHost) + void test::AlsaFormatEncodeDecodeTest(AudioDriverHost *testDriverHost) { static snd_pcm_format_t formats[] = { snd_pcm_format_t::SND_PCM_FORMAT_S16_LE, @@ -2444,7 +2588,7 @@ namespace pipedal alsaDriver->TestFormatEncodeDecode(format); } } - void MidiDecoderTest() + void test::MidiDecoderTest() { #ifdef JUNK AlsaDriverImpl::AlsaMidiDeviceImpl midiState; @@ -2513,37 +2657,41 @@ namespace pipedal { using namespace std; int savedPrecision = cout.precision(); - auto savedFlags = cout.flags(); + auto savedFlags = cout.flags(); size_t ix = bufferTraceIndex; if (ix < nEntries) { - ix = ix + bufferTraces.size()-nEntries; - } else { + ix = ix + bufferTraces.size() - nEntries; + } + else + { ix -= nEntries; } uint64_t t0; - if (bufferTraceIndex == 0) { - t0 = bufferTraces[bufferTraces.size()-1].time; - } else { - t0 = bufferTraces[bufferTraceIndex-1].time; + if (bufferTraceIndex == 0) + { + t0 = bufferTraces[bufferTraces.size() - 1].time; + } + else + { + t0 = bufferTraces[bufferTraceIndex - 1].time; } while (ix != bufferTraceIndex) { - auto&bufferTrace = bufferTraces[ix]; + auto &bufferTrace = bufferTraces[ix]; if (bufferTrace.time != 0) { int64_t dt = (int64_t)bufferTrace.time - (int64_t)t0; - - cout << bufferTrace.code << " " - << fixed << setprecision(3) << dt*0.001 - << " " << "inAvail: " << bufferTrace.inAvail - << " " << "outAvail: " << bufferTrace.outAvail - << " " << "buffered: " << bufferTrace.buffered - << " " << "total: " << bufferTrace.total - << endl; + cout << bufferTrace.code << " " + << fixed << setprecision(3) << dt * 0.001 + << " " << "inAvail: " << bufferTrace.inAvail + << " " << "outAvail: " << bufferTrace.outAvail + << " " << "buffered: " << bufferTrace.buffered + << " " << "total: " << bufferTrace.total + << endl; } ++ix; @@ -2557,4 +2705,51 @@ namespace pipedal cout.flags(savedFlags); } + AlsaDeviceInfo MakeDummyDeviceInfo(uint32_t channels) + { + AlsaDeviceInfo result; + constexpr int DUMMY_DEVICE_ID_OFFSET = 100974; + result.cardId_ = DUMMY_DEVICE_ID_OFFSET + channels; + result.id_ = SS("dummy:channels_" << channels); + result.name_ = SS("Dummy Device (" << channels << " channels)"); + result.longName_ = result.name_; + result.sampleRates_.push_back(44100); + result.sampleRates_.push_back(48000); + result.minBufferSize_ = 16; + result.maxBufferSize_ = 1024; + result.supportsCapture_ = true; + result.supportsPlayback_ = true; + return result; + } + + uint32_t GetDummyAudioChannels(const std::string &deviceName) + { + uint32_t channels; + int pos = deviceName.find_last_of('_'); + if (pos == std::string::npos) + { + throw std::runtime_error("Invalid dummy device name"); + } + std::istringstream ss(deviceName.substr(pos + 1)); + ss >> channels; + return channels; + } + AudioDriver *CreateDummyAudioDriver(AudioDriverHost *driverHost, const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection) + { + auto dummyServerSettings = jackServerSettings; + dummyServerSettings.UseDummyAudioDevice(); + auto dummyChannelSelection = channelSelection; + AudioDriver *driver = new AlsaDriverImpl(driverHost); + try + { + driver->Open(jackServerSettings, channelSelection); + return driver; + } + catch (const std::exception &e) + { + delete driver; + throw; + } + } + } // namespace diff --git a/src/AlsaDriver.hpp b/src/AlsaDriver.hpp index 3d833a8..a6a2c15 100644 --- a/src/AlsaDriver.hpp +++ b/src/AlsaDriver.hpp @@ -38,10 +38,17 @@ namespace pipedal { AudioDriver* CreateAlsaDriver(AudioDriverHost*driverHost); - // test only. - void AlsaFormatEncodeDecodeTest(AudioDriverHost*driverHost); - void MidiDecoderTest(); + namespace test { + // Private use, test only. + void AlsaFormatEncodeDecodeTest(AudioDriverHost*driverHost); + void MidiDecoderTest(); + } void FreeAlsaGlobals(); // for valgrind. Free the Alsa configuration cache. + + // AlsaDeviceInfo MakeDummyDeviceInfo(uint32_t channels); + uint32_t GetDummyAudioChannels(const std::string &deviceName); + // std::shared_ptr CreateDummyAudioDriver(AudioDriverHost*driverHost,const std::string&deviceId); + } diff --git a/src/AlsaDriverTest.cpp b/src/AlsaDriverTest.cpp index 9ef3504..1550f48 100644 --- a/src/AlsaDriverTest.cpp +++ b/src/AlsaDriverTest.cpp @@ -63,6 +63,8 @@ public: delete[] outputBuffers; } + bool OnRealtimeUpdateDeviceVus(size_t nFrames) override { return false; } + void initializeChannelSelection(JackConfiguration &jackConfiguration, ChannelSelection &channelSelection) { size_t nInputs = std::max(jackConfiguration.inputAudioPorts().size(),size_t(2)); @@ -85,7 +87,7 @@ public: void Test() { - AlsaFormatEncodeDecodeTest(this); + test::AlsaFormatEncodeDecodeTest(this); JackServerSettings serverSettings("hw:M2","hw:M2", 48000, 32, 3); @@ -295,5 +297,5 @@ TEST_CASE("alsa_midi_test", "[alsa_midi_test]") { AlsaTester alsaDriver(AlsaTester::TestType::Oscillator); - MidiDecoderTest(); + test::MidiDecoderTest(); } diff --git a/src/AudioDriver.hpp b/src/AudioDriver.hpp index ea88c28..cfcfb22 100644 --- a/src/AudioDriver.hpp +++ b/src/AudioDriver.hpp @@ -27,6 +27,7 @@ #include #include "AlsaSequencer.hpp" #include "MidiEvent.hpp" +#include "DeviceVus.hpp" @@ -37,15 +38,14 @@ namespace pipedal { class ChannelSelection; - class AudioDriverHost { public: virtual void OnProcess(size_t nFrames) = 0; + virtual bool OnRealtimeUpdateDeviceVus(size_t nFrames) = 0; + virtual void OnUnderrun() = 0; virtual void OnAlsaDriverStopped() = 0; virtual void OnAudioTerminated() = 0; - - }; class AudioDriver { public: @@ -60,12 +60,33 @@ namespace pipedal { virtual size_t GetMidiInputEventCount() = 0; virtual MidiEvent*GetMidiEvents() = 0; + virtual const ChannelSelection &GetChannelSelection() const = 0; + + + virtual std::vector &DeviceInputBuffers() = 0; + virtual std::vector &DeviceOutputBuffers() = 0; + + virtual std::vector &MainInputBuffers() = 0; + virtual std::vector &MainOutputBuffers() = 0; + virtual std::vector&MainInsertOutputBuffers() = 0; + virtual std::vector&MainVuOutputBuffers() = 0; + + virtual std::vector&AuxInputBuffers() = 0; + virtual std::vector&AuxOutputBuffers() = 0; + virtual std::vector&AuxVuOutputBuffers() = 0; + virtual size_t DeviceInputBufferCount() const = 0; virtual size_t DeviceOutputBufferCount() const = 0; + virtual float* GetDeviceInputBuffer(size_t channel) const = 0; + virtual float* GetDeviceOutputBuffer(size_t channel) const = 0; + virtual size_t MainInputBufferCount() const = 0; virtual float*GetMainInputBuffer(size_t channel) = 0; virtual size_t MainOutputBufferCount() const = 0; - virtual float*GetMainOutputBuffer(size_t channe) = 0; + virtual float*GetMainOutputBuffer(size_t channel) = 0; + + virtual size_t MainInsertOutputBufferCount() const = 0; + virtual float*GetMainInsertOutputBuffer(size_t channe) const = 0; virtual size_t AuxInputBufferCount() const = 0; @@ -75,9 +96,13 @@ namespace pipedal { virtual size_t SendInputBufferCount() const = 0; virtual float*GetSendInputBuffer(size_t channel) = 0; + virtual const std::vector&SendInputBuffers() const = 0; virtual size_t SendOutputBufferCount() const = 0; virtual float*GetSendOutputBuffer(size_t channel) = 0; + virtual const std::vector&SendOutputBuffers() const = 0; + virtual float*GetZeroInputBuffer() = 0; + virtual float*GetDiscardOutputBuffer() = 0; virtual void Open(const JackServerSettings & jackServerSettings,const ChannelSelection &channelSelection) = 0; virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) = 0; diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp index 212c327..fbd16cb 100644 --- a/src/AudioHost.cpp +++ b/src/AudioHost.cpp @@ -348,7 +348,8 @@ bool SystemMidiBinding::IsTriggered(const MidiEvent &event) if (currentBinding.switchControlType() == SwitchControlTypeT::TRIGGER_ON_RISING_EDGE) { result = value >= 0x64 && lastControlValue < 0x64; - } else if (currentBinding.switchControlType() == SwitchControlTypeT::TRIGGER_ON_ANY) + } + else if (currentBinding.switchControlType() == SwitchControlTypeT::TRIGGER_ON_ANY) { result = true; } @@ -529,8 +530,6 @@ private: std::shared_ptr currentMainInsertPedalboard; std::shared_ptr currentAuxInsertPedalboard; - - std::vector> activePedalboards; // pedalboards that have been sent to the audio queue. Lv2Pedalboard *realtimeActivePedalboard = nullptr; Lv2RoutingInserts realtimeRoutingInserts; @@ -544,7 +543,7 @@ private: std::string GetAtomObjectType(uint8_t *pData) { - LV2_Atom_Object *pAtom = (LV2_Atom_Object *)pData; + LV2_Atom_Object *pAtom = (LV2_Atom_Object *)pData; if (pAtom->atom.type != uris.atom_Object) { throw std::invalid_argument("Not an Lv2 Object"); @@ -629,7 +628,7 @@ private: { for (size_t i = 0; i < audioDriver->MainOutputBufferCount(); ++i) { - float * out = (float *)audioDriver->GetMainOutputBuffer(i); + float *out = (float *)audioDriver->GetMainOutputBuffer(i); if (out) { ZeroBuffer(out, nframes); @@ -638,7 +637,7 @@ private: } RealtimeVuBuffers *realtimeVuBuffers = nullptr; size_t vuSamplesPerUpdate = 0; - int64_t vuSamplesRemaining = 0; + int64_t realtimeVuSamplesRemaining = 0; void freeRealtimeVuConfiguration() { @@ -664,11 +663,15 @@ private: virtual void SetSystemMidiBindings(const std::vector &bindings); - void writeVu() + void realtimeWriteVus() { // throttling: we send one; but won't send another until the host thread // acknowledges receipt. + if (!realtimeVuBuffers) + { + return; + } if (!realtimeVuBuffers->waitingForAcknowledge) { auto pResult = realtimeVuBuffers->GetResult(currentSample); @@ -678,19 +681,27 @@ private: } } - void processMonitorPortSubscriptions(uint32_t nframes) + void processMonitorPortSubscriptions( + PedalboardType pedalboardType, + Lv2Pedalboard *pedalboard, + uint32_t nframes) { for (size_t i = 0; i < this->realtimeMonitorPortSubscriptions->subscriptions.size(); ++i) { auto &portSubscription = realtimeMonitorPortSubscriptions->subscriptions[i]; + if (pedalboardType != portSubscription.pedalboardType) + { + continue; + } + portSubscription.samplesToNextCallback -= portSubscription.sampleRate; if (portSubscription.samplesToNextCallback < 0) { portSubscription.samplesToNextCallback += portSubscription.sampleRate; if (!portSubscription.waitingForAck) { - float value = realtimeActivePedalboard->GetControlOutputValue( + float value = pedalboard->GetControlOutputValue( portSubscription.instanceIndex, portSubscription.portIndex); if (value != portSubscription.lastValue) @@ -852,7 +863,7 @@ private: { this->realtimeVuBuffers->waitingForAcknowledge = false; } - vuSamplesRemaining = vuSamplesPerUpdate; + realtimeVuSamplesRemaining = vuSamplesPerUpdate; break; } @@ -971,7 +982,7 @@ private: return (event.size == 3 && event.buffer[0] == 0xB0 && event.buffer[1] == 0x00); } - bool onMidiEvent(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event) + bool ProcessMidiMonitor(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event) { // eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer); @@ -995,7 +1006,7 @@ private: } if (isNote || isControl) { - MidiNotifyBody notifyBody (event.buffer[0],event.buffer[1], event.buffer[2]); + MidiNotifyBody notifyBody(event.buffer[0], event.buffer[1], event.buffer[2]); realtimeWriter.OnMidiListen(notifyBody); } } @@ -1108,7 +1119,7 @@ private: } else { - onMidiEvent(eventBufferWriter, iterator, event); + ProcessMidiMonitor(eventBufferWriter, iterator, event); } } @@ -1151,7 +1162,69 @@ private: } } } - void ProcessMidiInput() + bool GetAudioDriverBuffers(PedalboardType pedalboardType, + float **inputBuffers, + float **outputBuffers) + { + bool buffersValid = true; + if (!audioDriver) + { + inputBuffers[0] = nullptr; + outputBuffers[0] = nullptr; + return false; + } + switch (pedalboardType) + { + case PedalboardType::MainPedalboard: + for (int i = 0; i < audioDriver->MainInputBufferCount(); ++i) + { + float *input = (float *)audioDriver->GetMainInputBuffer(i); + if (input == nullptr) + { + buffersValid = false; + } + inputBuffers[i] = input; + } + inputBuffers[audioDriver->MainInputBufferCount()] = nullptr; + + for (int i = 0; i < audioDriver->MainOutputBufferCount(); ++i) + { + float *output = audioDriver->GetMainOutputBuffer(i); + if (output == nullptr) + { + buffersValid = false; + } + outputBuffers[i] = output; + } + outputBuffers[audioDriver->MainOutputBufferCount()] = nullptr; + break; + case PedalboardType::MainInserts: + for (int i = 0; i < audioDriver->MainInputBufferCount(); ++i) + { + float *input = (float *)audioDriver->GetMainInputBuffer(i); + if (input == nullptr) + { + buffersValid = false; + } + inputBuffers[i] = input; + } + inputBuffers[audioDriver->MainInputBufferCount()] = nullptr; + + for (int i = 0; i < audioDriver->MainOutputBufferCount(); ++i) + { + float *output = audioDriver->GetMainOutputBuffer(i); + if (output == nullptr) + { + buffersValid = false; + } + outputBuffers[i] = output; + } + outputBuffers[audioDriver->MainOutputBufferCount()] = nullptr; + break; + } + return buffersValid; + } + void ProcessGlobalMidiInput() { Lv2EventBufferWriter eventBufferWriter(this->eventBufferUrids); Lv2EventBufferWriter::LV2_EvBuf_Iterator iterator = eventBufferWriter.begin(); @@ -1190,11 +1263,253 @@ private: Lv2Log::info("Audio thread terminated."); } + PIPEDAL_NON_INLINE void ProcessLv2Pedalboard(PedalboardType pedalboardType, size_t nframes) + { + Lv2Pedalboard *pedalboard = nullptr; + + std::vector *pInputBuffers; + std::vector *pOutputBuffers; + switch (pedalboardType) + { + case PedalboardType::MainPedalboard: + pedalboard = this->realtimeActivePedalboard; + pInputBuffers = &(audioDriver->MainInputBuffers()); + if (this->realtimeRoutingInserts.mainInserts != nullptr) + { + pOutputBuffers = &(audioDriver->MainOutputBuffers()); + } + else + { + pOutputBuffers = &(audioDriver->MainInsertOutputBuffers()); + } + break; + case PedalboardType::MainInserts: + + pedalboard = this->realtimeRoutingInserts.mainInserts; + pInputBuffers = &(audioDriver->MainOutputBuffers()); + pOutputBuffers = &(audioDriver->MainInsertOutputBuffers()); + break; + case PedalboardType::AuxInserts: + pedalboard = this->realtimeRoutingInserts.auxInserts; + pInputBuffers = &(audioDriver->AuxInputBuffers()); + pOutputBuffers = &(audioDriver->AuxOutputBuffers()); + break; + } + if (pInputBuffers->size() == 0 || pOutputBuffers->size() == 0) + { + return; + } + float *inputBuffers[3]; + float *outputBuffers[3]; + bool buffersValid = true; + inputBuffers[0] = pInputBuffers->at(0); + inputBuffers[1] = pInputBuffers->size() >= 2 ? pInputBuffers->at(1) : nullptr; + inputBuffers[2] = nullptr; + + outputBuffers[0] = pOutputBuffers->at(0); + outputBuffers[1] = pOutputBuffers->size() >= 2 ? pOutputBuffers->at(1) : nullptr; + outputBuffers[2] = nullptr; + + if (pedalboard != nullptr) + { + pedalboard->ProcessParameterRequests(pParameterRequests, nframes); + + pedalboard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter); + pedalboard->GatherPatchProperties(pParameterRequests); + pedalboard->GatherPathPatchProperties(this); + + if (this->realtimeMonitorPortSubscriptions != nullptr) + { + processMonitorPortSubscriptions(pedalboardType, pedalboard, nframes); + } + } + else + { + switch (pedalboardType) + { + case PedalboardType::MainPedalboard: + { + // zero output buffers. + size_t ix = 0; + while (outputBuffers[ix]) + { + float *outputBuffer = outputBuffers[ix]; + for (size_t i = 0; i < nframes; ++i) + { + outputBuffer[i] = 0; + } + ++ix; + } + } + break; + case PedalboardType::MainInserts: + // do nothing. Main pedalboard wrote to Main insert outputs directly in this case. + break; + case PedalboardType::AuxInserts: + // Copy inputs to outputs. + { + if (outputBuffers[0] != nullptr && inputBuffers[0] != nullptr) + { + float *PIPEDAL_RESTRICT outputBuffer = outputBuffers[0]; + float *PIPEDAL_RESTRICT inputBuffer = inputBuffers[0]; + for (size_t i = 0; i < nframes; ++i) + { + outputBuffer[i] = inputBuffer[i]; + } + } + if (outputBuffers[1] != nullptr && inputBuffers[0] != nullptr) + { + float *PIPEDAL_RESTRICT outputBuffer = outputBuffers[1]; + float *PIPEDAL_RESTRICT inputBuffer = inputBuffers[1] != nullptr ? inputBuffers[1] : inputBuffers[0]; + for (size_t i = 0; i < nframes; ++i) + { + outputBuffer[i] = inputBuffer[i]; + } + } + } + break; + case PedalboardType::Invalid: + throw std::runtime_error("Invalid argument."); + break; + } + } + } + + void GetMasterChannels(PedalboardType pedalboardType, float **masterInputBuffers, float **masterOutputBuffers) + { + size_t inputIx = 0; + size_t outputIx = 0; + + masterInputBuffers[0] = nullptr; + masterInputBuffers[1] = nullptr; + masterOutputBuffers[0] = nullptr; + masterOutputBuffers[1] = nullptr; + + const ChannelSelection &channelSelection = audioDriver->GetChannelSelection(); + + switch (pedalboardType) + { + case PedalboardType::MainPedalboard: + for (auto nChannel : channelSelection.mainInputChannels()) + { + masterInputBuffers[inputIx++] = audioDriver->GetDeviceInputBuffer(nChannel); + } + for (auto nChannel : channelSelection.mainOutputChannels()) + { + masterOutputBuffers[outputIx++] = audioDriver->GetDeviceOutputBuffer(nChannel); + } + break; + case PedalboardType::MainInserts: + if (this->realtimeActivePedalboard) + { + for (float *buffer : this->realtimeActivePedalboard->GetoutputBuffers()) + { + masterInputBuffers[inputIx++] = buffer; + } + for (auto nChannel : channelSelection.mainOutputChannels()) + { + masterOutputBuffers[outputIx++] = audioDriver->GetDeviceOutputBuffer(nChannel); + } + } + break; + case PedalboardType::AuxInserts: + for (auto nChannel : channelSelection.auxInputChannels()) + { + switch (nChannel) + { + case ChannelRouterSettings::MAIN_OUT_LEFT_CHANNEL: + { + float *pBuffer = nullptr; + if (this->realtimeActivePedalboard) + { + auto &mainOutputBuffers = realtimeActivePedalboard->GetoutputBuffers(); + if (mainOutputBuffers.size() >= 1) + { + pBuffer = mainOutputBuffers[0]; + } + } + if (pBuffer == nullptr) + { + pBuffer = audioDriver->GetZeroInputBuffer(); + } + masterInputBuffers[inputIx++] = pBuffer; + } + break; + case ChannelRouterSettings::MAIN_OUT_RIGHT_CHANNEL: + { + float *pBuffer = nullptr; + if (this->realtimeActivePedalboard) + { + auto &mainOutputBuffers = realtimeActivePedalboard->GetoutputBuffers(); + if (mainOutputBuffers.size() == 1) + { + pBuffer = mainOutputBuffers[0]; + } + else if (mainOutputBuffers.size() == 2) + { + pBuffer = mainOutputBuffers[0]; + } + } + if (pBuffer == nullptr) + { + pBuffer = audioDriver->GetZeroInputBuffer(); + } + masterInputBuffers[inputIx++] = pBuffer; + } + break; + default: + masterInputBuffers[outputIx++] = audioDriver->GetDeviceInputBuffer(nChannel); + }; + } + for (auto nChannel : channelSelection.auxOutputChannels()) + { + masterOutputBuffers[outputIx++] = audioDriver->GetDeviceOutputBuffer(nChannel); + } + break; + + default: + throw std::runtime_error("GetMasterChannels: Argument out of range."); + } + } + bool OnRealtimeUpdateDeviceVus(size_t nFrames) override + { + // all lv2pedalboards processed, and all channels downmixed. + // Now we can do the real vu update work! + + float *masterInputBuffers[2]; + float *masterOutputBuffers[2]; + + if (this->realtimeActivePedalboard != nullptr) + { + GetMasterChannels(PedalboardType::MainPedalboard, masterInputBuffers, masterOutputBuffers); + realtimeActivePedalboard->ComputeVus(this->realtimeVuBuffers, nFrames, masterInputBuffers, masterOutputBuffers); + } + if (this->realtimeRoutingInserts.mainInserts) + { + GetMasterChannels(PedalboardType::MainInserts, masterInputBuffers, masterOutputBuffers); + this->realtimeRoutingInserts.mainInserts->ComputeVus(this->realtimeVuBuffers, nFrames, masterInputBuffers, masterOutputBuffers); + } + if (this->realtimeRoutingInserts.auxInserts) + { + GetMasterChannels(PedalboardType::AuxInserts, masterInputBuffers, masterOutputBuffers); + this->realtimeRoutingInserts.auxInserts->ComputeVus(this->realtimeVuBuffers, nFrames, masterInputBuffers, masterOutputBuffers); + } + + // periodically send updates. + realtimeVuSamplesRemaining -= nFrames; + if (realtimeVuSamplesRemaining <= 0) + { + realtimeWriteVus(); + realtimeVuSamplesRemaining = vuSamplesPerUpdate; + } + + return true; + } virtual void OnProcess(size_t nframes) { try { - float * restrict in , * restrict out; + float *restrict in, *restrict out; Lv2Pedalboard *pedalboard = nullptr; pedalboard = this->realtimeActivePedalboard; @@ -1202,6 +1517,15 @@ private: { pedalboard->ResetAtomBuffers(); } + if (this->realtimeRoutingInserts.auxInserts) + { + this->realtimeRoutingInserts.auxInserts->ResetAtomBuffers(); + } + if (this->realtimeRoutingInserts.mainInserts) + { + this->realtimeRoutingInserts.mainInserts->ResetAtomBuffers(); + } + while (true) { @@ -1212,71 +1536,18 @@ private: // else a new pedalboard was installed. start again. pedalboard = this->realtimeActivePedalboard; } - bool processed = false; if (pedalboard != nullptr) { - ProcessMidiInput(); - float *inputBuffers[4]; - float *outputBuffers[4]; - bool buffersValid = true; - for (int i = 0; i < audioDriver->MainInputBufferCount(); ++i) - { - float *input = (float *)audioDriver->GetMainInputBuffer(i); - if (input == nullptr) - { - buffersValid = false; - break; - } - inputBuffers[i] = input; - } - inputBuffers[audioDriver->MainInputBufferCount()] = nullptr; - - for (int i = 0; i < audioDriver->MainOutputBufferCount(); ++i) - { - float *output = audioDriver->GetMainOutputBuffer(i); - if (output == nullptr) - { - buffersValid = false; - break; - } - outputBuffers[i] = output; - } - outputBuffers[audioDriver->MainOutputBufferCount()] = nullptr; - - if (buffersValid) - { - pedalboard->ProcessParameterRequests(pParameterRequests,nframes); - - processed = pedalboard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter); - if (processed) - { - if (this->realtimeVuBuffers != nullptr) - { - pedalboard->ComputeVus(this->realtimeVuBuffers, (uint32_t)nframes, inputBuffers, outputBuffers); - - vuSamplesRemaining -= nframes; - if (vuSamplesRemaining <= 0) - { - writeVu(); - vuSamplesRemaining += vuSamplesPerUpdate; - } - } - if (this->realtimeMonitorPortSubscriptions != nullptr) - { - processMonitorPortSubscriptions(nframes); - } - } - pedalboard->GatherPatchProperties(pParameterRequests); - pedalboard->GatherPathPatchProperties(this); - } + ProcessGlobalMidiInput(); } - - if (!processed) + ProcessLv2Pedalboard(PedalboardType::MainPedalboard, nframes); + if (this->realtimeRoutingInserts.mainInserts != nullptr) // prevent zero-ing of main outputs if there's no mainInserts { - ZeroOutputBuffers(nframes); + ProcessLv2Pedalboard(PedalboardType::MainInserts, nframes); } + ProcessLv2Pedalboard(PedalboardType::AuxInserts, nframes); if (pParameterRequests != nullptr) { @@ -1319,13 +1590,12 @@ public: this->alsaDeviceMonitor->StartMonitoring( [this]( - AlsaSequencerDeviceMonitor::MonitorAction action, + AlsaSequencerDeviceMonitor::MonitorAction action, int client, - const std::string &clientName) - { - HandleAlsaSequencerDevicesChanged(action, client, clientName); - } - ); + const std::string &clientName) + { + HandleAlsaSequencerDevicesChanged(action, client, clientName); + }); } virtual ~AudioHostImpl() { @@ -1359,10 +1629,10 @@ public: } else if (action == AlsaSequencerDeviceMonitor::MonitorAction::DeviceAdded) { - pNotifyCallbacks->OnAlsaSequencerDeviceAdded(client,clientName); + pNotifyCallbacks->OnAlsaSequencerDeviceAdded(client, clientName); } } - } + } void HandleAudioTerminatedAbnormally() { Lv2Log::error("Audio processing terminated unexpectedly."); @@ -1509,7 +1779,7 @@ public: } else if (command == RingBufferCommand::SendVuUpdate) { - const std::vector *updates = nullptr; + const std::vector *updates = nullptr; hostReader.read(&updates); if (this->pNotifyCallbacks) @@ -1564,7 +1834,7 @@ public: if (property != nullptr && value != nullptr && property->type == uris.atom_URID) { LV2_URID propertyUrid = ((LV2_Atom_URID *)property)->body; - if (this->pNotifyCallbacks) + if (this->pNotifyCallbacks) { this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value); } @@ -1603,7 +1873,6 @@ public: { OnActivePedalboardReleased(body.auxInserts); } - } else if (command == RingBufferCommand::FreeSnapshot) { @@ -1743,16 +2012,8 @@ public: } isOpen = true; - if (jackServerSettings.IsDummyAudioDevice()) - { - this->isDummyAudioDriver = true; - this->audioDriver = std::unique_ptr(CreateDummyAudioDriver(this, jackServerSettings.GetAlsaInputDevice())); - } - else - { - this->isDummyAudioDriver = false; - this->audioDriver = std::unique_ptr(CreateAlsaDriver(this)); - } + this->isDummyAudioDriver = jackServerSettings.IsDummyAudioDevice(); + this->audioDriver = std::unique_ptr(CreateAlsaDriver(this)); this->currentSample = 0; this->underruns = 0; @@ -1839,24 +2100,23 @@ public: } virtual void SetChannelRoutingInserts( - const std::shared_ptr &mainInserts, - const std::shared_ptr &auxInserts - ) override { - if (active && mainInserts) + const std::shared_ptr &mainInserts, + const std::shared_ptr &auxInserts) override + { + if (active && mainInserts) { mainInserts->Activate(); this->activePedalboards.push_back(mainInserts); - } - if (active && auxInserts) + } + if (active && auxInserts) { auxInserts->Activate(); this->activePedalboards.push_back(auxInserts); } - Lv2RoutingInserts routingInserts { mainInserts: mainInserts.get(), auxInserts: auxInserts.get()}; + Lv2RoutingInserts routingInserts{mainInserts : mainInserts.get(), auxInserts : auxInserts.get()}; hostWriter.ReplaceRoutingInserts(routingInserts); } - virtual void SetBypass(uint64_t instanceId, bool enabled) { std::lock_guard guard(mutex); @@ -1871,7 +2131,6 @@ public: } } - virtual void SetPluginPreset(uint64_t instanceId, const std::vector &values) { std::lock_guard guard(mutex); @@ -1942,7 +2201,6 @@ public: virtual void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration) override; - void OnNotifyPathPatchPropertyReceived( int64_t instanceId, const std::string &pathPatchPropertyUri, @@ -1975,45 +2233,63 @@ public: PIPEDAL_NON_INLINE RealtimePedalboardItemIndex GetRealtimeItemIndex(int64_t instanceId) { - Pedalboard::PedalboardType instanceType = Pedalboard::GetInstanceTypeFromInstanceId(instanceId); + PedalboardType instanceType = Pedalboard::GetPedalboardTypeFromInstanceId(instanceId); int64_t index = -1; switch (instanceType) { - case Pedalboard::PedalboardType::MainPedalboard: - if (this->currentPedalboard) { - if (instanceId == Pedalboard::START_CONTROL_ID) { - index = Pedalboard::START_CONTROL_ID; - } else if (instanceId == Pedalboard::END_CONTROL_ID) { - index = Pedalboard::END_CONTROL_ID; - } else { - index = this->currentPedalboard->GetIndexOfInstanceId(instanceId); - } + case PedalboardType::MainPedalboard: + if (this->currentPedalboard) + { + if (instanceId == Pedalboard::START_CONTROL_ID) + { + index = Pedalboard::START_CONTROL_ID; } - break; - case Pedalboard::PedalboardType::MainInsert: - if (this->currentMainInsertPedalboard) { - if (instanceId == Pedalboard::MAIN_INSERT_START_CONTROL_ID) { - index = Pedalboard::MAIN_INSERT_START_CONTROL_ID; - } else if (instanceId == Pedalboard::MAIN_INSERT_END_CONTROL_ID) { - index = Pedalboard::END_CONTROL_ID; - } else { - index = this->currentMainInsertPedalboard->GetIndexOfInstanceId(instanceId); - } - } - break; - case Pedalboard::PedalboardType::AuxInsert: - if (this->currentAuxInsertPedalboard) { - if (instanceId == Pedalboard::AUX_INSERT_START_CONTROL_ID) { - index = Pedalboard::START_CONTROL_ID; - } else if (instanceId == Pedalboard::AUX_INSERT_END_CONTROL_ID) { - index = Pedalboard::END_CONTROL_ID; - } else { - index = this->currentAuxInsertPedalboard->GetIndexOfInstanceId(instanceId); - } + else if (instanceId == Pedalboard::END_CONTROL_ID) + { + index = Pedalboard::END_CONTROL_ID; } - break; + else + { + index = this->currentPedalboard->GetIndexOfInstanceId(instanceId); + } + } + break; + case PedalboardType::MainInserts: + if (this->currentMainInsertPedalboard) + { + if (instanceId == Pedalboard::MAIN_INSERT_START_CONTROL_ID) + { + index = Pedalboard::MAIN_INSERT_START_CONTROL_ID; + } + else if (instanceId == Pedalboard::MAIN_INSERT_END_CONTROL_ID) + { + index = Pedalboard::END_CONTROL_ID; + } + else + { + index = this->currentMainInsertPedalboard->GetIndexOfInstanceId(instanceId); + } + } + break; + case PedalboardType::AuxInserts: + if (this->currentAuxInsertPedalboard) + { + if (instanceId == Pedalboard::AUX_INSERT_START_CONTROL_ID) + { + index = Pedalboard::START_CONTROL_ID; + } + else if (instanceId == Pedalboard::AUX_INSERT_END_CONTROL_ID) + { + index = Pedalboard::END_CONTROL_ID; + } + else + { + index = this->currentAuxInsertPedalboard->GetIndexOfInstanceId(instanceId); + } + } + break; } - RealtimePedalboardItemIndex result { instanceType, index}; + RealtimePedalboardItemIndex result{instanceType, index}; return result; } virtual void SetVuSubscriptions(const std::vector &instanceIds) @@ -2034,54 +2310,79 @@ public: for (size_t i = 0; i < instanceIds.size(); ++i) { int64_t instanceId = instanceIds[i]; - auto effect = this->currentPedalboard->GetEffect(instanceId); - if (effect) + PedalboardType pedalboardType = Pedalboard::GetPedalboardTypeFromInstanceId(instanceId); + std::shared_ptr pedalboard; + switch (pedalboardType) { - RealtimePedalboardItemIndex index = this->GetRealtimeItemIndex(instanceIds[i]); + case PedalboardType::MainPedalboard: + pedalboard = this->currentPedalboard; + break; + case PedalboardType::MainInserts: + pedalboard = this->currentMainInsertPedalboard; + break; + case PedalboardType::AuxInserts: + pedalboard = this->currentAuxInsertPedalboard; + break; + default: + throw std::runtime_error("SetVuSubscriptions: Value out of range."); + break; + } + int64_t effectIndex = -1; + if (pedalboard != nullptr) + { + effectIndex = pedalboard->GetIndexOfInstanceId(instanceId); + } + + if (effectIndex != -1) + { + IEffect *effect = pedalboard->GetEffect(instanceId); + RealtimePedalboardItemIndex index = RealtimePedalboardItemIndex(pedalboardType, effectIndex); vuConfig->enabledIndexes.push_back(index); - VuUpdate v; + VuUpdateX v; + v.pedalboardType(pedalboardType); v.instanceId_ = instanceId; // Display mono VUs if a stereo device is being fed identical L/R inputs. - v.isStereoInput_ = effect->GetNumberOfInputAudioBuffers() >= 2 - && effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1); + v.isStereoInput_ = effect->GetNumberOfInputAudioBuffers() >= 2 && effect->GetAudioInputBuffer(0) != effect->GetAudioInputBuffer(1); v.isStereoOutput_ = effect->GetNumberOfOutputAudioBuffers() >= 2; vuConfig->vuUpdateWorkingData.push_back(v); vuConfig->vuUpdateResponseData.push_back(v); } else if ( - instanceId == Pedalboard::START_CONTROL_ID || instanceId == Pedalboard::END_CONTROL_ID - || instanceId == Pedalboard::MAIN_INSERT_START_CONTROL_ID || instanceId == Pedalboard::MAIN_INSERT_END_CONTROL_ID - || instanceId == Pedalboard::AUX_INSERT_START_CONTROL_ID || instanceId == Pedalboard::AUX_INSERT_END_CONTROL_ID - ) + instanceId == Pedalboard::START_CONTROL_ID || + instanceId == Pedalboard::END_CONTROL_ID || + instanceId == Pedalboard::MAIN_INSERT_START_CONTROL_ID || + instanceId == Pedalboard::MAIN_INSERT_END_CONTROL_ID || + instanceId == Pedalboard::AUX_INSERT_START_CONTROL_ID || + instanceId == Pedalboard::AUX_INSERT_END_CONTROL_ID) { auto index = GetRealtimeItemIndex(instanceId); - VuUpdate v; + VuUpdateX v; vuConfig->enabledIndexes.push_back(index); - + v.instanceId_ = instanceId; size_t nChannels = 0; - switch (instanceId) + switch (instanceId) { - case Pedalboard::START_CONTROL_ID: - nChannels = this->pHost->GetChannelSelection().mainInputChannels().size(); - break; - case Pedalboard::END_CONTROL_ID: - nChannels = this->pHost->GetChannelSelection().mainOutputChannels().size(); - break; - case Pedalboard::MAIN_INSERT_START_CONTROL_ID: - nChannels = this->pHost->GetChannelSelection().mainOutputChannels().size(); - break; - case Pedalboard::MAIN_INSERT_END_CONTROL_ID: - nChannels = this->pHost->GetChannelSelection().mainOutputChannels().size(); - break; - case Pedalboard::AUX_INSERT_START_CONTROL_ID: - nChannels = this->pHost->GetChannelSelection().auxInputChannels().size(); - break; - case Pedalboard::AUX_INSERT_END_CONTROL_ID: - nChannels = this->pHost->GetChannelSelection().auxOutputChannels().size(); - break; + case Pedalboard::START_CONTROL_ID: + nChannels = this->pHost->GetChannelSelection().mainInputChannels().size(); + break; + case Pedalboard::END_CONTROL_ID: + nChannels = this->pHost->GetChannelSelection().mainOutputChannels().size(); + break; + case Pedalboard::MAIN_INSERT_START_CONTROL_ID: + nChannels = this->pHost->GetChannelSelection().mainOutputChannels().size(); + break; + case Pedalboard::MAIN_INSERT_END_CONTROL_ID: + nChannels = this->pHost->GetChannelSelection().mainOutputChannels().size(); + break; + case Pedalboard::AUX_INSERT_START_CONTROL_ID: + nChannels = this->pHost->GetChannelSelection().auxInputChannels().size(); + break; + case Pedalboard::AUX_INSERT_END_CONTROL_ID: + nChannels = this->pHost->GetChannelSelection().auxOutputChannels().size(); + break; } v.isStereoInput_ = v.isStereoOutput_ = nChannels > 1; vuConfig->vuUpdateWorkingData.push_back(v); @@ -2094,10 +2395,25 @@ public: } } + Lv2Pedalboard *GetPedalboard(PedalboardType pedalboardType) + { + switch (pedalboardType) + { + case PedalboardType::MainPedalboard: + return this->currentPedalboard.get(); + case PedalboardType::MainInserts: + return this->realtimeRoutingInserts.mainInserts; + case PedalboardType::AuxInserts: + return this->realtimeRoutingInserts.auxInserts; + default: + throw std::runtime_error("AudioHostImpl::GetPedalboard: invalid argument."); + } + } RealtimeMonitorPortSubscription MakeRealtimeSubscription(const MonitorPortSubscription &subscription) { RealtimeMonitorPortSubscription result; result.subscriptionHandle = subscription.subscriptionHandle; + result.pedalboardType = Pedalboard::GetPedalboardTypeFromInstanceId(subscription.subscriptionHandle); result.instanceIndex = this->currentPedalboard->GetIndexOfInstanceId(subscription.instanceid); IEffect *pEffect = this->currentPedalboard->GetEffect(subscription.instanceid); @@ -2459,8 +2775,6 @@ void AudioHostImpl::SetAlsaSequencerConfiguration(const AlsaSequencerConfigurati this->alsaSequencer->SetConfiguration(alsaSequencerConfiguration); } - - JSON_MAP_BEGIN(JackHostStatus) JSON_MAP_REFERENCE(JackHostStatus, active) JSON_MAP_REFERENCE(JackHostStatus, errorMessage) diff --git a/src/AudioHost.hpp b/src/AudioHost.hpp index 843e0be..4c78e4f 100644 --- a/src/AudioHost.hpp +++ b/src/AudioHost.hpp @@ -148,6 +148,7 @@ namespace pipedal { public: int64_t subscriptionHandle; + PedalboardType pedalboardType; int64_t instanceid; std::string key; float updateInterval; @@ -159,7 +160,7 @@ namespace pipedal public: virtual void OnNotifyLv2StateChanged(uint64_t instanceId) = 0; virtual bool OnNotifyMaybeLv2StateChanged(uint64_t instanceId) = 0; - virtual void OnNotifyVusSubscription(const std::vector &updates) = 0; + virtual void OnNotifyVusSubscription(const std::vector &updates) = 0; virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) = 0; virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) = 0; virtual void OnNotifyMidiListen(uint8_t cc0, uint8_t cc1, uint8_t cc2) = 0; diff --git a/src/Banks.hpp b/src/Banks.hpp index cc24fce..3c793d1 100644 --- a/src/Banks.hpp +++ b/src/Banks.hpp @@ -240,7 +240,7 @@ namespace pipedal { // zero length? We can never have a zero-length bank. // Add a default preset and make it the selected preset. - Pedalboard pedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard); + Pedalboard pedalboard = Pedalboard::MakeDefault(PedalboardType::MainPedalboard); this->addPreset(pedalboard); newSelection = presets_[0]->instanceId(); } diff --git a/src/ChannelRouterSettings.cpp b/src/ChannelRouterSettings.cpp index 926b589..675a9d5 100644 --- a/src/ChannelRouterSettings.cpp +++ b/src/ChannelRouterSettings.cpp @@ -129,8 +129,8 @@ void ChannelSelection::normalizeChannelSelection() { ChannelRouterSettings::ChannelRouterSettings() -: mainInserts_(Pedalboard::PedalboardType::MainInsert) -, auxInserts_(Pedalboard::PedalboardType::AuxInsert) +: mainInserts_(PedalboardType::MainInserts) +, auxInserts_(PedalboardType::AuxInserts) { } diff --git a/src/ChannelRouterSettings.hpp b/src/ChannelRouterSettings.hpp index 15af777..6ab621f 100644 --- a/src/ChannelRouterSettings.hpp +++ b/src/ChannelRouterSettings.hpp @@ -33,6 +33,7 @@ namespace pipedal static constexpr int64_t CHANNEL_ROUTER_MAIN_INSERT_ID = -4; // Reserved Instance ID for Router Main Inserts. static constexpr int64_t CHANNEL_ROUTER_AUX_INSERT_ID = -5; // Reserved Instance ID for Router Aux inserts. + bool configured_ = false; int64_t channelRouterPresetId_ = -1; bool changed_ = false; @@ -51,6 +52,10 @@ namespace pipedal std::vector controlValues_; public: + + static constexpr int64_t MAIN_OUT_LEFT_CHANNEL = -2; + static constexpr int64_t MAIN_OUT_RIGHT_CHANNEL = -3; + using self = ChannelRouterSettings; using ptr = std::shared_ptr; diff --git a/src/DeviceVus.hpp b/src/DeviceVus.hpp new file mode 100644 index 0000000..4f192ec --- /dev/null +++ b/src/DeviceVus.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026 Robin E. R. Davies + * All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once +#include +#include "json.hpp" + + +namespace pipedal { + class RealtimeDeviceVus { + public: + static constexpr size_t MAX_CHANNELS = 64; + size_t inputChannels; + size_t outputChannels; + float values[MAX_CHANNELS*2]; + + size_t MessageSize() const { + return sizeof(RealtimeDeviceVus)-sizeof(float)*( + MAX_CHANNELS*2- + (inputChannels+outputChannels+1)/2*2); + } + }; +}; \ No newline at end of file diff --git a/src/DummyAudioDriver.cpp b/src/DummyAudioDriver.cpp index 0d298d7..a52c21b 100644 --- a/src/DummyAudioDriver.cpp +++ b/src/DummyAudioDriver.cpp @@ -58,6 +58,8 @@ using namespace pipedal; +#ifdef JUNK + namespace pipedal { @@ -524,3 +526,4 @@ uint32_t pipedal::GetDummyAudioChannels(const std::string &deviceName) } +#endif \ No newline at end of file diff --git a/src/DummyAudioDriver.hpp b/src/DummyAudioDriver.hpp index 59d5f09..59b2c9b 100644 --- a/src/DummyAudioDriver.hpp +++ b/src/DummyAudioDriver.hpp @@ -24,6 +24,7 @@ #pragma once +#ifdef JUNK #include #include "AudioDriver.hpp" #include "JackServerSettings.hpp" @@ -36,4 +37,5 @@ namespace pipedal { AudioDriver* CreateDummyAudioDriver(AudioDriverHost*driverHost,const std::string&deviceId); } +#endif diff --git a/src/JackConfiguration.cpp b/src/JackConfiguration.cpp index 2298e70..cd111f3 100644 --- a/src/JackConfiguration.cpp +++ b/src/JackConfiguration.cpp @@ -130,12 +130,8 @@ void JackConfiguration::AlsaInitialize( this->isValid_ = false; this->errorStatus_ = ""; - if (jackServerSettings.IsDummyAudioDevice()) - { - this->inputMidiDevices_.clear(); - } else { - this->inputMidiDevices_ = GetAlsaSequencers(); // NB: Sequencers, not rawmidi devices, anymore. - } + this->inputMidiDevices_ = GetAlsaSequencers(); // NB: Sequencers, not rawmidi devices, anymore. + if (jackServerSettings.IsValid()) { this->blockLength_ = jackServerSettings.GetBufferSize(); diff --git a/src/JackServerSettings.cpp b/src/JackServerSettings.cpp index 74830ac..15ce110 100644 --- a/src/JackServerSettings.cpp +++ b/src/JackServerSettings.cpp @@ -155,6 +155,8 @@ void JackServerSettings::FixUpDeviceNames() } } + + JSON_MAP_BEGIN(JackServerSettings) JSON_MAP_REFERENCE(JackServerSettings, valid) JSON_MAP_REFERENCE(JackServerSettings, isOnboarding) diff --git a/src/JackServerSettings.hpp b/src/JackServerSettings.hpp index d1c278d..dfcc0c2 100644 --- a/src/JackServerSettings.hpp +++ b/src/JackServerSettings.hpp @@ -60,7 +60,7 @@ namespace pipedal { FixUpDeviceNames(); } - + uint64_t GetSampleRate() const { return sampleRate_; } uint32_t GetBufferSize() const { return bufferSize_; } @@ -78,17 +78,17 @@ namespace pipedal void UseDummyAudioDevice() { this->valid_ = true; if (sampleRate_ == 0) sampleRate_ = 48000; - this->alsaDevice_ = "dummy:channels_2"; - this->alsaInputDevice_ = "dummy:channels_2"; - this->alsaInputDeviceName_ = "Stopped"; - this->alsaOutputDevice_ = "dummy:channels_2"; - this->alsaOutputDeviceName_ = "Stopped"; + bufferSize_ = 256; + numberOfBuffers_ = 6; + + this->alsaDevice_ = ""; + this->alsaInputDevice_ = "null"; + this->alsaInputDeviceName_ = "null"; + this->alsaOutputDevice_ = "null"; + this->alsaOutputDeviceName_ = "null"; } bool IsDummyAudioDevice() const { - return - this->alsaDevice_.starts_with("__DUMMY_AUDIO__") - || this->alsaDevice_.starts_with("dummy:") - || this->alsaInputDevice_.starts_with("dummy:"); + return alsaInputDevice_ == "null" && alsaOutputDevice_ == "null"; } bool IsValid() const { return valid_; } diff --git a/src/Lv2Pedalboard.cpp b/src/Lv2Pedalboard.cpp index ad2b5cf..090884f 100644 --- a/src/Lv2Pedalboard.cpp +++ b/src/Lv2Pedalboard.cpp @@ -29,6 +29,7 @@ #include "Lv2Log.hpp" #include "CrashGuard.hpp" #include "restrict.hpp" +#include "AudioDriver.hpp" using namespace pipedal; @@ -571,42 +572,46 @@ void Lv2Pedalboard::SetBypass(int effectIndex, bool enabled) effect->SetBypass(enabled); } -void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples, float **inputBuffers, float **outputBuffers) + +void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *realtimeVuBuffers, uint32_t samples, float**masterInputBuffers, float**masterOutputBuffers) { - for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i) + if (realtimeVuBuffers == nullptr) { - auto& rtIndex = vuConfiguration->enabledIndexes[i]; + return; + } + for (size_t i = 0; i < realtimeVuBuffers->enabledIndexes.size(); ++i) + { + auto& rtIndex = realtimeVuBuffers->enabledIndexes[i]; if (rtIndex.instanceType != this->pedalboardType) { continue; } int index = rtIndex.index; - VuUpdate *pUpdate = &vuConfiguration->vuUpdateWorkingData[i]; + VuUpdateX *pUpdate = &realtimeVuBuffers->vuUpdateWorkingData[i]; if (index == Pedalboard::START_CONTROL_ID) { - if (this->pedalboardInputBuffers.size() > 1) + if (this->pedalboardInputBuffers.size() == 2) { - GetInputBuffers(); - pUpdate->AccumulateInputs(inputBuffers[0], inputBuffers[1], samples); + pUpdate->AccumulateInputs(masterInputBuffers[0], masterInputBuffers[1], samples); pUpdate->AccumulateOutputs(&(this->pedalboardInputBuffers[0][0]), &(this->pedalboardInputBuffers[1][0]), samples); // after outputVolume applied. } - else + else if (this->pedalboardInputBuffers.size() == 1) { - pUpdate->AccumulateInputs(inputBuffers[0], samples); + pUpdate->AccumulateInputs(masterInputBuffers[0], samples); pUpdate->AccumulateOutputs(&(this->pedalboardInputBuffers[0][0]), samples); // after input volume applied. } } else if (index == Pedalboard::END_CONTROL_ID) { - if (this->pedalboardOutputBuffers.size() > 1) + if (this->pedalboardOutputBuffers.size() == 2) { pUpdate->AccumulateInputs(&(this->pedalboardOutputBuffers[0][0]), &(this->pedalboardOutputBuffers[1][0]), samples); - pUpdate->AccumulateOutputs(outputBuffers[0], outputBuffers[1], samples); + pUpdate->AccumulateOutputs(masterOutputBuffers[0], masterOutputBuffers[1], samples); } - else + else if (this->pedalboardOutputBuffers.size() == 1) { pUpdate->AccumulateInputs(&(this->pedalboardOutputBuffers[0][0]), samples); - pUpdate->AccumulateOutputs(outputBuffers[0], samples); + pUpdate->AccumulateOutputs(masterOutputBuffers[0], samples); } } else @@ -1029,11 +1034,11 @@ void Lv2Pedalboard::handleTapTempo(uint8_t value, const MidiTimestamp& timestamp size_t Lv2Pedalboard::GetNumberOfAudioInputChannels() const { switch (pedalboardType) { - case Pedalboard::PedalboardType::MainPedalboard: + case PedalboardType::MainPedalboard: return pHost->GetChannelSelection().mainInputChannels().size(); - case Pedalboard::PedalboardType::MainInsert: + case PedalboardType::MainInserts: return pHost->GetChannelSelection().mainOutputChannels().size(); - case Pedalboard::PedalboardType::AuxInsert: + case PedalboardType::AuxInserts: return pHost->GetChannelSelection().auxInputChannels().size(); default: throw std::runtime_error("Invalid argument"); @@ -1043,11 +1048,11 @@ size_t Lv2Pedalboard::GetNumberOfAudioInputChannels() const { size_t Lv2Pedalboard::GetNumberOfAudioOutputChannels() const { switch (pedalboardType) { - case Pedalboard::PedalboardType::MainPedalboard: + case PedalboardType::MainPedalboard: return pHost->GetChannelSelection().mainOutputChannels().size(); - case Pedalboard::PedalboardType::MainInsert: + case PedalboardType::MainInserts: return pHost->GetChannelSelection().mainOutputChannels().size(); - case Pedalboard::PedalboardType::AuxInsert: + case PedalboardType::AuxInserts: return pHost->GetChannelSelection().auxOutputChannels().size(); default: throw std::runtime_error("Invalid argument"); diff --git a/src/Lv2Pedalboard.hpp b/src/Lv2Pedalboard.hpp index 620c035..b478c14 100644 --- a/src/Lv2Pedalboard.hpp +++ b/src/Lv2Pedalboard.hpp @@ -31,6 +31,7 @@ namespace pipedal { + class AudioDriver; class IPatchWriterCallback; class RealtimeVuBuffers; class RealtimePatchPropertyRequest; @@ -51,7 +52,7 @@ namespace pipedal class Lv2Pedalboard { private: - Pedalboard::PedalboardType pedalboardType; + PedalboardType pedalboardType; IHost *pHost = nullptr; size_t currentFrameOffset = 0; DbDezipper inputVolume; @@ -173,7 +174,7 @@ namespace pipedal void SetOutputVolume(float value) { this->outputVolume.SetTarget(value); } void SetBypass(int effectIndex, bool enabled); - void ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples, float **inputBuffers, float **outputBuffers); + void ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samples, float**masterInputBuffers, float **masterOutputBuffers); float GetControlOutputValue(int effectIndex, int portIndex); diff --git a/src/Pedalboard.cpp b/src/Pedalboard.cpp index 6a3f0fb..6ddcce3 100644 --- a/src/Pedalboard.cpp +++ b/src/Pedalboard.cpp @@ -517,10 +517,10 @@ Pedalboard::Pedalboard(PedalboardType instanceType) case PedalboardType::MainPedalboard: nextInstanceId_ = 0; break; - case PedalboardType::MainInsert: + case PedalboardType::MainInserts: nextInstanceId_ = MAIN_INSERT_INSTANCE_BASE;; break; - case PedalboardType::AuxInsert: + case PedalboardType::AuxInserts: nextInstanceId_ = AUX_INSERT_INSTANCE_BASE;; break; } diff --git a/src/Pedalboard.hpp b/src/Pedalboard.hpp index df7f425..66a023f 100644 --- a/src/Pedalboard.hpp +++ b/src/Pedalboard.hpp @@ -25,13 +25,14 @@ #include "StateInterface.hpp" #include "atom_object.hpp" -namespace pipedal { +namespace pipedal +{ class SnapshotValue; class Snapshot; class PluginHost; - -#define SPLIT_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Split" -#define EMPTY_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Empty" + +#define SPLIT_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Split" +#define EMPTY_PEDALBOARD_ITEM_URI "uri://two-play/pipedal/pedalboard#Empty" #define SPLIT_SPLITTYPE_KEY "splitType" #define SPLIT_SELECT_KEY "select" @@ -41,280 +42,284 @@ namespace pipedal { #define SPLIT_PANR_KEY "panR" #define SPLIT_VOLR_KEY "volR" - -#define GETTER_SETTER_REF(name) \ - const decltype(name##_)& name() const { return name##_;} \ +#define GETTER_SETTER_REF(name) \ + const decltype(name##_) &name() const { return name##_; } \ void name(const decltype(name##_) &value) { name##_ = value; } -#define GETTER_SETTER_VEC(name) \ - decltype(name##_)& name() { return name##_;} \ - const decltype(name##_)& name() const { return name##_;} \ - void name(decltype(name##_)&&value) { this->name##_ = std::move(value); }\ - void name(const decltype(name##_)&value) { this->name##_ = value; } +#define GETTER_SETTER_VEC(name) \ + decltype(name##_) &name() { return name##_; } \ + const decltype(name##_) &name() const { return name##_; } \ + void name(decltype(name##_) &&value) { this->name##_ = std::move(value); } \ + void name(const decltype(name##_) &value) { this->name##_ = value; } - - -#define GETTER_SETTER(name) \ - decltype(name##_) name() const { return name##_;} \ +#define GETTER_SETTER(name) \ + decltype(name##_) name() const { return name##_; } \ void name(decltype(name##_) value) { name##_ = value; } -class ControlValue { -private: - std::string key_; - float value_; -public: - ControlValue() + class ControlValue { + private: + std::string key_; + float value_; - } - ControlValue(const char*key, float value) - :key_(key) - , value_(value) - { - - } - ControlValue(const std::string&key, float value) - :key_(key) - , value_(value) - { - - } - GETTER_SETTER_REF(key) - GETTER_SETTER_REF(value) - - DECLARE_JSON_MAP(ControlValue); - - -}; - -class PedalboardItem { -public: - using PropertyMap = std::map; - int64_t instanceId_ = 0; - std::string uri_; - std::string pluginName_; - bool isEnabled_ = true; - std::vector controlValues_; - std::vector topChain_; - std::vector bottomChain_; - std::vector midiBindings_; - std::optional midiChannelBinding_; - std::string vstState_; - uint32_t stateUpdateCount_ = 0; - Lv2PluginState lv2State_; - std::string lilvPresetUri_; - std::map pathProperties_; - std::string title_; - bool useModUi_ = false; - std::string iconColor_; - int64_t sideChainInputId_ = -1; - - // non persistent state. - PropertyMap patchProperties; -public: - ControlValue*GetControlValue(const std::string&symbol); - const ControlValue*GetControlValue(const std::string&symbol) const; - bool SetControlValue(const std::string&key, float value); - - bool IsStructurallyIdentical(const PedalboardItem&other) const; - - void ApplySnapshotValue(SnapshotValue*snapshotValue); - void ApplyDefaultValues(PluginHost&pluginHost); - bool hasLv2State() const { - return lv2State_.isValid_ != 0; - } - GETTER_SETTER(instanceId) - GETTER_SETTER_REF(uri) - GETTER_SETTER_REF(vstState); - GETTER_SETTER_REF(pluginName) - GETTER_SETTER(isEnabled) - GETTER_SETTER_VEC(controlValues) - GETTER_SETTER_VEC(topChain) - GETTER_SETTER_VEC(bottomChain) - GETTER_SETTER_VEC(midiBindings) - GETTER_SETTER_REF(midiChannelBinding) - GETTER_SETTER_REF(pathProperties) - GETTER_SETTER(stateUpdateCount) - GETTER_SETTER_REF(lv2State) - GETTER_SETTER_REF(title) - GETTER_SETTER_REF(iconColor) - GETTER_SETTER(useModUi) - GETTER_SETTER(sideChainInputId) - - Lv2PluginState&lv2State() { return lv2State_; } // non-const version. - GETTER_SETTER_REF(lilvPresetUri) - - PropertyMap&PatchProperties() { return patchProperties; } - const PropertyMap&PatchProperties() const { return patchProperties; } - - - bool isSplit() const - { - return uri_ == SPLIT_PEDALBOARD_ITEM_URI; - } - bool isEmpty() const { - return uri_ == EMPTY_PEDALBOARD_ITEM_URI; - } - - void AddToSnapshotFromCurrentSettings(Snapshot&snapshot) const; - void AddResetsForMissingProperties(Snapshot&snapshot, size_t*index) const; - - - // virtual void write_members(json_writer&writer) const { - // writer.write_member("instanceId",instanceId_); - // writer.write_member("uri",uri_); - // writer.write_member("pluginName",pluginName_); - // writer.write_member("isEnabled",isEnabled_); - // if (isSplit()) - // { - // writer.write_member("topChain",topChain_); - // writer.write_member("bottomChain",bottomChain_); - // } - // } - - DECLARE_JSON_MAP(PedalboardItem); - -}; - -class SnapshotValue { -public: - uint64_t instanceId_; - bool isEnabled_ = true; - std::vector controlValues_; - Lv2PluginState lv2State_; - std::map pathProperties_; - DECLARE_JSON_MAP(SnapshotValue); - - ControlValue*GetControlValue(const std::string& key) { - for (ControlValue&controlValue: controlValues_) { - if (controlValue.key() == key) { - return &controlValue; - } + public: + ControlValue() + { } - return nullptr; - } - void SetControlValue(const std::string& key,float value) { - for (ControlValue&controlValue: controlValues_) { - if (controlValue.key() == key) { - controlValue.value(value); - return; - } + ControlValue(const char *key, float value) + : key_(key), value_(value) + { } - controlValues_.push_back(ControlValue{key,value}); - } - -}; + ControlValue(const std::string &key, float value) + : key_(key), value_(value) + { + } + GETTER_SETTER_REF(key) + GETTER_SETTER_REF(value) -class Snapshot { -public: - std::string name_; - std::string color_; - bool isModified_ = false; - std::vector values_; - - DECLARE_JSON_MAP(Snapshot); - -}; - -class Pedalboard { - std::string name_; - float input_volume_db_ = 0; - float output_volume_db_ = 0; - - std::vector items_; - uint64_t nextInstanceId_ = 0; - uint64_t NextInstanceId() { return ++nextInstanceId_; } - - std::vector> snapshots_; - int64_t selectedSnapshot_ = -1; - - int64_t selectedPlugin_ = -1; - - - static constexpr int64_t TWO_POWER_52 = 4503599627370496; // Maximum Javascript integer value. - static constexpr int64_t TWO_POWER_48 = 281474976710656; // Number of possible instance IDs for Main Pedalboards. - static constexpr int64_t MAX_INSTANCE_ID = TWO_POWER_48; - -public: - - enum class PedalboardType { - MainPedalboard, - MainInsert, - AuxInsert + DECLARE_JSON_MAP(ControlValue); }; - static constexpr int64_t MAIN_INSERT_INSTANCE_BASE = TWO_POWER_52-2*MAX_INSTANCE_ID; - static constexpr int64_t AUX_INSERT_INSTANCE_BASE = TWO_POWER_52-1*MAX_INSTANCE_ID; - static constexpr int64_t CHANNEL_ROUTER_CONTROLS_INSTANCE_ID = -4; // Reserved Instance ID for Router Controls. + class PedalboardItem + { + public: + using PropertyMap = std::map; + int64_t instanceId_ = 0; + std::string uri_; + std::string pluginName_; + bool isEnabled_ = true; + std::vector controlValues_; + std::vector topChain_; + std::vector bottomChain_; + std::vector midiBindings_; + std::optional midiChannelBinding_; + std::string vstState_; + uint32_t stateUpdateCount_ = 0; + Lv2PluginState lv2State_; + std::string lilvPresetUri_; + std::map pathProperties_; + std::string title_; + bool useModUi_ = false; + std::string iconColor_; + int64_t sideChainInputId_ = -1; - static constexpr int64_t START_CONTROL_ID = -2; // synthetic PedalboardItem for input volume. - static constexpr int64_t END_CONTROL_ID = -3; // synthetic PedalboardItem for output volume. - static constexpr int64_t MAIN_INSERT_START_CONTROL_ID = MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -2; - static constexpr int64_t MAIN_INSERT_END_CONTROL_ID = MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -3; - static constexpr int64_t AUX_INSERT_START_CONTROL_ID = AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -2; - static constexpr int64_t AUX_INSERT_END_CONTROL_ID = AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -3; + // non persistent state. + PropertyMap patchProperties; - static PedalboardType GetInstanceTypeFromInstanceId(int64_t instanceId) { - if (instanceId == START_CONTROL_ID) return PedalboardType::MainPedalboard; - if (instanceId == END_CONTROL_ID) return PedalboardType::MainPedalboard; + public: + ControlValue *GetControlValue(const std::string &symbol); + const ControlValue *GetControlValue(const std::string &symbol) const; + bool SetControlValue(const std::string &key, float value); - if (instanceId >= MAIN_INSERT_INSTANCE_BASE) { - return PedalboardType::MainInsert; - } else if (instanceId >= AUX_INSERT_INSTANCE_BASE) { - return PedalboardType::AuxInsert; - } else { - return PedalboardType::MainPedalboard; + bool IsStructurallyIdentical(const PedalboardItem &other) const; + + void ApplySnapshotValue(SnapshotValue *snapshotValue); + void ApplyDefaultValues(PluginHost &pluginHost); + bool hasLv2State() const + { + return lv2State_.isValid_ != 0; } - } - PedalboardType GetInstanceType() const { - return GetInstanceTypeFromInstanceId(this->nextInstanceId_); // instanceId is irrelevant here. - } + GETTER_SETTER(instanceId) + GETTER_SETTER_REF(uri) + GETTER_SETTER_REF(vstState); + GETTER_SETTER_REF(pluginName) + GETTER_SETTER(isEnabled) + GETTER_SETTER_VEC(controlValues) + GETTER_SETTER_VEC(topChain) + GETTER_SETTER_VEC(bottomChain) + GETTER_SETTER_VEC(midiBindings) + GETTER_SETTER_REF(midiChannelBinding) + GETTER_SETTER_REF(pathProperties) + GETTER_SETTER(stateUpdateCount) + GETTER_SETTER_REF(lv2State) + GETTER_SETTER_REF(title) + GETTER_SETTER_REF(iconColor) + GETTER_SETTER(useModUi) + GETTER_SETTER(sideChainInputId) + Lv2PluginState &lv2State() { return lv2State_; } // non-const version. + GETTER_SETTER_REF(lilvPresetUri) + PropertyMap &PatchProperties() { return patchProperties; } + const PropertyMap &PatchProperties() const { return patchProperties; } - Pedalboard(PedalboardType instanceType = PedalboardType::MainPedalboard); + bool isSplit() const + { + return uri_ == SPLIT_PEDALBOARD_ITEM_URI; + } + bool isEmpty() const + { + return uri_ == EMPTY_PEDALBOARD_ITEM_URI; + } - // deep copy, breaking shared pointers. - Pedalboard DeepCopy(); - - bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value); - bool SetItemTitle(int64_t pedalItemId, const std::string &title, const std::string&iconColor); - bool SetItemEnabled(int64_t pedalItemId, bool enabled); - bool SetItemUseModUi(int64_t pedalItemId, bool enabled); - void SetCurrentSnapshotModified(bool modified); + void AddToSnapshotFromCurrentSettings(Snapshot &snapshot) const; + void AddResetsForMissingProperties(Snapshot &snapshot, size_t *index) const; - bool IsStructureIdentical(const Pedalboard &other) const; // caan we just send a snapshot-style uddate instead of reloading plugins? All settings are ignored. - Snapshot MakeSnapshotFromCurrentSettings(const Pedalboard &previousPedalboard); + // virtual void write_members(json_writer&writer) const { + // writer.write_member("instanceId",instanceId_); + // writer.write_member("uri",uri_); + // writer.write_member("pluginName",pluginName_); + // writer.write_member("isEnabled",isEnabled_); + // if (isSplit()) + // { + // writer.write_member("topChain",topChain_); + // writer.write_member("bottomChain",bottomChain_); + // } + // } - PedalboardItem*GetItem(int64_t pedalItemId); - const PedalboardItem*GetItem(int64_t pedalItemId) const; - std::vectorGetAllPlugins(); + DECLARE_JSON_MAP(PedalboardItem); + }; - bool HasItem(int64_t pedalItemid) const { return GetItem(pedalItemid) != nullptr; } - bool ApplySnapshot(int64_t snapshotIndex, PluginHost &pluginHost); + class SnapshotValue + { + public: + uint64_t instanceId_; + bool isEnabled_ = true; + std::vector controlValues_; + Lv2PluginState lv2State_; + std::map pathProperties_; + DECLARE_JSON_MAP(SnapshotValue); - GETTER_SETTER_REF(name) - GETTER_SETTER_VEC(items) - GETTER_SETTER(input_volume_db) - GETTER_SETTER(output_volume_db) - GETTER_SETTER_VEC(snapshots) - GETTER_SETTER(selectedSnapshot) - GETTER_SETTER(selectedPlugin) + ControlValue *GetControlValue(const std::string &key) + { + for (ControlValue &controlValue : controlValues_) + { + if (controlValue.key() == key) + { + return &controlValue; + } + } + return nullptr; + } + void SetControlValue(const std::string &key, float value) + { + for (ControlValue &controlValue : controlValues_) + { + if (controlValue.key() == key) + { + controlValue.value(value); + return; + } + } + controlValues_.push_back(ControlValue{key, value}); + } + }; + class Snapshot + { + public: + std::string name_; + std::string color_; + bool isModified_ = false; + std::vector values_; - DECLARE_JSON_MAP(Pedalboard); + DECLARE_JSON_MAP(Snapshot); + }; - PedalboardItem MakeEmptyItem(); - PedalboardItem MakeSplit(); + enum class PedalboardType + { + Invalid = 0, + MainPedalboard = 1, + MainInserts = 2, + AuxInserts = 3 + }; + class Pedalboard + { + std::string name_; + float input_volume_db_ = 0; + float output_volume_db_ = 0; - static Pedalboard MakeDefault(Pedalboard::PedalboardType instanceType = Pedalboard::PedalboardType::MainPedalboard); -}; + std::vector items_; + uint64_t nextInstanceId_ = 0; + uint64_t NextInstanceId() { return ++nextInstanceId_; } + + std::vector> snapshots_; + int64_t selectedSnapshot_ = -1; + + int64_t selectedPlugin_ = -1; + + static constexpr int64_t TWO_POWER_52 = 4503599627370496; // Maximum Javascript integer value. + static constexpr int64_t TWO_POWER_48 = 281474976710656; // Number of possible instance IDs for Main Pedalboards. + static constexpr int64_t MAX_INSTANCE_ID = TWO_POWER_48; + + public: + static constexpr int64_t MAIN_INSERT_INSTANCE_BASE = TWO_POWER_52 - 2 * MAX_INSTANCE_ID; + static constexpr int64_t AUX_INSERT_INSTANCE_BASE = TWO_POWER_52 - 1 * MAX_INSTANCE_ID; + + static constexpr int64_t CHANNEL_ROUTER_CONTROLS_INSTANCE_ID = -4; // Reserved Instance ID for Router Controls. + + static constexpr int64_t START_CONTROL_ID = -2; // synthetic PedalboardItem for input volume. + static constexpr int64_t END_CONTROL_ID = -3; // synthetic PedalboardItem for output volume. + static constexpr int64_t MAIN_INSERT_START_CONTROL_ID = MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID - 2; + static constexpr int64_t MAIN_INSERT_END_CONTROL_ID = MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID - 3; + static constexpr int64_t AUX_INSERT_START_CONTROL_ID = AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID - 2; + static constexpr int64_t AUX_INSERT_END_CONTROL_ID = AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID - 3; + + static PedalboardType GetPedalboardTypeFromInstanceId(int64_t instanceId) + { + if (instanceId == START_CONTROL_ID) + return PedalboardType::MainPedalboard; + if (instanceId == END_CONTROL_ID) + return PedalboardType::MainPedalboard; + + if (instanceId >= MAIN_INSERT_INSTANCE_BASE) + { + return PedalboardType::MainInserts; + } + else if (instanceId >= AUX_INSERT_INSTANCE_BASE) + { + return PedalboardType::AuxInserts; + } + else + { + return PedalboardType::MainPedalboard; + } + } + PedalboardType GetInstanceType() const + { + return GetPedalboardTypeFromInstanceId(this->nextInstanceId_); // instanceId is irrelevant here. + } + + Pedalboard(PedalboardType instanceType = PedalboardType::MainPedalboard); + + // deep copy, breaking shared pointers. + Pedalboard DeepCopy(); + + bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value); + bool SetItemTitle(int64_t pedalItemId, const std::string &title, const std::string &iconColor); + bool SetItemEnabled(int64_t pedalItemId, bool enabled); + bool SetItemUseModUi(int64_t pedalItemId, bool enabled); + void SetCurrentSnapshotModified(bool modified); + + bool IsStructureIdentical(const Pedalboard &other) const; // caan we just send a snapshot-style uddate instead of reloading plugins? All settings are ignored. + Snapshot MakeSnapshotFromCurrentSettings(const Pedalboard &previousPedalboard); + + PedalboardItem *GetItem(int64_t pedalItemId); + const PedalboardItem *GetItem(int64_t pedalItemId) const; + std::vector GetAllPlugins(); + + bool HasItem(int64_t pedalItemid) const { return GetItem(pedalItemid) != nullptr; } + bool ApplySnapshot(int64_t snapshotIndex, PluginHost &pluginHost); + + GETTER_SETTER_REF(name) + GETTER_SETTER_VEC(items) + GETTER_SETTER(input_volume_db) + GETTER_SETTER(output_volume_db) + GETTER_SETTER_VEC(snapshots) + GETTER_SETTER(selectedSnapshot) + GETTER_SETTER(selectedPlugin) + + DECLARE_JSON_MAP(Pedalboard); + + PedalboardItem MakeEmptyItem(); + PedalboardItem MakeSplit(); + + static Pedalboard MakeDefault(PedalboardType instanceType = PedalboardType::MainPedalboard); + }; #undef GETTER_SETTER_REF #undef GETTER_SETTER_VEC #undef GETTER_SETTER - - } // namespace pipedal \ No newline at end of file diff --git a/src/PiLatencyMain.cpp b/src/PiLatencyMain.cpp index d8deec9..1ad385a 100644 --- a/src/PiLatencyMain.cpp +++ b/src/PiLatencyMain.cpp @@ -169,6 +169,10 @@ public: delete[] inputBuffers; delete[] outputBuffers; } + virtual bool OnRealtimeUpdateDeviceVus(size_t nFrames) { + return true; + } + std::vector SelectChannels(const std::vector &available, const std::vector &selection) { std::vector result; diff --git a/src/PiPedalCommon.hpp b/src/PiPedalCommon.hpp index a464e39..3a8e138 100644 --- a/src/PiPedalCommon.hpp +++ b/src/PiPedalCommon.hpp @@ -30,3 +30,13 @@ #define PIPEDAL_NON_INLINE #endif #endif + +#ifndef PIPEDAL_RESTRICT +#if defined(__GNUC__) || defined(__clang__) +#define PIPEDAL_RESTRICT __restrict__ +#elif defined(_MSC_VER) +#define PIPEDAL_RESTRICT __restrict +#else +#define PIPEDAL_RESTRICT +#endif +#endif diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 1041dbe..5c256ae 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -82,7 +82,7 @@ PiPedalModel::PiPedalModel() this->updater = Updater::Create(); this->updater->Start(); this->currentUpdateStatus = updater->GetCurrentStatus(); - this->pedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard); + this->pedalboard = Pedalboard::MakeDefault(PedalboardType::MainPedalboard); this->jackServerSettings = this->storage.GetJackServerSettings(); @@ -394,7 +394,7 @@ void PiPedalModel::Load() if (CrashGuard::HasCrashed()) { // ignore the current preset, and load a blank pedalboard in order to avoid a potential plugin crash. - this->pedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard); + this->pedalboard = Pedalboard::MakeDefault(PedalboardType::MainPedalboard); } else { @@ -1732,7 +1732,7 @@ void PiPedalModel::OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, f } } -void PiPedalModel::OnNotifyVusSubscription(const std::vector &updates) +void PiPedalModel::OnNotifyVusSubscription(const std::vector &updates) { std::lock_guard lock(mutex); for (size_t i = 0; i < updates.size(); ++i) @@ -1746,6 +1746,22 @@ void PiPedalModel::OnNotifyVusSubscription(const std::vector &updates) } } +static bool isStartOrEndControl(int64_t controlId) +{ + switch (controlId) + { + case Pedalboard::START_CONTROL_ID: + case Pedalboard::END_CONTROL_ID: + case Pedalboard::MAIN_INSERT_START_CONTROL_ID: + case Pedalboard::MAIN_INSERT_END_CONTROL_ID: + case Pedalboard::AUX_INSERT_START_CONTROL_ID: + case Pedalboard::AUX_INSERT_END_CONTROL_ID: + return true; + default: + return false; + } +} + void PiPedalModel::UpdateRealtimeVuSubscriptions() { std::set addedInstances; @@ -1753,7 +1769,7 @@ void PiPedalModel::UpdateRealtimeVuSubscriptions() for (int i = 0; i < activeVuSubscriptions.size(); ++i) { auto instanceId = activeVuSubscriptions[i].instanceid; - if (pedalboard.HasItem(instanceId) || instanceId == Pedalboard::START_CONTROL_ID || instanceId == Pedalboard::END_CONTROL_ID) + if (pedalboard.HasItem(instanceId) || isStartOrEndControl(instanceId)) { addedInstances.insert(activeVuSubscriptions[i].instanceid); } @@ -1778,8 +1794,15 @@ int64_t PiPedalModel::MonitorPort(int64_t instanceId, const std::string &key, fl { std::lock_guard lock(mutex); int64_t subscriptionId = ++nextSubscriptionId; + PedalboardType pedalboardType = Pedalboard::GetPedalboardTypeFromInstanceId(instanceId); activeMonitorPortSubscriptions.push_back( - MonitorPortSubscription{subscriptionId, instanceId, key, updateInterval, onUpdate}); + MonitorPortSubscription{ + subscriptionId, + pedalboardType, + instanceId, + key, + updateInterval, + onUpdate}); UpdateRealtimeMonitorPortSubscriptions(); diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index 07203cf..107bb7e 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -75,7 +75,7 @@ namespace pipedal virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) = 0; virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0; virtual void OnChannelRouterSettingsChanged(int64_t clientId, const ChannelRouterSettings &channelRouterSettings) = 0; - virtual void OnVuMeterUpdate(const std::vector &updates) = 0; + virtual void OnVuMeterUpdate(const std::vector &updates) = 0; virtual void OnBankIndexChanged(const BankIndex &bankIndex) = 0; virtual void OnJackServerSettingsChanged(const JackServerSettings &jackServerSettings) = 0; virtual void OnJackConfigurationChanged(const JackConfiguration &jackServerConfiguration) = 0; @@ -251,7 +251,7 @@ namespace pipedal private: // IAudioHostCallbacks virtual void OnNotifyLv2StateChanged(uint64_t instanceId) override; virtual bool OnNotifyMaybeLv2StateChanged(uint64_t instanceId) override; - virtual void OnNotifyVusSubscription(const std::vector &updates) override; + virtual void OnNotifyVusSubscription(const std::vector &updates) override; virtual void OnNotifyMonitorPort(const MonitorPortUpdate &update) override; virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) override; virtual void OnNotifyMidiListen(uint8_t cc0, uint8_t cc1, uint8_t cc2) override; diff --git a/src/PiPedalSocket.cpp b/src/PiPedalSocket.cpp index 2509d57..9d6457b 100644 --- a/src/PiPedalSocket.cpp +++ b/src/PiPedalSocket.cpp @@ -2371,15 +2371,15 @@ private: int updateRequestOutstanding = 0; bool vuUpdateDropped = false; - virtual void OnVuMeterUpdate(const std::vector &updates) + virtual void OnVuMeterUpdate(const std::vector &updates) { std::lock_guard guard(subscriptionMutex); - if (updateRequestOutstanding < 5) // throttle to accomodate a web page that can't keep up. + if (updateRequestOutstanding < 1) // throttle to accomodate a web page that can't keep up. { vuUpdateDropped = false; for (int i = 0; i < updates.size(); ++i) { - const VuUpdate &vuUpdate = updates[i]; + const VuUpdateX &vuUpdate = updates[i]; bool interested = false; for (int i = 0; i < this->activeVuSubscriptions.size(); ++i) { @@ -2392,7 +2392,7 @@ private: if (interested) { updateRequestOutstanding++; - this->Request( + this->Request( "onVuUpdate", vuUpdate, [this](const bool &result) diff --git a/src/RingBufferReader.hpp b/src/RingBufferReader.hpp index 5e684a9..4b8bbb9 100644 --- a/src/RingBufferReader.hpp +++ b/src/RingBufferReader.hpp @@ -106,11 +106,11 @@ namespace pipedal struct RealtimePedalboardItemIndex { RealtimePedalboardItemIndex() - : instanceType(Pedalboard::PedalboardType::MainPedalboard), index(-1) + : instanceType(PedalboardType::MainPedalboard), index(-1) { } RealtimePedalboardItemIndex( - Pedalboard::PedalboardType instanceType, + PedalboardType instanceType, int64_t index) : instanceType(instanceType), index(index) { @@ -118,7 +118,7 @@ namespace pipedal RealtimePedalboardItemIndex(const RealtimePedalboardItemIndex& other) = default; RealtimePedalboardItemIndex& operator=(const RealtimePedalboardItemIndex& other) = default; - Pedalboard::PedalboardType instanceType = Pedalboard::PedalboardType::MainPedalboard; + PedalboardType instanceType = PedalboardType::MainPedalboard; int64_t index = -1; }; @@ -150,6 +150,7 @@ namespace pipedal { public: RealtimeMonitorPortSubscription() { delete callbackPtr; } + PedalboardType pedalboardType = PedalboardType::MainPedalboard; int64_t subscriptionHandle; int instanceIndex = 0; int portIndex = 0; @@ -174,7 +175,7 @@ namespace pipedal } bool waitingForAcknowledge = false; - const std::vector *GetResult(size_t currentSample) + const std::vector *GetResult(size_t currentSample) { for (size_t i = 0; i < vuUpdateWorkingData.size(); ++i) { @@ -186,8 +187,8 @@ namespace pipedal } std::vector enabledIndexes; - std::vector vuUpdateWorkingData; - std::vector vuUpdateResponseData; + std::vector vuUpdateWorkingData; + std::vector vuUpdateResponseData; void Reset() { @@ -494,7 +495,7 @@ namespace pipedal write(RingBufferCommand::SendMonitorPortUpdate, body); } - void SendVuUpdate(const std::vector *pUpdates) + void SendVuUpdate(const std::vector *pUpdates) { write(RingBufferCommand::SendVuUpdate, pUpdates); } diff --git a/src/Storage.cpp b/src/Storage.cpp index 49bc39c..265dd70 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -472,7 +472,7 @@ void Storage::LoadBankIndex() if (bankIndex.entries().size() == 0) { currentBank.clear(); - Pedalboard defaultPedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard); + Pedalboard defaultPedalboard = Pedalboard::MakeDefault(PedalboardType::MainPedalboard); int64_t instanceId = currentBank.addPreset(defaultPedalboard); currentBank.selectedPreset(instanceId); diff --git a/src/VuUpdate.cpp b/src/VuUpdate.cpp index da1c876..e322c2f 100644 --- a/src/VuUpdate.cpp +++ b/src/VuUpdate.cpp @@ -23,14 +23,15 @@ using namespace pipedal; -JSON_MAP_BEGIN(VuUpdate) - JSON_MAP_REFERENCE(VuUpdate,instanceId) - JSON_MAP_REFERENCE(VuUpdate,sampleTime) - JSON_MAP_REFERENCE(VuUpdate,isStereoInput) - JSON_MAP_REFERENCE(VuUpdate,isStereoOutput) - JSON_MAP_REFERENCE(VuUpdate,inputMaxValueL) - JSON_MAP_REFERENCE(VuUpdate,inputMaxValueR) - JSON_MAP_REFERENCE(VuUpdate,outputMaxValueL) - JSON_MAP_REFERENCE(VuUpdate,outputMaxValueR) +JSON_MAP_BEGIN(VuUpdateX) + JSON_MAP_REFERENCE(VuUpdateX,pedalboardType) + JSON_MAP_REFERENCE(VuUpdateX,instanceId) + JSON_MAP_REFERENCE(VuUpdateX,sampleTime) + JSON_MAP_REFERENCE(VuUpdateX,isStereoInput) + JSON_MAP_REFERENCE(VuUpdateX,isStereoOutput) + JSON_MAP_REFERENCE(VuUpdateX,inputMaxValueL) + JSON_MAP_REFERENCE(VuUpdateX,inputMaxValueR) + JSON_MAP_REFERENCE(VuUpdateX,outputMaxValueL) + JSON_MAP_REFERENCE(VuUpdateX,outputMaxValueR) JSON_MAP_END() diff --git a/src/VuUpdate.hpp b/src/VuUpdate.hpp index 277fa5b..7d0e81b 100644 --- a/src/VuUpdate.hpp +++ b/src/VuUpdate.hpp @@ -20,12 +20,14 @@ #pragma once #include "json.hpp" +#include "Pedalboard.hpp" namespace pipedal { - class VuUpdate + class VuUpdateX { public: + int pedalboardType_ = (int)PedalboardType::Invalid; int64_t instanceId_ = 0; long sampleTime_ = 0; bool isStereoInput_ = false; @@ -36,6 +38,9 @@ namespace pipedal float outputMaxValueR_ = 0; public: + + PedalboardType pedalboardType() const { return (PedalboardType)pedalboardType_; } + void pedalboardType(PedalboardType value) { pedalboardType_ = (int)value; } void reset() { inputMaxValueL_ = 0; inputMaxValueR_ = 0; @@ -45,7 +50,10 @@ namespace pipedal void AccumulateVu(float *value,float *input, uint32_t samples) { - + if (input == nullptr) { + *value = 0; + return; + } float v = *value; for (uint32_t i = 0; i < samples; ++i) { @@ -65,7 +73,6 @@ namespace pipedal { AccumulateVu(&inputMaxValueL_,inputL,samples); AccumulateVu(&inputMaxValueR_,inputR,samples); - } void AccumulateOutputs(float* output, uint32_t samples) { @@ -78,6 +85,6 @@ namespace pipedal AccumulateVu(&outputMaxValueR_,outputR,samples); } - DECLARE_JSON_MAP(VuUpdate); + DECLARE_JSON_MAP(VuUpdateX); }; } \ No newline at end of file diff --git a/vite/public/logo192.png b/vite/public/logo192.png index b5e5434..2bb4cca 100644 Binary files a/vite/public/logo192.png and b/vite/public/logo192.png differ diff --git a/vite/public/logo512.png b/vite/public/logo512.png index 9c7691a..99a73a1 100644 Binary files a/vite/public/logo512.png and b/vite/public/logo512.png differ diff --git a/vite/src/pipedal/ChannelRouterSettings.tsx b/vite/src/pipedal/ChannelRouterSettings.tsx index 3e65a42..3dc8c47 100644 --- a/vite/src/pipedal/ChannelRouterSettings.tsx +++ b/vite/src/pipedal/ChannelRouterSettings.tsx @@ -15,10 +15,24 @@ function isActiveChannel(channels: number[]): boolean { return channels[1] !== -1 || channels[0] !== -1; } -function chName(ch: number): string { +function chName(ch: number, maxChannels: number): string { if (ch === -1) { return "None"; } + if (ch === MAIN_OUT_LEFT_CHANNEL) { + return "Main Out L"; + } + if (ch === MAIN_OUT_RIGHT_CHANNEL) { + return "Main Out R"; + } + if (maxChannels <= 2) { + if (ch === 0) { + return "Left"; + } + if (ch === 1) { + return "Right"; + } + } return "Ch" + (ch + 1); } function channelPairName(channels: number[], maxChannels: number, input: boolean): string { @@ -39,17 +53,14 @@ function channelPairName(channels: number[], maxChannels: number, input: boolean if (channels[0] === 0 && (channels[0] === channels[1] || channels[1] === -1)) { return "Left"; } - if (channels[1] === 1 && (channels[0] === channels[1])) { + if (channels[1] === 1 && (channels[0] === channels[1] || channels[0] === -1)) { return "Right"; } - if (channels[0] === 1 && channels[1] === 0) { - return "Right,Left"; - } } - if (channels[0] === channels[1]) { - return chName(channels[0]); + if (channels[0] === channels[1] || channels[1] === -1) { + return chName(channels[0],maxChannels); } - return '[' + chName(channels[0]) + " " + chName(channels[1]) + "]"; + return '[' + chName(channels[0],maxChannels) + " " + chName(channels[1],maxChannels) + "]"; } diff --git a/vite/src/pipedal/ChannelRouterSettingsDialog.tsx b/vite/src/pipedal/ChannelRouterSettingsDialog.tsx index 57ae590..a5cbdbf 100644 --- a/vite/src/pipedal/ChannelRouterSettingsDialog.tsx +++ b/vite/src/pipedal/ChannelRouterSettingsDialog.tsx @@ -19,6 +19,7 @@ import React from 'react'; import { useState } from 'react'; +import VuMeter from './VuMeter'; import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; import Divider from '@mui/material/Divider'; import MoreVertIcon from '@mui/icons-material/MoreVert'; @@ -45,8 +46,8 @@ import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { JackConfiguration } from './Jack'; -let debugInputChannels: number | null = 1; -let debugOutputChannels: number | null = 1; +let debugInputChannels: number | null = null; +let debugOutputChannels: number | null = null; export interface ChannelRouterSettingsDialogProps { @@ -539,11 +540,25 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) { ) } - let Vu = () => { + let Vu = (routeType: RouteType, input: boolean) => { + let instanceId: number; + switch (routeType) { + case RouteType.Main: + instanceId = input ? Pedalboard.START_CONTROL : Pedalboard.MAIN_INSERT_END_CONTROL_ID; + break; + case RouteType.Aux: + instanceId = input ? Pedalboard.AUX_INSERT_START_CONTROL_ID : Pedalboard.AUX_INSERT_END_CONTROL_ID; + break; + case RouteType.Send: + instanceId = input ? Pedalboard.SEND_RETURN_CONTROL : Pedalboard.SEND_OUTPUT_CONTROL; + + } return (
-
- {/* */} + +
); } @@ -603,7 +618,7 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) { let PresetSelect = (width: number | string | undefined) => { return ( -
+
- + { }} />
@@ -658,14 +673,14 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) { {ChannelSelect(RouteType.Main, 0, true)} - {Vu()} + {Vu(RouteType.Main, true)} - {Vu()} + {Vu(RouteType.Main, false)} {ChannelSelect(RouteType.Main, 0, false)} @@ -691,18 +706,17 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) { {(ChannelSelect(RouteType.Aux, 0, true))} - {Vu()} + {Vu(RouteType.Aux, true)} - {Vu()} + {Vu(RouteType.Aux, false)} {(ChannelSelect(RouteType.Aux, 0, false))} @@ -728,11 +742,11 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) { {(ChannelSelect(RouteType.Send, 0, false))} - {Vu()} + {Vu(RouteType.Send,false)} {/* No button */} - {Vu()} + {Vu(RouteType.Send,true)} {(ChannelSelect(RouteType.Send, 0, true))} @@ -798,7 +812,7 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) { - + - + @@ -862,7 +876,7 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
{Vu()}{Vu(RouteType.Main,true)}
{Vu()}{Vu(RouteType.Main,false)}
- + - + @@ -929,7 +943,7 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
{Vu()}{Vu(RouteType.Aux, true)}
{Vu()}{Vu(RouteType.Aux, false)}
- + - + @@ -1000,11 +1014,11 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) { { landscape ? ( <> -
 
-
- {PresetSelect(200)} +
+
+ {PresetSelect("325px")}
-
 
+
) : ( diff --git a/vite/src/pipedal/Pedalboard.tsx b/vite/src/pipedal/Pedalboard.tsx index 423640f..affe053 100644 --- a/vite/src/pipedal/Pedalboard.tsx +++ b/vite/src/pipedal/Pedalboard.tsx @@ -381,6 +381,8 @@ export class Pedalboard implements Deserializable { static readonly START_CONTROL = -2; // synthetic PedalboardItem for input volume. static readonly END_CONTROL = -3; // synthetic PedalboardItem for output volume. + static readonly SEND_OUTPUT_CONTROL = -4; // synthetic PedalboardItem for send output volume. + static readonly SEND_RETURN_CONTROL = -5; // synthetic PedalboardItem for send return volume. static readonly MAIN_INSERT_START_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + -2 + MAX_INSTANCE_ID; static readonly MAIN_INSERT_END_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + -3 + MAX_INSTANCE_ID; static readonly AUX_INSERT_START_CONTROL_ID = Pedalboard.AUX_INSERT_INSTANCE_BASE + -2 + MAX_INSTANCE_ID diff --git a/vite/src/pipedal/PiPedalModel.tsx b/vite/src/pipedal/PiPedalModel.tsx index e753e52..1cafe2c 100644 --- a/vite/src/pipedal/PiPedalModel.tsx +++ b/vite/src/pipedal/PiPedalModel.tsx @@ -2517,6 +2517,7 @@ export class PiPedalModel //implements PiPedalModel } else { let newTarget = new VuSubscriptionTarget(); newTarget.subscribers.push(result); + this.vuSubscriptions[instanceId] = newTarget; this.webSocket.request("addVuSubscription", instanceId) .then((subscriptionHandle) => { @@ -2526,6 +2527,9 @@ export class PiPedalModel //implements PiPedalModel } else { newTarget.serverSubscriptionHandle = subscriptionHandle; } + }).catch((error) => { + // failed to subscribe. + this.vuSubscriptions[instanceId] = undefined; }); this.vuSubscriptions[instanceId] = newTarget; diff --git a/vite/src/pipedal/VuMeter.tsx b/vite/src/pipedal/VuMeter.tsx index ebcd333..d3d0d22 100644 --- a/vite/src/pipedal/VuMeter.tsx +++ b/vite/src/pipedal/VuMeter.tsx @@ -200,6 +200,7 @@ interface VuMeterProps extends WithStyles { instanceId: number; display: "input" | "output"; theme: Theme; + height: number | undefined; displayText?: boolean; } @@ -462,6 +463,20 @@ export const VuMeter = render() { let displayText = this.props.displayText??false; const classes = withStyles.getClasses(this.props); + if (this.props.height !== undefined) + { + let height = this.props.height; + let scale = height / DISPLAY_HEIGHT; + return ( +
+
+ { + this.renderVus() + } +
+
+ ); + } if (displayText) { return ( @@ -526,7 +541,7 @@ export const VuMeter = componentDidMount() { this.model.state.addOnChangedHandler(this.onStateChanged); - this.addVuSubscription(); + //this.addVuSubscription(); } componentWillUnmount() { this.removeVuSubscription(); diff --git a/vite/vite.config.ts b/vite/vite.config.ts index 6c069b2..065f001 100644 --- a/vite/vite.config.ts +++ b/vite/vite.config.ts @@ -14,7 +14,7 @@ export default defineConfig({ target: 'http://localhost:8080', changeOrigin: false, }, - '/var': { + '^/var/(?!config\\.json$).*': { target: 'http://localhost:8080', changeOrigin: false, },
{Vu()}{Vu(RouteType.Send, false)}
{Vu()}{Vu(RouteType.Send, true)}