diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e7db1e..1942a22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED OFF) set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -std=c++20") if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") include_directories(SYSTEM /usr/local/include) diff --git a/dsp/ImpulseResponse.cpp b/dsp/ImpulseResponse.cpp index e737fbb..6850f53 100644 --- a/dsp/ImpulseResponse.cpp +++ b/dsp/ImpulseResponse.cpp @@ -11,7 +11,8 @@ #include "ImpulseResponse.h" dsp::ImpulseResponse::ImpulseResponse(const char* fileName, const double sampleRate) -: mWavState(dsp::wav::LoadReturnCode::ERROR_OTHER), mSampleRate(sampleRate) +: mWavState(dsp::wav::LoadReturnCode::ERROR_OTHER) +, mSampleRate(sampleRate) { // Try to load the WAV this->mWavState = dsp::wav::Load(fileName, this->mRawAudio, this->mRawAudioSampleRate); diff --git a/dsp/NoiseGate.cpp b/dsp/NoiseGate.cpp index 212a425..0ce382b 100644 --- a/dsp/NoiseGate.cpp +++ b/dsp/NoiseGate.cpp @@ -5,7 +5,7 @@ // Created by Steven Atkinson on 2/5/23. // -#include // std::clamp +#include // std::clamp #include // memcpy #include // pow #include diff --git a/dsp/ResamplingContainer/Dependencies/LanczosResampler.h b/dsp/ResamplingContainer/Dependencies/LanczosResampler.h new file mode 100644 index 0000000..f734cdf --- /dev/null +++ b/dsp/ResamplingContainer/Dependencies/LanczosResampler.h @@ -0,0 +1,367 @@ +// File: LanczosResampler.h +// Created Date: Saturday December 16th 2023 +// Author: Steven Atkinson (steven@atkinson.mn) + + +// This file originally came from the iPlug2 library and has been subsequently modified; +// the following license is copied as required from +// https://github.com/iPlug2/iPlug2/blob/40ebb560eba68f096221e99ef0ae826611fc2bda/LICENSE.txt +// ------------------------------------------------------------------------------------- + +/* +iPlug 2 C++ Plug-in Framework. + +Copyright (C) the iPlug 2 Developers. Portions copyright other contributors, see each source file for more information. + +Based on WDL-OL/iPlug by Oli Larkin (2011-2018), and the original iPlug v1 (2008) by John Schwartz / Cockos + +LICENSE: + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable +for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it +and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If +you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not +required. +1. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original +software. +1. This notice may not be removed or altered from any source distribution. + +iPlug 2 includes the following 3rd party libraries (see each license info): + +* Cockos WDL https://www.cockos.com/wdl +* NanoVG https://github.com/memononen/nanovg +* NanoSVG https://github.com/memononen/nanosvg +* MetalNanoVG https://github.com/ollix/MetalNanoVG +* RTAudio https://www.music.mcgill.ca/~gary/rtaudio +* RTMidi https://www.music.mcgill.ca/~gary/rtmidi +*/ +// ------------------------------------------------------------------------------------- + +/* + This code is derived from + https://github.com/surge-synthesizer/sst-basic-blocks/blob/main/include/sst/basic-blocks/dsp/LanczosResampler.h + + The following license info is copied from the above file: + + * sst-basic-blocks - an open source library of core audio utilities + * built by Surge Synth Team. + * + * Provides a collection of tools useful on the audio thread for blocks, + * modulation, etc... or useful for adapting code to multiple environments. + * + * Copyright 2023, various authors, as described in the GitHub + * transaction log. Parts of this code are derived from similar + * functions original in Surge or ShortCircuit. + * + * sst-basic-blocks is released under the GNU General Public Licence v3 + * or later (GPL-3.0-or-later). The license is found in the "LICENSE" + * file in the root of this repository, or at + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * All source in sst-basic-blocks available at + * https://github.com/surge-synthesizer/sst-basic-blocks + + * A special note on licensing: This file (and only this file) + * has Paul Walker (baconpaul) as the sole author to date. + * + * In order to make this handy small function based on public + * information available to a set of open source projects + * adapting hardware to software, but which are licensed under + * MIT or BSD or similar licenses, this file and only this file + * can be used in an MIT/BSD context as well as a GPL3 context, by + * copying it and modifying it as you see fit. + * + * If you do that, you will need to replace the `sum_ps_to_float` + * call below with either an hadd if you are SSE3 or higher or + * an appropriate reduction operator from your toolkit. + * + * But basically: Need to resample 48k to variable rate with + * a small window and want to use this? Go for it! + * + * For avoidance of doubt, this license exception only + * applies to this file. + */ + +#pragma once + +#include +#include +#include +#include + +#if defined AUDIODSPTOOLS_SIMDE + #if defined(__arm64__) + #define SIMDE_ENABLE_NATIVE_ALIASES + #include "simde/x86/sse2.h" + #else + #include + #endif +#endif + +// #include "IPlugConstants.h" + +namespace dsp +{ +/* LanczosResampler + * + * A class that implement Lanczos resampling, optionally using SIMD instructions. + * Define AUDIODSPTOOLS_SIMDE at project level in order to use SIMD and if on non-x86_64 + * include the SIMDE library in your search paths in order to translate intel + * intrinsics to e.g. arm64 + * + * See https://en.wikipedia.org/wiki/Lanczos_resampling + * + * @tparam T the sampletype + * @tparam NCHANS the number of channels + * @tparam A The Lanczos filter size. A higher value makes the filter closer to an + ideal stop-band that rejects high-frequency content (anti-aliasing), + but at the expense of higher latency + */ +template +class LanczosResampler +{ +private: +#if AUDIODSPTOOLS_SIMDE + static_assert(std::is_same::value, "LanczosResampler requires T to be float when using SIMD instructions"); + static_assert(false, "SIMD version has not been checked! You need to remove this to use it at your own risk!"); +#endif + + // The buffer size. This needs to be at least as large as the largest block of samples + // that the input side will see. + static constexpr size_t kBufferSize = 4096; + // The filter width. 2x because the filter goes from -A to A + static constexpr size_t kFilterWidth = A * 2; + // The discretization resolution for the filter table. + static constexpr size_t kTablePoints = 8192; + static constexpr double kDeltaX = 1.0 / (kTablePoints); + +public: + /** Constructor + * @param inputRate The input sample rate + * @param outputRate The output sample rate + */ + LanczosResampler(float inputRate, float outputRate) + : mInputSampleRate(inputRate) + , mOutputSamplerate(outputRate) + , mPhaseOutIncr(mInputSampleRate / mOutputSamplerate) + { + ClearBuffer(); + + auto kernel = [](double x) { + if (std::fabs(x) < 1e-7) + return T(1.0); + + const auto pi = iplug::PI; + return T(A * std::sin(pi * x) * std::sin(pi * x / A) / (pi * pi * x * x)); + }; + + if (!sTablesInitialized) + { + for (auto t = 0; t < kTablePoints + 1; ++t) + { + const double x0 = kDeltaX * t; + + for (auto i = 0; i < kFilterWidth; ++i) + { + const double x = x0 + i - A; + sTable[t][i] = kernel(x); + } + } + + for (auto t = 0; t < kTablePoints; ++t) + { + for (auto i = 0; i < kFilterWidth; ++i) + { + sDeltaTable[t][i] = sTable[t + 1][i] - sTable[t][i]; + } + } + + for (auto i = 0; i < kFilterWidth; ++i) + { + // Wrap at the end - delta is the same + sDeltaTable[kTablePoints][i] = sDeltaTable[0][i]; + } + sTablesInitialized = true; + } + } + + inline size_t GetNumSamplesRequiredFor(size_t nOutputSamples) const + { + /* + * So (mPhaseIn + mPhaseInIncr * res - mPhaseOut - mPhaseOutIncr * nOutputSamples) * sri > A + 1 + * + * Use the fact that mPhaseInIncr = mInputSampleRate and find + * res > (A+1) - (mPhaseIn - mPhaseOut + mPhaseOutIncr * desiredOutputs) * sri + */ + auto res = A + 1.0 - (mPhaseIn - mPhaseOut - mPhaseOutIncr * nOutputSamples); + + return static_cast(std::max(res + 1.0, 0.0)); + } + + inline void PushBlock(T** inputs, size_t nFrames) + { + for (auto s = 0; s < nFrames; s++) + { + for (auto c = 0; c < NCHANS; c++) + { + mInputBuffer[c][mWritePos] = inputs[c][s]; + mInputBuffer[c][mWritePos + kBufferSize] = inputs[c][s]; // this way we can always wrap + } + + mWritePos = (mWritePos + 1) & (kBufferSize - 1); + mPhaseIn += mPhaseInIncr; + } + } + + size_t PopBlock(T** outputs, size_t max) + { + int populated = 0; + while (populated < max && (mPhaseIn - mPhaseOut) > A + 1) + { + ReadSamples((mPhaseIn - mPhaseOut), outputs, populated); + mPhaseOut += mPhaseOutIncr; + populated++; + } + return populated; + } + + inline void RenormalizePhases() + { + mPhaseIn -= mPhaseOut; + mPhaseOut = 0; + } + + void Reset() { ClearBuffer(); } + + void ClearBuffer() { memset(mInputBuffer, 0, NCHANS * kBufferSize * 2 * sizeof(T)); } + +private: +#ifdef IPLUG_SIMDE + inline void ReadSamples(double xBack, T** outputs, int s) const + { + float bufferReadPosition = static_cast(mWritePos - xBack); + int bufferReadIndex = static_cast(std::floor(bufferReadPosition)); + float bufferFracPosition = 1.0f - (bufferReadPosition - static_cast(bufferReadIndex)); + + bufferReadIndex = (bufferReadIndex + kBufferSize) & (kBufferSize - 1); + bufferReadIndex += (bufferReadIndex <= static_cast(A)) * kBufferSize; + + float tablePosition = bufferFracPosition * kTablePoints; + int tableIndex = static_cast(tablePosition); + float tableFracPosition = (tablePosition - tableIndex); + + __m128 sum[NCHANS]; + for (auto& v : sum) + { + v = _mm_setzero_ps(); // Initialize sum vectors to zero + } + + for (int i = 0; i < A; i += 4) // Process four samples at a time + { + // Load filter coefficients and input samples into SSE registers + __m128 f0 = _mm_load_ps(&sTable[tableIndex][i]); + __m128 df0 = _mm_load_ps(&sDeltaTable[tableIndex][i]); + __m128 f1 = _mm_load_ps(&sTable[tableIndex][A + i]); + __m128 df1 = _mm_load_ps(&sDeltaTable[tableIndex][A + i]); + + // Interpolate filter coefficients + __m128 tfp = _mm_set1_ps(tableFracPosition); + f0 = _mm_add_ps(f0, _mm_mul_ps(df0, tfp)); + f1 = _mm_add_ps(f1, _mm_mul_ps(df1, tfp)); + + for (int c = 0; c < NCHANS; c++) + { + // Load input data + __m128 d0 = + _mm_set_ps(mInputBuffer[c][bufferReadIndex - A + i + 3], mInputBuffer[c][bufferReadIndex - A + i + 2], + mInputBuffer[c][bufferReadIndex - A + i + 1], mInputBuffer[c][bufferReadIndex - A + i]); + __m128 d1 = _mm_set_ps(mInputBuffer[c][bufferReadIndex + i + 3], mInputBuffer[c][bufferReadIndex + i + 2], + mInputBuffer[c][bufferReadIndex + i + 1], mInputBuffer[c][bufferReadIndex + i]); + + // Perform multiplication and accumulate + __m128 result0 = _mm_mul_ps(f0, d0); + __m128 result1 = _mm_mul_ps(f1, d1); + sum[c] = _mm_add_ps(sum[c], _mm_add_ps(result0, result1)); + } + } + + // Extract the final sums and store them in the output + for (int c = 0; c < NCHANS; c++) + { + float sumArray[4]; + _mm_store_ps(sumArray, sum[c]); + outputs[c][s] = sumArray[0] + sumArray[1] + sumArray[2] + sumArray[3]; + } + } +#else // scalar + inline void ReadSamples(double xBack, T** outputs, int s) const + { + double bufferReadPosition = mWritePos - xBack; + int bufferReadIndex = std::floor(bufferReadPosition); + double bufferFracPosition = 1.0 - (bufferReadPosition - bufferReadIndex); + + bufferReadIndex = (bufferReadIndex + kBufferSize) & (kBufferSize - 1); + bufferReadIndex += (bufferReadIndex <= static_cast(A)) * kBufferSize; + + double tablePosition = bufferFracPosition * kTablePoints; + int tableIndex = static_cast(tablePosition); + double tableFracPosition = (tablePosition - tableIndex); + + T sum[NCHANS] = {0.0}; + + for (auto i = 0; i < A; i++) + { + auto f0 = sTable[tableIndex][i]; + const auto df0 = sDeltaTable[tableIndex][i]; + f0 += df0 * tableFracPosition; + + auto f1 = sTable[tableIndex][A + i]; + const auto df1 = sDeltaTable[tableIndex][A + i]; + f1 += df1 * tableFracPosition; + + for (auto c = 0; c < NCHANS; c++) + { + const auto d0 = mInputBuffer[c][bufferReadIndex - A + i]; + const auto d1 = mInputBuffer[c][bufferReadIndex + i]; + const auto rv = (f0 * d0) + (f1 * d1); + sum[c] += rv; + } + } + + for (auto c = 0; c < NCHANS; c++) + { + outputs[c][s] = sum[c]; + } + } +#endif + + static T sTable alignas(16)[kTablePoints + 1][kFilterWidth]; + static T sDeltaTable alignas(16)[kTablePoints + 1][kFilterWidth]; + static bool sTablesInitialized; + + T mInputBuffer[NCHANS][kBufferSize * 2]; + int mWritePos = 0; + const float mInputSampleRate; + const float mOutputSamplerate; + double mPhaseIn = 0.0; + double mPhaseOut = 0.0; + double mPhaseInIncr = 1.0; + double mPhaseOutIncr = 0.0; +}; + +template +T LanczosResampler::sTable alignas( + 16)[LanczosResampler::kTablePoints + 1][LanczosResampler::kFilterWidth]; + +template +T LanczosResampler::sDeltaTable alignas( + 16)[LanczosResampler::kTablePoints + 1][LanczosResampler::kFilterWidth]; + +template +bool LanczosResampler::sTablesInitialized{false}; + +} // namespace dsp diff --git a/dsp/ResamplingContainer/Dependencies/WDL/heapbuf.h b/dsp/ResamplingContainer/Dependencies/WDL/heapbuf.h new file mode 100644 index 0000000..4343499 --- /dev/null +++ b/dsp/ResamplingContainer/Dependencies/WDL/heapbuf.h @@ -0,0 +1,428 @@ +/* + WDL - heapbuf.h + Copyright (C) 2005 and later Cockos Incorporated + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + +/* + + This file provides the interface and implementation for WDL_HeapBuf, a simple + malloc() wrapper for resizeable blocks. + + Also in this file is WDL_TypedBuf which is a templated version WDL_HeapBuf + that manages type and type-size. + +*/ + +#ifndef _WDL_HEAPBUF_H_ +#define _WDL_HEAPBUF_H_ + +#ifndef WDL_HEAPBUF_IMPL_ONLY + + #ifdef WDL_HEAPBUF_TRACE + #include + #define WDL_HEAPBUF_TRACEPARM(x) , (x) + #else + #define WDL_HEAPBUF_TRACEPARM(x) + #endif + + #include "wdltypes.h" + +class WDL_HeapBuf +{ +public: + // interface + #ifdef WDL_HEAPBUF_INTF_ONLY + void* Resize(int newsize, bool resizedown = true); + void CopyFrom(const WDL_HeapBuf* hb, bool exactCopyOfConfig = false); + #endif + void* Get() const { return m_size ? m_buf : NULL; } // returns NULL if size is 0 + void* GetFast() const { return m_buf; } // returns last buffer if size is 0 + int GetSize() const { return m_size; } + void* GetAligned(int align) const { return (void*)(((UINT_PTR)Get() + (align - 1)) & ~(UINT_PTR)(align - 1)); } + + void SetGranul(int granul) { m_granul = granul; } + int GetGranul() const { return m_granul; } + + void* ResizeOK(int newsize, bool resizedown = true) + { + void* p = Resize(newsize, resizedown); + return GetSize() == newsize ? p : NULL; + } + + WDL_HeapBuf(const WDL_HeapBuf& cp) + { + m_buf = 0; + CopyFrom(&cp, true); + } + WDL_HeapBuf& operator=(const WDL_HeapBuf& cp) + { + CopyFrom(&cp, false); + return *this; + } + + + #ifndef WDL_HEAPBUF_TRACE + explicit WDL_HeapBuf(int granul = 4096) + : m_buf(NULL) + , m_alloc(0) + , m_size(0) + , m_granul(granul) + { + } + ~WDL_HeapBuf() { free(m_buf); } + #else + explicit WDL_HeapBuf(int granul = 4096, const char* tracetype = "WDL_HeapBuf") + : m_buf(NULL) + , m_alloc(0) + , m_size(0) + , m_granul(granul) + { + m_tracetype = tracetype; + wdl_log("WDL_HeapBuf: created type: %s granul=%d\n", tracetype, granul); + } + ~WDL_HeapBuf() + { + wdl_log("WDL_HeapBuf: destroying type: %s (alloc=%d, size=%d)\n", m_tracetype, m_alloc, m_size); + free(m_buf); + } + #endif + +#endif // !WDL_HEAPBUF_IMPL_ONLY + + // implementation bits +#ifndef WDL_HEAPBUF_INTF_ONLY + #ifdef WDL_HEAPBUF_IMPL_ONLY + void* WDL_HeapBuf::Resize(int newsize, bool resizedown) + #else + void* Resize(int newsize, bool resizedown = true) + #endif + { + if (newsize < 0) + newsize = 0; + #ifdef DEBUG_TIGHT_ALLOC // horribly slow, do not use for release builds + if (newsize == m_size) + return m_buf; + + int a = newsize; + if (a > m_size) + a = m_size; + void* newbuf = newsize ? malloc(newsize) : 0; + if (!newbuf && newsize) + { + #ifdef WDL_HEAPBUF_ONMALLOCFAIL + WDL_HEAPBUF_ONMALLOCFAIL(newsize) + #endif + return m_buf; + } + if (newbuf && m_buf) + memcpy(newbuf, m_buf, a); + m_size = m_alloc = newsize; + free(m_buf); + return m_buf = newbuf; + #endif + + if (newsize != m_size || (resizedown && newsize < m_alloc / 2)) + { + int resizedown_under = 0; + if (resizedown && newsize < m_size) + { + // shrinking buffer: only shrink if allocation decreases to min(alloc/2, alloc-granul*4) or 0 + resizedown_under = m_alloc - (m_granul << 2); + if (resizedown_under > m_alloc / 2) + resizedown_under = m_alloc / 2; + if (resizedown_under < 1) + resizedown_under = 1; + } + + if (newsize > m_alloc || newsize < resizedown_under) + { + int granul = newsize / 2; + int newalloc; + if (granul < m_granul) + granul = m_granul; + + if (newsize < 1) + newalloc = 0; + else if (m_granul < 4096) + newalloc = newsize + granul; + else + { + granul &= ~4095; + if (granul < 4096) + granul = 4096; + else if (granul > 4 * 1024 * 1024) + granul = 4 * 1024 * 1024; + newalloc = ((newsize + granul + 96) & ~4095) - 96; + } + + if (newalloc != m_alloc) + { + + #ifdef WDL_HEAPBUF_TRACE + wdl_log("WDL_HeapBuf: type %s realloc(%d) from %d\n", m_tracetype, newalloc, m_alloc); + #endif + if (newalloc <= 0) + { + free(m_buf); + m_buf = 0; + m_alloc = 0; + m_size = 0; + return 0; + } + void* nbuf = realloc(m_buf, newalloc); + if (!nbuf) + { + if (!(nbuf = malloc(newalloc))) + { + #ifdef WDL_HEAPBUF_ONMALLOCFAIL + WDL_HEAPBUF_ONMALLOCFAIL(newalloc); + #endif + return m_size ? m_buf : 0; // failed, do not resize + } + + if (m_buf) + { + int sz = newsize < m_size ? newsize : m_size; + if (sz > 0) + memcpy(nbuf, m_buf, sz); + free(m_buf); + } + } + + m_buf = nbuf; + m_alloc = newalloc; + } // alloc size change + } // need size up or down + m_size = newsize; + } // size change + return m_size ? m_buf : 0; + } + + #ifdef WDL_HEAPBUF_IMPL_ONLY + void WDL_HeapBuf::CopyFrom(const WDL_HeapBuf* hb, bool exactCopyOfConfig) + #else + void CopyFrom(const WDL_HeapBuf* hb, bool exactCopyOfConfig = false) + #endif + { + if (exactCopyOfConfig) // copy all settings + { + free(m_buf); + + #ifdef WDL_HEAPBUF_TRACE + m_tracetype = hb->m_tracetype; + #endif + m_granul = hb->m_granul; + + m_size = m_alloc = 0; + m_buf = hb->m_buf && hb->m_alloc > 0 ? malloc(m_alloc = hb->m_alloc) : NULL; + #ifdef WDL_HEAPBUF_ONMALLOCFAIL + if (!m_buf && m_alloc) + { + WDL_HEAPBUF_ONMALLOCFAIL(m_alloc) + }; + #endif + if (m_buf) + memcpy(m_buf, hb->m_buf, m_size = hb->m_size); + else + m_alloc = 0; + } + else // copy just the data + size + { + const int newsz = hb->GetSize(); + Resize(newsz, true); + if (GetSize() != newsz) + Resize(0); + else + memcpy(Get(), hb->Get(), newsz); + } + } + +#endif // ! WDL_HEAPBUF_INTF_ONLY + +#ifndef WDL_HEAPBUF_IMPL_ONLY + +private: + void* m_buf; + int m_alloc; + int m_size; + int m_granul; + + #if defined(_WIN64) || defined(__LP64__) +public: + int ___pad; // keep size 8 byte aligned + #endif + + #ifdef WDL_HEAPBUF_TRACE + const char* m_tracetype; + #endif +}; + +template +class WDL_TypedBuf +{ +public: + PTRTYPE* Get() const { return (PTRTYPE*)m_hb.Get(); } + PTRTYPE* GetFast() const { return (PTRTYPE*)m_hb.GetFast(); } + int GetSize() const { return m_hb.GetSize() / (unsigned int)sizeof(PTRTYPE); } + int GetSizeBytes() const { return m_hb.GetSize(); } + + PTRTYPE* Resize(int newsize, bool resizedown = true) + { + return (PTRTYPE*)m_hb.Resize(newsize * sizeof(PTRTYPE), resizedown); + } + PTRTYPE* ResizeOK(int newsize, bool resizedown = true) + { + return (PTRTYPE*)m_hb.ResizeOK(newsize * sizeof(PTRTYPE), resizedown); + } + + void SetToZero() { memset(m_hb.Get(), 0, m_hb.GetSize()); } + + PTRTYPE* GetAligned(int align) const { return (PTRTYPE*)m_hb.GetAligned(align); } + + PTRTYPE* Add(PTRTYPE val) + { + const int sz = GetSize(); + PTRTYPE* p = ResizeOK(sz + 1, false); + if (p) + { + p[sz] = val; + return p + sz; + } + return NULL; + } + PTRTYPE* Add(const PTRTYPE* buf, int bufsz) + { + if (bufsz > 0) + { + const int sz = GetSize(); + PTRTYPE* p = ResizeOK(sz + bufsz, false); + if (p) + { + p += sz; + if (buf) + memcpy(p, buf, bufsz * sizeof(PTRTYPE)); + else + memset((char*)p, 0, bufsz * sizeof(PTRTYPE)); + return p; + } + } + return NULL; + } + PTRTYPE* Set(const PTRTYPE* buf, int bufsz) + { + if (bufsz >= 0) + { + PTRTYPE* p = ResizeOK(bufsz, false); + if (p) + { + if (buf) + memcpy(p, buf, bufsz * sizeof(PTRTYPE)); + else + memset((char*)p, 0, bufsz * sizeof(PTRTYPE)); + return p; + } + } + return NULL; + } + PTRTYPE* Insert(PTRTYPE val, int idx) + { + const int sz = GetSize(); + if (idx >= 0 && idx <= sz) + { + PTRTYPE* p = ResizeOK(sz + 1, false); + if (p) + { + memmove(p + idx + 1, p + idx, (sz - idx) * sizeof(PTRTYPE)); + p[idx] = val; + return p + idx; + } + } + return NULL; + } + + void Delete(int idx) + { + PTRTYPE* p = Get(); + const int sz = GetSize(); + if (idx >= 0 && idx < sz) + { + memmove(p + idx, p + idx + 1, (sz - idx - 1) * sizeof(PTRTYPE)); + Resize(sz - 1, false); + } + } + + void SetGranul(int gran) { m_hb.SetGranul(gran); } + + int Find(PTRTYPE val) const + { + const PTRTYPE* p = Get(); + const int sz = GetSize(); + int i; + for (i = 0; i < sz; ++i) + if (p[i] == val) + return i; + return -1; + } + + #ifndef WDL_HEAPBUF_TRACE + explicit WDL_TypedBuf(int granul = 4096) + : m_hb(granul) + { + } + #else + explicit WDL_TypedBuf(int granul = 4096, const char* tracetype = "WDL_TypedBuf") + : m_hb(granul WDL_HEAPBUF_TRACEPARM(tracetype)) + { + } + #endif + ~WDL_TypedBuf() {} + + WDL_HeapBuf* GetHeapBuf() { return &m_hb; } + const WDL_HeapBuf* GetHeapBuf() const { return &m_hb; } + + int DeleteBatch(bool (*proc)(PTRTYPE* p, void* ctx), + void* ctx = NULL) // proc returns true to delete item. returns number deleted + { + const int sz = GetSize(); + int cnt = 0; + PTRTYPE *rd = Get(), *wr = rd; + for (int x = 0; x < sz; x++) + { + if (!proc(rd, ctx)) + { + if (rd != wr) + *wr = *rd; + wr++; + cnt++; + } + rd++; + } + if (cnt < sz) + Resize(cnt, false); + return sz - cnt; + } + +private: + WDL_HeapBuf m_hb; +}; + +#endif // ! WDL_HEAPBUF_IMPL_ONLY + +#endif // _WDL_HEAPBUF_H_ diff --git a/dsp/ResamplingContainer/Dependencies/WDL/ptrlist.h b/dsp/ResamplingContainer/Dependencies/WDL/ptrlist.h new file mode 100644 index 0000000..c5b402c --- /dev/null +++ b/dsp/ResamplingContainer/Dependencies/WDL/ptrlist.h @@ -0,0 +1,299 @@ +/* + WDL - ptrlist.h + Copyright (C) 2005 and later, Cockos Incorporated + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + +/* + + This file provides a simple templated class for a list of pointers. By default this list + doesn't free any of the pointers, but you can call Empty(true) or Delete(x,true) to delete the pointer, + or you can use Empty(true,free) etc to call free (or any other function). + + Note: on certain compilers, instantiating with WDL_PtrList bla; will give a warning, since + the template will create code for "delete (void *)x;" which isn't technically valid. Oh well. + +*/ + +#ifndef _WDL_PTRLIST_H_ +#define _WDL_PTRLIST_H_ + +#include "heapbuf.h" + +template +class WDL_PtrList +{ +public: + explicit WDL_PtrList(int defgran = 4096) + : m_hb(defgran WDL_HEAPBUF_TRACEPARM("WDL_PtrList")) + { + } + + ~WDL_PtrList() {} + + PTRTYPE** GetList() const { return (PTRTYPE**)m_hb.Get(); } + PTRTYPE* Get(INT_PTR index) const + { + PTRTYPE** list = (PTRTYPE**)m_hb.Get(); + if (list && (UINT_PTR)index < (UINT_PTR)(m_hb.GetSize() / sizeof(PTRTYPE*))) + return list[index]; + return NULL; + } + + int GetSize(void) const { return m_hb.GetSize() / (unsigned int)sizeof(PTRTYPE*); } + + int Find(const PTRTYPE* p) const + { + if (p) + { + PTRTYPE** list = (PTRTYPE**)m_hb.Get(); + int x; + const int n = GetSize(); + for (x = 0; x < n; x++) + if (list[x] == p) + return x; + } + return -1; + } + int FindR(const PTRTYPE* p) const + { + if (p) + { + PTRTYPE** list = (PTRTYPE**)m_hb.Get(); + int x = GetSize(); + while (--x >= 0) + if (list[x] == p) + return x; + } + return -1; + } + + PTRTYPE* Add(PTRTYPE* item) + { + const int s = GetSize(); + PTRTYPE** list = (PTRTYPE**)m_hb.ResizeOK((s + 1) * (unsigned int)sizeof(PTRTYPE*), false); + if (list) + { + list[s] = item; + return item; + } + return NULL; + } + + PTRTYPE* Set(int index, PTRTYPE* item) + { + PTRTYPE** list = (PTRTYPE**)m_hb.Get(); + if (list && index >= 0 && index < GetSize()) + return list[index] = item; + return NULL; + } + + PTRTYPE* Insert(int index, PTRTYPE* item) + { + int s = GetSize(); + PTRTYPE** list = (PTRTYPE**)m_hb.ResizeOK((s + 1) * (unsigned int)sizeof(PTRTYPE*), false); + + if (!list) + return item; + + if (index < 0) + index = 0; + + int x; + for (x = s; x > index; x--) + list[x] = list[x - 1]; + return (list[x] = item); + } + int FindSorted(const PTRTYPE* p, int (*compar)(const PTRTYPE** a, const PTRTYPE** b)) const + { + bool m; + int i = LowerBound(p, &m, compar); + return m ? i : -1; + } + PTRTYPE* InsertSorted(PTRTYPE* item, int (*compar)(const PTRTYPE** a, const PTRTYPE** b)) + { + bool m; + return Insert(LowerBound(item, &m, compar), item); + } + + void Delete(int index) + { + PTRTYPE** list = GetList(); + int size = GetSize(); + if (list && index >= 0 && index < size) + { + if (index < --size) + memmove(list + index, list + index + 1, (unsigned int)sizeof(PTRTYPE*) * (size - index)); + m_hb.Resize(size * (unsigned int)sizeof(PTRTYPE*), false); + } + } + void Delete(int index, bool wantDelete, void (*delfunc)(void*) = NULL) + { + PTRTYPE** list = GetList(); + int size = GetSize(); + if (list && index >= 0 && index < size) + { + if (wantDelete) + { + if (delfunc) + delfunc(Get(index)); + else + delete Get(index); + } + if (index < --size) + memmove(list + index, list + index + 1, (unsigned int)sizeof(PTRTYPE*) * (size - index)); + m_hb.Resize(size * (unsigned int)sizeof(PTRTYPE*), false); + } + } + void Delete(int index, void (*delfunc)(PTRTYPE*)) + { + PTRTYPE** list = GetList(); + int size = GetSize(); + if (list && index >= 0 && index < size) + { + if (delfunc) + delfunc(Get(index)); + if (index < --size) + memmove(list + index, list + index + 1, (unsigned int)sizeof(PTRTYPE*) * (size - index)); + m_hb.Resize(size * (unsigned int)sizeof(PTRTYPE*), false); + } + } + void DeletePtr(const PTRTYPE* p) { Delete(Find(p)); } + void DeletePtr(const PTRTYPE* p, bool wantDelete, void (*delfunc)(void*) = NULL) + { + Delete(Find(p), wantDelete, delfunc); + } + void DeletePtr(const PTRTYPE* p, void (*delfunc)(PTRTYPE*)) { Delete(Find(p), delfunc); } + + void Empty() { m_hb.Resize(0, false); } + void Empty(bool wantDelete, void (*delfunc)(void*) = NULL) + { + if (wantDelete) + { + int x; + for (x = GetSize() - 1; x >= 0; x--) + { + PTRTYPE* p = Get(x); + if (p) + { + if (delfunc) + delfunc(p); + else + delete p; + } + m_hb.Resize(x * (unsigned int)sizeof(PTRTYPE*), false); + } + } + m_hb.Resize(0, false); + } + void Empty(void (*delfunc)(PTRTYPE*)) + { + int x; + for (x = GetSize() - 1; x >= 0; x--) + { + PTRTYPE* p = Get(x); + if (delfunc && p) + delfunc(p); + m_hb.Resize(x * (unsigned int)sizeof(PTRTYPE*), false); + } + } + void EmptySafe(bool wantDelete = false, void (*delfunc)(void*) = NULL) + { + if (!wantDelete) + Empty(); + else + { + WDL_PtrList tmp; + int x; + for (x = 0; x < GetSize(); x++) + tmp.Add(Get(x)); + Empty(); + tmp.Empty(true, delfunc); + } + } + + int LowerBound(const PTRTYPE* key, bool* ismatch, int (*compar)(const PTRTYPE** a, const PTRTYPE** b)) const + { + int a = 0; + int c = GetSize(); + PTRTYPE** list = GetList(); + while (a != c) + { + int b = (a + c) / 2; + int cmp = compar((const PTRTYPE**)&key, (const PTRTYPE**)(list + b)); + if (cmp > 0) + a = b + 1; + else if (cmp < 0) + c = b; + else + { + *ismatch = true; + return b; + } + } + *ismatch = false; + return a; + } + + void Compact() { m_hb.Resize(m_hb.GetSize(), true); } + + + int DeleteBatch(bool (*proc)(PTRTYPE* p, void* ctx), + void* ctx = NULL) // proc returns true to remove item. returns number removed + { + const int sz = GetSize(); + int cnt = 0; + PTRTYPE **rd = GetList(), **wr = rd; + for (int x = 0; x < sz; x++) + { + if (!proc(*rd, ctx)) + { + if (rd != wr) + *wr = *rd; + wr++; + cnt++; + } + rd++; + } + if (cnt < sz) + m_hb.Resize(cnt * sizeof(PTRTYPE*), false); + return sz - cnt; + } + +private: + WDL_HeapBuf m_hb; +}; + + +template +class WDL_PtrList_DeleteOnDestroy : public WDL_PtrList +{ +public: + explicit WDL_PtrList_DeleteOnDestroy(void (*delfunc)(void*) = NULL, int defgran = 4096) + : WDL_PtrList(defgran) + , m_delfunc(delfunc) + { + } + ~WDL_PtrList_DeleteOnDestroy() { WDL_PtrList::EmptySafe(true, m_delfunc); } + +private: + void (*m_delfunc)(void*); +}; + +#endif diff --git a/dsp/ResamplingContainer/Dependencies/WDL/wdltypes.h b/dsp/ResamplingContainer/Dependencies/WDL/wdltypes.h new file mode 100644 index 0000000..1c23f29 --- /dev/null +++ b/dsp/ResamplingContainer/Dependencies/WDL/wdltypes.h @@ -0,0 +1,358 @@ +#ifndef _WDLTYPES_ +#define _WDLTYPES_ + +#ifdef _MSC_VER + +typedef __int64 WDL_INT64; +typedef unsigned __int64 WDL_UINT64; + +#else + +typedef long long WDL_INT64; +typedef unsigned long long WDL_UINT64; + +#endif + +#ifdef _MSC_VER + #define WDL_UINT64_CONST(x) (x##ui64) + #define WDL_INT64_CONST(x) (x##i64) +#else + #define WDL_UINT64_CONST(x) (x##ULL) + #define WDL_INT64_CONST(x) (x##LL) +#endif + +#ifdef _WIN32 + #define WDL_PRI_UINT64 "I64u" + #define WDL_PRI_INT64 "I64d" +#else + #define WDL_PRI_UINT64 "llu" + #define WDL_PRI_INT64 "lld" +#endif + +#if !defined(_MSC_VER) || _MSC_VER > 1200 + #define WDL_DLGRET INT_PTR CALLBACK +#else + #define WDL_DLGRET BOOL CALLBACK +#endif + + +#ifdef _WIN32 + #include + #include +#else + #include +typedef intptr_t INT_PTR; +typedef uintptr_t UINT_PTR; +#endif +#include + +#if defined(__ppc__) || !defined(__cplusplus) +typedef char WDL_bool; +#else +typedef bool WDL_bool; +#endif + +#ifndef GWLP_USERDATA + #define GWLP_USERDATA GWL_USERDATA + #define GWLP_WNDPROC GWL_WNDPROC + #define GWLP_HINSTANCE GWL_HINSTANCE + #define GWLP_HWNDPARENT GWL_HWNDPARENT + #define DWLP_USER DWL_USER + #define DWLP_DLGPROC DWL_DLGPROC + #define DWLP_MSGRESULT DWL_MSGRESULT + #define SetWindowLongPtr(a, b, c) SetWindowLong(a, b, c) + #define GetWindowLongPtr(a, b) GetWindowLong(a, b) + #define SetWindowLongPtrW(a, b, c) SetWindowLongW(a, b, c) + #define GetWindowLongPtrW(a, b) GetWindowLongW(a, b) + #define SetWindowLongPtrA(a, b, c) SetWindowLongA(a, b, c) + #define GetWindowLongPtrA(a, b) GetWindowLongA(a, b) + + #define GCLP_WNDPROC GCL_WNDPROC + #define GCLP_HICON GCL_HICON + #define GCLP_HICONSM GCL_HICONSM + #define SetClassLongPtr(a, b, c) SetClassLong(a, b, c) + #define GetClassLongPtr(a, b) GetClassLong(a, b) +#endif + +#if !defined(WDL_BIG_ENDIAN) && !defined(WDL_LITTLE_ENDIAN) + #ifdef __ppc__ + #define WDL_BIG_ENDIAN + #else + #define WDL_LITTLE_ENDIAN + #endif +#endif + +#if defined(WDL_BIG_ENDIAN) && defined(WDL_LITTLE_ENDIAN) + #error WDL_BIG_ENDIAN and WDL_LITTLE_ENDIAN both defined +#endif + + +#ifdef __GNUC__ + // for structures that contain doubles, or doubles in structures that are after stuff of questionable alignment (for + // OSX/linux) + #define WDL_FIXALIGN __attribute__((aligned(8))) + // usage: void func(int a, const char *fmt, ...) WDL_VARARG_WARN(printf,2,3); // note: if member function, this + // pointer is counted as well, so as member function that would be 3,4 + #define WDL_VARARG_WARN(x, n, s) __attribute__((format(x, n, s))) + #define WDL_STATICFUNC_UNUSED __attribute__((unused)) + +#else + #define WDL_FIXALIGN + #define WDL_VARARG_WARN(x, n, s) + #define WDL_STATICFUNC_UNUSED +#endif + +#ifndef WDL_WANT_NEW_EXCEPTIONS + #if defined(__cplusplus) + #include + #define WDL_NEW (std::nothrow) + #endif +#else + #define WDL_NEW +#endif + + +#if !defined(max) && defined(WDL_DEFINE_MINMAX) + #define max(x, y) ((x) < (y) ? (y) : (x)) + #define min(x, y) ((x) < (y) ? (x) : (y)) +#endif + +#ifndef wdl_max + #define wdl_max(x, y) ((x) < (y) ? (y) : (x)) + #define wdl_min(x, y) ((x) < (y) ? (x) : (y)) + #define wdl_abs(x) ((x) < 0 ? -(x) : (x)) + #define wdl_clamp(x, minv, maxv) \ + (WDL_NOT_NORMALLY((maxv) < (minv)) || (x) < (minv) ? (minv) : ((x) > (maxv) ? (maxv) : (x))) +#endif + +#ifndef _WIN32 + #ifndef strnicmp + #define strnicmp(x, y, z) strncasecmp(x, y, z) + #endif + #ifndef stricmp + #define stricmp(x, y) strcasecmp(x, y) + #endif +#endif + +#ifdef WDL_BACKSLASHES_ARE_ORDINARY + #define WDL_IS_DIRCHAR(x) ((x) == '/') +#else + // for multi-platform applications it seems better to treat backslashes as directory separators even if it + // isn't supported by the underying system (for resolving filenames, etc) + #ifdef _WIN32 + #define WDL_IS_DIRCHAR(x) ((x) == '\\' || (x) == '/') + #else + #define WDL_IS_DIRCHAR(x) ((x) == '/' || (x) == '\\') + #endif +#endif + +#if defined(_WIN32) && !defined(WDL_BACKSLASHES_ARE_ORDINARY) + #define WDL_DIRCHAR '\\' + #define WDL_DIRCHAR_STR "\\" +#else + #define WDL_DIRCHAR '/' + #define WDL_DIRCHAR_STR "/" +#endif + +#if defined(_WIN32) || defined(__APPLE__) + // on __APPLE__ we should ideally check the filesystem for case-sensitivity, assuming a case-insensitive-only match + #define wdl_filename_cmp(x, y) stricmp(x, y) + #define wdl_filename_cmpn(x, y, n) strnicmp(x, y, n) +#else + #define wdl_filename_cmp(x, y) strcmp(x, y) + #define wdl_filename_cmpn(x, y, n) strncmp(x, y, n) +#endif + +#if defined(__GNUC__) || defined(__INTEL_COMPILER) + #define WDL_likely(x) (__builtin_expect(!!(x), 1)) + #define WDL_unlikely(x) (__builtin_expect(!!(x), 0)) +#else + #define WDL_likely(x) (!!(x)) + #define WDL_unlikely(x) (!!(x)) +#endif + +#if defined(_DEBUG) || defined(DEBUG) + #include + + #ifdef _MSC_VER +// msvc assert failure allows message loop to run, potentially resulting in recursive asserts +static LONG WDL_ASSERT_INTERNALCNT; +static int WDL_ASSERT_END() +{ + WDL_ASSERT_INTERNALCNT = 0; + return 0; +} +static int WDL_ASSERT_BEGIN() +{ + return InterlockedCompareExchange(&WDL_ASSERT_INTERNALCNT, 1, 0) == 0; +} + #define WDL_ASSERT(x) \ + do \ + { \ + if (WDL_ASSERT_BEGIN()) \ + { \ + assert(x); \ + WDL_ASSERT_END(); \ + } \ + } while (0) + #else + #define WDL_ASSERT_BEGIN() (1) + #define WDL_ASSERT_END() (0) + #define WDL_ASSERT(x) assert(x) + #endif + #define WDL_NORMALLY(x) ((x) ? 1 : (WDL_ASSERT_BEGIN() && (assert(0 /*ignorethis*/ && (x)), WDL_ASSERT_END()))) + #define WDL_NOT_NORMALLY(x) ((x) ? !WDL_ASSERT_BEGIN() || (assert(0 /*ignorethis*/ && !(x)), !WDL_ASSERT_END()) : 0) +#else + #define WDL_ASSERT(x) + #define WDL_NORMALLY(x) WDL_likely(x) + #define WDL_NOT_NORMALLY(x) WDL_unlikely(x) +#endif + + +typedef unsigned int WDL_TICKTYPE; + +static WDL_bool WDL_STATICFUNC_UNUSED WDL_TICKS_IN_RANGE(WDL_TICKTYPE current, WDL_TICKTYPE refstart, + int len) // current >= refstart && current < refstart+len +{ + WDL_ASSERT(len > 0); + return (current - refstart) < (WDL_TICKTYPE)len; +} + +static WDL_bool WDL_STATICFUNC_UNUSED WDL_TICKS_IN_RANGE_ENDING_AT(WDL_TICKTYPE current, WDL_TICKTYPE refend, + int len) // current >= refend-len && current < refend +{ + const WDL_TICKTYPE refstart = refend - len; + WDL_ASSERT(len > 0); + return (current - refstart) < (WDL_TICKTYPE)len; + // return ((refend-1) - current) < (WDL_TICKTYPE)len; +} + +// use this if you want validate that nothing that includes wdltypes.h calls fopen() directly on win32 +// #define WDL_CHECK_FOR_NON_UTF8_FOPEN + +#if defined(WDL_CHECK_FOR_NON_UTF8_FOPEN) && !defined(_WDL_WIN32_UTF8_H_) + #ifdef fopen + #undef fopen + #endif + #include +static WDL_STATICFUNC_UNUSED FILE* WDL_fopenA(const char* fn, const char* mode) +{ + return fopen(fn, mode); +} + #define fopen this_should_be_fopenUTF8_include_win32_utf8.h +#else + // callers of WDL_fopenA don't mind being non-UTF8-compatible on win32 + // (this could map to either fopen() or fopenUTF8() + #define WDL_fopenA(fn, mode) fopen(fn, mode) +#endif + +#ifndef WDL_ALLOW_UNSIGNED_DEFAULT_CHAR +typedef char wdl_assert_failed_unsigned_char[((char)-1) > 0 ? -1 : 1]; +#endif + +// wdl_log() / printf() wrapper. no-op on release builds +#if !defined(_DEBUG) && !defined(WDL_LOG_ON_RELEASE) +static void WDL_STATICFUNC_UNUSED WDL_VARARG_WARN(printf, 1, 2) wdl_log(const char* format, ...) {} +#elif defined(_WIN32) +static void WDL_STATICFUNC_UNUSED WDL_VARARG_WARN(printf, 1, 2) wdl_log(const char* format, ...) +{ + int rv; + va_list va; + + char tmp[3800]; + va_start(va, format); + tmp[0] = 0; + rv = _vsnprintf(tmp, sizeof(tmp), format, va); // returns -1 if over, and does not null terminate, ugh + va_end(va); + + if (rv < 0 || rv >= (int)sizeof(tmp) - 1) + tmp[sizeof(tmp) - 1] = 0; + OutputDebugStringA(tmp); +} +#else + #define wdl_log printf +#endif + +static void WDL_STATICFUNC_UNUSED wdl_bswap_copy(void* bout, const void* bin, size_t nelem, size_t elemsz) +{ + char p[8], po[8]; + WDL_ASSERT(elemsz > 0); + if (elemsz > 1 && WDL_NORMALLY(elemsz <= sizeof(p))) + { + size_t i, x; + for (i = 0; i < nelem; i++) + { + memcpy(p, bin, elemsz); + for (x = 0; x < elemsz; x++) + po[x] = p[elemsz - 1 - x]; + memcpy(bout, po, elemsz); + bin = (const char*)bin + elemsz; + bout = (char*)bout + elemsz; + } + } + else if (bout != bin) + memmove(bout, bin, elemsz * nelem); +} + +static void WDL_STATICFUNC_UNUSED wdl_memcpy_le(void* bout, const void* bin, size_t nelem, size_t elemsz) +{ + WDL_ASSERT(elemsz > 0 && elemsz <= 8); +#ifdef WDL_BIG_ENDIAN + if (elemsz > 1) + wdl_bswap_copy(bout, bin, nelem, elemsz); + else +#endif + if (bout != bin) + memmove(bout, bin, elemsz * nelem); +} + +static void WDL_STATICFUNC_UNUSED wdl_memcpy_be(void* bout, const void* bin, size_t nelem, size_t elemsz) +{ + WDL_ASSERT(elemsz > 0 && elemsz <= 8); +#ifdef WDL_LITTLE_ENDIAN + if (elemsz > 1) + wdl_bswap_copy(bout, bin, nelem, elemsz); + else +#endif + if (bout != bin) + memmove(bout, bin, elemsz * nelem); +} + +static void WDL_STATICFUNC_UNUSED wdl_mem_store_int(void* bout, int v) +{ + memcpy(bout, &v, sizeof(v)); +} + +static void WDL_STATICFUNC_UNUSED wdl_mem_store_int_le(void* bout, int v) +{ + wdl_memcpy_le(bout, &v, 1, sizeof(v)); +} + +static void WDL_STATICFUNC_UNUSED wdl_mem_store_int_be(void* bout, int v) +{ + wdl_memcpy_be(bout, &v, 1, sizeof(v)); +} + +static int WDL_STATICFUNC_UNUSED wdl_mem_load_int(const void* rd) +{ + int v; + memcpy(&v, rd, sizeof(v)); + return v; +} + +static int WDL_STATICFUNC_UNUSED wdl_mem_load_int_le(const void* rd) +{ + int v; + wdl_memcpy_le(&v, rd, 1, sizeof(v)); + return v; +} + +static int WDL_STATICFUNC_UNUSED wdl_mem_load_int_be(const void* rd) +{ + int v; + wdl_memcpy_be(&v, rd, 1, sizeof(v)); + return v; +} + + +#endif diff --git a/dsp/ResamplingContainer/ResamplingContainer.h b/dsp/ResamplingContainer/ResamplingContainer.h new file mode 100644 index 0000000..4b8eb6c --- /dev/null +++ b/dsp/ResamplingContainer/ResamplingContainer.h @@ -0,0 +1,326 @@ +// File: ResamplingContainer.h +// Created Date: Saturday December 16th 2023 +// Author: Steven Atkinson (steven@atkinson.mn) + +// A container for real-time resampling using a Lanczos anti-aliasing filter + +// This file originally came from the iPlug2 library and has been subsequently modified; +// the following license is copied as required from +// https://github.com/iPlug2/iPlug2/blob/40ebb560eba68f096221e99ef0ae826611fc2bda/LICENSE.txt +// ------------------------------------------------------------------------------------- + +/* +iPlug 2 C++ Plug-in Framework. + +Copyright (C) the iPlug 2 Developers. Portions copyright other contributors, see each source file for more information. + +Based on WDL-OL/iPlug by Oli Larkin (2011-2018), and the original iPlug v1 (2008) by John Schwartz / Cockos + +LICENSE: + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable +for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it +and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If +you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not +required. +1. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original +software. +1. This notice may not be removed or altered from any source distribution. + +iPlug 2 includes the following 3rd party libraries (see each license info): + +* Cockos WDL https://www.cockos.com/wdl +* NanoVG https://github.com/memononen/nanovg +* NanoSVG https://github.com/memononen/nanosvg +* MetalNanoVG https://github.com/ollix/MetalNanoVG +* RTAudio https://www.music.mcgill.ca/~gary/rtaudio +* RTMidi https://www.music.mcgill.ca/~gary/rtmidi +*/ +// ------------------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +// #include "IPlugPlatform.h" + +// #include "heapbuf.h" +#include "Dependencies/WDL/ptrlist.h" + +#include "Dependencies/LanczosResampler.h" + +namespace dsp +{ + +/** A multi-channel real-time resampling container that can be used to resample + * audio processing to a specified sample rate for the situation where you have + * some arbitary DSP code that requires a specific sample rate, then back to + * the original external sample rate, encapsulating the arbitrary DSP code. + + * Three modes are supported: + * - Linear interpolation: simple linear interpolation between samples + * - Cubic interpolation: cubic interpolation between samples + * - Lanczos: Lanczos resampling uses an approximation of the sinc function to + * interpolate between samples. This is the highest quality resampling mode. + * + * The Lanczos resampler has a configurable filter size (A) that affects the + * latency of the resampler. It can also optionally use SIMD instructions to + * when T==float. + * + * + * @tparam T the sampletype + * @tparam NCHANS the number of channels + * @tparam A The Lanczos filter size for the LanczosResampler resampler mode + A higher value makes the filter closer to an + ideal stop-band that rejects high-frequency content (anti-aliasing), + but at the expense of higher latency + */ +template +class ResamplingContainer +{ +public: + using BlockProcessFunc = std::function; + using LanczosResampler = LanczosResampler; + + // :param renderingSampleRate: The sample rate required by the code to be encapsulated. + ResamplingContainer(double renderingSampleRate) + : mRenderingSampleRate(renderingSampleRate) + { + } + + ResamplingContainer(const ResamplingContainer&) = delete; + ResamplingContainer& operator=(const ResamplingContainer&) = delete; + + // :param inputSampleRate: The external sample rate interacting with this object. + // :param blockSize: The largest block size that will be given to this class to process until Reset() is called + // again. + void Reset(double inputSampleRate, int blockSize = DEFAULT_BLOCK_SIZE) + { + if (mInputSampleRate == inputSampleRate && mMaxBlockSize == blockSize) + { + ClearBuffers(); + return; + } + + mInputSampleRate = inputSampleRate; + mRatio1 = mInputSampleRate / mRenderingSampleRate; + mRatio2 = mRenderingSampleRate / mInputSampleRate; + // The buffers for the encapsulated code need to be long enough to hold the correesponding number of samples + mMaxBlockSize = blockSize; + mMaxEncapsulatedBlockSize = MaxEncapsulatedBlockSize(blockSize); + + mScratchExternalInputData.Resize(mMaxBlockSize * NCHANS); // This may contain junk right now. + mEncapsulatedInputData.Resize(mMaxEncapsulatedBlockSize * NCHANS); // This may contain junk right now. + mEncapsulatedOutputData.Resize(mMaxEncapsulatedBlockSize * NCHANS); // This may contain junk right now. + mScratchExternalInputPointers.Empty(); + mEncapsulatedInputPointers.Empty(); + mEncapsulatedOutputPointers.Empty(); + + for (auto chan = 0; chan < NCHANS; chan++) + { + mScratchExternalInputPointers.Add(mScratchExternalInputData.Get() + (chan * mMaxBlockSize)); + mEncapsulatedInputPointers.Add(mEncapsulatedInputData.Get() + (chan * mMaxEncapsulatedBlockSize)); + mEncapsulatedOutputPointers.Add(mEncapsulatedOutputData.Get() + (chan * mMaxEncapsulatedBlockSize)); + } + + { + mResampler1 = std::make_unique(mInputSampleRate, mRenderingSampleRate); + mResampler2 = std::make_unique(mRenderingSampleRate, mInputSampleRate); + + // Zeroes the scratch pointers so that we warm up with silence. + ClearBuffers(); + + // Warm up the resampling container with enough silence that the first real buffer can yield the required number + // of output samples. + const auto midSamples = mResampler2->GetNumSamplesRequiredFor(1); + mLatency = int(mResampler1->GetNumSamplesRequiredFor(midSamples)); + // 1. Push some silence through the first resampler. + // + mResampler1->PushBlock(mScratchExternalInputPointers.GetList(), mLatency); + const size_t populated = mResampler1->PopBlock(mEncapsulatedInputPointers.GetList(), midSamples); + if (populated < midSamples) + { + throw std::runtime_error("Didn't get enough samples required for pre-population!"); + } + // 2. "process" the warm-up in the encapsulated DSP. + // Since this is an audio effect, we can assume that (1) it's causal and (2) that it's silent until + // a non-silent input is given to it. + // Therefore, we don't *acutally* need to use `func()`--we can assume that it would output silence! + // func(mEncapsulatedInputPointers.GetList(), mEncapsulatedOutputPointers.GetList(), (int)populated); + FallbackFunc(mEncapsulatedInputPointers.GetList(), mEncapsulatedOutputPointers.GetList(), (int)populated); + mResampler2->PushBlock(mEncapsulatedOutputPointers.GetList(), populated); + // Now we're ready for the first "real" buffer. + } + } + + /** Resample an input block with a per-block function (up sample input -> process with function -> down sample) + * @param inputs Two-dimensional array containing the non-interleaved input buffers of audio samples for all channels + * @param outputs Two-dimensional array for audio output (non-interleaved). + * @param nFrames The block size for this block: number of samples per channel. + * @param func The function that processes the audio sample at the higher sampling rate. NOTE: std::function can call + * malloc if you pass in captures */ + void ProcessBlock(T** inputs, T** outputs, int nFrames, BlockProcessFunc func) + { + mResampler1->PushBlock(inputs, nFrames); + // This is the most samples the encapsualted context might get. Sometimes it'll get fewer. + const auto maxEncapsulatedLen = MaxEncapsulatedBlockSize(nFrames); + + // Process as much audio as you can with the encapsulated DSP, and push it into the second resampler. + // This will give the second reasmpler enough for it to pop the required buffer size to complete this function + // correctly. + while (mResampler1->GetNumSamplesRequiredFor(1) == 0) // i.e. there's more to process + { + // Get a block no larger than the encapsulated DSP is expecting. + const size_t populated1 = mResampler1->PopBlock(mEncapsulatedInputPointers.GetList(), maxEncapsulatedLen); + if (populated1 > maxEncapsulatedLen) + { + throw std::runtime_error("Got more encapsulated samples than the encapsulated DSP is prepared to handle!"); + } + func(mEncapsulatedInputPointers.GetList(), mEncapsulatedOutputPointers.GetList(), (int)populated1); + // And push the results into the second resampler so that it has what the external context requires. + mResampler2->PushBlock(mEncapsulatedOutputPointers.GetList(), populated1); + } + + // Pop the required output from the second resampler for the external context. + const auto populated2 = mResampler2->PopBlock(outputs, nFrames); + if (populated2 < nFrames) + { + throw std::runtime_error("Did not yield enough samples to provide the required output buffer!"); + } + // Get ready for the next block: + mResampler1->RenormalizePhases(); + mResampler2->RenormalizePhases(); + } + + int GetLatency() const { return mLatency; } + +private: + static inline int LinearInterpolate(T** inputs, T** outputs, int inputLen, double ratio, int maxOutputLen) + { + // FIXME check through this! + const auto outputLen = std::min(static_cast(std::ceil(static_cast(inputLen) / ratio)), maxOutputLen); + + for (auto writePos = 0; writePos < outputLen; writePos++) + { + const auto readPos = ratio * static_cast(writePos); + const auto readPostionTrunc = std::floor(readPos); + const auto readPosInt = static_cast(readPostionTrunc); + + if (readPosInt < inputLen) + { + const auto y = readPos - readPostionTrunc; + + for (auto chan = 0; chan < NCHANS; chan++) + { + const auto x0 = inputs[chan][readPosInt]; + const auto x1 = ((readPosInt + 1) < inputLen) ? inputs[chan][readPosInt + 1] : inputs[chan][readPosInt - 1]; + outputs[chan][writePos] = (1.0 - y) * x0 + y * x1; + } + } + } + + return outputLen; + } + + static inline int CubicInterpolate(T** inputs, T** outputs, int inputLen, double ratio, int maxOutputLen) + { + // FIXME check through this! + const auto outputLen = std::min(static_cast(std::ceil(static_cast(inputLen) / ratio)), maxOutputLen); + + for (auto writePos = 0; writePos < outputLen; writePos++) + { + const auto readPos = ratio * static_cast(writePos); + const auto readPostionTrunc = std::floor(readPos); + const auto readPosInt = static_cast(readPostionTrunc); + + if (readPosInt < inputLen) + { + const auto y = readPos - readPostionTrunc; + + for (auto chan = 0; chan < NCHANS; chan++) + { + const auto xm1 = ((readPosInt - 1) > 0) ? inputs[chan][readPosInt - 1] : 0.0f; + const auto x0 = ((readPosInt) < inputLen) ? inputs[chan][readPosInt] : inputs[chan][readPosInt - 1]; + const auto x1 = ((readPosInt + 1) < inputLen) ? inputs[chan][readPosInt + 1] : inputs[chan][readPosInt - 1]; + const auto x2 = ((readPosInt + 2) < inputLen) ? inputs[chan][readPosInt + 2] : inputs[chan][readPosInt - 1]; + + const auto c = (x1 - xm1) * 0.5; + const auto v = x0 - x1; + const auto w = c + v; + const auto a = w + v + (x2 - x0) * 0.5; + const auto b = w + a; + + outputs[chan][writePos] = ((((a * y) - b) * y + c) * y + x0); + } + } + } + + return outputLen; + } + + void ClearBuffers() + { + memset(mScratchExternalInputData.Get(), 0.0f, DataSize(mMaxBlockSize)); + const auto encapsulatedDataSize = DataSize(mMaxEncapsulatedBlockSize); + memset(mEncapsulatedInputData.Get(), 0.0f, encapsulatedDataSize); + memset(mEncapsulatedOutputData.Get(), 0.0f, encapsulatedDataSize); + + if (mResampler1 != nullptr) + { + mResampler1->ClearBuffer(); + } + if (mResampler2 != nullptr) + { + mResampler2->ClearBuffer(); + } + } + + // How big could the corresponding encapsulated buffer be for a buffer at the external sample rate of a given size? + int MaxEncapsulatedBlockSize(const int externalBlockSize) const + { + return static_cast(std::ceil(static_cast(externalBlockSize) / mRatio1)); + } + + // Size of the multi-channel data for a given block size + size_t DataSize(const int blockSize) const { return blockSize * NCHANS * sizeof(T); }; + + void FallbackFunc(T** inputs, T** outputs, int n) + { + for (int i = 0; i < NCHANS; i++) + { + memcpy(inputs[i], outputs[i], n * sizeof(T)); + } + } + + // Buffers for scratch input data for Reset() to use + WDL_TypedBuf mScratchExternalInputData; + WDL_PtrList mScratchExternalInputPointers; + // Buffers for the input & output to the encapsulated DSP + WDL_TypedBuf mEncapsulatedInputData; + WDL_PtrList mEncapsulatedInputPointers; + WDL_TypedBuf mEncapsulatedOutputData; + WDL_PtrList mEncapsulatedOutputPointers; + // Sample rate ratio from external to encapsulated, from encapsulated to external. + double mRatio1 = 0.0, mRatio2 = 0.0; + // Sample rate of the external context. + double mInputSampleRate = 0.0; + // The size of the largest block the external context may provide. (It might provide something smaller.) + int mMaxBlockSize = 0; + // The size of the largest possible encapsulated block + int mMaxEncapsulatedBlockSize = 0; + // How much latency this object adds due to both of its resamplers. This does _not_ include the latency due to the + // encapsulated `func()`. + int mLatency = 0; + // The sample rate required by the DSP that this object encapsulates + const double mRenderingSampleRate; + // Pair of resamplers for (1) external -> encapsulated, (2) encapsulated -> external + std::unique_ptr mResampler1, mResampler2; +}; + +}; // namespace dsp diff --git a/tools/benchmodel.cpp b/tools/benchmodel.cpp index 5b2dd8a..ec07ad7 100644 --- a/tools/benchmodel.cpp +++ b/tools/benchmodel.cpp @@ -7,8 +7,6 @@ // Test to benchmark model loading and running - - #include #include #include diff --git a/tools/loadmodel.cpp b/tools/loadmodel.cpp index 39c2ef1..42df81e 100644 --- a/tools/loadmodel.cpp +++ b/tools/loadmodel.cpp @@ -10,28 +10,30 @@ #include #include "dsp/dsp.h" -class Dummy: public dsp::DSP { - public: - ~Dummy() { - _DeallocateOutputPointers(); - } - DSP_SAMPLE** Process(DSP_SAMPLE** inputs, const size_t numChannels, const size_t numFrames) override { - if (numChannels > _GetNumChannels()) { +class Dummy : public dsp::DSP +{ +public: + ~Dummy() { _DeallocateOutputPointers(); } + DSP_SAMPLE** Process(DSP_SAMPLE** inputs, const size_t numChannels, const size_t numFrames) override + { + if (numChannels > _GetNumChannels()) + { throw std::runtime_error("Asked to process too many channels!\n"); } - if (numFrames > _GetNumFrames()) { + if (numFrames > _GetNumFrames()) + { throw std::runtime_error("Asked to process too many samples!\n"); } - for (int c = 0; c < numChannels; c++) { - for (int f=0; f