From f2bc2345c23931c6b4a7438295ca50bd0a408d09 Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Sun, 22 Sep 2024 19:10:58 -0400 Subject: [PATCH] Processors get independent streaming versions Processors and Bus FX get independent streaming versions which allow them to add and remove parameters independently of parent SV. Also add rotary speaker Closes #958 --- libs/sst/sst-basic-blocks | 2 +- libs/sst/sst-effects | 2 +- libs/sst/sst-waveshapers | 2 +- src-ui/app/mixer-screen/MixerScreen.cpp | 2 + .../components/PartEffectsPane.cpp | 1 + src/dsp/processor/processor.cpp | 42 +++++++ src/dsp/processor/processor.h | 10 ++ src/dsp/processor/processor_impl.h | 12 +- src/engine/bus.cpp | 68 +++++++++- src/engine/bus.h | 6 + src/engine/group_and_zone_impl.h | 6 + src/json/dsp_traits.h | 119 ++++++++++-------- src/json/engine_traits.h | 21 +++- 13 files changed, 237 insertions(+), 56 deletions(-) diff --git a/libs/sst/sst-basic-blocks b/libs/sst/sst-basic-blocks index 4e31e95c..4ebe5ca7 160000 --- a/libs/sst/sst-basic-blocks +++ b/libs/sst/sst-basic-blocks @@ -1 +1 @@ -Subproject commit 4e31e95cb3a1df6c679543282b5a4bae27e40922 +Subproject commit 4ebe5ca7a17d918785100122401245f6f72d30bf diff --git a/libs/sst/sst-effects b/libs/sst/sst-effects index e61a485c..9142611d 160000 --- a/libs/sst/sst-effects +++ b/libs/sst/sst-effects @@ -1 +1 @@ -Subproject commit e61a485c70140bf7425f7cb54d733f575cb7abde +Subproject commit 9142611db5b7cef9e456cd560df70ae92e56fe29 diff --git a/libs/sst/sst-waveshapers b/libs/sst/sst-waveshapers index 8a322587..86a2a81c 160000 --- a/libs/sst/sst-waveshapers +++ b/libs/sst/sst-waveshapers @@ -1 +1 @@ -Subproject commit 8a3225873dcccc915ffb14aa4911587c316f000c +Subproject commit 86a2a81c446347a074b5d5d2be9d5d2d5cb5a8c4 diff --git a/src-ui/app/mixer-screen/MixerScreen.cpp b/src-ui/app/mixer-screen/MixerScreen.cpp index fd63489a..59df7eee 100644 --- a/src-ui/app/mixer-screen/MixerScreen.cpp +++ b/src-ui/app/mixer-screen/MixerScreen.cpp @@ -147,6 +147,8 @@ std::string MixerScreen::effectDisplayName(engine::AvailableBusEffects t, bool f return forMenu ? "TreeMonster" : "TREEMONSTER"; case engine::bonsai: return forMenu ? "Bonsai" : "BONSAI"; + case engine::rotaryspeaker: + return forMenu ? "Rotary Speaker" : "ROTARY"; } return "GCC gives strictly correct, but not useful in this case, warnings"; diff --git a/src-ui/app/mixer-screen/components/PartEffectsPane.cpp b/src-ui/app/mixer-screen/components/PartEffectsPane.cpp index 95404b0b..d0315b4d 100644 --- a/src-ui/app/mixer-screen/components/PartEffectsPane.cpp +++ b/src-ui/app/mixer-screen/components/PartEffectsPane.cpp @@ -104,6 +104,7 @@ void PartEffectsPane::rebuild() CS(nimbus); CS(phaser); CS(treemonster); + CS(rotaryspeaker); CS(bonsai); case engine::AvailableBusEffects::none: diff --git a/src/dsp/processor/processor.cpp b/src/dsp/processor/processor.cpp index c62b5f32..f5a7730a 100644 --- a/src/dsp/processor/processor.cpp +++ b/src/dsp/processor/processor.cpp @@ -51,6 +51,8 @@ namespace detail using boolOp_t = bool (*)(); using constCharOp_t = const char *(*)(); using floatOp_t = float (*)(); +using int16Op_t = int16_t (*)(); +using reampFnOp_t = remapFn_t (*)(); template bool implIsProcessorImplemented() { @@ -126,6 +128,22 @@ template bool implGetForGroupOnly() return ProcessorForGroupOnly<(ProcessorType)I>::isGroupOnly; } +template int16_t implGetStreamingVersion() +{ + if constexpr (std::is_same::T, unimpl_t>::value) + return 0; + else + return ProcessorImplementor<(ProcessorType)I>::T::streamingVersion; +} + +template remapFn_t implGetRemapFn() +{ + if constexpr (std::is_same::T, unimpl_t>::value) + return nullptr; + else + return ProcessorImplementor<(ProcessorType)I>::T::remapParametersForStreamingVersion; +} + template auto getProcessorDisplayGroup(size_t ft, std::index_sequence) { constexpr constCharOp_t fnc[] = {detail::implGetProcessorDisplayGroup...}; @@ -138,6 +156,18 @@ template auto getProcessorDefaultMix(size_t ft, std::index_sequen return fnc[ft](); } +template auto getProcessorStreamingVersion(size_t ft, std::index_sequence) +{ + constexpr int16Op_t fnc[] = {detail::implGetStreamingVersion...}; + return fnc[ft](); +} + +template auto getProcessorRemapFn(size_t ft, std::index_sequence) +{ + constexpr reampFnOp_t fnc[] = {detail::implGetRemapFn...}; + return fnc[ft](); +} + template auto getForGroupOnly(size_t ft, std::index_sequence) { constexpr boolOp_t fnc[] = {detail::implGetForGroupOnly...}; @@ -222,6 +252,12 @@ const char *getProcessorStreamingName(ProcessorType id) id, std::make_index_sequence<(size_t)ProcessorType::proct_num_types>()); } +remapFn_t getProcessorRemapParametersFromStreamingVersion(ProcessorType id) +{ + return detail::getProcessorRemapFn( + id, std::make_index_sequence<(size_t)ProcessorType::proct_num_types>()); +} + const char *getProcessorDisplayGroup(ProcessorType id) { return detail::getProcessorDisplayGroup( @@ -240,6 +276,12 @@ bool getProcessorGroupOnly(ProcessorType id) id, std::make_index_sequence<(size_t)ProcessorType::proct_num_types>()); } +int16_t getProcessorStreamingVersion(ProcessorType id) +{ + return detail::getProcessorStreamingVersion( + id, std::make_index_sequence<(size_t)ProcessorType::proct_num_types>()); +} + std::optional fromProcessorStreamingName(const std::string &s) { // A bit gross but hey diff --git a/src/dsp/processor/processor.h b/src/dsp/processor/processor.h index 97c3fa6e..da7595e0 100644 --- a/src/dsp/processor/processor.h +++ b/src/dsp/processor/processor.h @@ -168,6 +168,9 @@ const char *getProcessorStreamingName(ProcessorType id); const char *getProcessorDisplayGroup(ProcessorType id); float getProcessorDefaultMix(ProcessorType id); bool getProcessorGroupOnly(ProcessorType id); +int16_t getProcessorStreamingVersion(ProcessorType id); +using remapFn_t = void (*)(int16_t, float *const, int *const); +remapFn_t getProcessorRemapParametersFromStreamingVersion(ProcessorType id); std::optional fromProcessorStreamingName(const std::string &s); struct ProcessorDescription @@ -206,6 +209,7 @@ struct ProcessorStorage bool isKeytracked{false}; int previousIsKeytracked{-1}; // make this an int and -1 means don't know previous bool isTemposynced{false}; + int16_t streamingVersion{-1}; bool operator==(const ProcessorStorage &other) const { @@ -283,6 +287,12 @@ struct Processor : MoveableOnly, SampleRateSupport virtual void setTempoPointer(double *t) { tempoPointer = t; } virtual double *getTempoPointer() const { return tempoPointer; } + virtual int16_t getStreamingVersion() const + { + assert(false); + return -1; + } + /* * The default behavior of a processor is stereo -> stereo and all * processors must implement process_stereo. diff --git a/src/dsp/processor/processor_impl.h b/src/dsp/processor/processor_impl.h index 72e39583..b4689624 100644 --- a/src/dsp/processor/processor_impl.h +++ b/src/dsp/processor/processor_impl.h @@ -28,6 +28,8 @@ #ifndef SCXT_SRC_DSP_PROCESSOR_PROCESSOR_IMPL_H #define SCXT_SRC_DSP_PROCESSOR_PROCESSOR_IMPL_H +#include + #include "configuration.h" #include "processor.h" #include "engine/memory_pool.h" @@ -158,7 +160,8 @@ HAS_MEMFN(enableKeytrack); HAS_MEMFN(getKeytrackDefault); HAS_MEMFN(getKeytrack); HAS_MEMFN(checkParameterConsistency); -HAS_MEMFN(getMonoToStereoSetting) +HAS_MEMFN(getMonoToStereoSetting); +HAS_MEMFN(remapParametersForStreamingVersion); #undef HAS_MEMFN @@ -175,6 +178,12 @@ template struct SSTVoiceEffectShim : T << " mono->stereo=" << HasMemFn_processMonoToStereo::value << " stereo->stereo=" << HasMemFn_processStereo::value); #endif + + static_assert(T::streamingVersion > 0, + "All template processors need independent streaming version"); + static_assert(HasMemFn_remapParametersForStreamingVersion::value, + "All template processors need a stream change handler"); + static_assert(std::is_same_v); @@ -260,6 +269,7 @@ template struct SSTVoiceEffectShim : T } } + int16_t getStreamingVersion() const override { return T::streamingVersion; } void onSampleRateChanged() override { if (mInitCalledOnce) diff --git a/src/engine/bus.cpp b/src/engine/bus.cpp index b27625fd..10fc0919 100644 --- a/src/engine/bus.cpp +++ b/src/engine/bus.cpp @@ -46,6 +46,7 @@ #include "sst/effects/Bonsai.h" #include "sst/effects/TreeMonster.h" #include "sst/effects/NimbusImpl.h" +#include "sst/effects/RotarySpeaker.h" #include "sst/effects/EffectCoreDetails.h" #include "sst/basic-blocks/mechanics/block-ops.h" @@ -126,15 +127,41 @@ struct Config static inline float dbToLinear(GlobalStorage *s, float f) { return dsp::dbTable.dbToLinear(f); } }; +#define HAS_MEMFN(M) \ + template class HasMemFn_##M \ + { \ + using No = uint8_t; \ + using Yes = uint64_t; \ + static_assert(sizeof(No) != sizeof(Yes)); \ + template static Yes test(decltype(&C::M) *); \ + template static No test(...); \ + \ + public: \ + enum \ + { \ + value = sizeof(test(nullptr)) == sizeof(Yes) \ + }; \ + }; + +HAS_MEMFN(remapParametersForStreamingVersion); +#undef HAS_MEMFN + template struct Impl : T { static_assert(T::numParams <= BusEffectStorage::maxBusEffectParams); Engine *engine{nullptr}; BusEffectStorage *pes{nullptr}; float *values{nullptr}; - Impl(Engine *e, BusEffectStorage *f, float *v) : engine(e), pes(f), values(v), T(e, f, v) {} + Impl(Engine *e, BusEffectStorage *f, float *v) : engine(e), pes(f), values(v), T(e, f, v) + { + f->streamingVersion = T::streamingVersion; + } void init(bool defaultsOverride) override { + static_assert(T::streamingVersion > 0, + "All template bus fx need independent streaming version"); + static_assert(HasMemFn_remapParametersForStreamingVersion::value, + "All template bus fx need streaming version support"); if (defaultsOverride) { for (int i = 0; i < T::numParams && i < BusEffectStorage::maxBusEffectParams; ++i) @@ -156,7 +183,6 @@ template struct Impl : T } // namespace dtl -// TODO consider the enum to type trick so these can skip the switch std::unique_ptr createEffect(AvailableBusEffects p, Engine *e, BusEffectStorage *s) { namespace sfx = sst::effects; @@ -189,6 +215,9 @@ std::unique_ptr createEffect(AvailableBusEffects p, Engine *e, BusEff case nimbus: return std::make_unique>>(e, s, s->params.data()); + case rotaryspeaker: + return std::make_unique>>( + e, s, s->params.data()); case bonsai: return std::make_unique>>(e, s, s->params.data()); @@ -196,6 +225,41 @@ std::unique_ptr createEffect(AvailableBusEffects p, Engine *e, BusEff return nullptr; } +std::pair getBusEffectRemapStreamingFunction(AvailableBusEffects p) +{ + namespace sfx = sst::effects; + +#define RETVAL(x) \ + { \ + sfx::x::streamingVersion, \ + &sfx::x::remapParametersForStreamingVersion \ + } + switch (p) + { + case none: + return {0, nullptr}; + case reverb1: + return RETVAL(reverb1::Reverb1); + case reverb2: + return RETVAL(reverb2::Reverb2); + case flanger: + return RETVAL(flanger::Flanger); + case phaser: + return RETVAL(phaser::Phaser); + case treemonster: + return RETVAL(treemonster::TreeMonster); + case delay: + return RETVAL(delay::Delay); + case nimbus: + return RETVAL(nimbus::Nimbus); + case rotaryspeaker: + return RETVAL(rotaryspeaker::RotarySpeaker); + case bonsai: + return RETVAL(bonsai::Bonsai); + } + return {0, nullptr}; +} + void Bus::setBusEffectType(Engine &e, int idx, scxt::engine::AvailableBusEffects t) { assert(idx >= 0 && idx < maxEffectsPerBus); diff --git a/src/engine/bus.h b/src/engine/bus.h index 41933e6f..2cbb0a69 100644 --- a/src/engine/bus.h +++ b/src/engine/bus.h @@ -92,6 +92,7 @@ enum AvailableBusEffects delay, treemonster, nimbus, + rotaryspeaker, bonsai // if you make bonsai not last, make sure to update the fromString range }; @@ -104,6 +105,7 @@ struct BusEffectStorage bool isTemposync{false}; std::array params{}; std::array deact{}; + int16_t streamingVersion; inline bool isDeactivated(int idx) { return deact[idx]; } inline bool isExtended(int idx) { return false; } @@ -121,6 +123,8 @@ struct BusEffect }; std::unique_ptr createEffect(AvailableBusEffects p, Engine *e, BusEffectStorage *s); +using busRemapFn_t = void (*)(int16_t, float *const); +std::pair getBusEffectRemapStreamingFunction(AvailableBusEffects); struct Bus : MoveableOnly, SampleRateSupport { @@ -248,6 +252,8 @@ inline std::string toStringAvailableBusEffects(const AvailableBusEffects &p) return "delay"; case nimbus: return "nimbus"; + case rotaryspeaker: + return "rotaryspeaker"; case bonsai: return "bonsai"; } diff --git a/src/engine/group_and_zone_impl.h b/src/engine/group_and_zone_impl.h index 02ccf98b..d82c80c5 100644 --- a/src/engine/group_and_zone_impl.h +++ b/src/engine/group_and_zone_impl.h @@ -65,9 +65,15 @@ void HasGroupZoneProcessors::setProcessorType(int whichProcessor, if (tmpProcessor) { + ps.streamingVersion = tmpProcessor->getStreamingVersion(); dsp::processor::unspawnProcessor(tmpProcessor); } + else + { + ps.streamingVersion = 0; + } + SCLOG("STREAMING VERSION IS " << ps.streamingVersion); asT()->onProcessorTypeChanged(whichProcessor, type); } diff --git a/src/json/dsp_traits.h b/src/json/dsp_traits.h index 77166885..efb6df95 100644 --- a/src/json/dsp_traits.h +++ b/src/json/dsp_traits.h @@ -39,59 +39,80 @@ namespace scxt::json { -SC_STREAMDEF(scxt::dsp::processor::ProcessorStorage, SC_FROM({ - auto &t = from; - if (t.type == dsp::processor::proct_none) - { - v = tao::json::empty_object; - } - else - { - v = {{"type", scxt::dsp::processor::getProcessorStreamingName(t.type)}, - {"mix", t.mix}, - {"out", t.outputCubAmp}, - {"isKeytracked", t.isKeytracked}, - {"isTemposynced", t.isTemposynced}, - {"floatParams", t.floatParams}, - {"intParams", t.intParams}, - {"deactivated", t.deactivated}, - {"isActive", t.isActive}}; - } - }), - SC_TO({ - auto &result = to; - const auto &object = v.get_object(); +SC_STREAMDEF( + scxt::dsp::processor::ProcessorStorage, SC_FROM({ + auto &t = from; + if (t.type == dsp::processor::proct_none) + { + v = tao::json::empty_object; + } + else + { + v = {{"type", scxt::dsp::processor::getProcessorStreamingName(t.type)}, + {"mix", t.mix}, + {"out", t.outputCubAmp}, + {"isKeytracked", t.isKeytracked}, + {"isTemposynced", t.isTemposynced}, + {"floatParams", t.floatParams}, + {"intParams", t.intParams}, + {"deactivated", t.deactivated}, + {"isActive", t.isActive}, + {"streamingVersion", t.streamingVersion}}; + } + }), + SC_TO({ + auto &result = to; + const auto &object = v.get_object(); - std::string type; - findOrSet( - v, {"t", "type"}, - scxt::dsp::processor::getProcessorStreamingName(dsp::processor::proct_none), - type); + std::string type; + findOrSet(v, {"t", "type"}, + scxt::dsp::processor::getProcessorStreamingName(dsp::processor::proct_none), + type); - auto optType = scxt::dsp::processor::fromProcessorStreamingName(type); + auto optType = scxt::dsp::processor::fromProcessorStreamingName(type); - if (optType.has_value()) - result.type = *optType; - else - result.type = scxt::dsp::processor::proct_none; + if (optType.has_value()) + result.type = *optType; + else + result.type = scxt::dsp::processor::proct_none; - if (result.type == dsp::processor::proct_none) - { - result = scxt::dsp::processor::ProcessorStorage(); - result.type = dsp::processor::proct_none; - } - else - { - findIf(v, "mix", result.mix); - findIf(v, "out", result.outputCubAmp); - fromArrayWithSizeDifference(v.at("floatParams"), result.floatParams); - fromArrayWithSizeDifference(v.at("intParams"), result.intParams); - findIf(v, "deactivated", result.deactivated); - findOrSet(v, "isActive", false, result.isActive); - findOrSet(v, "isKeytracked", false, result.isKeytracked); - findOrSet(v, "isTemposynced", false, result.isTemposynced); - } - })) + if (result.type == dsp::processor::proct_none) + { + result = scxt::dsp::processor::ProcessorStorage(); + result.type = dsp::processor::proct_none; + } + else + { + findIf(v, "mix", result.mix); + findIf(v, "out", result.outputCubAmp); + fromArrayWithSizeDifference(v.at("floatParams"), result.floatParams); + fromArrayWithSizeDifference(v.at("intParams"), result.intParams); + findIf(v, "deactivated", result.deactivated); + findOrSet(v, "isActive", false, result.isActive); + findOrSet(v, "isKeytracked", false, result.isKeytracked); + findOrSet(v, "isTemposynced", false, result.isTemposynced); + findOrSet(v, "streamingVersion", -1, result.streamingVersion); + + /* + * If engine stream correct here + */ + if (result.streamingVersion > 0) + { + auto csv = scxt::dsp::processor::getProcessorStreamingVersion(result.type); + if (csv != result.streamingVersion) + { + auto remapFn = + scxt::dsp::processor::getProcessorRemapParametersFromStreamingVersion( + result.type); + assert(remapFn); + if (remapFn) + remapFn(result.streamingVersion, result.floatParams.data(), + result.intParams.data()); + result.streamingVersion = csv; + } + } + } + })) SC_STREAMDEF(scxt::dsp::processor::ProcessorDescription, SC_FROM({ // Streaming this type as an int is fine since the processor storage diff --git a/src/json/engine_traits.h b/src/json/engine_traits.h index d6de33c9..2d0cca20 100644 --- a/src/json/engine_traits.h +++ b/src/json/engine_traits.h @@ -484,7 +484,8 @@ SC_STREAMDEF(engine::BusEffectStorage, SC_FROM({ {"isActive", from.isActive}, {"p", from.params}, {"deact", from.deact}, - {"temposync", from.isTemposync}}; + {"temposync", from.isTemposync}, + {"streamingVersion", from.streamingVersion}}; } }), SC_TO({ @@ -503,6 +504,24 @@ SC_STREAMDEF(engine::BusEffectStorage, SC_FROM({ findOrSet(v, "isActive", true, to.isActive); findIf(v, "deact", to.deact); findIf(v, "temposync", to.isTemposync); + + findOrSet(v, "streamingVersion", -1, result.streamingVersion); + + /* + * If engine stream correct here + */ + if (result.streamingVersion > 0) + { + auto [csv, fn] = + scxt::engine::getBusEffectRemapStreamingFunction(result.type); + if (csv != result.streamingVersion) + { + assert(fn); + if (fn) + fn(result.streamingVersion, result.params.data()); + result.streamingVersion = csv; + } + } } }));