diff --git a/.gitmodules b/.gitmodules index ec1c493cf86..eda1eac2895 100644 --- a/.gitmodules +++ b/.gitmodules @@ -47,3 +47,6 @@ [submodule "libs/pffft"] path = libs/pffft url = https://github.com/surge-synthesizer/pffft.git +[submodule "libs/sst/sst-basic-blocks"] + path = libs/sst/sst-basic-blocks + url = https://github.com/surge-synthesizer/sst-basic-blocks.git diff --git a/libs/sst/sst-basic-blocks b/libs/sst/sst-basic-blocks new file mode 160000 index 00000000000..98ba4587d5c --- /dev/null +++ b/libs/sst/sst-basic-blocks @@ -0,0 +1 @@ +Subproject commit 98ba4587d5cdcbb8dee090db814b32ce4c946a44 diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 568287ff53a..0bcf91049c0 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -35,6 +35,7 @@ surge_add_lib_subdirectory(sst/sst-cpputils) surge_add_lib_subdirectory(sst/sst-plugininfra) surge_add_lib_subdirectory(sst/sst-filters) surge_add_lib_subdirectory(sst/sst-waveshapers) +surge_add_lib_subdirectory(sst/sst-basic-blocks) set(PEGTL_BUILD_TESTS OFF CACHE BOOL "") set(PEGTL_BUILD_EXAMPLES OFF CACHE BOOL "") @@ -279,8 +280,6 @@ add_library(${PROJECT_NAME} dsp/utilities/DSPUtils.cpp dsp/utilities/DSPUtils.h dsp/utilities/FastMath.h - dsp/utilities/LanczosResampler.cpp - dsp/utilities/LanczosResampler.h dsp/utilities/SSEComplex.h dsp/utilities/SSESincDelayLine.h dsp/vembertech/basic_dsp.cpp @@ -378,6 +377,7 @@ target_link_libraries(${PROJECT_NAME} sst-filters sst-filters-extras sst-waveshapers + sst-basic-blocks surge::oddsound-mts surge::sqlite diff --git a/src/common/dsp/oscillators/TwistOscillator.cpp b/src/common/dsp/oscillators/TwistOscillator.cpp index 3584dbd2426..ec146c4d28f 100644 --- a/src/common/dsp/oscillators/TwistOscillator.cpp +++ b/src/common/dsp/oscillators/TwistOscillator.cpp @@ -27,10 +27,6 @@ #include "samplerate.h" -#if SAMPLERATE_LANCZOS -#include "LanczosResampler.h" -#endif - std::string twist_engine_name(int i) { switch (i) @@ -277,7 +273,8 @@ TwistOscillator::TwistOscillator(SurgeStorage *storage, OscillatorStorage *oscda : Oscillator(storage, oscdata, localcopy), charFilt(storage) { #if SAMPLERATE_LANCZOS - lancRes = std::make_unique(48000, storage->dsamplerate_os); + lancRes = std::make_unique>( + 48000, storage->dsamplerate_os); srcstate = nullptr; #else int error; diff --git a/src/common/dsp/oscillators/TwistOscillator.h b/src/common/dsp/oscillators/TwistOscillator.h index 280a4f1f18f..3d677f7a9bc 100644 --- a/src/common/dsp/oscillators/TwistOscillator.h +++ b/src/common/dsp/oscillators/TwistOscillator.h @@ -16,6 +16,7 @@ /* * What's our samplerate strategy */ +#include "globals.h" #define SAMPLERATE_SRC 0 #define SAMPLERATE_LANCZOS 1 @@ -26,7 +27,8 @@ #include "OscillatorCommonFunctions.h" #if SAMPLERATE_LANCZOS -#include "LanczosResampler.h" +// #include "LanczosResampler.h" +#include "sst/basic-blocks/dsp/LanczosResampler.h" #endif namespace plaits @@ -93,7 +95,7 @@ class TwistOscillator : public Oscillator bool useCorrectLPGBlockSize{false}; // See #6760 #if SAMPLERATE_LANCZOS - std::unique_ptr lancRes; + std::unique_ptr> lancRes; #endif float carryover[BLOCK_SIZE_OS][2]; diff --git a/src/common/dsp/utilities/LanczosResampler.cpp b/src/common/dsp/utilities/LanczosResampler.cpp deleted file mode 100644 index 9a02751cc77..00000000000 --- a/src/common/dsp/utilities/LanczosResampler.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* -** Surge Synthesizer is Free and Open Source Software -** -** Surge is made available under the Gnu General Public License, v3.0 -** https://www.gnu.org/licenses/gpl-3.0.en.html -** -** Copyright 2004-2021 by various individuals as described by the Git transaction log -** -** All source at: https://github.com/surge-synthesizer/surge.git -** -** Surge was a commercial product from 2004-2018, with Copyright and ownership -** in that period held by Claes Johanson at Vember Audio. Claes made Surge -** open source in September 2018. -*/ - -#include "LanczosResampler.h" - -float LanczosResampler::lanczosTable alignas( - 16)[LanczosResampler::tableObs + 1][LanczosResampler::filterWidth]; -float LanczosResampler::lanczosTableDX alignas( - 16)[LanczosResampler::tableObs + 1][LanczosResampler::filterWidth]; - -bool LanczosResampler::tablesInitialized = false; - -size_t LanczosResampler::populateNext(float *fL, float *fR, size_t max) -{ - int populated = 0; - while (populated < max && (phaseI - phaseO) > A + 1) - { - read((phaseI - phaseO), fL[populated], fR[populated]); - phaseO += dPhaseO; - populated++; - } - return populated; -} - -void LanczosResampler::populateNextBlockSizeOS(float *fL, float *fR) -{ - double r0 = phaseI - phaseO; - for (int i = 0; i < BLOCK_SIZE_OS; ++i) - { - read(r0 - i * dPhaseO, fL[i], fR[i]); - } - phaseO += BLOCK_SIZE_OS * dPhaseO; -} \ No newline at end of file diff --git a/src/common/dsp/utilities/LanczosResampler.h b/src/common/dsp/utilities/LanczosResampler.h deleted file mode 100644 index 68be3b4c1d1..00000000000 --- a/src/common/dsp/utilities/LanczosResampler.h +++ /dev/null @@ -1,197 +0,0 @@ -/* -** Surge Synthesizer is Free and Open Source Software -** -** Surge is made available under the Gnu General Public License, v3.0 -** https://www.gnu.org/licenses/gpl-3.0.en.html -** -** Copyright 2004-2021 by various individuals as described by the Git transaction log -** -** All source at: https://github.com/surge-synthesizer/surge.git -** -** Surge was a commercial product from 2004-2018, with Copyright and ownership -** in that period held by Claes Johanson at Vember Audio. Claes made Surge -** open source in September 2018. -*/ - -#ifndef SURGE_LANCZOSRESAMPLER_H -#define SURGE_LANCZOSRESAMPLER_H - -#include "globals.h" -#include "portable_intrinsics.h" -#include -#include -#include -#include -#include "DebugHelpers.h" - -/* - * See https://en.wikipedia.org/wiki/Lanczos_resampling - */ - -struct LanczosResampler -{ - static constexpr size_t A = 4; - static constexpr size_t BUFFER_SZ = 4096; - static constexpr size_t filterWidth = A * 2; - static constexpr size_t tableObs = 8192; - static constexpr double dx = 1.0 / (tableObs); - - // Fixme: Make this static and shared - static float lanczosTable alignas(16)[tableObs + 1][filterWidth], lanczosTableDX - alignas(16)[tableObs + 1][filterWidth]; - static bool tablesInitialized; - - // This is a stereo resampler - float input[2][BUFFER_SZ * 2]; - int wp = 0; - float sri, sro; - double phaseI, phaseO, dPhaseI, dPhaseO; - - inline double kernel(double x) - { - if (fabs(x) < 1e-7) - return 1; - return A * std::sin(M_PI * x) * std::sin(M_PI * x / A) / (M_PI * M_PI * x * x); - } - - LanczosResampler(float inputRate, float outputRate) : sri(inputRate), sro(outputRate) - { - phaseI = 0; - phaseO = 0; - - dPhaseI = 1.0; - dPhaseO = sri / sro; - - memset(input, 0, 2 * BUFFER_SZ * sizeof(float)); - if (!tablesInitialized) - { - for (size_t t = 0; t < tableObs + 1; ++t) - { - double x0 = dx * t; - for (size_t i = 0; i < filterWidth; ++i) - { - double x = x0 + i - A; - lanczosTable[t][i] = kernel(x); - } - } - for (size_t t = 0; t < tableObs; ++t) - { - for (size_t i = 0; i < filterWidth; ++i) - { - lanczosTableDX[t][i] = - lanczosTable[(t + 1) & (tableObs - 1)][i] - lanczosTable[t][i]; - } - } - for (size_t i = 0; i < filterWidth; ++i) - { - // Wrap at the end - deriv is the same - lanczosTableDX[tableObs][i] = lanczosTable[0][i]; - } - tablesInitialized = true; - } - } - - inline void push(float fL, float fR) - { - input[0][wp] = fL; - input[0][wp + BUFFER_SZ] = fL; // this way we can always wrap - input[1][wp] = fR; - input[1][wp + BUFFER_SZ] = fR; - wp = (wp + 1) & (BUFFER_SZ - 1); - phaseI += dPhaseI; - } - - inline void readZOH(double xBack, float &L, float &R) const - { - double p0 = wp - xBack; - int idx0 = (int)p0; - idx0 = (idx0 + BUFFER_SZ) & (BUFFER_SZ - 1); - if (idx0 <= (int)A) - idx0 += BUFFER_SZ; - L = input[0][idx0]; - R = input[1][idx0]; - } - - inline void readLin(double xBack, float &L, float &R) const - { - double p0 = wp - xBack; - int idx0 = (int)p0; - float frac = p0 - idx0; - idx0 = (idx0 + BUFFER_SZ) & (BUFFER_SZ - 1); - if (idx0 <= (int)A) - idx0 += BUFFER_SZ; - L = (1.0 - frac) * input[0][idx0] + frac * input[0][idx0 + 1]; - R = (1.0 - frac) * input[1][idx0] + frac * input[1][idx0 + 1]; - } - - inline void read(double xBack, float &L, float &R) const - { - double p0 = wp - xBack; - int idx0 = floor(p0); - double off0 = 1.0 - (p0 - idx0); - - idx0 = (idx0 + BUFFER_SZ) & (BUFFER_SZ - 1); - idx0 += (idx0 <= (int)A) * BUFFER_SZ; - - double off0byto = off0 * tableObs; - int tidx = (int)(off0byto); - double fidx = (off0byto - tidx); - - auto fl = _mm_set1_ps((float)fidx); - auto f0 = _mm_load_ps(&lanczosTable[tidx][0]); - auto df0 = _mm_load_ps(&lanczosTableDX[tidx][0]); - - f0 = _mm_add_ps(f0, _mm_mul_ps(df0, fl)); - - auto f1 = _mm_load_ps(&lanczosTable[tidx][4]); - auto df1 = _mm_load_ps(&lanczosTableDX[tidx][4]); - f1 = _mm_add_ps(f1, _mm_mul_ps(df1, fl)); - - auto d0 = _mm_loadu_ps(&input[0][idx0 - A]); - auto d1 = _mm_loadu_ps(&input[0][idx0]); - auto rv = _mm_add_ps(_mm_mul_ps(f0, d0), _mm_mul_ps(f1, d1)); - L = vSum(rv); - - d0 = _mm_loadu_ps(&input[1][idx0 - A]); - d1 = _mm_loadu_ps(&input[1][idx0]); - rv = _mm_add_ps(_mm_mul_ps(f0, d0), _mm_mul_ps(f1, d1)); - R = vSum(rv); - } - - inline size_t inputsRequiredToGenerateOutputs(size_t desiredOutputs) const - { - /* - * So (phaseI + dPhaseI * res - phaseO - dPhaseO * desiredOutputs) * sri > A + 1 - * - * Use the fact that dPhaseI = sri and find - * res > (A+1) - (phaseI - phaseO + dPhaseO * desiredOutputs) * sri - */ - double res = A + 1 - (phaseI - phaseO - dPhaseO * desiredOutputs); - - return (size_t)std::max(res + 1, 0.0); // Check this calculation - } - - size_t populateNext(float *fL, float *fR, size_t max); - - /* - * This is a dangerous but efficient function which more quickly - * populates BLOCK_SIZE_OS worth of items, but assumes you have - * checked the range. - */ - void populateNextBlockSizeOS(float *fL, float *fR); - - inline void advanceReadPointer(size_t n) { phaseO += n * dPhaseO; } - inline void snapOutToIn() - { - phaseO = 0; - phaseI = 0; - } - - inline void renormalizePhases() - { - phaseI -= phaseO; - phaseO = 0; - } -}; - -#endif // SURGE_LANCZOSRESAMPLER_H diff --git a/src/surge-testrunner/UnitTestsDSP.cpp b/src/surge-testrunner/UnitTestsDSP.cpp index ce610f77291..ec62dd77510 100644 --- a/src/surge-testrunner/UnitTestsDSP.cpp +++ b/src/surge-testrunner/UnitTestsDSP.cpp @@ -16,7 +16,6 @@ #include "SSEComplex.h" #include -#include "LanczosResampler.h" #include "sst/plugininfra/cpufeatures.h" using namespace Surge::Test; @@ -905,47 +904,6 @@ TEST_CASE("SSE std::complex", "[dsp]") } } -#if 0 -TEST_CASE("LanczosResampler", "[dsp]") -{ - SECTION("Can Interpolate Sine") - { - LanczosResampler lr(48000, 88100); - - std::cout << lr.inputsRequiredToGenerateOutputs(64) << std::endl; - // plot 'lancos_raw.csv' using 1:2 with lines, 'lancos_samp.csv' using 1:2 with lines - std::ofstream raw("lancos_raw.csv"), samp("lancos_samp.csv"); - int points = 2000; - double dp = 1.0 / 370; - float phase = 0; - for (auto i = 0; i < points; ++i) - { - auto obsS = std::sin(i * dp * 2.0 * M_PI); - auto obsR = phase * 2 - 1; - phase += dp; - if (phase > 1) - phase -= 1; - auto obs = i > 800 ? obsS : obsR; - lr.push(obs); - - raw << i << ", " << obs << "\n"; - } - std::cout << lr.inputsRequiredToGenerateOutputs(64) << std::endl; - - float outBlock[64]; - int q, gen; - while ((gen = lr.populateNext(outBlock, 64)) > 0) - { - for (int i = 0; i < gen; ++i) - { - samp << q * 48000.0 / 88100.0 << ", " << outBlock[i] << std::endl; - ++q; - } - } - } -} -#endif - // When we return to #1514 this is a good starting point #if 0 TEST_CASE( "NaN Patch from Issue 1514", "[dsp]" ) diff --git a/src/surge-xt/gui/overlays/MSEGEditor.cpp b/src/surge-xt/gui/overlays/MSEGEditor.cpp index daed0c5f506..a189057233c 100644 --- a/src/surge-xt/gui/overlays/MSEGEditor.cpp +++ b/src/surge-xt/gui/overlays/MSEGEditor.cpp @@ -1087,9 +1087,6 @@ struct MSEGCanvas : public juce::Component, public Surge::GUI::SkinConsumingComp virtual void paint(juce::Graphics &g) override { - // TimeThisBlock ttblock("msegcanvas" ); - - // ttblock.bump( "a0" ); auto uni = lfodata->unipolar.val.b; auto vs = getLocalBounds(); @@ -1099,21 +1096,13 @@ struct MSEGCanvas : public juce::Component, public Surge::GUI::SkinConsumingComp if (hotzones.empty()) recalcHotZones(juce::Point(vs.getX(), vs.getY())); - // ttblock.bump( "a1" ); g.fillAll(skin->getColor(Colors::MSEGEditor::Background)); - // ttblock.bump( "a1-1"); auto drawArea = getDrawArea(); - // ttblock.bump( "a1-2"); float maxt = drawDuration(); - // ttblock.bump( "a1-3"); auto valpx = valToPx(); - // ttblock.bump( "a1-4"); auto tpx = timeToPx(); - // ttblock.bump( "a1-5"); auto pxt = pxToTime(); - // ttblock.bump( "a1-6"); - // ttblock.bump( "a2" ); /* * Now draw the loop region */ @@ -1153,7 +1142,6 @@ struct MSEGCanvas : public juce::Component, public Surge::GUI::SkinConsumingComp } } - // ttblock.bump("a"); Surge::MSEG::EvaluatorState es, esdf; // This is different from the number in LFOMS::assign in draw mode on purpose es.seed(8675309); @@ -1273,8 +1261,6 @@ struct MSEGCanvas : public juce::Component, public Surge::GUI::SkinConsumingComp drawnLast = up > ms->totalDuration; } - // ttblock.bump("b"); - int uniLimit = uni ? -1 : 0; addP(fillpath, pathLastX, valpx(uniLimit)); addP(fillpath, uniLimit, valpx(uniLimit)); @@ -1306,19 +1292,13 @@ struct MSEGCanvas : public juce::Component, public Surge::GUI::SkinConsumingComp g.fillPath(fillpath, tfpath); } - // ttblock.bump("c"); - // draw vertical grid auto primaryGridColor = skin->getColor(Colors::MSEGEditor::Grid::Primary); auto secondaryHGridColor = skin->getColor(Colors::MSEGEditor::Grid::SecondaryHorizontal); auto secondaryVGridColor = skin->getColor(Colors::MSEGEditor::Grid::SecondaryVertical); - // ttblock.bump("c1"); - updateHTicks(); - // ttblock.bump("c2"); - for (auto hp : hTicks) { auto t = hp.first; @@ -1347,12 +1327,8 @@ struct MSEGCanvas : public juce::Component, public Surge::GUI::SkinConsumingComp } } - // ttblock.bump("c3"); - updateVTicks(); - // ttblock.bump("c4"); - for (auto vp : vTicks) { float val = std::get<0>(vp); @@ -1419,8 +1395,6 @@ struct MSEGCanvas : public juce::Component, public Surge::GUI::SkinConsumingComp } } - // ttblock.bump("d"); - // draw hover loop markers for (const auto &h : hotzones) { @@ -1465,8 +1439,6 @@ struct MSEGCanvas : public juce::Component, public Surge::GUI::SkinConsumingComp } } - // ttblock.bump("e"); - drawAxis(g); // draw segment curve @@ -1494,12 +1466,19 @@ struct MSEGCanvas : public juce::Component, public Surge::GUI::SkinConsumingComp { int sz = 13; int offx = 0, offy = 0; + bool showValue = false; if (h.active) + { offy = 1; + showValue = true; + } if (h.dragging) + { offy = 2; + showValue = true; + } if (h.zoneSubType == hotzone::SEGMENT_CONTROL) offx = 1; @@ -1507,27 +1486,98 @@ struct MSEGCanvas : public juce::Component, public Surge::GUI::SkinConsumingComp if (lassoSelector && lassoSelector->contains(h.associatedSegment)) offy = 2; - if (handleDrawable) - { - auto r = h.rect; + auto r = h.rect; - if (h.useDrawRect) - r = h.drawRect; + if (h.useDrawRect) + { + r = h.drawRect; + } - int cx = r.getCentreX(); + int cx = r.getCentreX(); + if (handleDrawable) + { if (cx >= drawArea.getX() && cx <= drawArea.getRight()) { juce::Graphics::ScopedSaveState gs(g); auto movet = juce::AffineTransform().translated(r.getTopLeft()); + g.addTransform(movet); - // g.reduceClipRegion(r.toNearestInt()); g.reduceClipRegion(juce::Rectangle(0, 0, sz, sz)); + auto at = juce::AffineTransform().translated(-offx * sz, -offy * sz); handleDrawable->draw(g, 1.0, at); } } + + if (showValue) + { + auto fillr = [&g](juce::Rectangle r, juce::Colour c) { + g.setColour(c); + g.fillRect(r); + }; + + int prec = 2; + + if (storage) + { + if (Surge::Storage::getUserDefaultValue( + storage, Surge::Storage::HighPrecisionReadouts, 0)) + { + prec = 6; + } + } + + g.setFont(skin->fontManager->lfoTypeFont); + + float val = h.associatedSegment >= ms->n_activeSegments - 1 + ? ms->segments[h.associatedSegment].v0 + : ms->segments[h.associatedSegment].nv1; + + std::string txt = fmt::format("X: {:.{}f}", pxt(cx), prec), + txt2 = fmt::format("Y: {:.{}f}", val, prec); + + int sw1 = g.getCurrentFont().getStringWidth(txt), + sw2 = g.getCurrentFont().getStringWidth(txt2); + + float dragX = r.getRight(), dragY = r.getBottom(); + float dragW = 6 + std::max(sw1, sw2), dragH = 22; + + // reposition the display if we've reached right or bottom edge of drawArea + if (dragX + dragW > drawArea.getRight()) + { + dragX -= int(dragX + dragW) % drawArea.getRight(); + } + + if (dragY + dragH > drawArea.getBottom()) + { + dragY -= int(dragY + dragH) % drawArea.getBottom(); + } + + auto readout = juce::Rectangle(dragX, dragY, dragW, dragH); + + // if the readout intersects with a node rect, shift it up + // (this only happens in bottom right corner) + if (readout.intersectRectangles(dragX, dragY, dragW, dragH, r.getX(), r.getY(), + r.getWidth(), r.getHeight())) + { + readout.translate(-(r.getHeight() / 2), -(r.getHeight() / 2)); + } + + fillr(readout, skin->getColor(Colors::LFO::StepSeq::InfoWindow::Border)); + readout = readout.reduced(1, 1); + fillr(readout, skin->getColor(Colors::LFO::StepSeq::InfoWindow::Background)); + + readout = readout.withTrimmedLeft(2).withHeight(10); + + g.setColour(skin->getColor(Colors::LFO::StepSeq::InfoWindow::Text)); + g.drawText(txt, readout, juce::Justification::centredLeft); + + readout = readout.translated(0, 10); + + g.drawText(txt2, readout, juce::Justification::centredLeft); + } } } } diff --git a/src/surge-xt/gui/widgets/LFOAndStepDisplay.cpp b/src/surge-xt/gui/widgets/LFOAndStepDisplay.cpp index bf850016eb7..f828fdfa330 100644 --- a/src/surge-xt/gui/widgets/LFOAndStepDisplay.cpp +++ b/src/surge-xt/gui/widgets/LFOAndStepDisplay.cpp @@ -1377,11 +1377,17 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g) } } - float dragX, dragY; - float dragW = (prec > 4 ? 60 : 40), dragH = (keyModMult ? 22 : 12); + g.setFont(skin->fontManager->lfoTypeFont); + + std::string txt = fmt::format("{:.{}f} %", ss->steps[draggedStep] * 100.f, prec); + + int sw = g.getCurrentFont().getStringWidth(txt); auto sr = steprect[draggedStep]; + float dragX = sr.getRight(), dragY; + float dragW = 6 + sw, dragH = (keyModMult ? 22 : 12); + // Draw to the right in the second half of the seq table if (draggedStep < n_stepseqsteps / 2) { @@ -1393,6 +1399,7 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g) } float yTop; + if (lfodata->unipolar.val.b) { auto sv = std::max(ss->steps[draggedStep], 0.f); @@ -1414,7 +1421,9 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g) } if (dragY < 2) + { dragY = 2; + } auto labelR = juce::Rectangle(dragX, dragY, dragW, dragH); @@ -1422,12 +1431,9 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g) labelR = labelR.reduced(1, 1); fillr(labelR, skin->getColor(Colors::LFO::StepSeq::InfoWindow::Background)); - labelR = labelR.withTrimmedLeft(1).withHeight(10); - - std::string txt = fmt::format("{:.{}f} %", ss->steps[draggedStep] * 100.f, prec); + labelR = labelR.withTrimmedLeft(2).withHeight(10); g.setColour(skin->getColor(Colors::LFO::StepSeq::InfoWindow::Text)); - g.setFont(skin->fontManager->lfoTypeFont); g.drawText(txt, labelR, juce::Justification::centredLeft); if (keyModMult > 0)