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));
+ }
+ }
+ }
+ }
+ }
+ }
+}