diff --git a/resources/surge-shared/configuration.xml b/resources/surge-shared/configuration.xml index a75211f1883..d1cca319e8d 100644 --- a/resources/surge-shared/configuration.xml +++ b/resources/surge-shared/configuration.xml @@ -149,6 +149,9 @@ + + + diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 05ba85777dc..1996fc1933c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -242,6 +242,8 @@ add_library(${PROJECT_NAME} dsp/effects/chowdsp/tape/LossFilter.h dsp/effects/chowdsp/tape/ToneControl.cpp dsp/effects/chowdsp/tape/ToneControl.h + dsp/effects/AudioInputEffect.cpp + dsp/effects/AudioInputEffect.h dsp/filters/AllpassFilter.h dsp/filters/BiquadFilter.h dsp/filters/VectorizedSVFilter.cpp diff --git a/src/common/SurgeStorage.cpp b/src/common/SurgeStorage.cpp index 08062d5dcc8..7811a009c44 100644 --- a/src/common/SurgeStorage.cpp +++ b/src/common/SurgeStorage.cpp @@ -2750,5 +2750,37 @@ string findReplaceSubstring(string &source, const string &from, const string &to return newString; } +Surge::Storage::ScenesOutputData::ScenesOutputData() +{ + for (int i = 0; i < n_scenes; i++) + { + for (int j = 0; j < N_OUTPUTS; j++) + { + std::shared_ptr block{new float[BLOCK_SIZE]{}}; + sceneData[i][j] = block; + } + } +} +const std::shared_ptr & +Surge::Storage::ScenesOutputData::getSceneData(int scene, int channel) const +{ + assert(scene < n_scenes && scene >= 0); + assert(channel < N_OUTPUTS && channel >= 0); + return sceneData[scene][channel]; +} +bool Surge::Storage::ScenesOutputData::thereAreClients(int scene) const +{ + return std::any_of(std::begin(sceneData[scene]), std::end(sceneData[scene]), + [](const auto &channel) { return channel.use_count() > 1; }); +} +void Surge::Storage::ScenesOutputData::provideSceneData(int scene, int channel, float *data) +{ + if (scene < n_scenes && scene >= 0 && channel < N_OUTPUTS && channel >= 0 && + sceneData[scene][channel].use_count() > 1) // we don't provide data if there are no clients + { + memcpy(sceneData[scene][channel].get(), data, BLOCK_SIZE * sizeof(float)); + } +} + } // namespace Storage } // namespace Surge diff --git a/src/common/SurgeStorage.h b/src/common/SurgeStorage.h index 433f9bdf0e2..d02e1e398e6 100644 --- a/src/common/SurgeStorage.h +++ b/src/common/SurgeStorage.h @@ -303,7 +303,7 @@ enum fxslot_positions }; // clang-format off -static int constexpr fxslot_order[n_fx_slots] = +static int constexpr fxslot_order[n_fx_slots] = {fxslot_ains1, fxslot_ains2, fxslot_ains3, fxslot_ains4, fxslot_bins1, fxslot_bins2, fxslot_bins3, fxslot_bins4, fxslot_send1, fxslot_send2, fxslot_send3, fxslot_send4, @@ -384,6 +384,7 @@ enum fx_type fxt_mstool, fxt_spring_reverb, fxt_bonsai, + fxt_audio_input, n_fx_types, }; @@ -416,19 +417,20 @@ const char fx_type_names[n_fx_types][32] = {"Off", "Waveshaper", "Mid-Side Tool", "Spring Reverb", - "Bonsai"}; + "Bonsai", + "Audio Input"}; const char fx_type_shortnames[n_fx_types][16] = { "Off", "Delay", "Reverb 1", "Phaser", "Rotary", "Distortion", "EQ", "Freq Shift", "Conditioner", "Chorus", "Vocoder", "Reverb 2", "Flanger", "Ring Mod", "Airwindows", "Neuron", "Graphic EQ", "Resonator", "CHOW", "Exciter", "Ensemble", "Combulator", "Nimbus", "Tape", - "Treemonster", "Waveshaper", "Mid-Side Tool", "Spring Reverb", "Bonsai"}; + "Treemonster", "Waveshaper", "Mid-Side Tool", "Spring Reverb", "Bonsai", "Audio In"}; const char fx_type_acronyms[n_fx_types][8] = { "OFF", "DLY", "RV1", "PH", "ROT", "DIST", "EQ", "FRQ", "DYN", "CH", "VOC", "RV2", "FL", "RM", "AW", "NEU", "GEQ", "RES", "CHW", "XCT", - "ENS", "CMB", "NIM", "TAPE", "TM", "WS", "M-S", "SRV", "BON"}; + "ENS", "CMB", "NIM", "TAPE", "TM", "WS", "M-S", "SRV", "BON", "IN"}; enum fx_bypass { @@ -665,7 +667,10 @@ struct LFOStorage struct FxStorage { - // Just a heads up: if you change this, please go look at fx_reorder in SurgeStorage too! + // Just a heads up: if you change this, please go look at reorderFx in SurgeSynthesizer too! + FxStorage(fxslot_positions slot) : fxslot(slot) {} + FxStorage() = delete; + fxslot_positions fxslot; Parameter type; Parameter return_level; Parameter p[n_fx_params]; @@ -983,7 +988,14 @@ class SurgePatch // data SurgeSceneStorage scene[n_scenes], morphscene; - FxStorage fx[n_fx_slots]; + + FxStorage fx[n_fx_slots]{ + FxStorage(fxslot_ains1), FxStorage(fxslot_ains2), FxStorage(fxslot_bins1), + FxStorage(fxslot_bins2), FxStorage(fxslot_send1), FxStorage(fxslot_send2), + FxStorage(fxslot_global1), FxStorage(fxslot_global2), FxStorage(fxslot_ains3), + FxStorage(fxslot_ains4), FxStorage(fxslot_bins3), FxStorage(fxslot_bins4), + FxStorage(fxslot_send3), FxStorage(fxslot_send4), FxStorage(fxslot_global3), + FxStorage(fxslot_global4)}; int scene_start[n_scenes], scene_size; std::unordered_map param_ptr_by_oscname; @@ -1102,6 +1114,18 @@ namespace Surge { namespace Storage { +struct ScenesOutputData +{ + std::shared_ptr sceneData[n_scenes][N_OUTPUTS]{{nullptr, nullptr}, + {nullptr, nullptr}}; + + public: + ScenesOutputData(); + const std::shared_ptr &getSceneData(int scene, int channel) const; + void provideSceneData(int scene, int channel, float *data); + bool thereAreClients(int scene) const; +}; + struct FxUserPreset; struct ModulatorPreset; } // namespace Storage @@ -1546,6 +1570,8 @@ class alignas(16) SurgeStorage std::atomic otherscene_clients; + Surge::Storage::ScenesOutputData scenesOutputData; + std::unordered_map helpURL_controlgroup; std::unordered_map helpURL_paramidentifier; std::unordered_map helpURL_specials; diff --git a/src/common/SurgeSynthesizer.cpp b/src/common/SurgeSynthesizer.cpp index 197709cde28..651bf361c94 100644 --- a/src/common/SurgeSynthesizer.cpp +++ b/src/common/SurgeSynthesizer.cpp @@ -4574,6 +4574,10 @@ void SurgeSynthesizer::process() sc_state[i] = play_scene[i]; } + for (int i = 0; i < n_scenes; i++) + for (int channel = 0; channel < N_OUTPUTS; channel++) + storage.scenesOutputData.provideSceneData(i, channel, sceneout[i][channel]); + // apply insert effects if (fx_bypass != fxb_no_fx) { @@ -4936,9 +4940,8 @@ void SurgeSynthesizer::reorderFx(int source, int target, FXReorderMode m) std::lock_guard lockModulation(storage.modRoutingMutex); - FxStorage so, to; - so = storage.getPatch().fx[source]; - to = storage.getPatch().fx[target]; + FxStorage so{storage.getPatch().fx[source]}; + FxStorage to{storage.getPatch().fx[target]}; fxmodsync[source].clear(); fxmodsync[target].clear(); diff --git a/src/common/SurgeSynthesizer.h b/src/common/SurgeSynthesizer.h index 7c9367d6fa2..6f7cd617e52 100644 --- a/src/common/SurgeSynthesizer.h +++ b/src/common/SurgeSynthesizer.h @@ -440,8 +440,14 @@ class alignas(16) SurgeSynthesizer // TODO: FIX SCENE ASSUMPTION (use std::array) std::array hpA, hpB; - bool fx_reload[n_fx_slots]; // if true, reload new effect parameters from fxsync - FxStorage fxsync[n_fx_slots]; // used for synchronisation of parameter init + bool fx_reload[n_fx_slots]; // if true, reload new effect parameters from fxsync + FxStorage fxsync[n_fx_slots]{ + FxStorage(fxslot_ains1), FxStorage(fxslot_ains2), FxStorage(fxslot_bins1), + FxStorage(fxslot_bins2), FxStorage(fxslot_send1), FxStorage(fxslot_send2), + FxStorage(fxslot_global1), FxStorage(fxslot_global2), FxStorage(fxslot_ains3), + FxStorage(fxslot_ains4), FxStorage(fxslot_bins3), FxStorage(fxslot_bins4), + FxStorage(fxslot_send3), FxStorage(fxslot_send4), FxStorage(fxslot_global3), + FxStorage(fxslot_global4)}; // used for synchronisation of parameter init bool fx_reload_mod[n_fx_slots]; struct FXModSyncItem diff --git a/src/common/dsp/Effect.cpp b/src/common/dsp/Effect.cpp index dc56fde1a4a..246a7525035 100644 --- a/src/common/dsp/Effect.cpp +++ b/src/common/dsp/Effect.cpp @@ -48,6 +48,7 @@ #include "chowdsp/SpringReverbEffect.h" #include "chowdsp/TapeEffect.h" #include "DebugHelpers.h" +#include "AudioInputEffect.h" using namespace std; @@ -113,6 +114,8 @@ Effect *spawn_effect(int id, SurgeStorage *storage, FxStorage *fxdata, pdata *pd return new chowdsp::SpringReverbEffect(storage, fxdata, pd); case fxt_bonsai: return new BonsaiEffect(storage, fxdata, pd); + case fxt_audio_input: + return new AudioInputEffect(storage, fxdata, pd); default: return 0; }; diff --git a/src/common/dsp/effects/AudioInputEffect.cpp b/src/common/dsp/effects/AudioInputEffect.cpp new file mode 100644 index 00000000000..2ccf79eafd4 --- /dev/null +++ b/src/common/dsp/effects/AudioInputEffect.cpp @@ -0,0 +1,262 @@ + +#include "AudioInputEffect.h" + +AudioInputEffect::AudioInputEffect(SurgeStorage *storage, FxStorage *fxdata, pdata *pd) + : Effect(storage, fxdata, pd) +{ + effect_slot_type slotType = getSlotType(fxdata->fxslot); + if (storage && (slotType == a_insert_slot || slotType == b_insert_slot)) + { + int scene = slotType == a_insert_slot ? 1 : 0; + for (int i = 0; i < N_OUTPUTS; i++) + sceneDataPtr[i] = storage->scenesOutputData.getSceneData(scene, i); + } +} +void AudioInputEffect::init_ctrltypes() +{ + Effect::init_ctrltypes(); + + // ----- Audio Input + fxdata->p[in_audio_input_channel].set_name("Channel"); + fxdata->p[in_audio_input_channel].set_type(ct_percent_bipolar_stereo); + fxdata->p[in_audio_input_channel].posy_offset = 1; + + fxdata->p[in_audio_input_pan].set_name("Pan"); + fxdata->p[in_audio_input_pan].set_type(ct_percent_bipolar_stereo); + fxdata->p[in_audio_input_pan].posy_offset = 1; + + fxdata->p[in_audio_input_level].set_name("Level"); + fxdata->p[in_audio_input_level].set_type(ct_decibel_attenuation_large); + fxdata->p[in_audio_input_level].posy_offset = 1; + + // ----- Effect Input + fxdata->p[in_effect_input_channel].set_name("Channel"); + fxdata->p[in_effect_input_channel].set_type(ct_percent_bipolar_stereo); + fxdata->p[in_effect_input_channel].posy_offset = 3; + + fxdata->p[in_effect_input_pan].set_name("Pan"); + fxdata->p[in_effect_input_pan].set_type(ct_percent_bipolar_stereo); + fxdata->p[in_effect_input_pan].posy_offset = 3; + + fxdata->p[in_effect_input_level].set_name("Level"); + fxdata->p[in_effect_input_level].set_type(ct_decibel_attenuation_large); + fxdata->p[in_effect_input_level].posy_offset = 3; + + // ----- Scene Input + effect_slot_type slot_type = getSlotType(fxdata->fxslot); + if (slot_type == a_insert_slot || slot_type == b_insert_slot) + { + fxdata->p[in_scene_input_channel].set_name("Channel"); + fxdata->p[in_scene_input_channel].set_type(ct_percent_bipolar_stereo); + fxdata->p[in_scene_input_channel].posy_offset = 5; + + fxdata->p[in_scene_input_pan].set_name("Pan"); + fxdata->p[in_scene_input_pan].set_type(ct_percent_bipolar_stereo); + fxdata->p[in_scene_input_pan].posy_offset = 5; + + fxdata->p[in_scene_input_level].set_name("Level"); + fxdata->p[in_scene_input_level].set_type(ct_decibel_attenuation_large); + fxdata->p[in_scene_input_level].posy_offset = 5; + } + + // ----- Output + fxdata->p[in_output_width].set_name("Width"); + fxdata->p[in_output_width].set_type(ct_percent_bipolar_stereo); + fxdata->p[in_output_width].posy_offset = 7; + + fxdata->p[in_output_mix].set_name("Mix"); + fxdata->p[in_output_mix].set_type(ct_percent); + fxdata->p[in_output_mix].posy_offset = 7; +} + +void AudioInputEffect::init_default_values() +{ + fxdata->p[in_audio_input_channel].val.f = 0.0; // p0 + fxdata->p[in_audio_input_pan].val.f = 0.0; // p1 + fxdata->p[in_audio_input_level].val.f = -96.0; // p2 + + fxdata->p[in_effect_input_channel].val.f = 0.0; // p3 + fxdata->p[in_effect_input_pan].val.f = 0.0; // p4 + fxdata->p[in_effect_input_level].val.f = 0.0; // p5 + + fxdata->p[in_scene_input_channel].val.f = 0.0; // p6 + fxdata->p[in_scene_input_pan].val.f = 0.0; // p7 + fxdata->p[in_scene_input_level].val.f = -96.0; // p8 + + fxdata->p[in_output_width].val.f = 1.0; // p9 + fxdata->p[in_output_mix].val.f = 1.0; // p10 +} +const char *AudioInputEffect::group_label(int id) +{ + std::vector group_labels = {{"Audio Input", "Effect Input", "Scene Input", "Output"}}; + effect_slot_type slot_type = getSlotType(fxdata->fxslot); + if (slot_type == a_insert_slot) + group_labels[2] = "Scene B Input"; + else if (slot_type == b_insert_slot) + group_labels[2] = "Scene A Input"; + else + group_labels.erase(group_labels.begin() + 2); + if (id >= 0 && id < group_labels.size()) + return group_labels[id]; + return 0; +} + +int AudioInputEffect::group_label_ypos(int id) +{ + std::vector ypos = {1, 9, 17, 25}; + effect_slot_type slot_type = getSlotType(fxdata->fxslot); + if (slot_type != a_insert_slot && slot_type != b_insert_slot) + ypos.erase(ypos.begin() + 2); + if (id >= 0 && id < ypos.size()) + return ypos[id]; + return 0; +} +AudioInputEffect::effect_slot_type AudioInputEffect::getSlotType(fxslot_positions p) +{ + switch (p) + { + case fxslot_ains1: + case fxslot_ains2: + case fxslot_ains3: + case fxslot_ains4: + return AudioInputEffect::a_insert_slot; + case fxslot_bins1: + case fxslot_bins2: + case fxslot_bins3: + case fxslot_bins4: + return AudioInputEffect::b_insert_slot; + case fxslot_send1: + case fxslot_send2: + case fxslot_send3: + case fxslot_send4: + return AudioInputEffect::send_slot; + default: + return AudioInputEffect::global_slot; + } +} + +void AudioInputEffect::process(float *dataL, float *dataR) +{ + + float &effectInputChannel = fxdata->p[in_effect_input_channel].val.f; + float &effectInputPan = fxdata->p[in_effect_input_pan].val.f; + float &effectInputLevelDb = fxdata->p[in_effect_input_level].val.f; + float *drySignal[] = {dataL, dataR}; + + float effectDataBuffer[2][BLOCK_SIZE]; + std::memcpy(effectDataBuffer[0], dataL, BLOCK_SIZE * sizeof(float)); + std::memcpy(effectDataBuffer[1], dataR, BLOCK_SIZE * sizeof(float)); + float *effectDataBuffers[]{effectDataBuffer[0], effectDataBuffer[1]}; + applySlidersControls(effectDataBuffers, effectInputChannel, effectInputPan, effectInputLevelDb); + + float &inputChannel = fxdata->p[in_audio_input_channel].val.f; + float &inputPan = fxdata->p[in_audio_input_pan].val.f; + float &inputLevelDb = fxdata->p[in_audio_input_level].val.f; + float *inputData[] = {storage->audio_in_nonOS[0], storage->audio_in_nonOS[1]}; + float inputDataBuffer[2][BLOCK_SIZE]; + + std::memcpy(inputDataBuffer[0], inputData[0], BLOCK_SIZE * sizeof(float)); + std::memcpy(inputDataBuffer[1], inputData[1], BLOCK_SIZE * sizeof(float)); + float *inputDataBuffers[] = {inputDataBuffer[0], inputDataBuffer[1]}; + + applySlidersControls(inputDataBuffers, inputChannel, inputPan, inputLevelDb); + + effect_slot_type slotType = getSlotType(fxdata->fxslot); + if (slotType == a_insert_slot || slotType == b_insert_slot) + { + float &sceneInputChannel = fxdata->p[in_scene_input_channel].val.f; + float &sceneInputPan = fxdata->p[in_scene_input_pan].val.f; + float &sceneInputLevelDb = fxdata->p[in_scene_input_level].val.f; + + float *sceneData[] = { + sceneDataPtr[0].get(), + sceneDataPtr[1].get(), + }; + float sceneDataBuffer[2][BLOCK_SIZE]; + std::memcpy(sceneDataBuffer[0], sceneData[0], BLOCK_SIZE * sizeof(float)); + std::memcpy(sceneDataBuffer[1], sceneData[1], BLOCK_SIZE * sizeof(float)); + float *sceneDataBuffers[]{sceneDataBuffer[0], sceneDataBuffer[1]}; + applySlidersControls(sceneDataBuffers, sceneInputChannel, sceneInputPan, sceneInputLevelDb); + // mixing the scene audio input and the effect audio input + for (int i = 0; i < BLOCK_SIZE; ++i) + { + effectDataBuffer[0][i] += sceneDataBuffer[0][i]; + effectDataBuffer[1][i] += sceneDataBuffer[1][i]; + } + } + // mixing the effect and audio input + for (int i = 0; i < BLOCK_SIZE; ++i) + { + effectDataBuffer[0][i] += inputDataBuffer[0][i]; + effectDataBuffer[1][i] += inputDataBuffer[1][i]; + } + + float &outputWidth = fxdata->p[in_output_width].val.f; + float &outputMix = fxdata->p[in_output_mix].val.f; + + float *dryL = drySignal[0]; + float *dryR = drySignal[1]; + float *wetL = effectDataBuffer[0]; + float *wetR = effectDataBuffer[1]; + + // Adjust width of wet signal + for (int i = 0; i < BLOCK_SIZE; ++i) + { + float mid = 0.5f * (wetL[i] + wetR[i]); // Mid (mono) signal + float side = outputWidth * 0.5f * (wetL[i] - wetR[i]); // Sides (stereo) signal + wetL[i] = mid + side; + wetR[i] = mid - side; + } + + // Mix dry and wet signal according to outputMix + for (int i = 0; i < BLOCK_SIZE; ++i) + { + float wetMix = outputMix; + float dryMix = 1.0f - outputMix; + dryL[i] = dryMix * dryL[i] + wetMix * wetL[i]; + dryR[i] = dryMix * dryR[i] + wetMix * wetR[i]; + } +} + +void AudioInputEffect::applySlidersControls(float *buffer[], const float &channel, const float &pan, + const float &levelDb) +{ + float leftGain, rightGain; + + if (channel < 0) + { + leftGain = 1.0f; + rightGain = 1.0f + channel; + } + else + { + leftGain = 1.0f - channel; + rightGain = 1.0f; + } + + for (int i = 0; i < BLOCK_SIZE; ++i) + { + buffer[0][i] *= leftGain; + buffer[1][i] *= rightGain; + } + + float tempBuffer[2][BLOCK_SIZE]; + std::memcpy(tempBuffer[0], buffer[0], BLOCK_SIZE * sizeof(float)); + std::memcpy(tempBuffer[1], buffer[1], BLOCK_SIZE * sizeof(float)); + + float leftToLeft = (pan < 0) ? 1.0f : 1.0f - pan; + float rightToRight = (pan > 0) ? 1.0f : 1.0f + pan; + + for (int i = 0; i < BLOCK_SIZE; ++i) + { + buffer[0][i] = buffer[0][i] * leftToLeft + tempBuffer[1][i] * (1.0f - rightToRight); + buffer[1][i] = buffer[1][i] * rightToRight + tempBuffer[0][i] * (1.0f - leftToLeft); + } + + float effectInputLevelGain = std::pow(10.0f, levelDb / 20.0f); + for (int i = 0; i < BLOCK_SIZE; ++i) + { + buffer[0][i] *= effectInputLevelGain; + buffer[1][i] *= effectInputLevelGain; + } +} \ No newline at end of file diff --git a/src/common/dsp/effects/AudioInputEffect.h b/src/common/dsp/effects/AudioInputEffect.h new file mode 100644 index 00000000000..00638b545b8 --- /dev/null +++ b/src/common/dsp/effects/AudioInputEffect.h @@ -0,0 +1,59 @@ +/* +** 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-2023 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. +*/ +#pragma once +#include "Effect.h" +class AudioInputEffect : public Effect +{ + public: + enum effect_slot_type + { + a_insert_slot, + b_insert_slot, + send_slot, + global_slot + }; + enum in_params + { + in_audio_input_channel = 0, + in_audio_input_pan, + in_audio_input_level, + + in_effect_input_channel, + in_effect_input_pan, + in_effect_input_level, + + in_scene_input_channel, + in_scene_input_pan, + in_scene_input_level, + + in_output_width, + in_output_mix, + + in_num_params + }; + AudioInputEffect(SurgeStorage *storage, FxStorage *fxdata, pdata *pd); + ~AudioInputEffect() = default; + void init_ctrltypes() override; + void init_default_values() override; + void process(float *dataL, float *dataR) override; + const char *group_label(int id) override; + int group_label_ypos(int id) override; + + private: + std::shared_ptr sceneDataPtr[N_OUTPUTS]{nullptr, nullptr}; + effect_slot_type getSlotType(fxslot_positions p); + void applySlidersControls(float *buffer[], const float &channel, const float &pan, + const float &levelDb); +}; diff --git a/src/surge-fx/SurgeFXEditor.cpp b/src/surge-fx/SurgeFXEditor.cpp index b781a3a6543..04ad0890036 100644 --- a/src/surge-fx/SurgeFXEditor.cpp +++ b/src/surge-fx/SurgeFXEditor.cpp @@ -25,6 +25,7 @@ #include "FxPresetAndClipboardManager.h" #include "tinyxml/tinyxml.h" #include "fmt/core.h" +#include "SurgeStorage.h" struct Picker : public juce::Component { @@ -470,6 +471,11 @@ void SurgefxAudioProcessorEditor::makeMenu() auto t = -1; c->QueryIntAttribute("i", &t); men.fxtype = t; + if (t == fxt_audio_input) // skip the "Audio In" effect + { + c = c->NextSiblingElement(); + continue; + } } else { diff --git a/src/surge-testrunner/CMakeLists.txt b/src/surge-testrunner/CMakeLists.txt index 3f1dbe38da8..1b45836bfb3 100644 --- a/src/surge-testrunner/CMakeLists.txt +++ b/src/surge-testrunner/CMakeLists.txt @@ -35,4 +35,10 @@ target_link_libraries(${PROJECT_NAME} PRIVATE surge-lua-src surge::catch2 surge::surge-common + juce::juce_audio_basics ) + +target_compile_definitions(${PROJECT_NAME} PUBLIC + JUCE_WEB_BROWSER=0 + JUCE_USE_CURL=0 + ) diff --git a/src/surge-testrunner/UnitTestUtilities.cpp b/src/surge-testrunner/UnitTestUtilities.cpp index 2e2b8b11993..65c1b8aa11e 100644 --- a/src/surge-testrunner/UnitTestUtilities.cpp +++ b/src/surge-testrunner/UnitTestUtilities.cpp @@ -272,6 +272,16 @@ void makePlotPNGFromData(std::string pngFileName, std::string plotTitle, float * << std::endl; #endif } +void setFX(std::shared_ptr surge, int slot, fx_type type) +{ + auto *pt = &(surge->storage.getPatch().fx[slot].type); + auto awv = 1.f * type / (pt->val_max.i - pt->val_min.i); + + auto did = surge->idForParameter(pt); + surge->setParameter01(did, awv, false); + for (int i = 0; i < 10; ++i) + surge->process(); +} } // namespace Test } // namespace Surge diff --git a/src/surge-testrunner/UnitTestUtilities.h b/src/surge-testrunner/UnitTestUtilities.h index e47939ff88c..70c80e52810 100644 --- a/src/surge-testrunner/UnitTestUtilities.h +++ b/src/surge-testrunner/UnitTestUtilities.h @@ -59,6 +59,8 @@ void setupStorageRanges(Parameter *start, Parameter *endIncluding, int &storage_ void makePlotPNGFromData(std::string pngFileName, std::string plotTitle, float *buffer, int nS, int nC, int startSample = -1, int endSample = -1); +void setFX(std::shared_ptr surge, int slot, fx_type type); + std::shared_ptr surgeOnPatch(const std::string &patchName); std::shared_ptr surgeOnTemplate(const std::string &, float sr = 44100); std::shared_ptr surgeOnSine(float sr = 44100); diff --git a/src/surge-testrunner/UnitTestsFX.cpp b/src/surge-testrunner/UnitTestsFX.cpp index 49b12a38d84..05b77c8617c 100644 --- a/src/surge-testrunner/UnitTestsFX.cpp +++ b/src/surge-testrunner/UnitTestsFX.cpp @@ -31,6 +31,7 @@ #include "catch2/catch2.hpp" #include "UnitTestUtilities.h" +#include "AudioInputEffect.h" using namespace Surge::Test; @@ -117,16 +118,6 @@ TEST_CASE("Airwindows Loud", "[fx]") TEST_CASE("FX Move with Modulation", "[fx]") { - auto setFX = [](auto surge, auto slot, auto type) { - auto *pt = &(surge->storage.getPatch().fx[slot].type); - auto awv = 1.f * fxt_chorus4 / (pt->val_max.i - pt->val_min.i); - - auto did = surge->idForParameter(pt); - surge->setParameter01(did, awv, false); - - for (int i = 0; i < 10; ++i) - surge->process(); - }; auto step = [](auto surge) { for (int i = 0; i < 10; ++i) surge->process(); @@ -167,7 +158,7 @@ TEST_CASE("FX Move with Modulation", "[fx]") REQUIRE(surge); step(surge); - setFX(surge, 0, fxt_combulator); + Surge::Test::setFX(surge, 0, fxt_combulator); surge->setModDepth01(surge->storage.getPatch().fx[0].p[2].id, ms_slfo1, 0, 0, 0.1); REQUIRE(surge->storage.getPatch().modulation_global.size() == 1); @@ -186,8 +177,8 @@ TEST_CASE("FX Move with Modulation", "[fx]") REQUIRE(surge); step(surge); - setFX(surge, 0, fxt_combulator); - setFX(surge, 4, fxt_chorus4); + Surge::Test::setFX(surge, 0, fxt_combulator); + Surge::Test::setFX(surge, 4, fxt_chorus4); surge->setModDepth01(surge->storage.getPatch().fx[0].p[2].id, ms_slfo1, 0, 0, 0.1); surge->setModDepth01(surge->storage.getPatch().fx[0].p[3].id, ms_slfo2, 0, 0, 0.2); @@ -208,7 +199,7 @@ TEST_CASE("FX Move with Modulation", "[fx]") REQUIRE(surge); step(surge); - setFX(surge, 0, fxt_combulator); + Surge::Test::setFX(surge, 0, fxt_combulator); surge->setModDepth01(surge->storage.getPatch().fx[0].p[2].id, ms_slfo1, 0, 0, 0.1); REQUIRE(surge->storage.getPatch().modulation_global.size() == 1); @@ -227,8 +218,8 @@ TEST_CASE("FX Move with Modulation", "[fx]") REQUIRE(surge); step(surge); - setFX(surge, 0, fxt_combulator); - setFX(surge, 1, fxt_chorus4); + Surge::Test::setFX(surge, 0, fxt_combulator); + Surge::Test::setFX(surge, 1, fxt_chorus4); surge->setModDepth01(surge->storage.getPatch().fx[0].p[2].id, ms_slfo1, 0, 0, 0.1); surge->setModDepth01(surge->storage.getPatch().fx[1].p[3].id, ms_slfo2, 0, 0, 0.1); @@ -248,8 +239,8 @@ TEST_CASE("FX Move with Modulation", "[fx]") REQUIRE(surge); step(surge); - setFX(surge, 0, fxt_combulator); - setFX(surge, 1, fxt_chorus4); + Surge::Test::setFX(surge, 0, fxt_combulator); + Surge::Test::setFX(surge, 1, fxt_chorus4); surge->setModDepth01(surge->storage.getPatch().fx[0].p[2].id, ms_slfo1, 0, 0, 0.1); surge->setModDepth01(surge->storage.getPatch().fx[1].p[3].id, ms_slfo2, 0, 0, 0.1); @@ -269,8 +260,8 @@ TEST_CASE("FX Move with Modulation", "[fx]") REQUIRE(surge); step(surge); - setFX(surge, 0, fxt_combulator); - setFX(surge, 1, fxt_chorus4); + Surge::Test::setFX(surge, 0, fxt_combulator); + Surge::Test::setFX(surge, 1, fxt_chorus4); surge->setModDepth01(surge->storage.getPatch().fx[0].p[2].id, ms_slfo1, 0, 0, 0.1); surge->setModDepth01(surge->storage.getPatch().fx[1].p[3].id, ms_slfo2, 0, 0, 0.1); @@ -422,3 +413,665 @@ TEST_CASE("High SampleRate Nimbus", "[fx]") } } } + +TEST_CASE("ScenesOutputData", "[fx]") +{ + SECTION("Providing data") + { + Surge::Storage::ScenesOutputData scenesOutputData{}; + REQUIRE(!scenesOutputData.thereAreClients(0)); + REQUIRE(!scenesOutputData.thereAreClients(1)); + + { + auto clientDataLeft = scenesOutputData.getSceneData(0, 0); + auto clientDataRight = scenesOutputData.getSceneData(0, 1); + REQUIRE(clientDataLeft); + REQUIRE(clientDataRight); + REQUIRE(scenesOutputData.thereAreClients(0)); + REQUIRE(!scenesOutputData.thereAreClients(1)); + + float data[32]{1.0f}; + scenesOutputData.provideSceneData(0, 0, data); + scenesOutputData.provideSceneData(0, 1, data); + REQUIRE(scenesOutputData.getSceneData(0, 0)[0] == 1.0f); + REQUIRE(scenesOutputData.getSceneData(0, 1)[0] == 1.0f); + + scenesOutputData.provideSceneData(1, 0, data); + scenesOutputData.provideSceneData(1, 1, data); + REQUIRE(scenesOutputData.getSceneData(1, 0)[0] == 0.0f); + REQUIRE(scenesOutputData.getSceneData(1, 1)[0] == 0.0f); + } + REQUIRE(!scenesOutputData.thereAreClients(0)); + REQUIRE(!scenesOutputData.thereAreClients(1)); + } +} + +void testExpectedValues(std::shared_ptr surge, int slot, float *leftInput, + float *rightInput, float *expectedLeftOutput, float *expectedRightOutput) +{ + surge->fx[slot]->process(leftInput, rightInput); + for (int i = 0; i < 4; ++i) + { + REQUIRE(leftInput[i] == Approx(expectedLeftOutput[i]).margin(0.001)); + REQUIRE(rightInput[i] == Approx(expectedRightOutput[i]).margin(0.001)); + } +} + +struct ControlParam +{ + int param; + float value; +}; +struct ExpectedOutput +{ + float expectedLeftOutput[BLOCK_SIZE]; + float expectedRightOutput[BLOCK_SIZE]; +}; + +struct SubTestCase +{ + std::string name; + std::vector controlParams; + ExpectedOutput expectedOutput; +}; + +struct InParamsGroup +{ + int slot; + + std::string testGroup; + float leftEffectInput alignas(16)[BLOCK_SIZE]; + float rightEffectInput alignas(16)[BLOCK_SIZE]; + + float sceneALeftInput alignas(16)[BLOCK_SIZE]; + float sceneARightInput alignas(16)[BLOCK_SIZE]; + + float sceneBLeftInput alignas(16)[BLOCK_SIZE]; + float sceneBRightInput alignas(16)[BLOCK_SIZE]; + + float audioLeftInput alignas(16)[BLOCK_SIZE]; + float audioRightInput alignas(16)[BLOCK_SIZE]; + + std::vector expectedOutput; + + void fillWithData(SurgeStorage *surgeStorage) + { + surgeStorage->scenesOutputData.provideSceneData(0, 0, sceneALeftInput); + surgeStorage->scenesOutputData.provideSceneData(0, 1, sceneARightInput); + surgeStorage->scenesOutputData.provideSceneData(1, 0, sceneBLeftInput); + surgeStorage->scenesOutputData.provideSceneData(1, 1, sceneBRightInput); + memcpy(surgeStorage->audio_in_nonOS[0], audioLeftInput, BLOCK_SIZE * sizeof(float)); + memcpy(surgeStorage->audio_in_nonOS[1], audioRightInput, BLOCK_SIZE * sizeof(float)); + } +}; + +TEST_CASE("AudioInputEffect", "[fx]") +{ + + std::map> slots{ + {AudioInputEffect::a_insert_slot, {fxslot_ains1, fxslot_ains2, fxslot_ains3, fxslot_ains4}}, + {AudioInputEffect::b_insert_slot, {fxslot_bins1, fxslot_bins2, fxslot_bins3, fxslot_bins4}}, + {AudioInputEffect::send_slot, {fxslot_send1, fxslot_send2, fxslot_send3, fxslot_send4}}, + {AudioInputEffect::global_slot, + {fxslot_global1, fxslot_global2, fxslot_global3, fxslot_global4}}, + }; + + std::vector inParamsGroups{ + { + AudioInputEffect::a_insert_slot, + "A Insert", + {0.1f, 0.1f, 0.1f, 0.1f}, // leftEffectInput + {0.05f, 0.05f, 0.05f, 0.05f}, // rightEffectInput (half of leftEffectInput) + {0.2f, 0.2f, 0.2f, 0.2f}, // sceneALeftInput + {0.1f, 0.1f, 0.1f, 0.1f}, // sceneARightInput (half of sceneALeftInput) + {0.1f, 0.1f, 0.1f, 0.1f}, // sceneBLeftInput + {0.05f, 0.05f, 0.05f, 0.05f}, // sceneBRightInput (half of sceneBLeftInput) + {0.1f, 0.1f, 0.1f, 0.1f}, // audioLeftInput + {0.05f, 0.05f, 0.05f, 0.05f}, // audioRightInput (half of audioLeftInput) + { + {"Stereo Output", + {{AudioInputEffect::in_output_mix, 1.0f}, + {AudioInputEffect::in_output_width, 1.0f}}, + { + // ExpectedOutput + {0.3f, 0.3f, 0.3f, + 0.3f}, // expectedLeftOutput (sum of + // leftEffectInput, sceneBLeftInput, and audioLeftInput) + {0.15f, 0.15f, 0.15f, 0.15f}, // expectedRightOutput (sum of + // rightEffectInput, + // sceneBRightInput, and audioRightInput) + }}, + {"Switching left and right", + {{AudioInputEffect::in_output_mix, 1.0f}, + {AudioInputEffect::in_output_width, -1.0f}}, + { + // ExpectedOutput + {0.15f, 0.15f, 0.15f, 0.15f}, // expectedRightOutput (sum of + // rightEffectInput, + // sceneBRightInput, and audioRightInput) + {0.3f, 0.3f, 0.3f, + 0.3f}, // expectedLeftOutput (sum of + // leftEffectInput, sceneBLeftInput, and audioLeftInput) + + }}, + {"Mono Output", + { + {AudioInputEffect::in_output_mix, 1.0f}, + {AudioInputEffect::in_output_width, 0.0f} // mono + }, + { + // ExpectedOutput + {0.225f, 0.225f, 0.225f, + 0.225f}, // expectedLeftOutput (sum of + // leftEffectInput, sceneBLeftInput, and audioLeftInput) + {0.225f, 0.225f, 0.225f, 0.225f}, // expectedRightOutput (sum of + // rightEffectInput, + // sceneBRightInput, and audioRightInput) + }}, + {"Leaving only dry signal, with width = 1", + { + {AudioInputEffect::in_output_mix, 0.0f}, + {AudioInputEffect::in_output_width, 1.0f} // stereo + }, + { + // ExpectedOutput stays attached + {0.1f, 0.1f, 0.1f, 0.1f}, // + {0.05f, 0.05f, 0.05f, 0.05f}, + } + + }, + {"Leaving only dry signal, with width = 0", + { + {AudioInputEffect::in_output_mix, 0.0f}, + {AudioInputEffect::in_output_width, 0.0f} // stereo + }, + { + // ExpectedOutput stays attached + {0.1f, 0.1f, 0.1f, 0.1f}, // + {0.05f, 0.05f, 0.05f, 0.05f}, + } + + }, + }, + + }, + { + AudioInputEffect::b_insert_slot, + "B Insert", + {0.1f, 0.1f, 0.1f, 0.1f}, // leftEffectInput + {0.05f, 0.05f, 0.05f, 0.05f}, // rightEffectInput (half of leftEffectInput) + {0.2f, 0.2f, 0.2f, 0.2f}, // sceneALeftInput + {0.1f, 0.1f, 0.1f, 0.1f}, // sceneARightInput (half of sceneALeftInput) + {0.1f, 0.1f, 0.1f, 0.1f}, // sceneBLeftInput + {0.05f, 0.05f, 0.05f, 0.05f}, // sceneBRightInput (half of sceneBLeftInput) + {0.1f, 0.1f, 0.1f, 0.1f}, // audioLeftInput + {0.05f, 0.05f, 0.05f, 0.05f}, // audioRightInput (half of audioLeftInput) + {{"Stereo Output", + {{AudioInputEffect::in_output_mix, 1.0f}, {AudioInputEffect::in_output_width, 1.0f}}, + { + {0.4f, 0.4f, 0.4f, 0.4f}, // expectedLeftOutput (sum of leftEffectInput, + // sceneALeftInput, and audioLeftInput) + {0.2f, 0.2f, 0.2f, 0.2f}, // expectedRightOutput (sum of rightEffectInput, + // sceneARightInput, and audioRightInput) + }}}, + }, + + { + AudioInputEffect::send_slot, + "Send", + {0.1f, 0.1f, 0.1f, 0.1f}, // leftEffectInput + {0.05f, 0.05f, 0.05f, 0.05f}, // rightEffectInput (half of leftEffectInput) + {0.2f, 0.2f, 0.2f, 0.2f}, // sceneALeftInput + {0.1f, 0.1f, 0.1f, 0.1f}, // sceneARightInput (half of sceneALeftInput) + {0.1f, 0.1f, 0.1f, 0.1f}, // sceneBLeftInput + {0.05f, 0.05f, 0.05f, 0.05f}, // sceneBRightInput (half of sceneBLeftInput) + {0.1f, 0.1f, 0.1f, 0.1f}, // audioLeftInput + {0.05f, 0.05f, 0.05f, 0.05f}, // audioRightInput (half of audioLeftInput) + {{"Stereo Output", + {{AudioInputEffect::in_output_mix, 1.0f}, {AudioInputEffect::in_output_width, 1.0f}}, + { + // ExpectedOutput + {0.2f, 0.2f, 0.2f, + 0.2f}, // expectedLeftOutput (sum of leftEffectInput and audioLeftInput) + {0.1f, 0.1f, 0.1f, 0.1f}, // expectedRightOutput (sum of rightEffectInput and + // audioRightInput) + }}}, + }, + { + AudioInputEffect::global_slot, + "Global", + {0.1f, 0.1f, 0.1f, 0.1f}, // leftEffectInput + {0.05f, 0.05f, 0.05f, 0.05f}, // rightEffectInput (half of leftEffectInput) + {0.2f, 0.2f, 0.2f, 0.2f}, // sceneALeftInput + {0.1f, 0.1f, 0.1f, 0.1f}, // sceneARightInput (half of sceneALeftInput) + {0.1f, 0.1f, 0.1f, 0.1f}, // sceneBLeftInput + {0.05f, 0.05f, 0.05f, 0.05f}, // sceneBRightInput (half of sceneBLeftInput) + {0.1f, 0.1f, 0.1f, 0.1f}, // audioLeftInput + {0.05f, 0.05f, 0.05f, 0.05f}, // audioRightInput (half of audioLeftInput) + {{"Stereo Output", + {{AudioInputEffect::in_output_mix, 1.0f}, {AudioInputEffect::in_output_width, 1.0f}}, + { + // ExpectedOutput + {0.2f, 0.2f, 0.2f, + 0.2f}, // expectedLeftOutput (sum of leftEffectInput and audioLeftInput) + {0.1f, 0.1f, 0.1f, 0.1f}, // expectedRightOutput (sum of rightEffectInput and + // audioRightInput) + }}}, + }, + + }; + std::vector panningTestCases = { + { + AudioInputEffect::a_insert_slot, + "Applying Panning to an audio input with the default params", + {0.0f}, // leftEffectInput + {0.0f}, // rightEffectInput + {}, + {}, // sceneALeftInput and sceneARight Input + { + 0.4f, + 0.2f, + 0.4f, + 0.2f, + }, // sceneBLeftInput + { + 0.2f, + 0.4f, + 0.2f, + 0.4f, + }, // sceneBLeftInput + {}, + {}, // audioLeftInput and audioRightInput + {{"the result should be unchanged", + { + {AudioInputEffect::in_scene_input_channel, 0.0f}, + {AudioInputEffect::in_scene_input_level, 0.0f}, + {AudioInputEffect::in_scene_input_pan, 0.0f}, + }, + { + // ExpectedOutput + {0.4f, 0.2f, 0.4f, + 0.2f}, // expectedLeftOutput (sum of leftEffectInput and audioLeftInput) + {0.2f, 0.4f, 0.2f, + 0.4f}, // expectedRightOutput (sum of rightEffectInput and audioRightInput) + }}}, + }, + { + AudioInputEffect::a_insert_slot, + "Applying Panning to an audio input with in_scene_input_channel = -1", + {0.0f}, // leftEffectInput + {0.0f}, // rightEffectInput + {}, + {}, // sceneALeftInput and sceneARight Input + { + 0.4f, + 0.2f, + 0.4f, + 0.2f, + }, // sceneBLeftInput + { + 0.2f, + 0.4f, + 0.2f, + 0.4f, + }, // sceneBLeftInput + {}, + {}, // audioLeftInput and audioRightInput + {{" it should only accept left channel", + { + {AudioInputEffect::in_scene_input_channel, -1.0f}, + {AudioInputEffect::in_scene_input_level, 0.0f}, + {AudioInputEffect::in_scene_input_pan, 0.0f}, + }, + { + // ExpectedOutput + {0.4f, 0.2f, 0.4f, 0.2f}, // expectedLeftOutput + {0.0f, 0.0f, 0.0f, 0.0f}, // expectedRightOutput + }}}, + }, + { + AudioInputEffect::a_insert_slot, + "Applying Panning to an audio input with in_scene_input_channel = 0.25", + {0.0f}, // leftEffectInput + {0.0f}, // rightEffectInput + {}, + {}, // sceneALeftInput and sceneARight Input + { + 0.4f, + 0.2f, + 0.4f, + 0.2f, + }, // sceneBLeftInput + { + 0.2f, + 0.4f, + 0.2f, + 0.4f, + }, // sceneBLeftInput + {}, + {}, // audioLeftInput and audioRightInput + {{"accepts 50% of left channel and 100% right", + { + {AudioInputEffect::in_scene_input_channel, 0.25f}, + {AudioInputEffect::in_scene_input_level, 0.0f}, + {AudioInputEffect::in_scene_input_pan, 0.0f}, + }, + { + // ExpectedOutput + {0.3f, 0.15f, 0.3f, 0.15f}, // expectedLeftOutput + {0.2f, 0.4f, 0.2f, 0.4f}, // expectedRightOutput + }}}, + }, + { + AudioInputEffect::a_insert_slot, + "Applying Panning to an audio input with in_scene_input_channel = -0.50", + {0.0f}, // leftEffectInput + {0.0f}, // rightEffectInput + {}, + {}, // sceneALeftInput and sceneARight Input + { + 0.4f, + 0.2f, + 0.4f, + 0.2f, + }, // sceneBLeftInput + { + 0.2f, + 0.4f, + 0.2f, + 0.4f, + }, // sceneBLeftInput + {}, + {}, // audioLeftInput and audioRightInput + {{" it should accept 100% of left channel and 50% right", + { + {AudioInputEffect::in_scene_input_channel, -0.50f}, + {AudioInputEffect::in_scene_input_level, 0.0f}, + {AudioInputEffect::in_scene_input_pan, 0.0f}, + }, + { + // ExpectedOutput + {0.4f, 0.2f, 0.4f, 0.2f}, // expectedLeftOutput + {0.1f, 0.2f, 0.1f, 0.2f}, // expectedRightOutput + }}}, + }, + { + AudioInputEffect::a_insert_slot, + "Applying Panning to an audio input with in_scene_input_channel = -0.50 and input " + "level = -5.995", + {0.0f}, // leftEffectInput + {0.0f}, // rightEffectInput + {}, + {}, // sceneALeftInput and sceneARight Input + { + 0.4f, + 0.2f, + 0.4f, + 0.2f, + }, // sceneBLeftInput + { + 0.2f, + 0.4f, + 0.2f, + 0.4f, + }, // sceneBLeftInput + {}, + {}, // audioLeftInput and audioRightInput + {{" it should accept 100% of left channel and 50% right with 50% input level", + { + {AudioInputEffect::in_scene_input_channel, -0.50f}, + {AudioInputEffect::in_scene_input_level, -5.995f}, + {AudioInputEffect::in_scene_input_pan, 0.0f}, + }, + { + // ExpectedOutput + {0.2f, 0.1f, 0.2f, 0.1f}, // expectedLeftOutput + {0.05f, 0.1f, 0.05f, 0.1f}, // expectedRightOutput + }}}, + }, + { + AudioInputEffect::a_insert_slot, + "Applying Panning to an audio input with in_scene_input_pan = -1.0", + {0.0f}, // leftEffectInput + {0.0f}, // rightEffectInput + {}, + {}, // sceneALeftInput and sceneARight Input + { + 0.4f, + 0.2f, + 0.4f, + 0.2f, + }, // sceneBLeftInput + { + 0.2f, + 0.4f, + 0.2f, + 0.4f, + }, // sceneBRightInput + {}, + {}, // audioLeftInput and audioRightInput + {{" channels should move to the left", + { + {AudioInputEffect::in_scene_input_channel, 0.0f}, + {AudioInputEffect::in_scene_input_level, 0.0f}, + {AudioInputEffect::in_scene_input_pan, -1.0f}, + }, + { + // ExpectedOutput + {0.6f, 0.6f, 0.6f, 0.6f}, // expectedLeftOutput + {0.0f, 0.0f, 0.0f, 0.0f}, // expectedRightOutput + }}}, + }, + { + AudioInputEffect::a_insert_slot, + "Applying Panning to an audio input with in_scene_input_pan = 1.0", + {0.0f}, // leftEffectInput + {0.0f}, // rightEffectInput + {}, + {}, // sceneALeftInput and sceneARight Input + { + 0.4f, + 0.2f, + 0.4f, + 0.2f, + }, // sceneBLeftInput + { + 0.2f, + 0.4f, + 0.2f, + 0.4f, + }, // sceneBRightInput + {}, + {}, // audioLeftInput and audioRightInput + {{" channels should move to the right", + { + {AudioInputEffect::in_scene_input_channel, 0.0f}, + {AudioInputEffect::in_scene_input_level, 0.0f}, + {AudioInputEffect::in_scene_input_pan, 1.0f}, + }, + { + // ExpectedOutput + {0.0f, 0.0f, 0.0f, 0.0f}, // expectedLeftOutput + {0.6f, 0.6f, 0.6f, 0.6f}, // expectedRightOutput + }}}, + }, + { + AudioInputEffect::a_insert_slot, + "Applying Panning to an audio input with in_scene_input_pan = 0.5", + {0.0f}, // leftEffectInput + {0.0f}, // rightEffectInput + {}, + {}, // sceneALeftInput and sceneARight Input + { + 0.4f, + 0.2f, + 0.4f, + 0.2f, + }, // sceneBLeftInput + { + 0.2f, + 0.4f, + 0.2f, + 0.4f, + }, // sceneBRightInput + {}, + {}, // audioLeftInput and audioRightInput + {{" channels should move to the right by 50%", + { + {AudioInputEffect::in_scene_input_channel, 0.0f}, + {AudioInputEffect::in_scene_input_level, 0.0f}, + {AudioInputEffect::in_scene_input_pan, 0.5f}, + }, + { + // ExpectedOutput + {0.2f, 0.1f, 0.2f, 0.1f}, // expectedLeftOutput + {0.4f, 0.5f, 0.4f, 0.5f}, // expectedRightOutput + }}}, + }, + { + AudioInputEffect::a_insert_slot, + "Applying Panning to an audio input with in_scene_input_channel = -1 and " + "in_scene_input_pan = 1.0", + {0.0f}, // leftEffectInput + {0.0f}, // rightEffectInput + {}, + {}, // sceneALeftInput and sceneARightInput + { + 0.4f, + 0.2f, + 0.4f, + 0.2f, + }, // sceneBLeftInput + { + 0.2f, + 0.4f, + 0.2f, + 0.4f, + }, // sceneBRightInput + {}, + {}, // audioLeftInput and audioRightInput + {{" left channels should move to the right and the right channel should be deleted", + { + {AudioInputEffect::in_scene_input_channel, -1.0f}, + {AudioInputEffect::in_scene_input_level, 0.0f}, + {AudioInputEffect::in_scene_input_pan, 1.0f}, + }, + { + // ExpectedOutput + {0.0f, 0.0f, 0.0f, 0.0f}, // expectedLeftOutput + {0.4f, 0.2f, 0.4f, 0.2f}, // expectedRightOutput + }}}, + }, + + }; + for (InParamsGroup &panningTestCase : panningTestCases) + { + /// ==================== Test with audio input from scene B ==================== + std::string testGroup = panningTestCase.testGroup; + panningTestCase.testGroup = testGroup + " (audio input from scene B)"; + inParamsGroups.push_back(panningTestCase); + + /// ==================== Test with audio input from scene A ==================== + panningTestCase.slot = AudioInputEffect::b_insert_slot; + memcpy(panningTestCase.sceneALeftInput, panningTestCase.sceneBLeftInput, + BLOCK_SIZE * sizeof(float)); + memcpy(panningTestCase.sceneARightInput, panningTestCase.sceneBRightInput, + BLOCK_SIZE * sizeof(float)); + panningTestCase.testGroup = testGroup + " (audio input from scene A)"; + inParamsGroups.push_back(panningTestCase); + + /// ==================== Test with audio input from a mic ==================== + panningTestCase.testGroup = testGroup + " (audio input is from a mic)"; + panningTestCase.slot = AudioInputEffect::a_insert_slot; + memcpy(panningTestCase.audioLeftInput, panningTestCase.sceneBLeftInput, + BLOCK_SIZE * sizeof(float)); + memcpy(panningTestCase.audioRightInput, panningTestCase.sceneBRightInput, + BLOCK_SIZE * sizeof(float)); + panningTestCase.expectedOutput[0].controlParams[0].param = + AudioInputEffect::in_audio_input_channel; + panningTestCase.expectedOutput[0].controlParams[1].param = + AudioInputEffect::in_audio_input_level; + panningTestCase.expectedOutput[0].controlParams[2].param = + AudioInputEffect::in_audio_input_pan; + float zeros[BLOCK_SIZE]{0.0f}; + memcpy(panningTestCase.sceneALeftInput, zeros, BLOCK_SIZE * sizeof(float)); + memcpy(panningTestCase.sceneARightInput, zeros, BLOCK_SIZE * sizeof(float)); + memcpy(panningTestCase.sceneBLeftInput, zeros, BLOCK_SIZE * sizeof(float)); + memcpy(panningTestCase.sceneBRightInput, zeros, BLOCK_SIZE * sizeof(float)); + inParamsGroups.push_back(panningTestCase); + + /// ==================== Test with audio effect input ==================== + panningTestCase.testGroup = testGroup + " (audio input is from an audio effect)"; + panningTestCase.slot = AudioInputEffect::a_insert_slot; + memcpy(panningTestCase.leftEffectInput, panningTestCase.audioLeftInput, + BLOCK_SIZE * sizeof(float)); + memcpy(panningTestCase.rightEffectInput, panningTestCase.audioRightInput, + BLOCK_SIZE * sizeof(float)); + panningTestCase.expectedOutput[0].controlParams[0].param = + AudioInputEffect::in_effect_input_channel; + panningTestCase.expectedOutput[0].controlParams[1].param = + AudioInputEffect::in_effect_input_level; + panningTestCase.expectedOutput[0].controlParams[2].param = + AudioInputEffect::in_effect_input_pan; + memcpy(panningTestCase.audioLeftInput, zeros, BLOCK_SIZE * sizeof(float)); + memcpy(panningTestCase.audioRightInput, zeros, BLOCK_SIZE * sizeof(float)); + + inParamsGroups.push_back(panningTestCase); + } + + for (InParamsGroup &inParamsGroup : inParamsGroups) + { + SECTION(inParamsGroup.testGroup) + { + for (int slot : slots[inParamsGroup.slot]) + { + + auto surge = Surge::Headless::createSurge(44100); + REQUIRE(surge); + + Surge::Test::setFX(surge, slot, fxt_audio_input); + SurgeStorage *surgeStorage = &surge->storage; + FxStorage *fxStorage = &surgeStorage->getPatch().fx[slot]; + + fxStorage->p[AudioInputEffect::in_audio_input_channel].val.f = 0.0f; + fxStorage->p[AudioInputEffect::in_audio_input_level].val.f = 0.0f; + fxStorage->p[AudioInputEffect::in_audio_input_pan].val.f = 0.0f; + + fxStorage->p[AudioInputEffect::in_scene_input_channel].val.f = 0.0f; + fxStorage->p[AudioInputEffect::in_scene_input_level].val.f = 0.0f; + fxStorage->p[AudioInputEffect::in_scene_input_pan].val.f = 0.0f; + + fxStorage->p[AudioInputEffect::in_effect_input_channel].val.f = 0.0f; + fxStorage->p[AudioInputEffect::in_effect_input_level].val.f = 0.0f; + fxStorage->p[AudioInputEffect::in_effect_input_pan].val.f = 0.0f; + + fxStorage->p[AudioInputEffect::in_output_width].val.f = 1.0f; + fxStorage->p[AudioInputEffect::in_output_mix].val.f = 1.0f; + REQUIRE(fxStorage->type.val.i == fxt_audio_input); + + for (SubTestCase &subTestCase : inParamsGroup.expectedOutput) + { + SECTION(inParamsGroup.testGroup + ", slot " + std::to_string(slot) + ", " + + subTestCase.name) + { + ExpectedOutput &expectedOutput = subTestCase.expectedOutput; + for (ControlParam &controlParam : subTestCase.controlParams) + { + fxStorage->p[controlParam.param].val.f = controlParam.value; + } + inParamsGroup.fillWithData(surgeStorage); + surge->fx[slot]->process(inParamsGroup.leftEffectInput, + inParamsGroup.rightEffectInput); + for (int i = 0; i < 4; ++i) + { + REQUIRE(inParamsGroup.leftEffectInput[i] == + Approx(expectedOutput.expectedLeftOutput[i]).margin(0.001)); + REQUIRE(expectedOutput.expectedRightOutput[i] == + Approx(expectedOutput.expectedRightOutput[i]).margin(0.001)); + } + } + } + } + } + } +}