diff --git a/src/plugin/AudioRingBuffer.hpp b/src/plugin/AudioRingBuffer.hpp new file mode 100644 index 0000000..51d979c --- /dev/null +++ b/src/plugin/AudioRingBuffer.hpp @@ -0,0 +1,273 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2024 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DISTRHO_AUDIO_RING_BUFFER_HPP_INCLUDED +#define DISTRHO_AUDIO_RING_BUFFER_HPP_INCLUDED + +#include "DistrhoUtils.hpp" + +#ifdef DISTRHO_OS_WINDOWS +# include +# include +#else +# include +#endif + +START_NAMESPACE_DISTRHO + +// -------------------------------------------------------------------------------------------------------------------- +// AudioRingBuffer class + +class AudioRingBuffer +{ +public: + /* + * Constructor for uninitialised ring buffer. + * A call to setRingBuffer is required to tied this control to a ring buffer struct; + * + */ + AudioRingBuffer() noexcept {} + + /* + * Destructor. + */ + ~AudioRingBuffer() noexcept + { + deleteBuffer(); + } + + bool createBuffer(const uint8_t numChannels, const uint32_t numSamples) noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(buffer.buf == nullptr, false); + DISTRHO_SAFE_ASSERT_RETURN(numChannels > 0, false); + DISTRHO_SAFE_ASSERT_RETURN(numSamples > 0, false); + + const uint32_t p2samples = d_nextPowerOf2(numSamples); + + try { + buffer.buf = new float*[numChannels]; + for (uint8_t c=0; c= buffer.tail ? 0 : buffer.samples; + + return wrap + buffer.head - buffer.tail; + } + + uint32_t getNumWritableSamples() const noexcept + { + const uint32_t wrap = buffer.tail > buffer.head ? 0 : buffer.samples; + + return wrap + buffer.tail - buffer.head - 1; + } + + // ---------------------------------------------------------------------------------------------------------------- + + /* + * Reset the ring buffer read and write positions, marking the buffer as empty. + * Requires a buffer struct tied to this class. + */ + void flush() noexcept + { + buffer.head = buffer.tail = 0; + errorWriting = false; + } + + // ---------------------------------------------------------------------------------------------------------------- + + bool read(float* const* const buffers, const uint32_t samples) noexcept + { + // empty + if (buffer.head == buffer.tail) + return false; + + const uint32_t head = buffer.head; + const uint32_t tail = buffer.tail; + const uint32_t wrap = head > tail ? 0 : buffer.samples; + + if (samples > wrap + head - tail) + { + if (! errorReading) + { + errorReading = true; + d_stderr2("RingBuffer::tryRead(%p, %u): failed, not enough space", buffers, samples); + } + return false; + } + + uint32_t readto = tail + samples; + + if (readto > buffer.samples) + { + readto -= buffer.samples; + + if (samples == 1) + { + for (uint8_t c=0; c head ? 0 : buffer.samples; + + if (samples >= wrap + tail - head) + { + if (! errorWriting) + { + errorWriting = true; + d_stderr2("RingBuffer::tryWrite(%p, %u): failed, not enough space", buffers, samples); + } + return false; + } + + uint32_t writeto = head + samples; + + if (writeto > buffer.samples) + { + writeto -= buffer.samples; + + if (samples == 1) + { + for (uint8_t c=0; c resamplerTo48kHz; + ScopedPointer resamplerFrom48kHz; #ifdef DISTRHO_OS_WINDOWS const WCHAR* envp; @@ -79,7 +84,8 @@ class DesktopPlugin : public Plugin, } portBaseNum = availablePortNum; - bufferSizeChanged(getBufferSize()); + + setupResampler(getSampleRate()); } ~DesktopPlugin() @@ -96,8 +102,8 @@ class DesktopPlugin : public Plugin, mod_ui.stop(); shm.deinit(); - delete[] tmpBuffers[0]; - delete[] tmpBuffers[1]; + delete[] tempBuffers[0]; + delete[] tempBuffers[1]; if (envp != nullptr) { @@ -142,7 +148,6 @@ class DesktopPlugin : public Plugin, const String jacksessionStr(appDir + DISTRHO_OS_SEP_STR "jack" DISTRHO_OS_SEP_STR "jack-session.conf"); const String servernameStr("mod-desktop-" + String(portBaseNum)); const String shmportStr(portBaseNum); - const String sampleRateStr(static_cast(getSampleRate())); const char* const jackd_args[] = { jackdStr.buffer(), @@ -151,7 +156,7 @@ class DesktopPlugin : public Plugin, "-n", servernameStr.buffer(), "-C", jacksessionStr.buffer(), "-d", "mod-desktop", - "-r", sampleRateStr.buffer(), + "-r", "48000", "-p", "128", "-s", shmportStr, nullptr @@ -352,9 +357,9 @@ class DesktopPlugin : public Plugin, void activate() override { - if (firstTimeActivating) + if (shouldStartRunner) { - firstTimeActivating = false; + shouldStartRunner = false; if (portBaseNum > 0 && run()) startRunner(500); @@ -364,12 +369,39 @@ class DesktopPlugin : public Plugin, shm.reset(); } - firstTimeProcessing = true; - numFramesInShmBuffer = numFramesInTmpBuffer = 0; + // make sure we have enough space to cover everything + const double sampleRate = getSampleRate(); + const uint32_t bufferSize = getBufferSize(); + const uint32_t bufferSizeInput = bufferSize * (sampleRate / 48000.0); + const uint32_t bufferSizeOutput = bufferSize * (48000.0 / sampleRate); + + audioBufferIn.createBuffer(2, (bufferSizeInput + 8192) * 2); + audioBufferOut.createBuffer(2, (bufferSizeOutput + 8192) * 2); + + numSamplesInTempBuffers = d_nextPowerOf2((std::max(bufferSizeInput, bufferSizeOutput) + 256) * 2); + delete[] tempBuffers[0]; + delete[] tempBuffers[1]; + tempBuffers[0] = new float[numSamplesInTempBuffers]; + tempBuffers[1] = new float[numSamplesInTempBuffers]; + std::memset(tempBuffers[0], 0, sizeof(float) * numSamplesInTempBuffers); + std::memset(tempBuffers[1], 0, sizeof(float) * numSamplesInTempBuffers); + + numSamplesUntilProcessing = d_isNotEqual(sampleRate, 48000.0) + ? d_nextPowerOf2(128.0 * (sampleRate / 48000.0)) + : 128; + + setLatency(numSamplesUntilProcessing); } void deactivate() override { + audioBufferIn.deleteBuffer(); + audioBufferOut.deleteBuffer(); + + delete[] tempBuffers[0]; + delete[] tempBuffers[1]; + tempBuffers[0] = tempBuffers[1] = nullptr; + numSamplesInTempBuffers = 0; } /** @@ -385,6 +417,82 @@ class DesktopPlugin : public Plugin, return; } + if (resamplerTo48kHz != nullptr) + { + resamplerTo48kHz->inp_count = frames; + resamplerTo48kHz->out_count = numSamplesInTempBuffers; + resamplerTo48kHz->inp_data = inputs; + resamplerTo48kHz->out_data = tempBuffers; + resamplerTo48kHz->process(); + DISTRHO_SAFE_ASSERT(resamplerTo48kHz->inp_count == 0); + + audioBufferIn.write(tempBuffers, numSamplesInTempBuffers - resamplerTo48kHz->out_count); + } + else + { + audioBufferIn.write(inputs, frames); + } + + while (audioBufferIn.getNumReadableSamples() >= 128) + { + float* shmbuffers[2] = { shm.data->audio, shm.data->audio + 128 }; + + audioBufferIn.read(shmbuffers, 128); + + if (! shm.process()) + { + d_stdout("shm processing failed"); + processing = false; + std::memset(outputs[0], 0, sizeof(float) * frames); + std::memset(outputs[1], 0, sizeof(float) * frames); + return; + } + + if (resamplerFrom48kHz != nullptr) + { + resamplerFrom48kHz->inp_count = 128; + resamplerFrom48kHz->out_count = numSamplesInTempBuffers; + resamplerFrom48kHz->inp_data = shmbuffers; + resamplerFrom48kHz->out_data = tempBuffers; + resamplerFrom48kHz->process(); + DISTRHO_SAFE_ASSERT(resamplerFrom48kHz->inp_count == 0); + + audioBufferOut.write(tempBuffers, numSamplesInTempBuffers - resamplerFrom48kHz->out_count); + } + else + { + audioBufferOut.write(shmbuffers, 128); + } + } + + if (numSamplesUntilProcessing >= frames) + { + numSamplesUntilProcessing -= frames; + std::memset(outputs[0], 0, sizeof(float) * frames); + std::memset(outputs[1], 0, sizeof(float) * frames); + return; + } + + if (numSamplesUntilProcessing != 0) + { + const uint32_t start = numSamplesUntilProcessing; + const uint32_t remaining = frames - start; + numSamplesUntilProcessing = 0; + + std::memset(outputs[0], 0, sizeof(float) * start); + std::memset(outputs[1], 0, sizeof(float) * start); + + float* offsetbuffers[2] = { + outputs[0] + start, + outputs[1] + start, + }; + audioBufferOut.read(offsetbuffers, remaining); + return; + } + + audioBufferOut.read(outputs, frames); + +#if 0 // FIXME not quite right, crashes if (getBufferSize() != frames) { @@ -557,19 +665,10 @@ class DesktopPlugin : public Plugin, numFramesInShmBuffer = ti; numFramesInTmpBuffer = to; +#endif } - void bufferSizeChanged(const uint32_t bufferSize) override - { - delete[] tmpBuffers[0]; - delete[] tmpBuffers[1]; - tmpBuffers[0] = new float[bufferSize + 256]; - tmpBuffers[1] = new float[bufferSize + 256]; - std::memset(tmpBuffers[0], 0, sizeof(float) * (bufferSize + 256)); - std::memset(tmpBuffers[1], 0, sizeof(float) * (bufferSize + 256)); - } - - void sampleRateChanged(double) override + void sampleRateChanged(const double sampleRate) override { if (portBaseNum < 0 || shm.data == nullptr) return; @@ -584,8 +683,25 @@ class DesktopPlugin : public Plugin, jackd.stop(); shm.deinit(); + setupResampler(sampleRate); + + shouldStartRunner = true; + } - firstTimeActivating = true; + void setupResampler(const double sampleRate) + { + if (d_isNotEqual(sampleRate, 48000.0)) + { + resamplerTo48kHz = new Resampler(); + resamplerTo48kHz->setup(sampleRate, 48000, 2, 32); + resamplerFrom48kHz = new Resampler(); + resamplerFrom48kHz->setup(48000, sampleRate, 2, 32); + } + else + { + resamplerTo48kHz = nullptr; + resamplerFrom48kHz = nullptr; + } } // ------------------------------------------------------------------------------------------------------- diff --git a/src/plugin/Makefile b/src/plugin/Makefile index 8259199..61bde78 100644 --- a/src/plugin/Makefile +++ b/src/plugin/Makefile @@ -15,7 +15,12 @@ NAME = mod-desktop # --------------------------------------------------------------------------------------------------------------------- # Files to build -FILES_DSP = DesktopPlugin.cpp utils.cpp +FILES_DSP = \ + DesktopPlugin.cpp \ + utils.cpp \ + zita-resampler/resampler.cc \ + zita-resampler/resampler-table.cc + FILES_UI = DesktopUI.cpp NanoButton.cpp utils.cpp ifeq ($(MACOS),true) diff --git a/src/plugin/SharedMemory.hpp b/src/plugin/SharedMemory.hpp index ad94f54..08c5ea1 100644 --- a/src/plugin/SharedMemory.hpp +++ b/src/plugin/SharedMemory.hpp @@ -119,20 +119,20 @@ class SharedMemory #ifdef DISTRHO_OS_WINDOWS std::snprintf(shmName, 31, "Local\\mod-desktop-shm-%d", portBaseNum); - const HANDLE shm = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, shmName); + const HANDLE testshm = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, shmName); - if (shm == nullptr) + if (testshm == nullptr) return true; - CloseHandle(shm); + CloseHandle(testshm); #else std::snprintf(shmName, 31, "/mod-desktop-shm-%d", portBaseNum); - const int fd = shm_open(shmName, O_RDONLY, 0); + const int testshmfd = shm_open(shmName, O_RDONLY, 0); - if (fd < 0) + if (testshmfd < 0) return true; - close(fd); + close(testshmfd); #endif return false; @@ -349,32 +349,23 @@ class SharedMemory data->magic = 1337; } - bool process(float** output, const uint32_t offset) + bool process() { // unlock RT waiter post(); // wait for processing - if (! wait()) - return false; - - // copy processed buffer - std::memcpy(output[0] + offset, data->audio, sizeof(float) * 128); - std::memcpy(output[1] + offset, data->audio + 128, sizeof(float) * 128); - - return true; + return wait(); } // ---------------------------------------------------------------------------------------------------------------- private: - static constexpr const size_t kDataSize = sizeof(Data) + sizeof(float) * 128 * 2; - // ---------------------------------------------------------------------------------------------------------------- // shared memory details #ifdef DISTRHO_OS_WINDOWS - HANDLE shm; + HANDLE shm = nullptr; #else int shmfd = -1; #endif @@ -389,40 +380,32 @@ class SharedMemory ScopedPointer semServer2; #endif + static constexpr const size_t kDataSize = sizeof(Data) + sizeof(float) * 128 * 2; + // ---------------------------------------------------------------------------------------------------------------- // semaphore details - #if defined(DISTRHO_OS_MAC) void post() { + #if defined(DISTRHO_OS_MAC) semaphore_signal(sem1); - } - - bool wait() - { - const mach_timespec timeout = { 1, 0 }; - return semaphore_timedwait(sem2, timeout) == KERN_SUCCESS; - } - #elif defined(DISTRHO_OS_WINDOWS) - void post() - { + #elif defined(DISTRHO_OS_WINDOWS) ReleaseSemaphore(data->sem1, 1, nullptr); - } - - bool wait() - { - return WaitForSingleObject(data->sem2, 1000) == WAIT_OBJECT_0; - } - #else - void post() - { + #else const bool unlocked = __sync_bool_compare_and_swap(&data->sem1, 0, 1); DISTRHO_SAFE_ASSERT_RETURN(unlocked,); syscall(__NR_futex, &data->sem1, FUTEX_WAKE, 1, nullptr, nullptr, 0); + #endif } bool wait() { + #if defined(DISTRHO_OS_MAC) + const mach_timespec timeout = { 1, 0 }; + return semaphore_timedwait(sem2, timeout) == KERN_SUCCESS; + #elif defined(DISTRHO_OS_WINDOWS) + return WaitForSingleObject(data->sem2, 1000) == WAIT_OBJECT_0; + #else const timespec timeout = { 1, 0 }; for (;;) @@ -434,8 +417,8 @@ class SharedMemory if (errno != EAGAIN && errno != EINTR) return false; } + #endif } - #endif bool fail_deinit() { diff --git a/src/plugin/zita-resampler/resampler.cc b/src/plugin/zita-resampler/resampler.cc index 526af07..8401458 100644 --- a/src/plugin/zita-resampler/resampler.cc +++ b/src/plugin/zita-resampler/resampler.cc @@ -197,7 +197,7 @@ bool Resampler::reset (void) noexcept bool Resampler::process (void) { - unsigned int hl, np, ph, dp, in, nr, nz, di, i, j, n; + unsigned int hl, np, ph, dp, in, nr, nz, di, i, j, n, inp_i, outp_i; float *c1, *c2, *p1, *p2, *q1, *q2; if (!_table) return false; @@ -212,102 +212,93 @@ bool Resampler::process (void) p1 = _buff + in; p2 = p1 + 2 * hl - nr; di = 2 * hl + _inmax; + inp_i = outp_i = 0; while (out_count) { while (nr && inp_count) { - if (inp_data) - { - for (j = 0; j < _nchan; j++) p2 [j * di] = inp_data [j]; - inp_data += _nchan; - nz = 0; - } - else - { - for (j = 0; j < _nchan; j++) p2 [j * di] = 0; - if (nz < 2 * hl) nz++; - } - p2++; - nr--; - inp_count--; + for (j = 0; j < _nchan; j++) p2 [j * di] = inp_data[j][inp_i]; + nz = 0; + ++p2; + --nr; + --inp_count; + ++inp_i; } if (nr) break; - if (out_data) + if (nz < 2 * hl) { - if (nz < 2 * hl) - { - c1 = _table->_ctab + hl * ph; - c2 = _table->_ctab + hl * (np - ph); + c1 = _table->_ctab + hl * ph; + c2 = _table->_ctab + hl * (np - ph); #if defined(__SSE2_MATH__) && !defined(_WIN32) - __m128 C1, C2, Q1, Q2, S; - for (j = 0; j < _nchan; j++) + __m128 C1, C2, Q1, Q2, S; + for (j = 0; j < _nchan; j++) + { + q1 = p1 + j * di; + q2 = p2 + j * di; + S = _mm_setzero_ps (); + for (i = 0; i < hl; i += 4) { - q1 = p1 + j * di; - q2 = p2 + j * di; - S = _mm_setzero_ps (); - for (i = 0; i < hl; i += 4) - { - C1 = _mm_load_ps (c1 + i); - Q1 = _mm_loadu_ps (q1); - q2 -= 4; - S = _mm_add_ps (S, _mm_mul_ps (C1, Q1)); - C2 = _mm_loadr_ps (c2 + i); - Q2 = _mm_loadu_ps (q2); - q1 += 4; - S = _mm_add_ps (S, _mm_mul_ps (C2, Q2)); - } - *out_data++ = S [0] + S [1] + S [2] + S [3]; + C1 = _mm_load_ps (c1 + i); + Q1 = _mm_loadu_ps (q1); + q2 -= 4; + S = _mm_add_ps (S, _mm_mul_ps (C1, Q1)); + C2 = _mm_loadr_ps (c2 + i); + Q2 = _mm_loadu_ps (q2); + q1 += 4; + S = _mm_add_ps (S, _mm_mul_ps (C2, Q2)); } + out_data[j][outp_i] = S [0] + S [1] + S [2] + S [3]; + } #elif (defined(__ARM_NEON) || defined(__ARM_NEON__)) && !defined(_WIN32) - // ARM64 version by Nicolas Belin - float32x4_t *C1 = (float32x4_t *)c1; - float32x4_t *C2 = (float32x4_t *)c2; - float32x4_t S, T; - for (j = 0; j < _nchan; j++) + // ARM64 version by Nicolas Belin + float32x4_t *C1 = (float32x4_t *)c1; + float32x4_t *C2 = (float32x4_t *)c2; + float32x4_t S, T; + for (j = 0; j < _nchan; j++) + { + q1 = p1 + j * di; + q2 = p2 + j * di - 4; + T = vrev64q_f32 (vld1q_f32 (q2)); + S = vmulq_f32 (vextq_f32 (T, T, 2), C2 [0]); + S = vmlaq_f32 (S, vld1q_f32(q1), C1 [0]); + for (i = 1; i < (hl>>2); i++) { - q1 = p1 + j * di; - q2 = p2 + j * di - 4; + q2 -= 4; + q1 += 4; T = vrev64q_f32 (vld1q_f32 (q2)); - S = vmulq_f32 (vextq_f32 (T, T, 2), C2 [0]); - S = vmlaq_f32 (S, vld1q_f32(q1), C1 [0]); - for (i = 1; i < (hl>>2); i++) - { - q2 -= 4; - q1 += 4; - T = vrev64q_f32 (vld1q_f32 (q2)); - S = vmlaq_f32 (S, vextq_f32 (T, T, 2), C2 [i]); - S = vmlaq_f32 (S, vld1q_f32 (q1), C1 [i]); - } - *out_data++ = S [0] + S [1] + S [2] + S [3]; + S = vmlaq_f32 (S, vextq_f32 (T, T, 2), C2 [i]); + S = vmlaq_f32 (S, vld1q_f32 (q1), C1 [i]); } + out_data[j][outp_i] = S [0] + S [1] + S [2] + S [3]; + } #else - float s; - for (j = 0; j < _nchan; j++) + float s; + for (j = 0; j < _nchan; j++) + { + q1 = p1 + j * di; + q2 = p2 + j * di; + s = 1e-30f; + for (i = 0; i < hl; i++) { - q1 = p1 + j * di; - q2 = p2 + j * di; - s = 1e-30f; - for (i = 0; i < hl; i++) - { - q2--; - s += *q1 * c1 [i] + *q2 * c2 [i]; - q1++; - } - *out_data++ = s - 1e-30f; + q2--; + s += *q1 * c1 [i] + *q2 * c2 [i]; + q1++; } -#endif - } - else - { - for (j = 0; j < _nchan; j++) *out_data++ = 0; + out_data[j][outp_i] = s - 1e-30f; } +#endif + } + else + { + for (j = 0; j < _nchan; j++) out_data[j][outp_i] = 0; } - out_count--; + --out_count; + ++outp_i; ph += dp; if (ph >= np) diff --git a/src/plugin/zita-resampler/resampler.h b/src/plugin/zita-resampler/resampler.h index 38b1fe4..df223c4 100644 --- a/src/plugin/zita-resampler/resampler.h +++ b/src/plugin/zita-resampler/resampler.h @@ -53,10 +53,8 @@ class Resampler unsigned int inp_count; unsigned int out_count; - float *inp_data; - float *out_data; - float **inp_list; - float **out_list; + const float *const *inp_data; + float* *out_data; private: