diff --git a/.gitignore b/.gitignore index 6303c464..6d2b104b 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,8 @@ sdcard # Editor related files *.swp *.swo + +.vscode/ +src/test/bin/ +src/test/objects/ +src/test/results/ \ No newline at end of file diff --git a/src/CFileDevice.hpp b/src/CFileDevice.hpp new file mode 100644 index 00000000..c5958096 --- /dev/null +++ b/src/CFileDevice.hpp @@ -0,0 +1,78 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// CFileDevice.h +// +// Device implementation that writes into a file. +// This device is a singleton dedicated to allow the Circle CLogger class to output log event into the file instead of the internal buffer. +// Author: Vincent Gauché +// + +#pragma once + +#include "extra_features.h" + +#include +#include +#include +#include +#include +#include + +class CFileDevice : public CDevice +{ + DISALLOW_COPY_AND_ASSIGN(CFileDevice); + +private: + CFileDevice() : + m_file(new FIL()) + { + FRESULT res = f_open(this->m_file, "SD:/debuglog.txt", FA_WRITE | FA_CREATE_ALWAYS); + assert(res == FR_OK); + } + +public: + static void UseMeForLogger() + { + CLogger::Get()->SetNewTarget(&CFileDevice::GetInstance()); + } + + static CFileDevice& GetInstance() + { + static CFileDevice Instance; + + return Instance; + } + + ~CFileDevice() + { + f_sync(this->m_file); + f_close(this->m_file); + } + + virtual int Write(const void* pBuffer, size_t nCount) override + { + UINT nb = 0; + if(nCount > 0) + { + f_write(this->m_file, pBuffer, nCount, &nb); + f_sync(this->m_file); + } + + return (int)nb; + } + +private: + FIL* m_file; +}; diff --git a/src/Makefile b/src/Makefile index 540ae684..b9f244b8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -6,10 +6,38 @@ CIRCLE_STDLIB_DIR = ../circle-stdlib SYNTH_DEXED_DIR = ../Synth_Dexed/src CMSIS_DIR = ../CMSIS_5/CMSIS -OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ - mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ - sysexfileloader.o performanceconfig.o perftimer.o \ - effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o +OBJS := main.o +OBJS += kernel.o +OBJS += minidexed.o +OBJS += config.o +OBJS += userinterface.o +OBJS += uimenu.o +OBJS += mididevice.o +OBJS += midikeyboard.o +OBJS += serialmididevice.o +OBJS += pckeyboard.o +OBJS += sysexfileloader.o +OBJS += performanceconfig.o +OBJS += perftimer.o +OBJS += effect_compressor.o +OBJS += effect_platervbstereo.o +OBJS += fx.o +OBJS += fx_components.o +OBJS += fx_svf.o +OBJS += fx_tube.o +OBJS += fx_chorus.o +OBJS += fx_flanger.o +OBJS += fx_orbitone.o +OBJS += fx_phaser.o +OBJS += fx_delay.o +OBJS += fx_shimmer_helper.o +OBJS += fx_diffuser.o +OBJS += fx_pitch_shifter.o +OBJS += fx_reverberator.o +OBJS += fx_shimmer_reverb.o +OBJS += fx_dry.o +OBJS += uibuttons.o +OBJS += midipin.o OPTIMIZE = -O3 diff --git a/src/debug.hpp b/src/debug.hpp new file mode 100644 index 00000000..24a3adca --- /dev/null +++ b/src/debug.hpp @@ -0,0 +1,117 @@ +#pragma once + +#if defined(DEBUG) + +#include +#include +#include +#include + +typedef size_t (*ValueInpector)(const std::string& tag, const float32_t valueToTest, const float32_t _min, float32_t _max, bool outDebugInfo); + +inline size_t nanValueInspector(const std::string& tag, const float32_t valueToTest, const float32_t _min, float32_t _max, bool outDebugInfo) +{ + if(std::isnan(valueToTest)) + { + if(outDebugInfo) + { + std::cerr << tag << ": nan" << std::endl; + } + return 1u; + } + + return 0u; +} + +inline size_t normalizedValueInspector(const std::string& tag, const float32_t valueToTest, const float32_t _min, float32_t _max, bool outDebugInfo) +{ + if(valueToTest > _max || valueToTest < _min) + { + if(outDebugInfo) + { + std::cerr << tag << ": out of bounds - value: " << valueToTest << " - boundaries: [" << _min << ", " << _max << "]" << std::endl; + } + return 1u; + } + + return 0u; +} + +inline size_t fullInspector(const std::string& tag, const float32_t valueToTest, const float32_t _min = -1.0f, float32_t _max = 1.0f, bool outDebugInfo = true) +{ + if(std::isnan(valueToTest)) + { + if(outDebugInfo) + { + std::cerr << tag << ": nan" << std::endl; + } + return 1u; + } + else if(valueToTest > _max || valueToTest < _min) + { + if(outDebugInfo) + { + std::cerr << tag << ": out of bounds - value: " << valueToTest << " - boundaries: [" << _min << ", " << _max << "]" << std::endl; + } + return 1u; + } + + return 0u; +} + +#define SS_RESET(ss, prec, align) ss.str(""); ss.precision(prec); ss << align; ss << std::fixed +#define SS_SPACE(ss, spc, nb, align, sep) ss.fill(spc); ss.width(nb); ss << "" << sep +#define SS__TEXT(ss, spc, nb, align, sep, txt) ss << align; ss.fill(spc); ss.width(nb); ss << txt << sep + +class ValueInpectorDebugger +{ +public: + ValueInpectorDebugger() {} + virtual ~ValueInpectorDebugger() {} + + virtual void dump(std::ostream& out, bool deepInspection = true, const std::string& tag = "") const = 0; + virtual size_t inspect(ValueInpector inspector, bool deepInspection = true, const std::string& tag = "") const = 0; +}; + +#define INSPECTABLE(clazz) clazz : public ValueInpectorDebugger +#define IMPLEMENT_DUMP(code) \ +public:\ + __attribute__((noinline)) virtual void dump(std::ostream& out, bool deepInspection = true, const std::string& tag = "") const override\ + {\ + code\ + } + +#define DUMP(clazz, out) clazz->dump(out, true, "") +#define DUMP2(clazz, out, tag) clazz->dump(out, true, tag) +#define FAST_DUMP(clazz, out, tag) clazz->dump(out, false, "") +#define FAST_DUMP2(clazz, out, tag) clazz->dump(out, false, tag) + +#define IMPLEMENT_INSPECT(code) \ +public:\ + __attribute__((noinline)) virtual size_t inspect(ValueInpector inspector, bool deepInspection = true, const std::string& tag = "") const override\ + {\ + code\ + } + +#define INSPECT(obj, inspector) obj->inspect(inspector, true) +#define INSPECT2(obj, inspector, deepInspection) obj->inspect(inspector, deepInspection) +#define FULL_INSPECT(obj, deepInspection) obj->inspect(fullInspector, deepInspection) +#define FULL_INSPECT2(obj, deepInspection, tag) obj->inspect(fullInspector, deepInspection, tag) + +#else + +#define INSPECTABLE(clazz) clazz +#define IMPLEMENT_DUMP(code) +#define IMPLEMENT_INSPECT(code) + +#define DUMP(clazz, out) +#define DUMP2(clazz, out, tag) +#define FAST_DUMP(clazz, out, tag) +#define FAST_DUMP2(clazz, out, tag) + +#define INSPECT(obj, inspector) +#define INSPECT2(obj, inspector, deepInspection) +#define FULL_INSPECT(obj, deepInspection) +#define FULL_INSPECT2(obj, deepInspection, tag) + +#endif \ No newline at end of file diff --git a/src/effect_mixer.hpp b/src/effect_mixer.hpp index 44184ab5..88dac62f 100644 --- a/src/effect_mixer.hpp +++ b/src/effect_mixer.hpp @@ -33,15 +33,16 @@ template class AudioMixer delete [] sumbufL; } - void doAddMix(uint8_t channel, float32_t* in) + void doAddMix(uint8_t channel, float32_t* in) { - float32_t tmp[buffer_length]; - assert(in); - if(multiplier[channel]!=UNITY_GAIN) + float32_t tmp[buffer_length]; + bool isScaled = (multiplier[channel]!=UNITY_GAIN); + + if(isScaled) arm_scale_f32(in,multiplier[channel],tmp,buffer_length); - arm_add_f32(sumbufL, tmp, sumbufL, buffer_length); + arm_add_f32(sumbufL, (isScaled ? tmp : in), sumbufL, buffer_length); } void gain(uint8_t channel, float32_t gain) @@ -119,37 +120,36 @@ template class AudioStereoMixer : public AudioMixer void doAddMix(uint8_t channel, float32_t* in) { - float32_t tmp[buffer_length]; - assert(in); + float32_t tmp[buffer_length]; + // left - arm_scale_f32(in, panorama[channel][0], tmp, buffer_length); - if(multiplier[channel]!=UNITY_GAIN) - arm_scale_f32(tmp,multiplier[channel],tmp,buffer_length); + arm_scale_f32(in, panorama[channel][0] * multiplier[channel], tmp, buffer_length); arm_add_f32(sumbufL, tmp, sumbufL, buffer_length); + // right - arm_scale_f32(in, panorama[channel][1], tmp, buffer_length); - if(multiplier[channel]!=UNITY_GAIN) - arm_scale_f32(tmp,multiplier[channel],tmp,buffer_length); + arm_scale_f32(in, panorama[channel][1] * multiplier[channel], tmp, buffer_length); arm_add_f32(sumbufR, tmp, sumbufR, buffer_length); } void doAddMix(uint8_t channel, float32_t* inL, float32_t* inR) { - float32_t tmp[buffer_length]; - assert(inL); assert(inR); + float32_t tmp[buffer_length]; + bool isScaled = (multiplier[channel]!=UNITY_GAIN); + // left - if(multiplier[channel]!=UNITY_GAIN) + if(isScaled) arm_scale_f32(inL,multiplier[channel],tmp,buffer_length); - arm_add_f32(sumbufL, tmp, sumbufL, buffer_length); + arm_add_f32(sumbufL, (isScaled ? tmp : inL), sumbufL, buffer_length); + // right - if(multiplier[channel]!=UNITY_GAIN) + if(isScaled) arm_scale_f32(inR,multiplier[channel],tmp,buffer_length); - arm_add_f32(sumbufR, tmp, sumbufR, buffer_length); + arm_add_f32(sumbufR, (isScaled ? tmp : inR), sumbufR, buffer_length); } void getMix(float32_t* bufferL, float32_t* bufferR) diff --git a/src/effect_platervbstereo.cpp b/src/effect_platervbstereo.cpp index ce2af1e4..7aa046a6 100644 --- a/src/effect_platervbstereo.cpp +++ b/src/effect_platervbstereo.cpp @@ -83,11 +83,11 @@ const int16_t AudioWaveformSine[257] = { -4808, -4011, -3212, -2410, -1608, -804, 0 }; -AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate) +AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate) : + FXElement(samplerate, 2.54f), + input_attn(0.5f), + in_allp_k(INP_ALLP_COEFF) { - input_attn = 0.5f; - in_allp_k = INP_ALLP_COEFF; - memset(in_allp1_bufL, 0, sizeof(in_allp1_bufL)); memset(in_allp2_bufL, 0, sizeof(in_allp2_bufL)); memset(in_allp3_bufL, 0, sizeof(in_allp3_bufL)); @@ -97,6 +97,8 @@ AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate) in_allp3_idxL = 0; in_allp4_idxL = 0; + in_allp_out_L = 0.0f; + memset(in_allp1_bufR, 0, sizeof(in_allp1_bufR)); memset(in_allp2_bufR, 0, sizeof(in_allp2_bufR)); memset(in_allp3_bufR, 0, sizeof(in_allp3_bufR)); @@ -148,6 +150,9 @@ AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate) master_lowpass_l = 0.0f; master_lowpass_r = 0.0f; + rv_time_k = 0.0f; + rv_time_scaler = 0.0f; + lfo1_phase_acc = 0; lfo1_adder = (UINT32_MAX + 1)/(samplerate * LFO1_FREQ_HZ); lfo2_phase_acc = 0; @@ -156,9 +161,33 @@ AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate) reverb_level = 0.0f; } +AudioEffectPlateReverb::~AudioEffectPlateReverb() +{ +} + // #define sat16(n, rshift) signed_saturate_rshift((n), 16, (rshift)) -void AudioEffectPlateReverb::doReverb(const float32_t* inblockL, const float32_t* inblockR, float32_t* rvbblockL, float32_t* rvbblockR, uint16_t len) +void AudioEffectPlateReverb::reset() +{ + memset(in_allp1_bufL, 0, sizeof(in_allp1_bufL)); + memset(in_allp2_bufL, 0, sizeof(in_allp2_bufL)); + memset(in_allp3_bufL, 0, sizeof(in_allp3_bufL)); + memset(in_allp4_bufL, 0, sizeof(in_allp4_bufL)); + memset(in_allp1_bufR, 0, sizeof(in_allp1_bufR)); + memset(in_allp2_bufR, 0, sizeof(in_allp2_bufR)); + memset(in_allp3_bufR, 0, sizeof(in_allp3_bufR)); + memset(in_allp4_bufR, 0, sizeof(in_allp4_bufR)); + memset(lp_allp1_buf, 0, sizeof(lp_allp1_buf)); + memset(lp_allp2_buf, 0, sizeof(lp_allp2_buf)); + memset(lp_allp3_buf, 0, sizeof(lp_allp3_buf)); + memset(lp_allp4_buf, 0, sizeof(lp_allp4_buf)); + memset(lp_dly1_buf, 0, sizeof(lp_dly1_buf)); + memset(lp_dly2_buf, 0, sizeof(lp_dly2_buf)); + memset(lp_dly3_buf, 0, sizeof(lp_dly3_buf)); + memset(lp_dly4_buf, 0, sizeof(lp_dly4_buf)); +} + +void AudioEffectPlateReverb::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) { float32_t input, acc, temp1, temp2; uint16_t temp16; @@ -169,6 +198,259 @@ void AudioEffectPlateReverb::doReverb(const float32_t* inblockL, const float32_t int32_t y0, y1; int64_t y; uint32_t idx; + + rv_time = rv_time_k; + + // do the LFOs + lfo1_phase_acc += lfo1_adder; + idx = lfo1_phase_acc >> 24; // 8bit lookup table address + y0 = AudioWaveformSine[idx]; + y1 = AudioWaveformSine[idx+1]; + idx = lfo1_phase_acc & 0x00FFFFFF; // lower 24 bit = fractional part + y = (int64_t)y0 * (0x00FFFFFF - idx); + y += (int64_t)y1 * idx; + lfo1_out_sin = (int32_t) (y >> (32-8)); // 16bit output + idx = ((lfo1_phase_acc >> 24)+64) & 0xFF; + y0 = AudioWaveformSine[idx]; + y1 = AudioWaveformSine[idx + 1]; + y = (int64_t)y0 * (0x00FFFFFF - idx); + y += (int64_t)y1 * idx; + lfo1_out_cos = (int32_t) (y >> (32-8)); // 16bit output + + lfo2_phase_acc += lfo2_adder; + idx = lfo2_phase_acc >> 24; // 8bit lookup table address + y0 = AudioWaveformSine[idx]; + y1 = AudioWaveformSine[idx+1]; + idx = lfo2_phase_acc & 0x00FFFFFF; // lower 24 bit = fractional part + y = (int64_t)y0 * (0x00FFFFFF - idx); + y += (int64_t)y1 * idx; + lfo2_out_sin = (int32_t) (y >> (32-8)); //32-8->output 16bit, + idx = ((lfo2_phase_acc >> 24)+64) & 0xFF; + y0 = AudioWaveformSine[idx]; + y1 = AudioWaveformSine[idx + 1]; + y = (int64_t)y0 * (0x00FFFFFF - idx); + y += (int64_t)y1 * idx; + lfo2_out_cos = (int32_t) (y >> (32-8)); // 16bit output + + input = inL * input_attn; + + // chained input allpasses, channel L + acc = in_allp1_bufL[in_allp1_idxL] + input * in_allp_k; + in_allp1_bufL[in_allp1_idxL] = input - in_allp_k * acc; + input = acc; + if (++in_allp1_idxL >= sizeof(in_allp1_bufL)/sizeof(float32_t)) in_allp1_idxL = 0; + + acc = in_allp2_bufL[in_allp2_idxL] + input * in_allp_k; + in_allp2_bufL[in_allp2_idxL] = input - in_allp_k * acc; + input = acc; + if (++in_allp2_idxL >= sizeof(in_allp2_bufL)/sizeof(float32_t)) in_allp2_idxL = 0; + + acc = in_allp3_bufL[in_allp3_idxL] + input * in_allp_k; + in_allp3_bufL[in_allp3_idxL] = input - in_allp_k * acc; + input = acc; + if (++in_allp3_idxL >= sizeof(in_allp3_bufL)/sizeof(float32_t)) in_allp3_idxL = 0; + + acc = in_allp4_bufL[in_allp4_idxL] + input * in_allp_k; + in_allp4_bufL[in_allp4_idxL] = input - in_allp_k * acc; + in_allp_out_L = acc; + if (++in_allp4_idxL >= sizeof(in_allp4_bufL)/sizeof(float32_t)) in_allp4_idxL = 0; + + input = inR * input_attn; + + // chained input allpasses, channel R + acc = in_allp1_bufR[in_allp1_idxR] + input * in_allp_k; + in_allp1_bufR[in_allp1_idxR] = input - in_allp_k * acc; + input = acc; + if (++in_allp1_idxR >= sizeof(in_allp1_bufR)/sizeof(float32_t)) in_allp1_idxR = 0; + + acc = in_allp2_bufR[in_allp2_idxR] + input * in_allp_k; + in_allp2_bufR[in_allp2_idxR] = input - in_allp_k * acc; + input = acc; + if (++in_allp2_idxR >= sizeof(in_allp2_bufR)/sizeof(float32_t)) in_allp2_idxR = 0; + + acc = in_allp3_bufR[in_allp3_idxR] + input * in_allp_k; + in_allp3_bufR[in_allp3_idxR] = input - in_allp_k * acc; + input = acc; + if (++in_allp3_idxR >= sizeof(in_allp3_bufR)/sizeof(float32_t)) in_allp3_idxR = 0; + + acc = in_allp4_bufR[in_allp4_idxR] + input * in_allp_k; + in_allp4_bufR[in_allp4_idxR] = input - in_allp_k * acc; + in_allp_out_R = acc; + if (++in_allp4_idxR >= sizeof(in_allp4_bufR)/sizeof(float32_t)) in_allp4_idxR = 0; + + // input allpases done, start loop allpases + input = lp_allp_out + in_allp_out_R; + acc = lp_allp1_buf[lp_allp1_idx] + input * loop_allp_k; // input is the lp allpass chain output + lp_allp1_buf[lp_allp1_idx] = input - loop_allp_k * acc; + input = acc; + if (++lp_allp1_idx >= sizeof(lp_allp1_buf)/sizeof(float32_t))lp_allp1_idx = 0; + + acc = lp_dly1_buf[lp_dly1_idx]; // read the end of the delay + lp_dly1_buf[lp_dly1_idx] = input; // write new sample + input = acc; + if (++lp_dly1_idx >= sizeof(lp_dly1_buf)/sizeof(float32_t)) lp_dly1_idx = 0; // update index + + // hi/lo shelving filter + temp1 = input - lpf1; + lpf1 += temp1 * lp_lowpass_f; + temp2 = input - lpf1; + temp1 = lpf1 - hpf1; + hpf1 += temp1 * lp_hipass_f; + acc = lpf1 + temp2*lp_hidamp_k + hpf1*lp_lodamp_k; + acc = acc * rv_time * rv_time_scaler; // scale by the reveb time + + input = acc + in_allp_out_L; + + acc = lp_allp2_buf[lp_allp2_idx] + input * loop_allp_k; + lp_allp2_buf[lp_allp2_idx] = input - loop_allp_k * acc; + input = acc; + if (++lp_allp2_idx >= sizeof(lp_allp2_buf)/sizeof(float32_t)) lp_allp2_idx = 0; + acc = lp_dly2_buf[lp_dly2_idx]; // read the end of the delay + lp_dly2_buf[lp_dly2_idx] = input; // write new sample + input = acc; + if (++lp_dly2_idx >= sizeof(lp_dly2_buf)/sizeof(float32_t)) lp_dly2_idx = 0; // update index + // hi/lo shelving filter + temp1 = input - lpf2; + lpf2 += temp1 * lp_lowpass_f; + temp2 = input - lpf2; + temp1 = lpf2 - hpf2; + hpf2 += temp1 * lp_hipass_f; + acc = lpf2 + temp2*lp_hidamp_k + hpf2*lp_lodamp_k; + acc = acc * rv_time * rv_time_scaler; + + input = acc + in_allp_out_R; + + acc = lp_allp3_buf[lp_allp3_idx] + input * loop_allp_k; + lp_allp3_buf[lp_allp3_idx] = input - loop_allp_k * acc; + input = acc; + if (++lp_allp3_idx >= sizeof(lp_allp3_buf)/sizeof(float32_t)) lp_allp3_idx = 0; + acc = lp_dly3_buf[lp_dly3_idx]; // read the end of the delay + lp_dly3_buf[lp_dly3_idx] = input; // write new sample + input = acc; + if (++lp_dly3_idx >= sizeof(lp_dly3_buf)/sizeof(float32_t)) lp_dly3_idx = 0; // update index + // hi/lo shelving filter + temp1 = input - lpf3; + lpf3 += temp1 * lp_lowpass_f; + temp2 = input - lpf3; + temp1 = lpf3 - hpf3; + hpf3 += temp1 * lp_hipass_f; + acc = lpf3 + temp2*lp_hidamp_k + hpf3*lp_lodamp_k; + acc = acc * rv_time * rv_time_scaler; + + input = acc + in_allp_out_L; + + acc = lp_allp4_buf[lp_allp4_idx] + input * loop_allp_k; + lp_allp4_buf[lp_allp4_idx] = input - loop_allp_k * acc; + input = acc; + if (++lp_allp4_idx >= sizeof(lp_allp4_buf)/sizeof(float32_t)) lp_allp4_idx = 0; + acc = lp_dly4_buf[lp_dly4_idx]; // read the end of the delay + lp_dly4_buf[lp_dly4_idx] = input; // write new sample + input = acc; + if (++lp_dly4_idx >= sizeof(lp_dly4_buf)/sizeof(float32_t)) lp_dly4_idx= 0; // update index + // hi/lo shelving filter + temp1 = input - lpf4; + lpf4 += temp1 * lp_lowpass_f; + temp2 = input - lpf4; + temp1 = lpf4 - hpf4; + hpf4 += temp1 * lp_hipass_f; + acc = lpf4 + temp2*lp_hidamp_k + hpf4*lp_lodamp_k; + acc = acc * rv_time * rv_time_scaler; + + lp_allp_out = acc; + + // channel L: +#ifdef TAP1_MODULATED + temp16 = (lp_dly1_idx + lp_dly1_offset_L + (lfo1_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); + temp1 = lp_dly1_buf[temp16++]; // sample now + if (temp16 >= sizeof(lp_dly1_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly1_buf[temp16]; // sample next + input = (float32_t)(lfo1_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc = (temp1*(1.0f-input) + temp2*input)* 0.8f; +#else + temp16 = (lp_dly1_idx + lp_dly1_offset_L) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); + acc = lp_dly1_buf[temp16]* 0.8f; +#endif + + +#ifdef TAP2_MODULATED + temp16 = (lp_dly2_idx + lp_dly2_offset_L + (lfo1_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); + temp1 = lp_dly2_buf[temp16++]; + if (temp16 >= sizeof(lp_dly2_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly2_buf[temp16]; + input = (float32_t)(lfo1_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc += (temp1*(1.0f-input) + temp2*input)* 0.7f; +#else + temp16 = (lp_dly2_idx + lp_dly2_offset_L) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); + acc += (temp1*(1.0f-input) + temp2*input)* 0.6f; +#endif + + temp16 = (lp_dly3_idx + lp_dly3_offset_L + (lfo2_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly3_buf)/sizeof(float32_t)); + temp1 = lp_dly3_buf[temp16++]; + if (temp16 >= sizeof(lp_dly3_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly3_buf[temp16]; + input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc += (temp1*(1.0f-input) + temp2*input)* 0.6f; + + temp16 = (lp_dly4_idx + lp_dly4_offset_L + (lfo2_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly4_buf)/sizeof(float32_t)); + temp1 = lp_dly4_buf[temp16++]; + if (temp16 >= sizeof(lp_dly4_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly4_buf[temp16]; + input = (float32_t)(lfo2_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc += (temp1*(1.0f-input) + temp2*input)* 0.5f; + + // Master lowpass filter + temp1 = acc - master_lowpass_l; + master_lowpass_l += temp1 * master_lowpass_f; + + outL = master_lowpass_l * this->OutputLevelCorrector; + + // Channel R +#ifdef TAP1_MODULATED + temp16 = (lp_dly1_idx + lp_dly1_offset_R + (lfo2_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); + temp1 = lp_dly1_buf[temp16++]; // sample now + if (temp16 >= sizeof(lp_dly1_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly1_buf[temp16]; // sample next + input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + + acc = (temp1*(1.0f-input) + temp2*input)* 0.8f; + #else + temp16 = (lp_dly1_idx + lp_dly1_offset_R) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); + acc = lp_dly1_buf[temp16] * 0.8f; + #endif +#ifdef TAP2_MODULATED + temp16 = (lp_dly2_idx + lp_dly2_offset_R + (lfo1_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); + temp1 = lp_dly2_buf[temp16++]; + if (temp16 >= sizeof(lp_dly2_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly2_buf[temp16]; + input = (float32_t)(lfo1_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc += (temp1*(1.0f-input) + temp2*input)* 0.7f; +#else + temp16 = (lp_dly2_idx + lp_dly2_offset_R) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); + acc += (temp1*(1.0f-input) + temp2*input)* 0.7f; +#endif + temp16 = (lp_dly3_idx + lp_dly3_offset_R + (lfo2_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly3_buf)/sizeof(float32_t)); + temp1 = lp_dly3_buf[temp16++]; + if (temp16 >= sizeof(lp_dly3_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly3_buf[temp16]; + input = (float32_t)(lfo2_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc += (temp1*(1.0f-input) + temp2*input)* 0.6f; + + temp16 = (lp_dly4_idx + lp_dly4_offset_R + (lfo1_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly4_buf)/sizeof(float32_t)); + temp1 = lp_dly4_buf[temp16++]; + if (temp16 >= sizeof(lp_dly4_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly4_buf[temp16]; + input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc += (temp1*(1.0f-input) + temp2*input)* 0.5f; + + // Master lowpass filter + temp1 = acc - master_lowpass_r; + master_lowpass_r += temp1 * master_lowpass_f; + + outR = master_lowpass_r * this->OutputLevelCorrector; +} + +void AudioEffectPlateReverb::doReverb(const float32_t* inblockL, const float32_t* inblockR, float32_t* rvbblockL, float32_t* rvbblockR, uint16_t len) +{ static bool cleanup_done = false; // handle bypass, 1st call will clean the buffers to avoid continuing the previous reverb tail @@ -176,23 +458,7 @@ void AudioEffectPlateReverb::doReverb(const float32_t* inblockL, const float32_t { if (!cleanup_done) { - memset(in_allp1_bufL, 0, sizeof(in_allp1_bufL)); - memset(in_allp2_bufL, 0, sizeof(in_allp2_bufL)); - memset(in_allp3_bufL, 0, sizeof(in_allp3_bufL)); - memset(in_allp4_bufL, 0, sizeof(in_allp4_bufL)); - memset(in_allp1_bufR, 0, sizeof(in_allp1_bufR)); - memset(in_allp2_bufR, 0, sizeof(in_allp2_bufR)); - memset(in_allp3_bufR, 0, sizeof(in_allp3_bufR)); - memset(in_allp4_bufR, 0, sizeof(in_allp4_bufR)); - memset(lp_allp1_buf, 0, sizeof(lp_allp1_buf)); - memset(lp_allp2_buf, 0, sizeof(lp_allp2_buf)); - memset(lp_allp3_buf, 0, sizeof(lp_allp3_buf)); - memset(lp_allp4_buf, 0, sizeof(lp_allp4_buf)); - memset(lp_dly1_buf, 0, sizeof(lp_dly1_buf)); - memset(lp_dly2_buf, 0, sizeof(lp_dly2_buf)); - memset(lp_dly3_buf, 0, sizeof(lp_dly3_buf)); - memset(lp_dly4_buf, 0, sizeof(lp_dly4_buf)); - + this->reset(); cleanup_done = true; } @@ -200,255 +466,8 @@ void AudioEffectPlateReverb::doReverb(const float32_t* inblockL, const float32_t } cleanup_done = false; - rv_time = rv_time_k; - for (uint16_t i=0; i < len; i++) { - // do the LFOs - lfo1_phase_acc += lfo1_adder; - idx = lfo1_phase_acc >> 24; // 8bit lookup table address - y0 = AudioWaveformSine[idx]; - y1 = AudioWaveformSine[idx+1]; - idx = lfo1_phase_acc & 0x00FFFFFF; // lower 24 bit = fractional part - y = (int64_t)y0 * (0x00FFFFFF - idx); - y += (int64_t)y1 * idx; - lfo1_out_sin = (int32_t) (y >> (32-8)); // 16bit output - idx = ((lfo1_phase_acc >> 24)+64) & 0xFF; - y0 = AudioWaveformSine[idx]; - y1 = AudioWaveformSine[idx + 1]; - y = (int64_t)y0 * (0x00FFFFFF - idx); - y += (int64_t)y1 * idx; - lfo1_out_cos = (int32_t) (y >> (32-8)); // 16bit output - - lfo2_phase_acc += lfo2_adder; - idx = lfo2_phase_acc >> 24; // 8bit lookup table address - y0 = AudioWaveformSine[idx]; - y1 = AudioWaveformSine[idx+1]; - idx = lfo2_phase_acc & 0x00FFFFFF; // lower 24 bit = fractional part - y = (int64_t)y0 * (0x00FFFFFF - idx); - y += (int64_t)y1 * idx; - lfo2_out_sin = (int32_t) (y >> (32-8)); //32-8->output 16bit, - idx = ((lfo2_phase_acc >> 24)+64) & 0xFF; - y0 = AudioWaveformSine[idx]; - y1 = AudioWaveformSine[idx + 1]; - y = (int64_t)y0 * (0x00FFFFFF - idx); - y += (int64_t)y1 * idx; - lfo2_out_cos = (int32_t) (y >> (32-8)); // 16bit output - - input = inblockL[i] * input_attn; - - // chained input allpasses, channel L - acc = in_allp1_bufL[in_allp1_idxL] + input * in_allp_k; - in_allp1_bufL[in_allp1_idxL] = input - in_allp_k * acc; - input = acc; - if (++in_allp1_idxL >= sizeof(in_allp1_bufL)/sizeof(float32_t)) in_allp1_idxL = 0; - - acc = in_allp2_bufL[in_allp2_idxL] + input * in_allp_k; - in_allp2_bufL[in_allp2_idxL] = input - in_allp_k * acc; - input = acc; - if (++in_allp2_idxL >= sizeof(in_allp2_bufL)/sizeof(float32_t)) in_allp2_idxL = 0; - - acc = in_allp3_bufL[in_allp3_idxL] + input * in_allp_k; - in_allp3_bufL[in_allp3_idxL] = input - in_allp_k * acc; - input = acc; - if (++in_allp3_idxL >= sizeof(in_allp3_bufL)/sizeof(float32_t)) in_allp3_idxL = 0; - - acc = in_allp4_bufL[in_allp4_idxL] + input * in_allp_k; - in_allp4_bufL[in_allp4_idxL] = input - in_allp_k * acc; - in_allp_out_L = acc; - if (++in_allp4_idxL >= sizeof(in_allp4_bufL)/sizeof(float32_t)) in_allp4_idxL = 0; - - input = inblockR[i] * input_attn; - - // chained input allpasses, channel R - acc = in_allp1_bufR[in_allp1_idxR] + input * in_allp_k; - in_allp1_bufR[in_allp1_idxR] = input - in_allp_k * acc; - input = acc; - if (++in_allp1_idxR >= sizeof(in_allp1_bufR)/sizeof(float32_t)) in_allp1_idxR = 0; - - acc = in_allp2_bufR[in_allp2_idxR] + input * in_allp_k; - in_allp2_bufR[in_allp2_idxR] = input - in_allp_k * acc; - input = acc; - if (++in_allp2_idxR >= sizeof(in_allp2_bufR)/sizeof(float32_t)) in_allp2_idxR = 0; - - acc = in_allp3_bufR[in_allp3_idxR] + input * in_allp_k; - in_allp3_bufR[in_allp3_idxR] = input - in_allp_k * acc; - input = acc; - if (++in_allp3_idxR >= sizeof(in_allp3_bufR)/sizeof(float32_t)) in_allp3_idxR = 0; - - acc = in_allp4_bufR[in_allp4_idxR] + input * in_allp_k; - in_allp4_bufR[in_allp4_idxR] = input - in_allp_k * acc; - in_allp_out_R = acc; - if (++in_allp4_idxR >= sizeof(in_allp4_bufR)/sizeof(float32_t)) in_allp4_idxR = 0; - - // input allpases done, start loop allpases - input = lp_allp_out + in_allp_out_R; - acc = lp_allp1_buf[lp_allp1_idx] + input * loop_allp_k; // input is the lp allpass chain output - lp_allp1_buf[lp_allp1_idx] = input - loop_allp_k * acc; - input = acc; - if (++lp_allp1_idx >= sizeof(lp_allp1_buf)/sizeof(float32_t)) lp_allp1_idx = 0; - - acc = lp_dly1_buf[lp_dly1_idx]; // read the end of the delay - lp_dly1_buf[lp_dly1_idx] = input; // write new sample - input = acc; - if (++lp_dly1_idx >= sizeof(lp_dly1_buf)/sizeof(float32_t)) lp_dly1_idx = 0; // update index - - // hi/lo shelving filter - temp1 = input - lpf1; - lpf1 += temp1 * lp_lowpass_f; - temp2 = input - lpf1; - temp1 = lpf1 - hpf1; - hpf1 += temp1 * lp_hipass_f; - acc = lpf1 + temp2*lp_hidamp_k + hpf1*lp_lodamp_k; - acc = acc * rv_time * rv_time_scaler; // scale by the reveb time - - input = acc + in_allp_out_L; - - acc = lp_allp2_buf[lp_allp2_idx] + input * loop_allp_k; - lp_allp2_buf[lp_allp2_idx] = input - loop_allp_k * acc; - input = acc; - if (++lp_allp2_idx >= sizeof(lp_allp2_buf)/sizeof(float32_t)) lp_allp2_idx = 0; - acc = lp_dly2_buf[lp_dly2_idx]; // read the end of the delay - lp_dly2_buf[lp_dly2_idx] = input; // write new sample - input = acc; - if (++lp_dly2_idx >= sizeof(lp_dly2_buf)/sizeof(float32_t)) lp_dly2_idx = 0; // update index - // hi/lo shelving filter - temp1 = input - lpf2; - lpf2 += temp1 * lp_lowpass_f; - temp2 = input - lpf2; - temp1 = lpf2 - hpf2; - hpf2 += temp1 * lp_hipass_f; - acc = lpf2 + temp2*lp_hidamp_k + hpf2*lp_lodamp_k; - acc = acc * rv_time * rv_time_scaler; - - input = acc + in_allp_out_R; - - acc = lp_allp3_buf[lp_allp3_idx] + input * loop_allp_k; - lp_allp3_buf[lp_allp3_idx] = input - loop_allp_k * acc; - input = acc; - if (++lp_allp3_idx >= sizeof(lp_allp3_buf)/sizeof(float32_t)) lp_allp3_idx = 0; - acc = lp_dly3_buf[lp_dly3_idx]; // read the end of the delay - lp_dly3_buf[lp_dly3_idx] = input; // write new sample - input = acc; - if (++lp_dly3_idx >= sizeof(lp_dly3_buf)/sizeof(float32_t)) lp_dly3_idx = 0; // update index - // hi/lo shelving filter - temp1 = input - lpf3; - lpf3 += temp1 * lp_lowpass_f; - temp2 = input - lpf3; - temp1 = lpf3 - hpf3; - hpf3 += temp1 * lp_hipass_f; - acc = lpf3 + temp2*lp_hidamp_k + hpf3*lp_lodamp_k; - acc = acc * rv_time * rv_time_scaler; - - input = acc + in_allp_out_L; - - acc = lp_allp4_buf[lp_allp4_idx] + input * loop_allp_k; - lp_allp4_buf[lp_allp4_idx] = input - loop_allp_k * acc; - input = acc; - if (++lp_allp4_idx >= sizeof(lp_allp4_buf)/sizeof(float32_t)) lp_allp4_idx = 0; - acc = lp_dly4_buf[lp_dly4_idx]; // read the end of the delay - lp_dly4_buf[lp_dly4_idx] = input; // write new sample - input = acc; - if (++lp_dly4_idx >= sizeof(lp_dly4_buf)/sizeof(float32_t)) lp_dly4_idx= 0; // update index - // hi/lo shelving filter - temp1 = input - lpf4; - lpf4 += temp1 * lp_lowpass_f; - temp2 = input - lpf4; - temp1 = lpf4 - hpf4; - hpf4 += temp1 * lp_hipass_f; - acc = lpf4 + temp2*lp_hidamp_k + hpf4*lp_lodamp_k; - acc = acc * rv_time * rv_time_scaler; - - lp_allp_out = acc; - - // channel L: -#ifdef TAP1_MODULATED - temp16 = (lp_dly1_idx + lp_dly1_offset_L + (lfo1_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); - temp1 = lp_dly1_buf[temp16++]; // sample now - if (temp16 >= sizeof(lp_dly1_buf)/sizeof(float32_t)) temp16 = 0; - temp2 = lp_dly1_buf[temp16]; // sample next - input = (float32_t)(lfo1_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k - acc = (temp1*(1.0f-input) + temp2*input)* 0.8f; -#else - temp16 = (lp_dly1_idx + lp_dly1_offset_L) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); - acc = lp_dly1_buf[temp16]* 0.8f; -#endif - - -#ifdef TAP2_MODULATED - temp16 = (lp_dly2_idx + lp_dly2_offset_L + (lfo1_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); - temp1 = lp_dly2_buf[temp16++]; - if (temp16 >= sizeof(lp_dly2_buf)/sizeof(float32_t)) temp16 = 0; - temp2 = lp_dly2_buf[temp16]; - input = (float32_t)(lfo1_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k - acc += (temp1*(1.0f-input) + temp2*input)* 0.7f; -#else - temp16 = (lp_dly2_idx + lp_dly2_offset_L) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); - acc += (temp1*(1.0f-input) + temp2*input)* 0.6f; -#endif - - temp16 = (lp_dly3_idx + lp_dly3_offset_L + (lfo2_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly3_buf)/sizeof(float32_t)); - temp1 = lp_dly3_buf[temp16++]; - if (temp16 >= sizeof(lp_dly3_buf)/sizeof(float32_t)) temp16 = 0; - temp2 = lp_dly3_buf[temp16]; - input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k - acc += (temp1*(1.0f-input) + temp2*input)* 0.6f; - - temp16 = (lp_dly4_idx + lp_dly4_offset_L + (lfo2_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly4_buf)/sizeof(float32_t)); - temp1 = lp_dly4_buf[temp16++]; - if (temp16 >= sizeof(lp_dly4_buf)/sizeof(float32_t)) temp16 = 0; - temp2 = lp_dly4_buf[temp16]; - input = (float32_t)(lfo2_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k - acc += (temp1*(1.0f-input) + temp2*input)* 0.5f; - - // Master lowpass filter - temp1 = acc - master_lowpass_l; - master_lowpass_l += temp1 * master_lowpass_f; - - rvbblockL[i] = master_lowpass_l; - - // Channel R - #ifdef TAP1_MODULATED - temp16 = (lp_dly1_idx + lp_dly1_offset_R + (lfo2_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); - temp1 = lp_dly1_buf[temp16++]; // sample now - if (temp16 >= sizeof(lp_dly1_buf)/sizeof(float32_t)) temp16 = 0; - temp2 = lp_dly1_buf[temp16]; // sample next - input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k - - acc = (temp1*(1.0f-input) + temp2*input)* 0.8f; - #else - temp16 = (lp_dly1_idx + lp_dly1_offset_R) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); - acc = lp_dly1_buf[temp16] * 0.8f; - #endif -#ifdef TAP2_MODULATED - temp16 = (lp_dly2_idx + lp_dly2_offset_R + (lfo1_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); - temp1 = lp_dly2_buf[temp16++]; - if (temp16 >= sizeof(lp_dly2_buf)/sizeof(float32_t)) temp16 = 0; - temp2 = lp_dly2_buf[temp16]; - input = (float32_t)(lfo1_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k - acc += (temp1*(1.0f-input) + temp2*input)* 0.7f; -#else - temp16 = (lp_dly2_idx + lp_dly2_offset_R) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); - acc += (temp1*(1.0f-input) + temp2*input)* 0.7f; -#endif - temp16 = (lp_dly3_idx + lp_dly3_offset_R + (lfo2_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly3_buf)/sizeof(float32_t)); - temp1 = lp_dly3_buf[temp16++]; - if (temp16 >= sizeof(lp_dly3_buf)/sizeof(float32_t)) temp16 = 0; - temp2 = lp_dly3_buf[temp16]; - input = (float32_t)(lfo2_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k - acc += (temp1*(1.0f-input) + temp2*input)* 0.6f; - - temp16 = (lp_dly4_idx + lp_dly4_offset_R + (lfo1_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly4_buf)/sizeof(float32_t)); - temp1 = lp_dly4_buf[temp16++]; - if (temp16 >= sizeof(lp_dly4_buf)/sizeof(float32_t)) temp16 = 0; - temp2 = lp_dly4_buf[temp16]; - input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k - acc += (temp1*(1.0f-input) + temp2*input)* 0.5f; - - // Master lowpass filter - temp1 = acc - master_lowpass_r; - master_lowpass_r += temp1 * master_lowpass_f; - - rvbblockR[i] = master_lowpass_r; + this->processSample(inblockL[i], inblockR[i], rvbblockL[i], rvbblockR[i]); } } diff --git a/src/effect_platervbstereo.h b/src/effect_platervbstereo.h index 23538c46..9d249266 100644 --- a/src/effect_platervbstereo.h +++ b/src/effect_platervbstereo.h @@ -44,9 +44,7 @@ #ifndef _EFFECT_PLATERVBSTEREO_H #define _EFFECT_PLATERVBSTEREO_H -#include -#include -#include "common.h" +#include "fx_components.h" /*** * Loop delay modulation: comment/uncomment to switch sin/cos @@ -56,11 +54,18 @@ //#define TAP1_MODULATED #define TAP2_MODULATED -class AudioEffectPlateReverb +class AudioEffectPlateReverb : public FXElement { + DISALLOW_COPY_AND_ASSIGN(AudioEffectPlateReverb); + public: AudioEffectPlateReverb(float32_t samplerate); - void doReverb(const float32_t* inblockL, const float32_t* inblockR, float32_t* rvbblockL, float32_t* rvbblockR,uint16_t len); + virtual ~AudioEffectPlateReverb(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void doReverb(const float32_t* inblockL, const float32_t* inblockR, float32_t* rvbblockL, float32_t* rvbblockR, uint16_t len); void size(float n) { @@ -191,6 +196,9 @@ class AudioEffectPlateReverb uint32_t lfo2_phase_acc; // LFO 2 uint32_t lfo2_adder; + + IMPLEMENT_DUMP() + IMPLEMENT_INSPECT(return 0u;) }; #endif // _EFFECT_PLATEREV_H diff --git a/src/extra_features.h b/src/extra_features.h new file mode 100644 index 00000000..f3ee5088 --- /dev/null +++ b/src/extra_features.h @@ -0,0 +1,75 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// extra_features.h +// +// Header file that centralizes MACROS to enable / disable extra features +// Author: Vincent Gauché +// +#pragma once + +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete + +#if defined(ARM_ALLOW_MULTI_CORE) + + #if RASPPI < 3 + #define PLATE_REVERB_ENABLE // Add support for the PlateReverb + #else + #define MIXING_CONSOLE_ENABLE // Add support for the MixingConsole + #endif + +#endif + +#ifdef DEBUG + +#include +#include +#include +#include +#include + +using namespace std; + +inline long long int getElapseTime(std::string marker = "") +{ + static std::unordered_map marker_times; + + auto current_time = std::chrono::high_resolution_clock::now(); + auto it = marker_times.find(marker); + if (it != marker_times.end()) + { + auto duration = std::chrono::duration_cast(current_time - it->second); + marker_times.erase(it); + return duration.count(); + } + else + { + marker_times[marker] = current_time; + return 0; + } +} + +#define LAP_TIME(marker) getElapseTime(marker) +#define LOG_LAP_TIME(marker) { auto __d = getElapseTime(marker); if(__d > 0) std::cout << "Execution time for " << marker << ": " << __d << std::endl; } + +#define DEBUG_VALUE(lbl, idx, v) std::cout << lbl << " " << idx << ": " << v << std::endl + +#else + +#define LAP_TIME(marker) +#define LOG_LAP_TIME(marker) + +#endif diff --git a/src/fx.cpp b/src/fx.cpp new file mode 100644 index 00000000..7652e9e4 --- /dev/null +++ b/src/fx.cpp @@ -0,0 +1,45 @@ +#include "fx.h" + +FXBase::FXBase(float32_t sampling_rate) : + SamplingRate(sampling_rate) +{ +} + +FXBase::~FXBase() +{ +} + +float32_t FXBase::getSamplingRate() const +{ + return this->SamplingRate; +} + +FXElement::FXElement(float32_t sampling_rate, float32_t output_level_corrector) : + FXBase(sampling_rate), + OutputLevelCorrector(output_level_corrector), + bypass_fx_process_(false) +{ +} + +FXElement::~FXElement() +{ +} + +void FXElement::bypassFXProcess(bool bypass) +{ + this->bypass_fx_process_ = bypass; +} + +bool FXElement::bypassFXProcess() const +{ + return this->bypass_fx_process_; +} + +FX::FX(float32_t sampling_rate) : + FXBase(sampling_rate) +{ +} + +FX::~FX() +{ +} diff --git a/src/fx.h b/src/fx.h new file mode 100644 index 00000000..27a0f50e --- /dev/null +++ b/src/fx.h @@ -0,0 +1,83 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx.h +// +// Base classes for Stereo audio effects proposed in the context of the MiniDexed project +// Author: Vincent Gauché +// +#pragma once + +#include +#include +#include "common.h" + +#include "debug.hpp" +#include "fx_base.h" + +#define MAKE_INTEGRAL_FRACTIONAL(x) \ + size_t x ## _integral = static_cast(x); \ + float32_t x ## _fractional = x - static_cast(x ## _integral) + +class INSPECTABLE(FXBase) +{ + DISALLOW_COPY_AND_ASSIGN(FXBase); + +protected: + FXBase(float32_t sampling_rate); + +public: + virtual ~FXBase(); + + float32_t getSamplingRate() const; + + virtual void reset() = 0; + +private: + const float32_t SamplingRate; +}; + +class FXElement : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(FXElement); + +protected: + FXElement(float32_t sampling_rate, float32_t output_level_corrector = 1.0f); + + const float32_t OutputLevelCorrector; + +public: + virtual ~FXElement(); + + virtual void bypassFXProcess(bool bypass); + virtual bool bypassFXProcess() const; + + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) = 0; + +private: + bool bypass_fx_process_; +}; + +class FX : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(FX); + +protected: + FX(float32_t sampling_rate); + +public: + virtual ~FX(); + + virtual void process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) = 0; +}; diff --git a/src/fx_base.h b/src/fx_base.h new file mode 100644 index 00000000..9ae019ba --- /dev/null +++ b/src/fx_base.h @@ -0,0 +1,30 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_base.h +// +// Base header file for the FX section of the MiniDexed project +// Author: Vincent Gauché +// + +#pragma once + +#include "extra_features.h" + +enum StereoChannels +{ + Left = 0, + Right, + kNumChannels +}; diff --git a/src/fx_chorus.cpp b/src/fx_chorus.cpp new file mode 100644 index 00000000..a9f66eaa --- /dev/null +++ b/src/fx_chorus.cpp @@ -0,0 +1,100 @@ +#include "fx_chorus.h" + +#include + +#define LFO1_MAX_FREQ 0.25f +#define LFO2_MAX_FREQ 0.35f + +Chorus::Chorus(float32_t sampling_rate) : + FXElement(sampling_rate, 1.1035f), + engine_(sampling_rate, 0.0f), + rate_(0.0f), + depth_(0.0f), + fullscale_depth_(0.0f), + feedback_(0.0f) +{ + this->lfo_[LFOIndex::Sin1] = new LFO(sampling_rate, 0.0f, LFO1_MAX_FREQ, 0.0f, false); + this->lfo_[LFOIndex::Cos1] = new LFO(sampling_rate, 0.0f, LFO1_MAX_FREQ, Constants::MPI_2, false); + this->lfo_[LFOIndex::Sin2] = new LFO(sampling_rate, 0.0f, LFO2_MAX_FREQ, 0.0f, false); + this->lfo_[LFOIndex::Cos2] = new LFO(sampling_rate, 0.0f, LFO2_MAX_FREQ, Constants::MPI_2, false); + + this->setRate(0.1f); + this->setDepth(0.15f); +} + +Chorus::~Chorus() +{ + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) + { + delete this->lfo_[i]; + } +} + +void Chorus::reset() +{ + this->engine_.reset(); + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) + { + this->lfo_[i]->reset(); + } +} + +void Chorus::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + typedef Engine::Reserve<2047> Memory; + static Engine::DelayLine line; + Engine::Context c; + + this->engine_.start(&c); + + // Update LFO. + float32_t sin_1 = this->lfo_[LFOIndex::Sin1]->process(); + float32_t cos_1 = this->lfo_[LFOIndex::Cos1]->process(); + float32_t sin_2 = this->lfo_[LFOIndex::Sin2]->process(); + float32_t cos_2 = this->lfo_[LFOIndex::Cos2]->process(); + + float32_t wet; + + // Sum L & R channel to send to chorus line. + c.read(inL + inR, 0.5f); + c.writeAndLoad(line, 0.0f); + + c.interpolate(line, sin_1 * this->fullscale_depth_ + 1200, 0.5f); + c.interpolate(line, sin_2 * this->fullscale_depth_ + 800, 0.5f); + c.writeAndLoad(wet, 0.0f); + outL = wet * this->OutputLevelCorrector; + + c.interpolate(line, cos_1 * this->fullscale_depth_ + 800, 0.5f); + c.interpolate(line, cos_2 * this->fullscale_depth_ + 1200, 0.5f); + c.writeAndLoad(wet, 0.0f); + outR = wet * this->OutputLevelCorrector; +} + +void Chorus::setRate(float32_t rate) +{ + this->rate_ = constrain(rate, 0.0f, 1.0f); + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) + { + this->lfo_[i]->setNormalizedFrequency(this->rate_); + } +} + +float32_t Chorus::getRate() const +{ + return this->depth_; +} + +void Chorus::setDepth(float32_t depth) +{ + depth = constrain(depth, 0.0f, 1.0f); + if(this->depth_ != depth) + { + this->depth_ = depth; + this->fullscale_depth_ = this->depth_ * CHORUS_FULLSCALE_DEPTH_RATIO; + } +} + +float32_t Chorus::getDepth() const +{ + return this->depth_; +} diff --git a/src/fx_chorus.h b/src/fx_chorus.h new file mode 100644 index 00000000..2186e645 --- /dev/null +++ b/src/fx_chorus.h @@ -0,0 +1,127 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_chorus.h +// +// Stereo Chorus audio effects proposed in the context of the MiniDexed project +// This implemelntation is based on the Chorus FX from the Rings Eurorack module from Mutable Instruments +// Ported by: Vincent Gauché +// +#pragma once + +#include "fx_components.h" +#include "fx_engine.hpp" + +#define CHORUS_FULLSCALE_DEPTH_RATIO 1536.0f + +class Chorus : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(Chorus); + +public: + enum LFOIndex + { + Sin1 = 0, + Sin2, + Cos1, + Cos2, + kLFOCount + }; + + Chorus(float32_t sampling_rate); + virtual ~Chorus(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setDepth(float32_t depth); + float32_t getDepth() const; + + void setRate(float32_t rate); + float32_t getRate() const; + +private: + typedef FxEngine<2048, Format::FORMAT_FLOAT32, false> Engine; + Engine engine_; + + float32_t rate_; // Normalized frequency for the 2 LFOs frequencies (0.0 - 10.0) + float32_t depth_; // Depth of the chorus in milliseconds (0.0 - 10.0) + float32_t fullscale_depth_; // Equivalent to depth_ but in the range of (0.0 - 384.0) + float32_t feedback_; // Feedback level of the chorus (0.0 - 1.0) + + LFO* lfo_[LFOIndex::kLFOCount]; + + IMPLEMENT_DUMP( + const size_t space = 16; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + if(deepInspection) + { + this->engine_.dump(out, deepInspection, tag + ".engine_"); + } + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "rate_"); + SS__TEXT(ss, ' ', space, std::left, '|', "depth_"); + SS__TEXT(ss, ' ', space, std::left, '|', "fullscale_depth_"); + SS__TEXT(ss, ' ', space, std::left, '|', "feedback_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->rate_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->depth_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->fullscale_depth_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->feedback_); + out << "\t" << ss.str() << std::endl; + + if(deepInspection) + { + for(size_t i = 0; i < LFOIndex::kLFOCount; ++i) + { + this->lfo_[i]->dump(out, deepInspection, tag + ".lfo_[ " + std::to_string(i) + " ]"); + } + } + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + if(deepInspection) + { + nb_errors += this->engine_.inspect(inspector, deepInspection, tag + ".engine_"); + for(size_t i = 0; i < LFOIndex::kLFOCount; ++i) + { + nb_errors += this->lfo_[i]->inspect(inspector, deepInspection, tag + ".lfo_[ " + std::to_string(i) + " ]"); + } + } + nb_errors += inspector(tag + ".rate_", this->rate_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".depth_", this->depth_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".fullscale_depth_", this->fullscale_depth_, 0.0f, CHORUS_FULLSCALE_DEPTH_RATIO, deepInspection); + + return nb_errors; + ) +}; diff --git a/src/fx_components.cpp b/src/fx_components.cpp new file mode 100644 index 00000000..04ba3b96 --- /dev/null +++ b/src/fx_components.cpp @@ -0,0 +1,825 @@ +#include "fx_components.h" + +#include + +/////////////////////////////// +// Constants implemlentation // +/////////////////////////////// +const float32_t Constants::M_PI_POW_2 = PI * PI; +const float32_t Constants::M_PI_POW_3 = Constants::M_PI_POW_2 * PI; +const float32_t Constants::M_PI_POW_5 = Constants::M_PI_POW_2 * Constants::M_PI_POW_3; +const float32_t Constants::M_PI_POW_7 = Constants::M_PI_POW_2 * Constants::M_PI_POW_5; +const float32_t Constants::M_PI_POW_9 = Constants::M_PI_POW_2 * Constants::M_PI_POW_7; +const float32_t Constants::M_PI_POW_11 = Constants::M_PI_POW_2 * Constants::M_PI_POW_9; + +const float32_t Constants::M2PI = 2.0f * PI; +const float32_t Constants::MPI_2 = PI / 2.0f; +const float32_t Constants::MPI_3 = PI / 3.0f; +const float32_t Constants::MPI_4 = PI / 4.0f; +const float32_t Constants::M1_PI = 1.0f / PI; + + +///////////////////////////// +// FastLFO implemlentation // +///////////////////////////// +FastLFO::FastLFO(float32_t sampling_rate, float32_t min_frequency, float32_t max_frequency, float32_t initial_phase, bool centered) : + FXBase(sampling_rate), + InitialPhase(initial_phase), + min_frequency_(min_frequency), + max_frequency_(max_frequency), + centered_(centered), + frequency_(0.0f), + nb_sub_increment_(1), + sub_increment_(0), + y0_(0.0f), + y1_(0.0f), + iir_coefficient_(0.0f), + initial_amplitude_(0.0f), + current_(0.0f) +{ + assert(this->min_frequency_ <= this->max_frequency_); + assert(this->max_frequency_ < sampling_rate / 2.0f); + + this->setFrequency(this->min_frequency_); +} + +FastLFO::~FastLFO() +{ +} + +void FastLFO::setNormalizedFrequency(float32_t normalized_frequency) +{ + normalized_frequency = constrain(normalized_frequency, 0.0f, 1.0f); + if(this->normalized_frequency_ != normalized_frequency) + { + float32_t frequency = mapfloat(normalized_frequency, 0.0f, 1.0f, this->min_frequency_, this->max_frequency_); + this->normalized_frequency_ = normalized_frequency; + this->frequency_ = frequency; + this->unitary_frequency_ = this->frequency_ / this->getSamplingRate(); + + this->nb_sub_increment_ = (frequency >= 3.0f ? 10 : 100); + this->unitary_frequency_ *= static_cast(this->nb_sub_increment_); + + this->updateCoefficient(); + } +} + +float32_t FastLFO::getNormalizedFrequency() const +{ + return this->normalized_frequency_; +} + +void FastLFO::setFrequency(float32_t frequency) +{ + frequency = constrain(frequency, this->min_frequency_, this->max_frequency_); + if(this->frequency_ != frequency) + { + float32_t normalized_frequency = mapfloat(frequency, this->min_frequency_, this->max_frequency_, 0.0f, 1.0f); + this->normalized_frequency_ = normalized_frequency; + this->frequency_ = frequency; + this->unitary_frequency_ = this->frequency_ / this->getSamplingRate(); + + this->nb_sub_increment_ = (frequency >= 3.0f ? 10 : (frequency < 0.1f ? 1000 : 100)); + this->unitary_frequency_ *= static_cast(this->nb_sub_increment_); + + this->updateCoefficient(); + } +} + +float32_t FastLFO::getFrequency() const +{ + return this->frequency_; +} + +void FastLFO::updateCoefficient() +{ + this->iir_coefficient_ = 2.0f * cos(Constants::M2PI * this->unitary_frequency_); + this->initial_amplitude_ = this->iir_coefficient_ * 0.25f; + + this->reset(); +} + +void FastLFO::reset() +{ + static const float32_t epsilon = 1e-3; + + this->sub_increment_ = 0.0f; + + // computing cos(0) = sin(-PI/2) + this->y1_ = this->initial_amplitude_; + this->y0_ = 0.5f; + + if(this->unitary_frequency_ == 0.0f) + { + return; + } + + float32_t p_i = Constants::M2PI * this->unitary_frequency_ / static_cast(this->nb_sub_increment_); + float32_t p = Constants::MPI_2; + float32_t oldP = 1000.0f; + float32_t t_p = this->InitialPhase; + const float32_t target = sin(this->InitialPhase); + if(t_p < p) + { + p -= Constants::M2PI; + } + float32_t tuning = -3.0f; + while(p < t_p || abs(tuning - target) > epsilon) + { + oldP = p; + tuning = this->process(); + p += p_i; + if(oldP == p) + { + return; + } + } +} + +float32_t FastLFO::process() +{ + float32_t temp = this->y0_; + float32_t current = temp + 0.5f; + if(this->centered_) + { + current = current * 2.0f - 1.0f; + } + + this->sub_increment_++; + if(this->sub_increment_ >= this->nb_sub_increment_) + { + this->sub_increment_ = 0; + this->y0_ = this->iir_coefficient_ * this->y0_ - this->y1_; + this->y1_ = temp; + this->current_ = current; + return current; + } + + return mapfloat(this->sub_increment_, 0, this->nb_sub_increment_, this->current_, current); +} + +float32_t FastLFO::current() const +{ + return this->current_; +} + + +///////////////////////////// +// FastLFO2 implemlentation // +///////////////////////////// +FastLFO2::FastLFO2(float32_t sampling_rate, float32_t min_frequency, float32_t max_frequency, float32_t initial_phase, bool centered) : + FXBase(sampling_rate), + InitialPhase(initial_phase), + min_frequency_(min_frequency), + max_frequency_(max_frequency), + centered_(centered), + frequency_(0.0f), + normalized_frequency_(0.0f), + phase_(initial_phase), + phase_increment_(0.0f), + current_(0.0f) +{ + assert(this->min_frequency_ <= this->max_frequency_); + assert(this->max_frequency_ < sampling_rate / 2.0f); + + this->setFrequency(this->min_frequency_); +} + +FastLFO2::~FastLFO2() +{ +} + +void FastLFO2::setNormalizedFrequency(float32_t normalized_frequency) +{ + normalized_frequency = constrain(normalized_frequency, 0.0f, 1.0f); + if(this->normalized_frequency_ != normalized_frequency) + { + float32_t frequency = mapfloat(normalized_frequency, 0.0f, 1.0f, this->min_frequency_, this->max_frequency_); + this->normalized_frequency_ = normalized_frequency; + this->frequency_ = frequency; + + this->phase_increment_ = Constants::M2PI * frequency / this->getSamplingRate(); + } +} + +float32_t FastLFO2::getNormalizedFrequency() const +{ + return this->normalized_frequency_; +} + +void FastLFO2::setFrequency(float32_t frequency) +{ + frequency = constrain(frequency, this->min_frequency_, this->max_frequency_); + if(this->frequency_ != frequency) + { + float32_t normalized_frequency = mapfloat(frequency, this->min_frequency_, this->max_frequency_, 0.0f, 1.0f); + this->normalized_frequency_ = normalized_frequency; + this->frequency_ = frequency; + + this->phase_increment_ = Constants::M2PI * frequency / this->getSamplingRate(); + } +} + +float32_t FastLFO2::getFrequency() const +{ + return this->frequency_; +} + +void FastLFO2::reset() +{ + this->phase_ = this->InitialPhase; +} + +float32_t FastLFO2::process() +{ + static const float32_t K = 5.0f * Constants::M_PI_POW_2; + + float32_t x = this->phase_; + float32_t f = 4.0f; + if(x > PI) + { + x -= PI; + f = -4.0f; + } + + float32_t tmp = 4.0f * x * (PI - x); + this->current_ = f * tmp / (K - tmp); + + if(!this->centered_) + { + this->current_ = this->current_ * 0.5f + 0.5f; + } + + this->phase_ += this->phase_increment_; + if(this->phase_ > Constants::M2PI) + { + this->phase_ -= Constants::M2PI; + } + + return this->current_; +} + +float32_t FastLFO2::current() const +{ + return this->current_; +} + + +//////////////////////////////////////////////// +// InterpolatedSineOscillator implemlentation // +//////////////////////////////////////////////// +float32_t InterpolatedSineOscillator::Sin(float32_t phase) +{ + static bool initialized = false; + if(!initialized) + { + initialized = InterpolatedSineOscillator::ClassInitializer(); + } + + if(phase < 0.0f) while(phase < 0.0f) phase += Constants::M2PI; + else while(phase > Constants::M2PI) phase -= Constants::M2PI; + + float32_t findex = phase / InterpolatedSineOscillator::DeltaTime; + + size_t index1 = static_cast(findex); + size_t index2 = index1 + 1; + + float32_t f1 = InterpolatedSineOscillator::CenteredDataPoints[index1]; + float32_t f2 = InterpolatedSineOscillator::CenteredDataPoints[index2]; + float32_t r = findex - index1; + + return f1 + (f2 - f1) * r * InterpolatedSineOscillator::DeltaTime; +} + +float32_t InterpolatedSineOscillator::Cos(float32_t phase) +{ + return InterpolatedSineOscillator::Sin(Constants::MPI_2 - phase); +} + +bool InterpolatedSineOscillator::ClassInitializer() +{ + static bool initialized = false; + + if(!initialized) + { + float32_t phase_increment = Constants::M2PI / static_cast(InterpolatedSineOscillator::DataPointSize); + float32_t phase = 0.0; + for(size_t i = 0; i <= InterpolatedSineOscillator::DataPointSize; ++i) + { + InterpolatedSineOscillator::CenteredDataPoints[i] = std::sin(phase); + InterpolatedSineOscillator::UpliftDataPoints[i] = InterpolatedSineOscillator::CenteredDataPoints[i] * 0.5f + 0.5f; + phase += phase_increment; + } + + initialized = true; + } + + return initialized; +} + +float32_t InterpolatedSineOscillator::CenteredDataPoints[InterpolatedSineOscillator::DataPointSize + 1]; +float32_t InterpolatedSineOscillator::UpliftDataPoints[InterpolatedSineOscillator::DataPointSize + 1]; + +const float32_t InterpolatedSineOscillator::DeltaTime = Constants::M2PI / static_cast(InterpolatedSineOscillator::DataPointSize); + +InterpolatedSineOscillator::InterpolatedSineOscillator(float32_t sampling_rate, float32_t min_frequency, float32_t max_frequency, float32_t initial_phase, bool centered) : + FXBase(sampling_rate), + InitialPhase(initial_phase), + min_frequency_(min_frequency), + max_frequency_(max_frequency), + centered_(centered), + frequency_(0.0f), + normalized_frequency_(-1.0f), + phase_index_(initial_phase / InterpolatedSineOscillator::DeltaTime), + phase_index_increment_(0.0f), + current_sample_(0.0f) +{ + static bool initialized = false; + if(!initialized) + { + initialized = InterpolatedSineOscillator::ClassInitializer(); + } + + assert(this->min_frequency_ <= this->max_frequency_); + assert(this->max_frequency_ < sampling_rate / 2.0f); + + this->setFrequency(this->min_frequency_); +} + +InterpolatedSineOscillator::~InterpolatedSineOscillator() +{ +} + +void InterpolatedSineOscillator::setNormalizedFrequency(float32_t normalized_frequency) +{ + normalized_frequency = constrain(normalized_frequency, 0.0f, 1.0f); + if(this->normalized_frequency_ != normalized_frequency) + { + float32_t frequency = mapfloat(normalized_frequency, 0.0f, 1.0f, this->min_frequency_, this->max_frequency_); + this->normalized_frequency_ = normalized_frequency; + this->frequency_ = frequency; + this->phase_index_increment_ = static_cast(InterpolatedSineOscillator::DataPointSize) * this->frequency_ / this->getSamplingRate(); + } +} + +float32_t InterpolatedSineOscillator::getNormalizedFrequency() const +{ + return this->normalized_frequency_; +} + +void InterpolatedSineOscillator::setFrequency(float32_t frequency) +{ + frequency = constrain(frequency, this->min_frequency_, this->max_frequency_); + if(this->frequency_ != frequency) + { + float32_t normalized_frequency = mapfloat(frequency, this->min_frequency_, this->max_frequency_, 0.0f, 1.0f); + this->normalized_frequency_ = normalized_frequency; + this->frequency_ = frequency; + this->phase_index_increment_ = static_cast(InterpolatedSineOscillator::DataPointSize) * this->frequency_ / this->getSamplingRate(); + } +} + +float32_t InterpolatedSineOscillator::getFrequency() const +{ + return this->frequency_; +} + +void InterpolatedSineOscillator::reset() +{ + this->phase_index_ = this->InitialPhase / InterpolatedSineOscillator::DeltaTime; + this->current_sample_ = 0.0f; +} + +float32_t InterpolatedSineOscillator::process() +{ + float32_t* dataPoints = this->centered_ ? InterpolatedSineOscillator::CenteredDataPoints : InterpolatedSineOscillator::UpliftDataPoints; + + float32_t out = 0.0f; + + float32_t findex = this->phase_index_; + size_t index1 = static_cast(findex); + size_t index2 = index1 + 1; + + float32_t f1 = dataPoints[index1]; + float32_t f2 = dataPoints[index2]; + float32_t r = findex - index1; + + out = f1 + (f2 - f1) * r * InterpolatedSineOscillator::DeltaTime; + + this->phase_index_ += this->phase_index_increment_; + if(this->phase_index_ > InterpolatedSineOscillator::DataPointSize) + { + this->phase_index_ -= InterpolatedSineOscillator::DataPointSize; + } + + return this->current_sample_ = out; +} + +float32_t InterpolatedSineOscillator::current() const +{ + return this->current_sample_; +} + + +//////////////////////////////// +// ComplexLFO implemlentation // +//////////////////////////////// +ComplexLFO::ComplexLFO(float32_t sampling_rate, float32_t min_frequency, float32_t max_frequency, float32_t initial_phase, bool centered) : + FXBase(sampling_rate), + InitialPhase(initial_phase), + min_frequency_(min_frequency), + max_frequency_(max_frequency), + centered_(centered), + normalized_frequency_(-1.0f), + frequency_(0.0f), + phase_(initial_phase), + phase_increment_(0.0f), + current_sample_(0.0f), + new_phase_(true), + rnd_generator_(rnd_device_()), + rnd_distribution_(-1.0f, 1.0f) +{ + assert(this->min_frequency_ <= this->max_frequency_); + assert(this->max_frequency_ < sampling_rate / 2.0f); + + this->setWaveform(Waveform::Sine); + this->setFrequency(this->min_frequency_); +} + +ComplexLFO::~ComplexLFO() +{ +} + +void ComplexLFO::setWaveform(Waveform waveform) +{ + this->waveform_ = waveform; +} + +ComplexLFO::Waveform ComplexLFO::getWaveform() const +{ + return this->waveform_; +} + +void ComplexLFO::setNormalizedFrequency(float32_t normalized_frequency) +{ + normalized_frequency = constrain(normalized_frequency, 0.0f, 1.0f); + if(this->normalized_frequency_ != normalized_frequency) + { + float32_t frequency = mapfloat(normalized_frequency, 0.0f, 1.0f, this->min_frequency_, this->max_frequency_); + this->normalized_frequency_ = normalized_frequency; + this->frequency_ = frequency; + this->phase_increment_ = Constants::M2PI * this->frequency_ / this->getSamplingRate(); + } +} + +float32_t ComplexLFO::getNormalizedFrequency() const +{ + return this->normalized_frequency_; +} + +void ComplexLFO::setFrequency(float32_t frequency) +{ + frequency = constrain(frequency, this->min_frequency_, this->max_frequency_); + if(this->frequency_ != frequency) + { + float32_t normalized_frequency = mapfloat(frequency, this->min_frequency_, this->max_frequency_, 0.0f, 1.0f); + this->normalized_frequency_ = normalized_frequency; + this->frequency_ = frequency; + this->phase_increment_ = Constants::M2PI * this->frequency_ / this->getSamplingRate(); + } +} + +float32_t ComplexLFO::getFrequency() const +{ + return this->frequency_; +} + +void ComplexLFO::reset() +{ + this->phase_ = this->InitialPhase; + this->current_sample_ = 0.0f; +} + +float32_t ComplexLFO::process() +{ + float32_t out = 0.0f; + switch(this->waveform_) + { + case Waveform::Sine: + out = arm_sin_f32(this->phase_); + break; + case Waveform::Saw: + out = Constants::M1_PI * this->phase_ - 1.0f; + break; + case Waveform::Square: + out = this->phase_ < PI ? 1.0 : -1.0; + break; + case Waveform::SH: + if(this->new_phase_) + { + out = this->rnd_distribution_(this->rnd_generator_); + } + else + { + out = this->current_sample_; + } + break; + case Waveform::Noise: + out = this->rnd_distribution_(this->rnd_generator_); + break; + } + + if(!this->centered_) + { + out = out * 0.5f + 0.5f; + } + + this->current_sample_ = out; + + this->phase_ += this->phase_increment_; + if(this->phase_ >= Constants::M2PI) + { + this->phase_ -= Constants::M2PI; + this->new_phase_ = true; + } + else + { + this->new_phase_ = false; + } + + return out; +} + +float32_t ComplexLFO::current() const +{ + return this->current_sample_; +} + + +//////////////////////////////////// +// JitterGenerator implementation // +//////////////////////////////////// +JitterGenerator::JitterGenerator(float32_t sampling_rate) : + FXBase(sampling_rate), + rnd_generator_(rnd_device_()), + rnd_distribution_(-1.0f, 1.0f), + speed_(-1.0f), + magnitude_(-1.0f), + phase_(0.0f), + phase_increment_(0.0f) +{ + this->setSpeed(1.0f); + this->setMagnitude(0.1f); +} + +JitterGenerator::~JitterGenerator() +{ +} + +void JitterGenerator::setSpeed(float32_t speed) +{ + static const float32_t max_frequency = 0.45f * this->getSamplingRate(); + + speed = constrain(speed, 0.0f, max_frequency); + if(this->speed_ != speed) + { + this->speed_ = speed; + this->phase_increment_ = Constants::M2PI * this->speed_ / this->getSamplingRate(); + } +} + +float32_t JitterGenerator::getSpeed() const +{ + return this->speed_; +} + +void JitterGenerator::setMagnitude(float32_t magnitude) +{ + this->magnitude_ = constrain(magnitude, 0.0f, 1.0f); +} + +float32_t JitterGenerator::getMagnitude() const +{ + return this->magnitude_; +} + +void JitterGenerator::reset() +{ + this->phase_ = 0.0f; +} + +float32_t JitterGenerator::process() +{ + float32_t out = InterpolatedSineOscillator::Sin(this->phase_); + + this->phase_ += this->phase_increment_ * (1.0f + this->magnitude_ * this->rnd_distribution_(this->rnd_generator_)); + if(this->phase_ > Constants::M2PI) + { + this->phase_ -= Constants::M2PI; + } + + return out; +} + + +////////////////////////////////////////// +// PerlinNoiseGenerator implemlentation // +////////////////////////////////////////// +#define MAX_FREQUENCY_PERLIN_NOISE_GENERATOR 0.5f + +const float32_t PerlinNoiseGenerator::Gradients[] = +{ + -1.0f, +1.0f, + -1.0f, -1.0f, + +1.0f, -1.0f, + +1.0f, +1.0f +}; + +PerlinNoiseGenerator::PerlinNoiseGenerator(float32_t sampling_rate, float32_t rate) : + FXBase(sampling_rate), + rate_(0.0f), + phase_(0.0f), + phase_increment_(0.0f), + current_(0.0f) +{ + this->setRate(rate); + + this->reset(); +} + +PerlinNoiseGenerator::~PerlinNoiseGenerator() +{ +} + +void PerlinNoiseGenerator::setRate(float32_t rate) +{ + rate = constrain(rate, 0.0f, 1.0f); + if(rate != this->rate_) + { + this->rate_ = rate; + this->phase_increment_ = Constants::M2PI * rate / this->getSamplingRate(); + } +} + +float32_t PerlinNoiseGenerator::getRate() const +{ + return this->rate_; +} + +float32_t PerlinNoiseGenerator::current() const +{ + return this->current_; +} + +void PerlinNoiseGenerator::reset() +{ + this->phase_ = 0.0f; + this->current_ = 0.0f; +} + +float32_t PerlinNoiseGenerator::process() +{ + if(this->rate_ == 0.0f) + { + return this->current_ = 0.0f; + } + + this->current_ = PerlinNoiseGenerator::perlin(this->phase_); + this->phase_ += this->phase_increment_; + if(this->phase_ >= Constants::M2PI) + { + this->phase_ -= Constants::M2PI; + } + + return this->current_; +} + +int PerlinNoiseGenerator::hash(int x) +{ + x = ((x << 13) ^ x); + return (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff; +} + +float32_t PerlinNoiseGenerator::interpolate(float32_t a, float32_t b, float32_t x) +{ + float32_t ft = x * PI; + float32_t f = (1.0f - arm_cos_f32(ft)) * 0.5; + return a * (1.0f - f) + b * f; +} + +float32_t PerlinNoiseGenerator::perlin(float32_t x) +{ + // Find the unit square that contains x + int squareX = (int)x; + + // Find the relative x of x within that square + double relX = x - squareX; + + // Calculate the hashes for the square's four corners + int h1 = PerlinNoiseGenerator::hash(squareX); + int h2 = PerlinNoiseGenerator::hash(squareX + 1); + + // Calculate the gradients for each corner + double grad1 = PerlinNoiseGenerator::Gradients[h1 & 3]; + double grad2 = PerlinNoiseGenerator::Gradients[h2 & 3]; + + // Calculate the dot products between the gradient vectors and the distance vectors + double dot1 = grad1 * relX; + double dot2 = grad2 * (relX - 1); + + // Interpolate the dot products and return the final noise value + return PerlinNoiseGenerator::interpolate(dot1, dot2, relX); +} + +////////////////////////////////// +// softSaturate implemlentation // +////////////////////////////////// +float32_t softSaturator1(float32_t in, float32_t threshold) +{ + float32_t x = std::abs(in); + float32_t y = 0.0f; + if(x < threshold) + { + y = x; + } + else if(x > threshold) + { + y = threshold + (x - threshold) / (1.0f + std::pow((x - threshold) / (1.0f - threshold), 2.0f)); + } + else if(x > 1.0f) + { + y = (threshold + 1.0f) / 2.0f; + } + + float32_t g = 2.0f / (1.0f + threshold); + y *= g; + + return (in < 0.0f) ? -y : y; +} + +float32_t softSaturator2(float32_t input, float32_t saturation) +{ + const static float kTubeCurve = 4.0f; + const static float kTubeBias = 0.5f; + + float absInput = std::abs(input); + float output = 0.0f; + if(absInput > kTubeBias) + { + output = (kTubeCurve + saturation) * (absInput - kTubeBias) / (1.0f - kTubeBias); + } + else + { + output = (kTubeCurve + saturation) * absInput / (1.0f + kTubeCurve * absInput); + } + + // Clip the output if overdrive is set to 1 + // output = std::min(1.0f, output); + if(output > 1.0f) + { + output = 1.0f; + } + else + { + output -= output * output * output / 3.0f; + } + + if(input < 0.0f) + { + output = -output; + } + + return output; +} + +float32_t softSaturator3(float32_t input, float32_t overdrive) +{ + const float32_t w = (1.0f + overdrive) * Constants::MPI_4; + return constrain(std::tan(w * input), -1.0f, 1.0f); +} + +float32_t softSaturator4(float32_t input, float32_t saturator_factor) +{ + float32_t x = input * saturator_factor; + float32_t abs_x = std::abs(x); + float32_t sat_x = std::log(1.0f + abs_x) / std::log(1.0f + saturator_factor); + return x > 0 ? sat_x : -sat_x; +} + +float32_t waveFolder(float32_t input, float32_t bias) +{ + bias = 0.5 + (2.0f - bias) / 4.0f; + float32_t output = std::abs(input) / bias; + + if(output > 1.0f) + { + output = 2.0f - output; + } + + if(input < 0.0f) + { + output = -output; + } + + return output; +} \ No newline at end of file diff --git a/src/fx_components.h b/src/fx_components.h new file mode 100644 index 00000000..45276e1a --- /dev/null +++ b/src/fx_components.h @@ -0,0 +1,568 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_components.h +// +// Several tools and components used in the implemlentation of FX +// Quthor: Vincent Gauché +// +#pragma once + +#include "fx.h" + +#include +#include +#include + +#define LFO_MIN_FREQUENCY 0.01f +#define LFO_MAX_FREQUENCY 10.0f + +struct Constants +{ + const static float32_t M_PI_POW_2; // PI^2 + const static float32_t M_PI_POW_3; // PI^3 + const static float32_t M_PI_POW_5; // PI^5 + const static float32_t M_PI_POW_7; // PI^7 + const static float32_t M_PI_POW_9; // PI^9 + const static float32_t M_PI_POW_11; // PI^11 + + const static float32_t M2PI; // 2 * PI + const static float32_t MPI_2; // PI / 2 + const static float32_t MPI_3; // PI / 3 + const static float32_t MPI_4; // PI / 4 + const static float32_t M1_PI; // 1 / PI +}; + + +class FastLFO : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(FastLFO); + +public: + FastLFO(float32_t sampling_rate, float32_t min_frequency = LFO_MIN_FREQUENCY, float32_t max_frequency = LFO_MAX_FREQUENCY, float32_t initial_phase = 0.0f, bool centered = true); + virtual ~FastLFO(); + + void setNormalizedFrequency(float32_t normalized_frequency); + float32_t getNormalizedFrequency() const; + + void setFrequency(float32_t frequency); + float32_t getFrequency() const; + + virtual void reset() override; + float32_t process(); + float32_t current() const; + +private: + void updateCoefficient(); + + const float32_t InitialPhase; + const float32_t min_frequency_; + const float32_t max_frequency_; + const bool centered_; + float32_t frequency_; + float32_t normalized_frequency_; + float32_t unitary_frequency_; + size_t nb_sub_increment_; + size_t sub_increment_; + + float32_t y0_; + float32_t y1_; + float32_t iir_coefficient_; + float32_t initial_amplitude_; + float32_t current_; + + IMPLEMENT_DUMP( + const size_t space = 21; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "InitialPhase"); + SS__TEXT(ss, ' ', space, std::left, '|', "frequency_"); + SS__TEXT(ss, ' ', space, std::left, '|', "normalized_frequency_"); + SS__TEXT(ss, ' ', space, std::left, '|', "unitary_frequency_"); + SS__TEXT(ss, ' ', space, std::left, '|', "y0_"); + SS__TEXT(ss, ' ', space, std::left, '|', "y1_"); + SS__TEXT(ss, ' ', space, std::left, '|', "iir_coefficient_"); + SS__TEXT(ss, ' ', space, std::left, '|', "initial_amplitude_"); + SS__TEXT(ss, ' ', space, std::left, '|', "current_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->InitialPhase); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->frequency_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->normalized_frequency_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->unitary_frequency_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->y0_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->y1_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->iir_coefficient_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->initial_amplitude_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->current_); + + out << "\t" << ss.str() << std::endl; + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".InitialPhase", this->InitialPhase, 0.0f, Constants::M2PI, deepInspection); + nb_errors += inspector(tag + ".frequency_", this->frequency_, this->min_frequency_, this->max_frequency_, deepInspection); + nb_errors += inspector(tag + ".normalized_frequency_", this->normalized_frequency_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".unitary_frequency_", this->unitary_frequency_, this->min_frequency_ / this->getSamplingRate(), this->max_frequency_ / this->getSamplingRate(), deepInspection); + nb_errors += inspector(tag + ".current_", this->current_, -1.0f, 1.0f, deepInspection); + + return nb_errors; + ) +}; + + +class FastLFO2 : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(FastLFO2); + +public: + FastLFO2(float32_t sampling_rate, float32_t min_frequency = LFO_MIN_FREQUENCY, float32_t max_frequency = LFO_MAX_FREQUENCY, float32_t initial_phase = 0.0f, bool centered = true); + virtual ~FastLFO2(); + + void setNormalizedFrequency(float32_t normalized_frequency); + float32_t getNormalizedFrequency() const; + + void setFrequency(float32_t frequency); + float32_t getFrequency() const; + + virtual void reset() override; + float32_t process(); + float32_t current() const; + +private: + const float32_t InitialPhase; + const float32_t min_frequency_; + const float32_t max_frequency_; + const bool centered_; + float32_t frequency_; + float32_t normalized_frequency_; + float32_t phase_; + float32_t phase_increment_; + float32_t current_; + + IMPLEMENT_DUMP( + const size_t space = 21; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "InitialPhase"); + SS__TEXT(ss, ' ', space, std::left, '|', "frequency_"); + SS__TEXT(ss, ' ', space, std::left, '|', "normalized_frequency_"); + SS__TEXT(ss, ' ', space, std::left, '|', "phase_"); + SS__TEXT(ss, ' ', space, std::left, '|', "phase_increment_"); + SS__TEXT(ss, ' ', space, std::left, '|', "current_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->InitialPhase); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->frequency_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->normalized_frequency_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_increment_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->current_); + + out << "\t" << ss.str() << std::endl; + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".InitialPhase", this->InitialPhase, 0.0f, Constants::M2PI, deepInspection); + nb_errors += inspector(tag + ".frequency_", this->frequency_, this->min_frequency_, this->max_frequency_, deepInspection); + nb_errors += inspector(tag + ".normalized_frequency_", this->normalized_frequency_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".phase_", this->phase_, 0.0f, Constants::M2PI, deepInspection); + nb_errors += inspector(tag + ".phase_", this->phase_increment_, 0.0f, Constants::M2PI, deepInspection); + nb_errors += inspector(tag + ".current_", this->current_, -1.0f, 1.0f, deepInspection); + + return nb_errors; + ) +}; + + +class InterpolatedSineOscillator : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(InterpolatedSineOscillator); + +public: + static float32_t Sin(float32_t phase); + static float32_t Cos(float32_t phase); + + InterpolatedSineOscillator(float32_t sampling_rate, float32_t min_frequency = LFO_MIN_FREQUENCY, float32_t max_frequency = LFO_MAX_FREQUENCY, float32_t initial_phase = 0.0f, bool centered = true); + virtual ~InterpolatedSineOscillator(); + + void setNormalizedFrequency(float32_t normalized_frequency); + float32_t getNormalizedFrequency() const; + + void setFrequency(float32_t frequency); + float32_t getFrequency() const; + + virtual void reset() override; + float32_t process(); + float32_t current() const; + +private: + static bool ClassInitializer(); + static const size_t DataPointSize = 176400; + static const float32_t DeltaTime; + static float32_t CenteredDataPoints[]; + static float32_t UpliftDataPoints[]; + + const float32_t InitialPhase; + const float32_t min_frequency_; + const float32_t max_frequency_; + const bool centered_; + float32_t frequency_; + float32_t normalized_frequency_; + float32_t phase_index_; + float32_t phase_index_increment_; + float32_t current_sample_; + + IMPLEMENT_DUMP( + const size_t space = 22; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "InitialPhase"); + SS__TEXT(ss, ' ', space, std::left, '|', "normalized_frequency_"); + SS__TEXT(ss, ' ', space, std::left, '|', "frequency_"); + SS__TEXT(ss, ' ', space, std::left, '|', "phase_index_"); + SS__TEXT(ss, ' ', space, std::left, '|', "phase_index_increment_"); + SS__TEXT(ss, ' ', space, std::left, '|', "current_sample_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->InitialPhase); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->normalized_frequency_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->frequency_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_index_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_index_increment_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->current_sample_); + out << "\t" << ss.str() << std::endl; + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".InitialPhase", this->InitialPhase, 0.0f, Constants::M2PI, deepInspection); + nb_errors += inspector(tag + ".normalized_frequency_", this->normalized_frequency_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".frequency_", this->frequency_, this->min_frequency_, this->max_frequency_, deepInspection); + nb_errors += inspector(tag + ".phase_index_", this->phase_index_, 0.0f, static_cast(InterpolatedSineOscillator::DataPointSize), deepInspection); + nb_errors += inspector(tag + ".current_sample_", this->current_sample_, -1.0f, 1.0f, deepInspection); + + return nb_errors; + ) +}; + +class ComplexLFO : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(ComplexLFO); + +public: + typedef enum { + Sine, + Saw, + Square, + SH, + Noise + } Waveform; + + ComplexLFO(float32_t sampling_rate, float32_t min_frequency = LFO_MIN_FREQUENCY, float32_t max_frequency = LFO_MAX_FREQUENCY, float32_t initial_phase = 0.0f, bool centered = true); + virtual ~ComplexLFO(); + + void setWaveform(Waveform waveform); + Waveform getWaveform() const; + + void setNormalizedFrequency(float32_t normalized_frequency); + float32_t getNormalizedFrequency() const; + + void setFrequency(float32_t frequency); + float32_t getFrequency() const; + + virtual void reset() override; + float32_t process(); + float32_t current() const; + +private: + const float32_t InitialPhase; + const float32_t min_frequency_; + const float32_t max_frequency_; + const bool centered_; + Waveform waveform_; + float32_t normalized_frequency_; + float32_t frequency_; + float32_t phase_; + float32_t phase_increment_; + float32_t current_sample_; + bool new_phase_; + std::random_device rnd_device_; + std::mt19937 rnd_generator_; + std::uniform_real_distribution rnd_distribution_; + + IMPLEMENT_DUMP( + const size_t space = 21; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "InitialPhase"); + SS__TEXT(ss, ' ', space, std::left, '|', "normalized_frequency_"); + SS__TEXT(ss, ' ', space, std::left, '|', "frequency_"); + SS__TEXT(ss, ' ', space, std::left, '|', "phase_"); + SS__TEXT(ss, ' ', space, std::left, '|', "phase_increment_"); + SS__TEXT(ss, ' ', space, std::left, '|', "current_sample_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->InitialPhase); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->normalized_frequency_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->frequency_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_increment_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->current_sample_); + out << "\t" << ss.str() << std::endl; + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".InitialPhase", this->InitialPhase, 0.0f, Constants::M2PI, deepInspection); + nb_errors += inspector(tag + ".normalized_frequency_", this->normalized_frequency_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".frequency_", this->frequency_, this->min_frequency_, this->max_frequency_, deepInspection); + nb_errors += inspector(tag + ".phase_", this->phase_, 0.0f, Constants::M2PI, deepInspection); + nb_errors += inspector(tag + ".phase_increment_", this->phase_increment_, 0.0f, Constants::M2PI, deepInspection); + nb_errors += inspector(tag + ".current_sample_", this->current_sample_, -1.0f, 1.0f, deepInspection); + + return nb_errors; + ) +}; + + +typedef FastLFO2 LFO; + + +class JitterGenerator : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(JitterGenerator); + +public: + JitterGenerator(float32_t sampling_rate); + virtual ~JitterGenerator(); + + void setSpeed(float32_t speed); + float32_t getSpeed() const; + + void setMagnitude(float32_t magnitude); + float32_t getMagnitude() const; + + virtual void reset() override; + float32_t process(); + +private: + std::random_device rnd_device_; + std::mt19937 rnd_generator_; + std::uniform_real_distribution rnd_distribution_; + float32_t speed_; + float32_t magnitude_; + float32_t phase_; + float32_t phase_increment_; + + IMPLEMENT_DUMP( + const size_t space = 16; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "speed_"); + SS__TEXT(ss, ' ', space, std::left, '|', "magnitude_"); + SS__TEXT(ss, ' ', space, std::left, '|', "phase_"); + SS__TEXT(ss, ' ', space, std::left, '|', "phase_increment_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->speed_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->magnitude_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_increment_); + out << "\t" << ss.str() << std::endl; + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".speed_", this->speed_, 0.0f, 0.45f * this->getSamplingRate(), deepInspection); + nb_errors += inspector(tag + ".magnitude_", this->magnitude_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".phase_", this->phase_, 0.0f, Constants::M2PI, deepInspection); + nb_errors += inspector(tag + ".phase_increment_", this->phase_increment_, 0.0f, 0.45f * Constants::M2PI, deepInspection); + + return nb_errors; + ) +}; + + +class PerlinNoiseGenerator : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(PerlinNoiseGenerator); + +public: + PerlinNoiseGenerator(float32_t sampling_rate, float32_t rate = 0.2f); + virtual ~PerlinNoiseGenerator(); + + void setRate(float32_t rate); + float32_t getRate() const; + + float32_t current() const; + + virtual void reset() override; + float32_t process(); + +private: + static int hash(int x); + static float32_t interpolate(float32_t a, float32_t b, float32_t x); + static float32_t perlin(float32_t x); + + float32_t rate_; + float32_t phase_; + float32_t phase_increment_; + float32_t current_; + + static const float32_t Gradients[]; + + IMPLEMENT_DUMP( + const size_t space = 16; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "rate_"); + SS__TEXT(ss, ' ', space, std::left, '|', "phase_"); + SS__TEXT(ss, ' ', space, std::left, '|', "phase_increment_"); + SS__TEXT(ss, ' ', space, std::left, '|', "current_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->rate_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_increment_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->current_); + out << "\t" << ss.str() << std::endl; + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".rate_", this->rate_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".phase_", this->phase_, 0.0f, Constants::M2PI, deepInspection); + nb_errors += inspector(tag + ".phase_increment_", this->phase_increment_, 0.0f, Constants::M2PI / this->getSamplingRate(), deepInspection); + nb_errors += inspector(tag + ".current_", this->current_, -1.0f, 1.0f, deepInspection); + + return nb_errors; + ) +}; + +float32_t softSaturator1(float32_t in, float32_t threshold); +float32_t softSaturator2(float32_t in, float32_t saturation); +float32_t softSaturator3(float32_t in, float32_t saturation); +float32_t softSaturator4(float32_t in, float32_t saturation); + +float32_t waveFolder(float32_t input, float32_t bias); diff --git a/src/fx_delay.cpp b/src/fx_delay.cpp new file mode 100644 index 00000000..6ddaacba --- /dev/null +++ b/src/fx_delay.cpp @@ -0,0 +1,157 @@ +#include "fx_delay.h" + +#include + +#define MAX_DELAY_TIME 1.0f +#define MAX_FLUTTER_DELAY_TIME 0.2f +#define MAX_FLUTTER_DELAY_AMOUNT 0.01f + +#define LPF_CUTOFF_REF 12000.0f +#define HPF_CUTOFF_REF 80.0f + +Delay::LowHighPassFilter::LowHighPassFilter(float32_t sampling_rate) : + FXElement(sampling_rate), + lpf_(sampling_rate, StateVariableFilter::FilterMode::LPF, LPF_CUTOFF_REF), + hpf_(sampling_rate, StateVariableFilter::FilterMode::HPF, HPF_CUTOFF_REF), + ratio_(1.0f) +{ + this->setCutoffChangeRatio(0.0f); + this->lpf_.setGainDB(0.82f); + this->hpf_.setGainDB(0.82f); +} + +Delay::LowHighPassFilter::~LowHighPassFilter() +{ +} + +void Delay::LowHighPassFilter::setCutoffChangeRatio(float32_t ratio) +{ + static const float32_t weight = 4.0f; + + ratio = constrain(ratio, -1.0f, 1.0f); + if(ratio != this->ratio_) + { + this->ratio_ = ratio; + ratio /= 10.0f; + this->lpf_.setCutoff(LPF_CUTOFF_REF * (1.0f - ratio / weight)); + this->hpf_.setCutoff(HPF_CUTOFF_REF * (1.0f + ratio * weight)); + } +} + +void Delay::LowHighPassFilter::reset() +{ + this->lpf_.reset(); + this->hpf_.reset(); +} + +void Delay::LowHighPassFilter::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + this->lpf_.processSample(inL, inR, outL, outR); + this->hpf_.processSample(outL, outR, outL, outR); +} + +Delay::Delay(const float32_t sampling_rate, float32_t default_delay_time, float32_t default_flutter_level, float32_t default_feedback_level) : + FXElement(sampling_rate, 2.2587f), + MaxSampleDelayTime((MAX_DELAY_TIME + MAX_FLUTTER_DELAY_TIME) * sampling_rate), + write_pos_L_(0), + write_pos_R_(0), + filter_(sampling_rate) +{ + this->buffer_L_ = new float32_t[this->MaxSampleDelayTime]; + this->buffer_R_ = new float32_t[this->MaxSampleDelayTime]; + + this->setLeftDelayTime(default_delay_time); + this->setRightDelayTime(default_delay_time); + this->setFeedback(default_feedback_level); + + this->reset(); +} + +Delay::~Delay() +{ + delete[] this->buffer_L_; + delete[] this->buffer_R_; +} + +void Delay::reset() +{ + memset(this->buffer_L_, 0, this->MaxSampleDelayTime * sizeof(float32_t)); + memset(this->buffer_R_, 0, this->MaxSampleDelayTime * sizeof(float32_t)); + this->write_pos_L_ = 0; + this->write_pos_R_ = 0; + this->filter_.reset(); +} + +void Delay::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + // Write input to delay buffers + this->buffer_L_[this->write_pos_L_] *= this->getFeedback();; + this->buffer_L_[this->write_pos_L_] += inL; + this->buffer_R_[this->write_pos_R_] *= this->getFeedback();; + this->buffer_R_[this->write_pos_R_] += inR; + + // Calculate read positions + float32_t delay_time_L = std::abs(MAX_DELAY_TIME * this->getLeftDelayTime() ) * this->getSamplingRate(); + float32_t delay_time_R = std::abs(MAX_DELAY_TIME * this->getRightDelayTime()) * this->getSamplingRate(); + float32_t signL = this->getLeftDelayTime() >= 0 ? 1.0f : -1.0f; + float32_t signR = this->getRightDelayTime() >= 0 ? 1.0f : -1.0f; + unsigned read_pos_L = static_cast(this->MaxSampleDelayTime + signL * this->write_pos_L_ - delay_time_L) % this->MaxSampleDelayTime; + unsigned read_pos_R = static_cast(this->MaxSampleDelayTime + signR * this->write_pos_R_ - delay_time_R) % this->MaxSampleDelayTime; + + + // Read from delay buffers and apply feedback + this->filter_.processSample( + this->buffer_L_[read_pos_L], + this->buffer_R_[read_pos_R], + outL, + outR + ); + + this->buffer_L_[this->write_pos_L_] += outL * this->getFeedback(); + this->buffer_R_[this->write_pos_R_] += outR * this->getFeedback(); + + // Increment read positions + ++ this->write_pos_L_; + if(this->write_pos_L_ >= this->MaxSampleDelayTime) + { + this->write_pos_L_ -= this->MaxSampleDelayTime; + } + ++ this->write_pos_R_; + if(this->write_pos_R_ >= this->MaxSampleDelayTime) + { + this->write_pos_R_ -= this->MaxSampleDelayTime; + } + + outL *= this->OutputLevelCorrector; + outR *= this->OutputLevelCorrector; +} + +void Delay::setLeftDelayTime(float32_t delay_time) +{ + this->delay_time_L_ = constrain(delay_time, -1.0f, 1.0f); +} + +float32_t Delay::getLeftDelayTime() const +{ + return this->delay_time_L_; +} + +void Delay::setRightDelayTime(float32_t delay_time) +{ + this->delay_time_R_ = constrain(delay_time, -1.0f, 1.0f); +} + +float32_t Delay::getRightDelayTime() const +{ + return this->delay_time_R_; +} + +void Delay::setFeedback(float32_t feedback) +{ + this->feedback_ = constrain(feedback, 0.0, 1.0); +} + +float32_t Delay::getFeedback() const +{ + return this->feedback_; +} diff --git a/src/fx_delay.h b/src/fx_delay.h new file mode 100644 index 00000000..30e063ea --- /dev/null +++ b/src/fx_delay.h @@ -0,0 +1,204 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +// +// fx_tape_delay.h +// +// Stereo Delay proposed in the context of the MiniDexed project +// Author: Vincent Gauché +// +#pragma once + +#include "fx_components.h" +#include "fx_svf.h" + +class Delay : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(Delay); + + class LowHighPassFilter : public FXElement + { + DISALLOW_COPY_AND_ASSIGN(LowHighPassFilter); + + public: + LowHighPassFilter(float32_t sampling_rate); + virtual ~LowHighPassFilter(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setCutoffChangeRatio(float32_t ratio); + + private: + StateVariableFilter lpf_; + StateVariableFilter hpf_; + float32_t ratio_; + + IMPLEMENT_DUMP( + const size_t space = 10; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "ratio_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->ratio_); + out << "\t" << ss.str() << std::endl; + + if(deepInspection) + { + out << "\t" << std::endl; + this->lpf_.dump(out, deepInspection, tag + ".lpf_"); + this->hpf_.dump(out, deepInspection, tag + ".hpf_"); + } + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + if(deepInspection) + { + nb_errors += this->lpf_.inspect(inspector, deepInspection, tag + ".lpf_"); + nb_errors += this->hpf_.inspect(inspector, deepInspection, tag + ".hpf_"); + } + nb_errors += inspector(tag + ".ratio_", this->ratio_, -1.0f, 1.0f, deepInspection); + + return nb_errors; + ) + }; + +public: + Delay(const float32_t sampling_rate, float32_t default_delay_time = 0.25f, float32_t default_flutter_level = 1.0f, float32_t default_wet_level = 0.5f); + virtual ~Delay(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setLeftDelayTime(float32_t delay_time); + float32_t getLeftDelayTime() const; + + void setRightDelayTime(float32_t delay_time); + float32_t getRightDelayTime() const; + + void setFeedback(float32_t feedback); + float32_t getFeedback() const; + +private: + const size_t MaxSampleDelayTime; + unsigned write_pos_L_; + unsigned write_pos_R_; + float32_t* buffer_L_; + float32_t* buffer_R_; + float32_t delay_time_L_; // Left delay time in seconds (0.0 - 2.0) + float32_t delay_time_R_; // Right delay time in seconds (0.0 - 2.0) + float32_t feedback_; // Feedback (0.0 - 1.0) + + LowHighPassFilter filter_; + + IMPLEMENT_DUMP( + const size_t space = 18; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "write_pos_L_"); + SS__TEXT(ss, ' ', space, std::left, '|', "write_pos_R_"); + SS__TEXT(ss, ' ', space, std::left, '|', "delay_time_L_"); + SS__TEXT(ss, ' ', space, std::left, '|', "delay_time_R_"); + SS__TEXT(ss, ' ', space, std::left, '|', "feedback_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->write_pos_L_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->write_pos_R_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->delay_time_L_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->delay_time_R_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->feedback_); + out << "\t" << ss.str() << std::endl; + + if(deepInspection) + { + out << "Flanger internal delay lines:" << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "index"); + SS__TEXT(ss, ' ', space, std::left, '|', "buffer_L_"); + SS__TEXT(ss, ' ', space, std::left, '|', "buffer_R_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + for(size_t i = 0; i < this->MaxSampleDelayTime; ++i) + { + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", i); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->buffer_L_[i]); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->buffer_R_[i]); + out << "\t" << ss.str() << std::endl; + } + + this->filter_.dump(out, deepInspection, tag + ".filter_"); + } + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0; + nb_errors += inspector(tag + ".write_pos_L_", static_cast(this->write_pos_L_), 0.0f, static_cast(this->MaxSampleDelayTime), deepInspection); + nb_errors += inspector(tag + ".write_pos_R_", static_cast(this->write_pos_R_), 0.0f, static_cast(this->MaxSampleDelayTime), deepInspection); + nb_errors += inspector(tag + ".delay_time_L_", this->delay_time_L_, -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".delay_time_R_", this->delay_time_R_, -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".feedback_", this->feedback_, 0.0f, 1.0f, deepInspection); + + if(deepInspection) + { + for(size_t i = 0; i < this->MaxSampleDelayTime; ++i) + { + nb_errors += inspector(tag + ".buffer_L_[ " + std::to_string(i) + " ]", this->buffer_L_[i], -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".buffer_R_[ " + std::to_string(i) + " ]", this->buffer_R_[i], -1.0f, 1.0f, deepInspection); + } + + nb_errors += this->filter_.inspect(inspector, deepInspection, tag + ".filter_"); + } + + return nb_errors; + ) +}; diff --git a/src/fx_diffuser.cpp b/src/fx_diffuser.cpp new file mode 100644 index 00000000..936f2d3c --- /dev/null +++ b/src/fx_diffuser.cpp @@ -0,0 +1,71 @@ +#include "fx_diffuser.h" + +#include +#include + +#define TAIL , -1 + +Diffuser::Diffuser(float32_t sampling_frequency) : + FXElement(sampling_frequency), + engine_(sampling_frequency) +{ +} + +Diffuser::~Diffuser() +{ +} + +void Diffuser::reset() +{ + this->engine_.reset(); +} + +void Diffuser::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + typedef Engine::Reserve<126, + Engine::Reserve<180, + Engine::Reserve<269, + Engine::Reserve<444, + Engine::Reserve<151, + Engine::Reserve<205, + Engine::Reserve<245, + Engine::Reserve<405> > > > > > > > Memory; + Engine::DelayLine apl1; + Engine::DelayLine apl2; + Engine::DelayLine apl3; + Engine::DelayLine apl4; + Engine::DelayLine apr1; + Engine::DelayLine apr2; + Engine::DelayLine apr3; + Engine::DelayLine apr4; + Engine::Context c; + + const float32_t kap = 0.625f; + float wet = 0.0f; + + engine_.start(&c); + + c.load(inL); + c.read(apl1 TAIL, kap); + c.writeAllPass(apl1, -kap); + c.read(apl2 TAIL, kap); + c.writeAllPass(apl2, -kap); + c.read(apl3 TAIL, kap); + c.writeAllPass(apl3, -kap); + c.read(apl4 TAIL, kap); + c.writeAllPass(apl4, -kap); + c.writeAndLoad(wet, 0.0f); + outL = wet; + + c.load(inR); + c.read(apr1 TAIL, kap); + c.writeAllPass(apr1, -kap); + c.read(apr2 TAIL, kap); + c.writeAllPass(apr2, -kap); + c.read(apr3 TAIL, kap); + c.writeAllPass(apr3, -kap); + c.read(apr4 TAIL, kap); + c.writeAllPass(apr4, -kap); + c.writeAndLoad(wet, 0.0f); + outR = wet; +} diff --git a/src/fx_diffuser.h b/src/fx_diffuser.h new file mode 100644 index 00000000..2fe1d48d --- /dev/null +++ b/src/fx_diffuser.h @@ -0,0 +1,65 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +// +// fx_shimmer_reverb3.h +// +// Stereo Diffuser proposed in the context of the MiniDexed project +// It is adapted from the Diffuser that could be found on Cloud EuroRack module from Mutable Instrruments +// Ported by: Vincent Gauché +// +#pragma once + +#include "fx_components.h" +#include "fx_engine.hpp" + +#define DIFFUSER_BUFFER_SIZE 2048 + +class Diffuser : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(Diffuser); + +public: + Diffuser(float32_t sampling_frequency); + virtual ~Diffuser(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + +private: + typedef FxEngine Engine; + Engine engine_; + + IMPLEMENT_DUMP( + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + if(deepInspection) + { + this->engine_.dump(out, deepInspection, tag + ".engine_"); + } + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + if(deepInspection) + { + nb_errors += this->engine_.inspect(inspector, deepInspection, tag + ".engine_"); + } + + return nb_errors; + ) +}; diff --git a/src/fx_dry.cpp b/src/fx_dry.cpp new file mode 100644 index 00000000..a4c29314 --- /dev/null +++ b/src/fx_dry.cpp @@ -0,0 +1,21 @@ +#include "fx_dry.h" + +Dry::Dry(float32_t samplingRate) : + FXElement(samplingRate) +{ +} + +Dry::~Dry() +{ +} + +void Dry::reset() +{ + // nothing to be done +} + +void Dry::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + outL = inL; + outR = inR; +} diff --git a/src/fx_dry.h b/src/fx_dry.h new file mode 100644 index 00000000..52cd5526 --- /dev/null +++ b/src/fx_dry.h @@ -0,0 +1,43 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_dry.h +// +// An FX that does nothing but used to generalize the processing. +// Author: Vincent Gauché +// +#pragma once + +#include "fx_components.h" + +class Dry : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(Dry); + +public: + Dry(float32_t sampling_rate); + virtual ~Dry(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + IMPLEMENT_DUMP( + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + return 0u; + ) +}; \ No newline at end of file diff --git a/src/fx_engine.hpp b/src/fx_engine.hpp new file mode 100644 index 00000000..239969f7 --- /dev/null +++ b/src/fx_engine.hpp @@ -0,0 +1,515 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_engine.h +// +// FX Engine used in some of the Mutable Instruments Eurorack modules. +// This version is ported for the MiniDexed project and brings some additional optimization to avoid unecessary multiplications. +// Ported by: Vincent Gauché +// + +#pragma once + +#include +#include +#include + +#include "fx_components.h" + +enum Format +{ + FORMAT_12_BIT, + FORMAT_16_BIT, + FORMAT_32_BIT, + FORMAT_FLOAT32 +}; + +template +struct DataType +{ +}; + +inline int16_t clip16(int32_t x) +{ + if(x > INT16_MAX) + { + return INT16_MAX; + } + + if(x < INT16_MIN) + { + return INT16_MIN; + } + + return static_cast(x); +} + +template <> +struct DataType +{ + typedef uint16_t T; + + static inline float32_t decompress(T value) + { + return static_cast(static_cast(value)) / 4096.0f; + } + + static inline T compress(float32_t value) + { + return clip16(static_cast(value * 4096.0f)); + } +}; + +template <> +struct DataType +{ + typedef uint32_t T; + + static inline float32_t decompress(T value) + { + return static_cast(static_cast(value)) / 65536.0f; + } + + static inline T compress(float32_t value) + { + return clip16(static_cast(value * 65536.0f)); + } +}; + +template <> +struct DataType +{ + typedef uint32_t T; + + static inline float32_t decompress(T value) + { + return static_cast(static_cast(value)) / static_cast(UINT32_MAX); + } + + static inline T compress(float32_t value) + { + return value * static_cast(INT32_MAX); + } +}; + +template <> +struct DataType +{ + typedef float32_t T; + + static inline float32_t decompress(T value) + { + return value; + } + + static inline T compress(float32_t value) + { + return constrain(value, -1.0f, 1.0f); + } +}; + +template < + size_t size, + Format format, + bool enable_lfo = true> +class FxEngine : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(FxEngine); + +public: + typedef typename DataType::T T; + + enum LFOIndex + { + LFO_1 = 0, + LFO_2, + kLFOCount + }; + + FxEngine(float32_t sampling_rate, float32_t max_lfo_frequency = 20.0f) : + FXBase(sampling_rate), + write_ptr_(0) + { + this->buffer_ = new T[size]; + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) this->lfo_[i] = enable_lfo ? new LFO(sampling_rate, 0.0f, max_lfo_frequency, 0.0f, false) : nullptr; + this->clear(); + } + + ~FxEngine() + { + delete[] this->buffer_; + if(enable_lfo) + { + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) delete this->lfo_[i]; + } + } + + void clear() + { + memset(this->buffer_, 0, size * sizeof(T)); + this->write_ptr_ = 0; + } + + virtual void reset() override + { + this->clear(); + if(enable_lfo) + { + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) this->lfo_[i]->reset(); + } + } + + struct Empty + { + }; + + template + struct Reserve + { + typedef T Tail; + enum + { + length = _length + }; + }; + + template + struct DelayLine + { + enum + { + length = DelayLine::length, + base = DelayLine::base + DelayLine::length + 1 + }; + }; + + template + struct DelayLine + { + enum + { + length = Memory::length, + base = 0 + }; + }; + + class Context + { + DISALLOW_COPY_AND_ASSIGN(Context); + friend class FxEngine; + + public: + Context() : + accumulator_(0.0f), + previous_read_(0.0f), + buffer_(nullptr), + write_ptr_(0) + { + memset(this->lfo_value_, 0, LFOIndex::kLFOCount * sizeof(float32_t)); + } + + ~Context() + { + } + + inline void load(float32_t value) + { + this->accumulator_ = value; + } + + inline void read(float32_t value) + { + this->accumulator_ += value; + } + + inline void read(float32_t value, float32_t scale) + { + this->accumulator_ += value * scale; + } + + inline void write(float32_t& value) + { + value = this->accumulator_; + } + + inline void write(float32_t& value, float32_t scale) + { + value = this->accumulator_; + this->accumulator_ *= scale; + } + + inline void writeAndLoad(float32_t& value, float32_t newValue) + { + value = this->accumulator_; + this->load(newValue); + } + + template + inline void directWrite(float32_t value, D& d) + { + this->load(value); + this->writeAndLoad(d, 0, 0.0f); + } + + template + inline void write(D& d, int32_t offset) + { + assert((D::base + D::length) <= size); + + T w = DataType::compress(this->accumulator_); + if(offset == -1) + { + this->buffer_[(this->write_ptr_ + D::base + D::length - 1) & MASK] = w; + } + else + { + this->buffer_[(this->write_ptr_ + D::base + offset) & MASK] = w; + } + } + + template + inline void write(D& d, int32_t offset, float32_t scale) + { + this->write(d, offset); + this->accumulator_ *= scale; + } + + template + inline void writeAndLoad(D& d, int32_t offset, float32_t newValue) + { + this->write(d, offset); + this->load(newValue); + } + + template + inline void write(D& d, float32_t scale) + { + this->write(d, 0, scale); + } + + template + inline void writeAndLoad(D& d, float32_t newValue) + { + this->writeAndLoad(d, 0, newValue); + } + + template + inline void writeAllPass(D& d, int32_t offset, float32_t scale) + { + this->write(d, offset, scale); + this->accumulator_ += this->previous_read_; + } + + template + inline void writeAllPass(D& d, float32_t scale) + { + this->writeAllPass(d, 0, scale); + } + + template + inline void read(D& d, int32_t offset, float32_t scale) + { + assert((D::base + D::length) <= size); + + T r; + if(offset == -1) + { + r = this->buffer_[(this->write_ptr_ + D::base + D::length - 1) & MASK]; + } + else + { + r = this->buffer_[(this->write_ptr_ + D::base + offset) & MASK]; + } + float32_t r_f = DataType::decompress(r); + this->previous_read_ = r_f; + this->accumulator_ += r_f * scale; + } + + template + inline void read(D& d, float32_t scale) + { + this->read(d, 0, scale); + } + + inline void lp(float32_t& state, float32_t coefficient) + { + state += coefficient * (this->accumulator_ - state); + this->accumulator_ = state; + } + + inline void hp(float32_t& state, float32_t coefficient) + { + state += coefficient * (this->accumulator_ - state); + this->accumulator_ -= state; + } + + template + inline void interpolate(D& d, float32_t offset, float32_t scale) + { + assert((D::base + D::length) <= size); + + MAKE_INTEGRAL_FRACTIONAL(offset); + + int32_t index = this->write_ptr_ + offset_integral + D::base; + float32_t a = DataType::decompress(this->buffer_[index & MASK]); + float32_t b = DataType::decompress(this->buffer_[(index + 1) & MASK]); + float32_t x = a + (b - a) * offset_fractional; + + this->previous_read_ = x; + this->accumulator_ += x * scale; + } + + template + inline void interpolate(D& d, float32_t offset, LFOIndex index, float32_t amplitude, float32_t scale) + { + assert(index < LFOIndex::kLFOCount); + + this->interpolate(d, offset + amplitude * (this->lfo_value_[index] * 0.5f + 0.5f), scale); + } + + private: + float32_t accumulator_; + float32_t previous_read_; + float32_t lfo_value_[LFOIndex::kLFOCount]; + T* buffer_; + int32_t write_ptr_; + }; + + inline void setLFOFrequency(LFOIndex index, float32_t frequency) + { + assert(index < LFOIndex::kLFOCount); + if(enable_lfo) + { + this->lfo_[index]->setFrequency(frequency); + } + } + + inline void setLFONormalizedFrequency(LFOIndex index, float32_t normalized_frequency) + { + assert(index < LFOIndex::kLFOCount); + if(enable_lfo) + { + this->lfo_[index]->setNormalizedFrequency(normalized_frequency); + } + } + + inline void start(Context* c) + { + --this->write_ptr_; + if(this->write_ptr_ < 0) + { + this->write_ptr_ += size; + } + c->accumulator_ = 0.0f; + c->previous_read_ = 0.0f; + c->buffer_ = this->buffer_; + c->write_ptr_ = this->write_ptr_; + if(enable_lfo) + { + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) + { + c->lfo_value_[i] = this->lfo_[i]->process(); + } + } + } + +private: + enum + { + MASK = size - 1 + }; + + T* buffer_; + int32_t write_ptr_; + + LFO* lfo_[LFOIndex::kLFOCount]; + + IMPLEMENT_DUMP( + const size_t space = 10; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "write_ptr_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->write_ptr_); + out << "\t" << ss.str() << std::endl; + + if(deepInspection) + { + out << "FXEngine internal buffer:" << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "index"); + SS__TEXT(ss, ' ', space, std::left, '|', "buffer_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + for(size_t i = 0; i < size; ++i) + { + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", i); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->buffer_[i]); + out << "\t" << ss.str() << std::endl; + } + + if(enable_lfo) + { + for(size_t i = 0; i < LFOIndex::kLFOCount; ++i) + { + this->lfo_[i]->dump(out, deepInspection, tag + ".lfo_[ " + std::to_string(i) + " ]"); + } + } + } + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".write_ptr_", static_cast(this->write_ptr_), 0.0f, static_cast(size), deepInspection); + if(deepInspection) + { + for(size_t i = 0; i < size; ++i) + { + nb_errors += inspector(tag + ".buffer[ " + std::to_string(i) + " ]", this->buffer_[i], -1.0f, 1.0f, deepInspection); + } + + if(enable_lfo) + { + for(size_t i = 0; i < LFOIndex::kLFOCount; ++i) + { + this->lfo_[i]->inspect(inspector, deepInspection, tag + ".lfo_[ " + std::to_string(i) + " ]"); + } + } + } + + return nb_errors; + + ) +}; diff --git a/src/fx_flanger.cpp b/src/fx_flanger.cpp new file mode 100644 index 00000000..09081380 --- /dev/null +++ b/src/fx_flanger.cpp @@ -0,0 +1,145 @@ +#include "fx_flanger.h" + +Flanger::Flanger(float32_t sampling_rate, float32_t rate, float32_t depth, float32_t feedback) : + FXElement(sampling_rate, 0.928f), + MaxDelayLineSize(static_cast(MAX_FLANGER_DELAY * sampling_rate)), + write_index_(0) +{ + this->delay_lineL_ = new float32_t[this->MaxDelayLineSize]; + this->delay_lineR_ = new float32_t[this->MaxDelayLineSize]; + + this->lfo_[LFOIndex::LFO_L] = new LFO(sampling_rate, 0.1f, 5.0f, 0.0f, false); + this->lfo_[LFOIndex::LFO_R] = new LFO(sampling_rate, 0.1f, 5.0f, Constants::MPI_2, false); + + this->setRate(rate); + this->setDepth(depth); + this->setFeedback(feedback); + + this->reset(); +} + +Flanger::~Flanger() +{ + delete[] this->delay_lineL_; + delete[] this->delay_lineR_; + + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) + { + delete this->lfo_[i]; + } +} + +inline float32_t linearIterpolationnterp(float32_t inX, float32_t inY, float32_t inPhase) +{ + return (1.0f - inPhase) * inX + inPhase * inY; +} + +void Flanger::reset() +{ + memset(this->delay_lineL_, 0, this->MaxDelayLineSize * sizeof(float32_t)); + memset(this->delay_lineR_, 0, this->MaxDelayLineSize * sizeof(float32_t)); + memset(this->feedback_samples_, 0, StereoChannels::kNumChannels * sizeof(float32_t)); + this->write_index_ = 0; + + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) + { + this->lfo_[i]->reset(); + } +} + +void Flanger::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + // Write sample and any feedback into delay buffers + this->delay_lineL_[this->write_index_] = inL + this->feedback_samples_[StereoChannels::Left ]; + this->delay_lineR_[this->write_index_] = inR + this->feedback_samples_[StereoChannels::Right]; + + ++this->write_index_; + if(this->write_index_ >= this->MaxDelayLineSize) + { + this->write_index_ -= this->MaxDelayLineSize; + } + + // Configure LFO for effect processing + float32_t lfo_l = this->lfo_[LFOIndex::LFO_L]->process() * this->depth_; + float32_t lfo_r = this->lfo_[LFOIndex::LFO_R]->process() * this->depth_; + + // Map LFO range to millisecond range according to Chorus or Flanger effect + float32_t lfoMappedL = mapfloat(lfo_l, -1.0f, 1.0f, 0.001f, 0.005f); + float32_t lfoMappedR = mapfloat(lfo_r, -1.0f, 1.0f, 0.001f, 0.005f); + + // Calculate delay lengths in samples + float32_t delayTimeSamplesL = this->getSamplingRate() * lfoMappedL; + float32_t delayTimeSamplesR = this->getSamplingRate() * lfoMappedR; + + // Calculate read head positions + float32_t delayReadHeadL = this->write_index_ - delayTimeSamplesL; + if(delayReadHeadL < 0.0f) + { + delayReadHeadL += this->MaxDelayLineSize; + } + float32_t delayReadHeadR = this->write_index_ - delayTimeSamplesR; + if(delayReadHeadR < 0.0f) + { + delayReadHeadR += this->MaxDelayLineSize; + } + + // Calculate linear interpolation point for left channel + int32_t currentL = static_cast(delayReadHeadL); + int32_t nextL = currentL + 1; + float32_t fractionL = delayReadHeadL - currentL; + if(nextL >= static_cast(this->MaxDelayLineSize)) + { + nextL -= this->MaxDelayLineSize; + } + + // Calculate linear interpolation point for right channel + int32_t currentR = static_cast(delayReadHeadR); + int32_t nextR = currentR + 1; + float32_t fractionR = delayReadHeadR - currentR; + if(nextR >= static_cast(this->MaxDelayLineSize)) + { + nextR -= this->MaxDelayLineSize; + } + + // Interpolate and read from delay buffer + float32_t delay_sample_l = linearIterpolationnterp(this->delay_lineL_[currentL], this->delay_lineL_[nextL], fractionL); + float32_t delay_sample_r = linearIterpolationnterp(this->delay_lineR_[currentR], this->delay_lineR_[nextR], fractionR); + + // Store delayed samples as feedback + this->feedback_samples_[StereoChannels::Left ] = delay_sample_l * this->feedback_; + this->feedback_samples_[StereoChannels::Right] = delay_sample_r * this->feedback_; + + outL = delay_sample_l * this->OutputLevelCorrector; + outR = delay_sample_r * this->OutputLevelCorrector; +} + +void Flanger::setRate(float32_t rate) +{ + this->lfo_[LFOIndex::LFO_L]->setNormalizedFrequency(rate); + this->lfo_[LFOIndex::LFO_R]->setNormalizedFrequency(rate); +} + +float32_t Flanger::getRate() const +{ + return this->lfo_[LFOIndex::LFO_L]->getNormalizedFrequency(); +} + +void Flanger::setDepth(float32_t depth) +{ + this->depth_ = constrain(depth, 0.0f, 1.0f); +} + +float32_t Flanger::getDepth() const +{ + return this->depth_; +} + +void Flanger::setFeedback(float32_t feedback) +{ + this->feedback_ = constrain(feedback, 0.0f, 0.97f); +} + +float32_t Flanger::getFeedback() const +{ + return this->feedback_; +} diff --git a/src/fx_flanger.h b/src/fx_flanger.h new file mode 100644 index 00000000..89c7513e --- /dev/null +++ b/src/fx_flanger.h @@ -0,0 +1,155 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_flanger.h +// +// Stereo Flanger audio effects proposed in the context of the MiniDexed project +// Author: Vincent Gauché +// +#pragma once + +#include "fx_components.h" + +#define MAX_FLANGER_DELAY 2.0f + +class Flanger : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(Flanger); + +public: + enum LFOIndex + { + LFO_L = 0, + LFO_R, + kLFOCount + }; + + Flanger(float32_t sampling_rate, float32_t rate = 0.5f, float32_t depth = 0.5f, float32_t feedback = 0.0f); + virtual ~Flanger(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setRate(float32_t rate); + float32_t getRate() const; + + void setDepth(float32_t depth); + float32_t getDepth() const; + + void setFeedback(float32_t feedback); + float32_t getFeedback() const; + +private: + const unsigned MaxDelayLineSize; + float32_t* delay_lineL_; + float32_t* delay_lineR_; + unsigned write_index_; + float32_t feedback_samples_[StereoChannels::kNumChannels]; + + LFO* lfo_[LFOIndex::kLFOCount]; + float32_t depth_; // Depth of the flanger effect in milliseconds (0.0 - 10.0) + float32_t feedback_; // Amount of feedback to apply to the delay line + + IMPLEMENT_DUMP( + const size_t space = 22; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "write_index_"); + SS__TEXT(ss, ' ', space, std::left, '|', "feedback_samples_[ L ]"); + SS__TEXT(ss, ' ', space, std::left, '|', "feedback_samples_[ R ]"); + SS__TEXT(ss, ' ', space, std::left, '|', "depth_"); + SS__TEXT(ss, ' ', space, std::left, '|', "feedback_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->write_index_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->feedback_samples_[StereoChannels::Left ]); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->feedback_samples_[StereoChannels::Right]); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->depth_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->feedback_); + out << "\t" << ss.str() << std::endl; + + if(deepInspection) + { + out << "Flanger internal delay lines:" << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "index"); + SS__TEXT(ss, ' ', space, std::left, '|', "delay_lineL_"); + SS__TEXT(ss, ' ', space, std::left, '|', "delay_lineR_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + for(size_t i = 0; i < this->MaxDelayLineSize; ++i) + { + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", i); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->delay_lineL_[i]); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->delay_lineR_[i]); + out << "\t" << ss.str() << std::endl; + } + + for(size_t i = 0; i < LFOIndex::kLFOCount; ++i) + { + this->lfo_[i]->dump(out, deepInspection, tag + ".lfo_[ " + std::to_string(i) + " ]"); + } + } + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".write_index_", static_cast(this->write_index_), 0.0, static_cast(this->MaxDelayLineSize), deepInspection); + nb_errors += inspector(tag + ".feedback_samples_[ L ]", this->feedback_samples_[StereoChannels::Left ], -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".feedback_samples_[ R ]", this->feedback_samples_[StereoChannels::Right], -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".depth_", this->depth_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".feedback_", this->feedback_, 0.0f, 0.97f, deepInspection); + + if(deepInspection) + { + for(size_t i = 0; i < this->MaxDelayLineSize; ++i) + { + nb_errors += inspector(tag + ".delay_lineL_[ " + std::to_string(i) + " ]", this->delay_lineL_[i], -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".delay_lineR_[ " + std::to_string(i) + " ]", this->delay_lineR_[i], -1.0f, 1.0f, deepInspection); + } + + for(size_t i = 0; i < LFOIndex::kLFOCount; ++i) + { + nb_errors += this->lfo_[i]->inspect(inspector, deepInspection, tag + ".lfo_[ " + std::to_string(i) + " ]"); + } + } + + return nb_errors; + ) +}; diff --git a/src/fx_orbitone.cpp b/src/fx_orbitone.cpp new file mode 100644 index 00000000..764b46d8 --- /dev/null +++ b/src/fx_orbitone.cpp @@ -0,0 +1,116 @@ +#include "fx_orbitone.h" + +#define LFO_SLOW_MAX_FREQUENCY 1.0f +#define LFO_FAST_MAX_FREQUENCY 8.8f + +Orbitone::Orbitone(float32_t sampling_rate, float32_t rate, float32_t depth) : + FXElement(sampling_rate, 1.4426f), + engine_(sampling_rate, 0.0f), + depth_(0.0f), + fullscale_depth_(0.0f) +{ + this->lfo_[LFOIndex::Slow0 ] = new LFO(sampling_rate, 0.0f, LFO_SLOW_MAX_FREQUENCY, 0.0f, false); + this->lfo_[LFOIndex::Slow120] = new LFO(sampling_rate, 0.0f, LFO_SLOW_MAX_FREQUENCY, 2.0f * PI / 3.0, false); + this->lfo_[LFOIndex::Slow240] = new LFO(sampling_rate, 0.0f, LFO_SLOW_MAX_FREQUENCY, 4.0f * PI / 3.0, false); + + this->lfo_[LFOIndex::Fast0 ] = new LFO(sampling_rate, 0.0f, LFO_FAST_MAX_FREQUENCY, 0.0f, false); + this->lfo_[LFOIndex::Fast120] = new LFO(sampling_rate, 0.0f, LFO_FAST_MAX_FREQUENCY, 2.0f * PI / 3.0, false); + this->lfo_[LFOIndex::Fast240] = new LFO(sampling_rate, 0.0f, LFO_FAST_MAX_FREQUENCY, 4.0f * PI / 3.0, false); + + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) + { + this->lfo_[i]->setNormalizedFrequency(rate); + } + + this->setDepth(depth); +} + +Orbitone::~Orbitone() +{ + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) + { + delete this->lfo_[i]; + } +} + +void Orbitone::reset() +{ + this->engine_.reset(); + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) + { + this->lfo_[i]->reset(); + } +} + +void Orbitone::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + typedef Engine::Reserve<2047, Engine::Reserve<2047> > Memory; + Engine::DelayLine line_l; + Engine::DelayLine line_r; + Engine::Context c; + + this->engine_.start(&c); + + float32_t slow_0 = this->lfo_[LFOIndex::Slow0 ]->process(); + float32_t slow_120 = this->lfo_[LFOIndex::Slow120]->process(); + float32_t slow_240 = this->lfo_[LFOIndex::Slow240]->process(); + + float32_t fast_0 = this->lfo_[LFOIndex::Fast0 ]->process(); + float32_t fast_120 = this->lfo_[LFOIndex::Fast120]->process(); + float32_t fast_240 = this->lfo_[LFOIndex::Fast240]->process(); + + float32_t a = this->fullscale_depth_ * 1.0f; + float32_t b = this->fullscale_depth_ * 0.1f; + + float32_t mod_1 = slow_0 * a + fast_0 * b; + float32_t mod_2 = slow_120 * a + fast_120 * b; + float32_t mod_3 = slow_240 * a + fast_240 * b; + + float32_t wet = 0.0f; + + c.directWrite(inL, line_l); + c.interpolate(line_l, mod_1 + 1024, 0.33f); + c.interpolate(line_l, mod_2 + 1024, 0.33f); + c.interpolate(line_r, mod_3 + 1024, 0.33f); + c.writeAndLoad(wet, 0.0f); + outL = wet * this->OutputLevelCorrector; + + c.directWrite(inR, line_r); + c.interpolate(line_r, mod_1 + 1024, 0.33f); + c.interpolate(line_r, mod_2 + 1024, 0.33f); + c.interpolate(line_l, mod_3 + 1024, 0.33f); + c.writeAndLoad(wet, 0.0f); + outR = wet * this->OutputLevelCorrector; +} + +void Orbitone::setRate(float32_t rate) +{ + rate = constrain(rate, 0.0f, 1.0f); + if(this->lfo_[LFOIndex::Slow0]->getNormalizedFrequency() != rate) + { + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) + { + this->lfo_[i]->setNormalizedFrequency(rate); + } + } +} + +float32_t Orbitone::getRate() const +{ + return this->lfo_[LFOIndex::Slow0]->getNormalizedFrequency(); +} + +void Orbitone::setDepth(float32_t depth) +{ + depth = constrain(depth, 0.0f, 1.0f); + if(this->depth_ != depth) + { + this->depth_ = depth; + this->fullscale_depth_ = this->depth_ * ORBITONE_FULLSCALE_DEPTH_RATIO; + } +} + +float32_t Orbitone::getDepth() const +{ + return this->depth_; +} diff --git a/src/fx_orbitone.h b/src/fx_orbitone.h new file mode 100644 index 00000000..f8e81e94 --- /dev/null +++ b/src/fx_orbitone.h @@ -0,0 +1,119 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_orbitone.h +// +// Stereo Orbitone audio effects proposed in the context of the MiniDexed project +// This audio effect is based on the Ensemble audio effect of the Rings Eurorack module by Mutable Instruments +// Ported by: Vincent Gauché +// +#pragma once + +#include "fx_components.h" +#include "fx_engine.hpp" + +#define ORBITONE_FULLSCALE_DEPTH_RATIO 256.0f + +class Orbitone : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(Orbitone); + +public: + enum LFOIndex + { + Slow0 = 0, + Slow120, + Slow240, + Fast0, + Fast120, + Fast240, + kLFOCount + }; + + Orbitone(float32_t sampling_rate, float32_t rate = 0.5f, float32_t depth = 0.5f); + virtual ~Orbitone(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setRate(float32_t rate); + float32_t getRate() const; + + void setDepth(float32_t depth); + float32_t getDepth() const; + +private: + typedef FxEngine<4096, Format::FORMAT_FLOAT32, false> Engine; + Engine engine_; + + float32_t depth_; + float32_t fullscale_depth_; + + LFO* lfo_[LFOIndex::kLFOCount]; + + IMPLEMENT_DUMP( + const size_t space = 16; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "depth_"); + SS__TEXT(ss, ' ', space, std::left, '|', "fullscale_depth_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->depth_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->fullscale_depth_); + out << "\t" << ss.str() << std::endl; + + if(deepInspection) + { + this->engine_.dump(out, deepInspection, tag + ".engine_"); + + for(size_t i = 0; i < LFOIndex::kLFOCount; ++i) + { + this->lfo_[i]->dump(out, deepInspection, tag + ".lfo_[ " + std::to_string(i) + " ]"); + } + } + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".depth_", this->depth_, -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".fullscale_depth_", this->fullscale_depth_, 0.0f, ORBITONE_FULLSCALE_DEPTH_RATIO, deepInspection); + + if(deepInspection) + { + this->engine_.inspect(inspector, deepInspection, tag + ".engine_"); + + for(size_t i = 0; i < LFOIndex::kLFOCount; ++i) + { + this->lfo_[i]->inspect(inspector, deepInspection, tag + ".lfo_[ " + std::to_string(i) + " ]"); + } + } + + return nb_errors; + ) +}; diff --git a/src/fx_phaser.cpp b/src/fx_phaser.cpp new file mode 100644 index 00000000..ba4f3757 --- /dev/null +++ b/src/fx_phaser.cpp @@ -0,0 +1,152 @@ +#include "fx_phaser.h" + +Phaser::AllpassDelay::AllpassDelay() : + FXElement(0.0f) +{ + this->reset(); +} + +Phaser::AllpassDelay::~AllpassDelay() +{ +} + +void Phaser::AllpassDelay::reset() +{ + memset(this->a1_, 0, StereoChannels::kNumChannels * sizeof(float32_t)); + memset(this->z_, 0, StereoChannels::kNumChannels * sizeof(float32_t)); +} + +void Phaser::AllpassDelay::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + outL = inL * -this->a1_[StereoChannels::Left ] + this->z_[StereoChannels::Left ]; + this->z_[StereoChannels::Left ] = outL * this->a1_[StereoChannels::Left ] + inL; + + outR = inR * -this->a1_[StereoChannels::Right] + this->z_[StereoChannels::Right]; + this->z_[StereoChannels::Right] = outR * this->a1_[StereoChannels::Right] + inR; +} + +void Phaser::AllpassDelay::setDelay(float32_t delayL, float32_t delayR) +{ + this->a1_[StereoChannels::Left ] = (1.0f - delayL) / (1.0f + delayL); + this->a1_[StereoChannels::Right] = (1.0f - delayR) / (1.0f + delayR); +} + + +Phaser::Phaser(float32_t sampling_rate, float32_t rate, float32_t depth, float32_t feedback, unsigned nb_stages) : + FXElement(sampling_rate, 1.3804f), + depth_(0.0f), + gain_(1.0f), + feedback_(0.0f), + dmin_(0.0f), + dmax_(0.0f) +{ + this->lfo_[StereoChannels::Left ] = new LFO(sampling_rate, 0.0f, 2.5f, 0.0f, false); + this->lfo_[StereoChannels::Right] = new LFO(sampling_rate, 0.0f, 2.5f, Constants::MPI_2, false); + + this->setRate(rate); + this->setDepth(depth); + this->setFeedback(feedback); + this->setNbStages(nb_stages); + this->setFrequencyRange(440.0f, 1600.0f); + + this->reset(); +} + +Phaser::~Phaser() +{ + delete this->lfo_[StereoChannels::Left ]; + delete this->lfo_[StereoChannels::Right]; +} + +void Phaser::reset() +{ + memset(this->z_, 0, StereoChannels::kNumChannels * sizeof(float32_t)); + + for(unsigned i = 0; i < MAX_NB_PHASES; ++i) + { + this->stages_[i].reset(); + } + this->lfo_[StereoChannels::Left ]->reset(); + this->lfo_[StereoChannels::Right]->reset(); +} + +void Phaser::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + float32_t dL = this->dmin_ + (this->dmax_ - this->dmin_) * this->lfo_[StereoChannels::Left ]->process(); + float32_t dR = this->dmin_ + (this->dmax_ - this->dmin_) * this->lfo_[StereoChannels::Right]->process(); + + float32_t sampleL = inL + this->feedback_ * this->z_[StereoChannels::Left ]; + float32_t sampleR = inR + this->feedback_ * this->z_[StereoChannels::Right]; + for(unsigned i = 0; i < this->nb_stages_; ++i) + { + this->stages_[i].setDelay(dL, dR); + this->stages_[i].processSample(sampleL, sampleR, sampleL, sampleR); + } + this->z_[StereoChannels::Left ] = sampleL; + this->z_[StereoChannels::Right] = sampleR; + + outL = inL + this->z_[StereoChannels::Left ] * this->depth_; + outR = inR + this->z_[StereoChannels::Right] * this->depth_; + + outL *= this->gain_; + outR *= this->gain_; +} + +void Phaser::setFrequencyRange(float32_t min_frequency, float32_t max_frequency) +{ + this->dmin_ = 2.0f * std::min(min_frequency, max_frequency) / this->getSamplingRate(); + this->dmax_ = 2.0f * std::max(min_frequency, max_frequency) / this->getSamplingRate(); +} + +void Phaser::setRate(float32_t rate) +{ + rate = constrain(rate, 0.0f, 1.0f); + this->lfo_[StereoChannels::Left ]->setNormalizedFrequency(rate); + this->lfo_[StereoChannels::Right]->setNormalizedFrequency(rate); +} + +float32_t Phaser::getRate() const +{ + return this->lfo_[StereoChannels::Left]->getNormalizedFrequency(); +} + +void Phaser::setDepth(float32_t depth) +{ + depth = constrain(depth, 0.0f, 1.0f); + this->depth_ = depth; + this->gain_ = this->OutputLevelCorrector / (1.0f + depth); +} + +float32_t Phaser::getDepth() const +{ + return this->depth_; +} + +void Phaser::setFeedback(float32_t feedback) +{ + feedback = constrain(feedback, 0.0f, 0.97f); + this->feedback_ = feedback; +} + +float32_t Phaser::getFeedback() const +{ + return this->feedback_; +} + +void Phaser::setNbStages(unsigned nb_stages) +{ + if(nb_stages < 2) + { + nb_stages = 2; + } + else if(nb_stages > MAX_NB_PHASES) + { + nb_stages = MAX_NB_PHASES; + } + this->nb_stages_ = nb_stages; +} + +unsigned Phaser::getNbStages() const +{ + return this->nb_stages_; +} diff --git a/src/fx_phaser.h b/src/fx_phaser.h new file mode 100644 index 00000000..6ff605bc --- /dev/null +++ b/src/fx_phaser.h @@ -0,0 +1,177 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_phaser.h +// +// Stereo Phaser audio effects proposed in the context of the MiniDexed project +// Author: Vincent Gauché +// +#pragma once + +#include "fx_components.h" + +#define MAX_NB_PHASES 24 + +class Phaser : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(Phaser); + +public: + class AllpassDelay : public FXElement + { + DISALLOW_COPY_AND_ASSIGN(AllpassDelay); + + public: + AllpassDelay(); + virtual ~AllpassDelay(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setDelay(float32_t delayL, float32_t delayR); + + private: + float32_t a1_[StereoChannels::kNumChannels]; + float32_t z_[StereoChannels::kNumChannels]; + + IMPLEMENT_DUMP( + const size_t space = 10; + const size_t precision = 6; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "a1_"); + SS__TEXT(ss, ' ', space, std::left, '|', "z_[ L ]"); + SS__TEXT(ss, ' ', space, std::left, '|', "z_[ R ]"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->a1_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->z_[StereoChannels::Left ]); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->z_[StereoChannels::Right]); + out << "\t" << ss.str() << std::endl; + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + return 0u; + ) + }; + + Phaser(float32_t sampling_rate, float32_t rate = 0.5f, float32_t depth = 1.0f, float32_t feedback = 0.7f, unsigned nb_stages = 12); + virtual ~Phaser(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setFrequencyRange(float32_t min_frequency, float32_t max_frequency); + + void setRate(float32_t rate); + float32_t getRate() const; + + void setDepth(float32_t depth); + float32_t getDepth() const; + + void setFeedback(float32_t depth); + float32_t getFeedback() const; + + void setNbStages(unsigned nb_stages); + unsigned getNbStages() const; + +private: + LFO* lfo_[StereoChannels::kNumChannels]; + float32_t depth_; + float32_t gain_; + float32_t feedback_; + float32_t dmin_; + float32_t dmax_; + unsigned nb_stages_; + AllpassDelay stages_[MAX_NB_PHASES]; + float32_t z_[StereoChannels::kNumChannels]; + + IMPLEMENT_DUMP( + const size_t space = 12; + const size_t precision = 6; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "depth_"); + SS__TEXT(ss, ' ', space, std::left, '|', "feedback_"); + SS__TEXT(ss, ' ', space, std::left, '|', "dmin_"); + SS__TEXT(ss, ' ', space, std::left, '|', "dmax_"); + SS__TEXT(ss, ' ', space, std::left, '|', "nb_stages_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->depth_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->feedback_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->dmin_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->dmax_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->nb_stages_); + out << "\t" << ss.str() << std::endl; + + if(deepInspection) + { + this->lfo_[StereoChannels::Left ]->dump(out, deepInspection, tag + ".lfo_[ L ]"); + this->lfo_[StereoChannels::Right]->dump(out, deepInspection, tag + ".lfo_[ R ]"); + for(unsigned i = 0; i < MAX_NB_PHASES; ++i) + { + this->stages_[i].dump(out, deepInspection, tag + ".stages_[ " + std::to_string(i) + " ]"); + } + } + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".depth_", this->depth_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".feedback_", this->feedback_, 0.0f, 0.97f, deepInspection); + nb_errors += inspector(tag + ".nb_stages_", static_cast(this->nb_stages_), 0.0f, static_cast(MAX_NB_PHASES), deepInspection); + + if(deepInspection) + { + nb_errors += this->lfo_[StereoChannels::Left ]->inspect(inspector, deepInspection, tag + ".lfo_[ L ]"); + nb_errors += this->lfo_[StereoChannels::Right]->inspect(inspector, deepInspection, tag + ".lfo_[ R ]"); + for(unsigned i = 0; i < MAX_NB_PHASES; ++i) + { + nb_errors += this->stages_[i].inspect(inspector, deepInspection, tag + ".stages_[ " + std::to_string(i) + " ]"); + } + } + + return nb_errors; + ) +}; \ No newline at end of file diff --git a/src/fx_pitch_shifter.cpp b/src/fx_pitch_shifter.cpp new file mode 100644 index 00000000..5bb053ad --- /dev/null +++ b/src/fx_pitch_shifter.cpp @@ -0,0 +1,104 @@ +#include "fx_pitch_shifter.h" +#include "fx_shimmer_helper.h" + +#include +#include + +#define ONE_POLE(out, in, coefficient) out += (coefficient) * ((in) - out); + +#define TAIL , -1 + +PitchShifter::PitchShifter(float32_t sampling_rate, float32_t transpose_boundary) : + FXElement(sampling_rate), + engine_(sampling_rate), + TransposeBoundary(transpose_boundary), + phase_(0.0f), + transpose_(0.0f), + ratio_(0.0f), + size_(-1.0f), + sample_size_(0.0f) +{ + this->setTranspose(0.0f); + this->setSize(0.5f); +} + +PitchShifter::~PitchShifter() +{ +} + +void PitchShifter::reset() +{ + this->engine_.reset(); +} + +void PitchShifter::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + typedef Engine::Reserve<2047, Engine::Reserve<2047> > Memory; + Engine::DelayLine left; + Engine::DelayLine right; + Engine::Context c; + + this->engine_.start(&c); + + this->phase_ += (1.0f - this->ratio_) / this->sample_size_; + if(this->phase_ >= 1.0f) + { + phase_ -= 1.0f; + } + if(this->phase_ <= 0.0f) + { + this->phase_ += 1.0f; + } + + float tri = 2.0f * (this->phase_ >= 0.5f ? 1.0f - phase_ : phase_); + float phase = this->phase_ * this->sample_size_; + float half = phase + this->sample_size_ * 0.5f; + if(half >= this->sample_size_) + { + half -= this->sample_size_; + } + + c.load(inL); + c.writeAndLoad(left, 0.0f); + c.interpolate(left, phase, tri); + c.interpolate(left, half, 1.0f - tri); + c.writeAndLoad(outL, 0.0f); + + c.load(inR); + c.writeAndLoad(right, 0.0f); + c.interpolate(right, phase, tri); + c.interpolate(right, half, 1.0f - tri); + c.writeAndLoad(outR, 0.0f); +} + +void PitchShifter::setTranspose(float32_t transpose) +{ + transpose = constrain(transpose, -this->TransposeBoundary, this->TransposeBoundary); + if(this->transpose_ != transpose) + { + this->transpose_ = transpose; + this->ratio_ = semitoneToRatio(transpose); + } +} + +float32_t PitchShifter::getTranspose() const +{ + return this->transpose_; +} + +void PitchShifter::setSize(float32_t size) +{ + size = constrain(size, 0.0f, 1.0f); + if(size != this->size_) + { + this->size_ = size; + + float32_t target_size = 128.0f + (2047.0f - 128.0f) * size * size * size; + ONE_POLE(this->sample_size_, target_size, 0.05f); + } +} + +float32_t PitchShifter::getSize() const +{ + return this->size_; +} \ No newline at end of file diff --git a/src/fx_pitch_shifter.h b/src/fx_pitch_shifter.h new file mode 100644 index 00000000..f7fdeec0 --- /dev/null +++ b/src/fx_pitch_shifter.h @@ -0,0 +1,115 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +// +// fx_shimmer_reverb3.h +// +// Stereo Pitch Shifter proposed in the context of the MiniDexed project +// It is adapted from the Pitch Shifter that could be found on Cloud EuroRack module from Mutable Instrruments +// Ported by: Vincent Gauché +// +#pragma once + +#include "fx_components.h" +#include "fx_engine.hpp" + +#define PITCH_SHIFTER_BUFFER_SIZE 4096 +#define PITCH_SHIFTER_TRANSPOSE_BOUNDARY 36.0f + +class PitchShifter : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(PitchShifter); + +public: + PitchShifter(float32_t sampling_rate, float32_t transpose_boundary = PITCH_SHIFTER_TRANSPOSE_BOUNDARY); + virtual ~PitchShifter(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setTranspose(float32_t transpose); + float32_t getTranspose() const; + + void setSize(float32_t size); + float32_t getSize() const; + +private: + typedef FxEngine Engine; + Engine engine_; + + const float32_t TransposeBoundary; + float32_t phase_; + float32_t transpose_; + float32_t ratio_; + float32_t size_; + float32_t sample_size_; + + IMPLEMENT_DUMP( + const size_t space = 12; + const size_t precision = 6; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "phase_"); + SS__TEXT(ss, ' ', space, std::left, '|', "transpose_"); + SS__TEXT(ss, ' ', space, std::left, '|', "ratio_"); + SS__TEXT(ss, ' ', space, std::left, '|', "size_"); + SS__TEXT(ss, ' ', space, std::left, '|', "sample_size_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->transpose_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->ratio_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->size_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->sample_size_); + out << "\t" << ss.str() << std::endl; + + if(deepInspection) + { + this->engine_.dump(out, deepInspection, tag + ".engine_"); + } + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".phase_", this->phase_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".transpose_", this->transpose_, -PITCH_SHIFTER_TRANSPOSE_BOUNDARY, PITCH_SHIFTER_TRANSPOSE_BOUNDARY, deepInspection); + nb_errors += inspector(tag + ".ratio_", this->ratio_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".size_", this->size_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".sample_size_", this->sample_size_, 0.0f, 1.0f, deepInspection); + + if(deepInspection) + { + nb_errors += this->engine_.inspect(inspector, deepInspection, tag + ".engine_"); + } + + return nb_errors; + ) +}; diff --git a/src/fx_rack.cpp b/src/fx_rack.cpp new file mode 100644 index 00000000..a4e2bc17 --- /dev/null +++ b/src/fx_rack.cpp @@ -0,0 +1,153 @@ +#include "fx_rack.h" + +#include + +FXRack::FXRack(float32_t sampling_rate, bool enable, float32_t wet) : + FX(sampling_rate), + FXElement(sampling_rate), + enable_(enable), + wet_level_(wet), + fx_chain_() +{ + this->fxTube_ = new FXUnit(sampling_rate); + this->fxChorus_ = new FXUnit(sampling_rate); + this->fxFlanger_ = new FXUnit(sampling_rate); + this->fxOrbitone_ = new FXUnit(sampling_rate); + this->fxPhaser_ = new FXUnit(sampling_rate); + this->fxDelay_ = new FXUnit(sampling_rate); + this->fxReverberator_ = new FXUnit(sampling_rate); + + this->registerFX(this->fxTube_); + this->registerFX(this->fxChorus_); + this->registerFX(this->fxFlanger_); + this->registerFX(this->fxOrbitone_); + this->registerFX(this->fxPhaser_); + this->registerFX(this->fxDelay_); + this->registerFX(this->fxReverberator_); +} + +FXRack::~FXRack() +{ + this->fx_chain_.clear(); + + delete this->fxTube_; + delete this->fxChorus_; + delete this->fxFlanger_; + delete this->fxOrbitone_; + delete this->fxPhaser_; + delete this->fxDelay_; + delete this->fxReverberator_; +} + +inline void FXRack::reset() +{ + for(FXElement* fx : this->fx_chain_) + { + fx->reset(); + } +} + +inline void FXRack::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + for(FXElement* fx : this->fx_chain_) + { + fx->processSample(inL, inR, outL, outR); + + inL = outL; + inR = outR; + } +} + +void FXRack::process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) +{ + for(unsigned i = 0; i < nSamples; ++i) + { + float32_t sampleInL = *left_input; + float32_t sampleInR = *right_input; + float32_t sampleOutL = 0.0f; + float32_t sampleOutR = 0.0f; + + if(this->isEnable()) + { + this->processSample(sampleInL, sampleInR, sampleOutL, sampleOutR); + + float32_t dryLevel = 1.0f - this->getWetLevel(); + *left_output = this->getWetLevel() * sampleOutL + dryLevel * (*left_input); + *right_output = this->getWetLevel() * sampleOutR + dryLevel * (*right_input); + } + else + { + *left_output = sampleInL; + *right_output = sampleInR; + } + + // Move inputs by 1 sample + ++left_input; + ++right_input; + + // Move outputs by 1 sample + ++left_output; + ++right_output; + } +} + +void FXRack::setEnable(bool enable) +{ + this->enable_ = enable; +} + +bool FXRack::isEnable() const +{ + return this->enable_; +} + +void FXRack::setWetLevel(float32_t wet_level) +{ + this->wet_level_ = constrain(wet_level, 0.0f, 1.0f); +} + +float32_t FXRack::getWetLevel() const +{ + return this->wet_level_; +} + +void FXRack::registerFX(FXElement* fx) +{ + assert(fx); + this->fx_chain_.push_back(fx); +} + +FXUnit* FXRack::getTube() +{ + return this->fxTube_; +} + +FXUnit* FXRack::getChorus() +{ + return this->fxChorus_; +} + +FXUnit* FXRack::getFlanger() +{ + return this->fxFlanger_; +} + +FXUnit* FXRack::getOrbitone() +{ + return this->fxOrbitone_; +} + +FXUnit* FXRack::getPhaser() +{ + return this->fxPhaser_; +} + +FXUnit* FXRack::getDelay() +{ + return this->fxDelay_; +} + +FXUnit* FXRack::getReverberator() +{ + return this->fxReverberator_; +} diff --git a/src/fx_rack.h b/src/fx_rack.h new file mode 100644 index 00000000..0c61d722 --- /dev/null +++ b/src/fx_rack.h @@ -0,0 +1,133 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_rack.h +// +// Rack of audio effects proposed in the context of the MiniDexed project +// Author: Vincent Gauché +// +#pragma once + +#include "fx.h" +#include "fx_tube.h" +#include "fx_chorus.h" +#include "fx_flanger.h" +#include "fx_orbitone.h" +#include "fx_phaser.h" +#include "fx_delay.h" +#include "fx_reverberator.h" +#include "fx_unit.hpp" + +#include + +typedef std::vector FXChain; + +class FXRack : virtual public FX, virtual public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(FXRack); + +public: + FXRack(float32_t sampling_rate, bool enable = true, float32_t wet = 1.0f); + virtual ~FXRack(); + + virtual void reset() override; + virtual inline void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + virtual void process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) override; + + void setEnable(bool enable = true); + bool isEnable() const; + + void setWetLevel(float32_t wet_level); + float32_t getWetLevel() const; + + FXUnit* getTube(); + FXUnit* getChorus(); + FXUnit* getFlanger(); + FXUnit* getOrbitone(); + FXUnit* getPhaser(); + FXUnit* getDelay(); + FXUnit* getReverberator(); + +private: + void registerFX(FXElement* fx); + + bool enable_; + float32_t wet_level_; + + FXChain fx_chain_; + FXUnit* fxTube_; + FXUnit* fxChorus_; + FXUnit* fxFlanger_; + FXUnit* fxOrbitone_; + FXUnit* fxPhaser_; + FXUnit* fxDelay_; + FXUnit* fxReverberator_; + + IMPLEMENT_DUMP( + const size_t space = 10; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "enable_"); + SS__TEXT(ss, ' ', space, std::left, '|', "wet_level_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->enable_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->wet_level_); + out << "\t" << ss.str() << std::endl; + + if(deepInspection) + { + this->fxTube_->dump(out, deepInspection, tag + ".fxTube_"); + this->fxChorus_->dump(out, deepInspection, tag + ".fxChorus_"); + this->fxFlanger_->dump(out, deepInspection, tag + ".fxFlanger_"); + this->fxOrbitone_->dump(out, deepInspection, tag + ".fxOrbitone_"); + this->fxPhaser_->dump(out, deepInspection, tag + ".fxPhaser_"); + this->fxDelay_->dump(out, deepInspection, tag + ".fxDelay_"); + this->fxReverberator_->dump(out, deepInspection, tag + ".fxReverberator_"); + } + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0; + + nb_errors += inspector(tag + ".enable_", this->enable_, -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".wet_level_", this->wet_level_, -1.0f, 1.0f, deepInspection); + + if(deepInspection) + { + nb_errors += this->fxTube_->inspect(inspector, deepInspection, tag + ".fxTube_"); + nb_errors += this->fxChorus_->inspect(inspector, deepInspection, tag + ".fxChorus_"); + nb_errors += this->fxFlanger_->inspect(inspector, deepInspection, tag + ".fxFlanger_"); + nb_errors += this->fxOrbitone_->inspect(inspector, deepInspection, tag + ".fxOrbitone_"); + nb_errors += this->fxPhaser_->inspect(inspector, deepInspection, tag + ".fxPhaser_"); + nb_errors += this->fxDelay_->inspect(inspector, deepInspection, tag + ".fxDelay_"); + nb_errors += this->fxReverberator_->inspect(inspector, deepInspection, tag + ".fxReverberator_"); + } + + return nb_errors; + ) +}; \ No newline at end of file diff --git a/src/fx_reverberator.cpp b/src/fx_reverberator.cpp new file mode 100644 index 00000000..139970f3 --- /dev/null +++ b/src/fx_reverberator.cpp @@ -0,0 +1,160 @@ +#include "fx_reverberator.h" + +#define TAIL , -1 + +Reverberator::Reverberator(float32_t sampling_rate) : + FXElement(sampling_rate), + engine_(sampling_rate), + input_gain_(-1.0f), + reverb_time_(0.0f), + diffusion_(-1.0f), + lp_(-1.0f), + lp_decay_1_(0.0f), + lp_decay_2_(0.0f) +{ + this->engine_.setLFOFrequency(Engine::LFOIndex::LFO_1, 0.5f); + this->engine_.setLFOFrequency(Engine::LFOIndex::LFO_2, 0.3f); + + this->setInputGain(1.0f); + this->setTime(0.7f); + this->setDiffusion(0.625f); + this->setLP(0.7f); + + this->reset(); +} + +Reverberator::~Reverberator() +{ +} + +void Reverberator::reset() +{ + this->engine_.reset(); + this->lp_decay_1_ = 0.0f; + this->lp_decay_2_ = 0.0f; +} + +void Reverberator::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + // This is the Griesinger topology described in the Dattorro paper + // (4 AP diffusers on the input, then a loop of 2x 2AP+1Delay). + // Modulation is applied in the loop of the first diffuser AP for additional + // smearing; and to the two long delays for a slow shimmer/chorus effect. + typedef Engine::Reserve< 113, + Engine::Reserve< 162, + Engine::Reserve< 241, + Engine::Reserve< 399, + Engine::Reserve<1653, + Engine::Reserve<2038, + Engine::Reserve<3411, + Engine::Reserve<1913, + Engine::Reserve<1663, + Engine::Reserve<4782> > > > > > > > > > Memory; + Engine::DelayLine ap1; + Engine::DelayLine ap2; + Engine::DelayLine ap3; + Engine::DelayLine ap4; + Engine::DelayLine dap1a; + Engine::DelayLine dap1b; + Engine::DelayLine del1; + Engine::DelayLine dap2a; + Engine::DelayLine dap2b; + Engine::DelayLine del2; + Engine::Context c; + + const float32_t kap = this->diffusion_; + const float32_t klp = this->lp_; + const float32_t krt = this->reverb_time_; + const float32_t gain = this->input_gain_; + + float32_t lp_1 = this->lp_decay_1_; + float32_t lp_2 = this->lp_decay_2_; + + float32_t wet = 0.0f; + float32_t apout = 0.0f; + engine_.start(&c); + + // Smear AP1 inside the loop. + c.interpolate(ap1, 10.0f, Engine::LFOIndex::LFO_1, 60.0f, 1.0f); + c.writeAndLoad(ap1, 100, 0.0f); + c.read(inL + inR, gain); + + // Diffuse through 4 allpasses. + c.read(ap1 TAIL, kap); + c.writeAllPass(ap1, -kap); + c.read(ap2 TAIL, kap); + c.writeAllPass(ap2, -kap); + c.read(ap3 TAIL, kap); + c.writeAllPass(ap3, -kap); + c.read(ap4 TAIL, kap); + c.writeAllPass(ap4, -kap); + c.write(apout); + + // Main reverb loop. + c.load(apout); + c.interpolate(del2, 4680.0f, Engine::LFOIndex::LFO_2, 100.0f, krt); + c.lp(lp_1, klp); + c.read(dap1a TAIL, -kap); + c.writeAllPass(dap1a, kap); + c.read(dap1b TAIL, kap); + c.writeAllPass(dap1b, -kap); + c.write(del1, 2.0f); + c.writeAndLoad(wet, 0.0f); + + outL = wet; + + c.load(apout); + c.read(del1 TAIL, krt); + c.lp(lp_2, klp); + c.read(dap2a TAIL, kap); + c.writeAllPass(dap2a, -kap); + c.read(dap2b TAIL, -kap); + c.writeAllPass(dap2b, kap); + c.write(del2, 2.0f); + c.writeAndLoad(wet, 0.0f); + + outR = wet; + + this->lp_decay_1_ = lp_1; + this->lp_decay_2_ = lp_2; +} + +void Reverberator::setInputGain(float32_t gain) +{ + this->input_gain_ = constrain(gain, 0.0f, 1.0f); +} + +float32_t Reverberator::getInputGain() const +{ + return this->input_gain_; +} + +void Reverberator::setTime(float32_t time) +{ + this->reverb_time_ = constrain(time, 0.0f, 1.0f); +} + +float32_t Reverberator::getTime() const +{ + return this->reverb_time_; +} + +void Reverberator::setDiffusion(float32_t diffusion) +{ + this->diffusion_ = constrain(diffusion, 0.0f, 1.0f); +} + +float32_t Reverberator::getDiffusion() const +{ + return this->diffusion_; +} + +void Reverberator::setLP(float32_t lp) +{ + this->lp_ = constrain(lp, 0.0f, 1.0f); +} + +float32_t Reverberator::getLP() const +{ + return this->lp_; +} diff --git a/src/fx_reverberator.h b/src/fx_reverberator.h new file mode 100644 index 00000000..ef79883d --- /dev/null +++ b/src/fx_reverberator.h @@ -0,0 +1,124 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +// +// fx_reverberator.h +// +// Stereo Reverberator proposed in the context of the MiniDexed project +// It is adapted from the Reverb that could be found on Cloud EuroRack module from Mutable Instrruments +// Ported by: Vincent Gauché +// +#pragma once + +#include "fx_components.h" +#include "fx_engine.hpp" + +#define REVERBERATOR_BUFFER_SIZE 16384 + +class Reverberator : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(Reverberator); + +public: + Reverberator(float32_t sampling_rate); + virtual ~Reverberator(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setInputGain(float32_t gain); + float32_t getInputGain() const; + + void setTime(float32_t time); + float32_t getTime() const; + + void setDiffusion(float32_t diffusion); + float32_t getDiffusion() const; + + void setLP(float32_t lp); + float32_t getLP() const; + +private: + typedef FxEngine Engine; + Engine engine_; + + float32_t input_gain_; + float32_t reverb_time_; + float32_t diffusion_; + float32_t lp_; + + float32_t lp_decay_1_; + float32_t lp_decay_2_; + + IMPLEMENT_DUMP( + const size_t space = 12; + const size_t precision = 6; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "input_gain_"); + SS__TEXT(ss, ' ', space, std::left, '|', "reverb_time_"); + SS__TEXT(ss, ' ', space, std::left, '|', "diffusion_"); + SS__TEXT(ss, ' ', space, std::left, '|', "lp_"); + SS__TEXT(ss, ' ', space, std::left, '|', "lp_decay_1_"); + SS__TEXT(ss, ' ', space, std::left, '|', "lp_decay_2_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->input_gain_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->reverb_time_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->diffusion_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->lp_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->lp_decay_1_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->lp_decay_2_); + out << "\t" << ss.str() << std::endl; + + if(deepInspection) + { + this->engine_.dump(out, deepInspection, tag + ".engine_"); + } + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".input_gain_", this->input_gain_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".reverb_time_", this->reverb_time_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".diffusion_", this->diffusion_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".lp_", this->lp_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".lp_decay_1_", this->lp_decay_1_, -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".lp_decay_2_", this->lp_decay_2_, -1.0f, 1.0f, deepInspection); + + if(deepInspection) + { + nb_errors += this->engine_.inspect(inspector, deepInspection, tag + ".engine_"); + } + + return nb_errors; + ) +}; diff --git a/src/fx_shimmer_helper.cpp b/src/fx_shimmer_helper.cpp new file mode 100644 index 00000000..bed1fad0 --- /dev/null +++ b/src/fx_shimmer_helper.cpp @@ -0,0 +1,135 @@ +#include "fx_shimmer_helper.h" + +const float lut_pitch_ratio_high[] = { + 6.151958251e-04, 6.517772725e-04, 6.905339660e-04, 7.315952524e-04, + 7.750981699e-04, 8.211879055e-04, 8.700182794e-04, 9.217522585e-04, + 9.765625000e-04, 1.034631928e-03, 1.096154344e-03, 1.161335073e-03, + 1.230391650e-03, 1.303554545e-03, 1.381067932e-03, 1.463190505e-03, + 1.550196340e-03, 1.642375811e-03, 1.740036559e-03, 1.843504517e-03, + 1.953125000e-03, 2.069263856e-03, 2.192308688e-03, 2.322670146e-03, + 2.460783301e-03, 2.607109090e-03, 2.762135864e-03, 2.926381010e-03, + 3.100392680e-03, 3.284751622e-03, 3.480073118e-03, 3.687009034e-03, + 3.906250000e-03, 4.138527712e-03, 4.384617376e-03, 4.645340293e-03, + 4.921566601e-03, 5.214218180e-03, 5.524271728e-03, 5.852762019e-03, + 6.200785359e-03, 6.569503244e-03, 6.960146235e-03, 7.374018068e-03, + 7.812500000e-03, 8.277055425e-03, 8.769234752e-03, 9.290680586e-03, + 9.843133202e-03, 1.042843636e-02, 1.104854346e-02, 1.170552404e-02, + 1.240157072e-02, 1.313900649e-02, 1.392029247e-02, 1.474803614e-02, + 1.562500000e-02, 1.655411085e-02, 1.753846950e-02, 1.858136117e-02, + 1.968626640e-02, 2.085687272e-02, 2.209708691e-02, 2.341104808e-02, + 2.480314144e-02, 2.627801298e-02, 2.784058494e-02, 2.949607227e-02, + 3.125000000e-02, 3.310822170e-02, 3.507693901e-02, 3.716272234e-02, + 3.937253281e-02, 4.171374544e-02, 4.419417382e-02, 4.682209615e-02, + 4.960628287e-02, 5.255602595e-02, 5.568116988e-02, 5.899214454e-02, + 6.250000000e-02, 6.621644340e-02, 7.015387802e-02, 7.432544469e-02, + 7.874506562e-02, 8.342749089e-02, 8.838834765e-02, 9.364419230e-02, + 9.921256575e-02, 1.051120519e-01, 1.113623398e-01, 1.179842891e-01, + 1.250000000e-01, 1.324328868e-01, 1.403077560e-01, 1.486508894e-01, + 1.574901312e-01, 1.668549818e-01, 1.767766953e-01, 1.872883846e-01, + 1.984251315e-01, 2.102241038e-01, 2.227246795e-01, 2.359685782e-01, + 2.500000000e-01, 2.648657736e-01, 2.806155121e-01, 2.973017788e-01, + 3.149802625e-01, 3.337099635e-01, 3.535533906e-01, 3.745767692e-01, + 3.968502630e-01, 4.204482076e-01, 4.454493591e-01, 4.719371563e-01, + 5.000000000e-01, 5.297315472e-01, 5.612310242e-01, 5.946035575e-01, + 6.299605249e-01, 6.674199271e-01, 7.071067812e-01, 7.491535384e-01, + 7.937005260e-01, 8.408964153e-01, 8.908987181e-01, 9.438743127e-01, + 1.000000000e+00, 1.059463094e+00, 1.122462048e+00, 1.189207115e+00, + 1.259921050e+00, 1.334839854e+00, 1.414213562e+00, 1.498307077e+00, + 1.587401052e+00, 1.681792831e+00, 1.781797436e+00, 1.887748625e+00, + 2.000000000e+00, 2.118926189e+00, 2.244924097e+00, 2.378414230e+00, + 2.519842100e+00, 2.669679708e+00, 2.828427125e+00, 2.996614154e+00, + 3.174802104e+00, 3.363585661e+00, 3.563594873e+00, 3.775497251e+00, + 4.000000000e+00, 4.237852377e+00, 4.489848193e+00, 4.756828460e+00, + 5.039684200e+00, 5.339359417e+00, 5.656854249e+00, 5.993228308e+00, + 6.349604208e+00, 6.727171322e+00, 7.127189745e+00, 7.550994501e+00, + 8.000000000e+00, 8.475704755e+00, 8.979696386e+00, 9.513656920e+00, + 1.007936840e+01, 1.067871883e+01, 1.131370850e+01, 1.198645662e+01, + 1.269920842e+01, 1.345434264e+01, 1.425437949e+01, 1.510198900e+01, + 1.600000000e+01, 1.695140951e+01, 1.795939277e+01, 1.902731384e+01, + 2.015873680e+01, 2.135743767e+01, 2.262741700e+01, 2.397291323e+01, + 2.539841683e+01, 2.690868529e+01, 2.850875898e+01, 3.020397801e+01, + 3.200000000e+01, 3.390281902e+01, 3.591878555e+01, 3.805462768e+01, + 4.031747360e+01, 4.271487533e+01, 4.525483400e+01, 4.794582646e+01, + 5.079683366e+01, 5.381737058e+01, 5.701751796e+01, 6.040795601e+01, + 6.400000000e+01, 6.780563804e+01, 7.183757109e+01, 7.610925536e+01, + 8.063494719e+01, 8.542975067e+01, 9.050966799e+01, 9.589165292e+01, + 1.015936673e+02, 1.076347412e+02, 1.140350359e+02, 1.208159120e+02, + 1.280000000e+02, 1.356112761e+02, 1.436751422e+02, 1.522185107e+02, + 1.612698944e+02, 1.708595013e+02, 1.810193360e+02, 1.917833058e+02, + 2.031873347e+02, 2.152694823e+02, 2.280700718e+02, 2.416318240e+02, + 2.560000000e+02, 2.712225522e+02, 2.873502844e+02, 3.044370214e+02, + 3.225397888e+02, 3.417190027e+02, 3.620386720e+02, 3.835666117e+02, + 4.063746693e+02, 4.305389646e+02, 4.561401437e+02, 4.832636481e+02, + 5.120000000e+02, 5.424451043e+02, 5.747005687e+02, 6.088740429e+02, + 6.450795775e+02, 6.834380053e+02, 7.240773439e+02, 7.671332234e+02, + 8.127493386e+02, 8.610779292e+02, 9.122802874e+02, 9.665272962e+02, + 1.024000000e+03, 1.084890209e+03, 1.149401137e+03, 1.217748086e+03, + 1.290159155e+03, 1.366876011e+03, 1.448154688e+03, 1.534266447e+03, +}; + +const float lut_pitch_ratio_low[] = { + 1.000000000e+00, 1.000225659e+00, 1.000451370e+00, 1.000677131e+00, + 1.000902943e+00, 1.001128806e+00, 1.001354720e+00, 1.001580685e+00, + 1.001806701e+00, 1.002032768e+00, 1.002258886e+00, 1.002485055e+00, + 1.002711275e+00, 1.002937546e+00, 1.003163868e+00, 1.003390242e+00, + 1.003616666e+00, 1.003843141e+00, 1.004069668e+00, 1.004296246e+00, + 1.004522874e+00, 1.004749554e+00, 1.004976285e+00, 1.005203068e+00, + 1.005429901e+00, 1.005656786e+00, 1.005883722e+00, 1.006110709e+00, + 1.006337747e+00, 1.006564836e+00, 1.006791977e+00, 1.007019169e+00, + 1.007246412e+00, 1.007473707e+00, 1.007701053e+00, 1.007928450e+00, + 1.008155898e+00, 1.008383398e+00, 1.008610949e+00, 1.008838551e+00, + 1.009066205e+00, 1.009293910e+00, 1.009521667e+00, 1.009749475e+00, + 1.009977334e+00, 1.010205245e+00, 1.010433207e+00, 1.010661221e+00, + 1.010889286e+00, 1.011117403e+00, 1.011345571e+00, 1.011573790e+00, + 1.011802061e+00, 1.012030384e+00, 1.012258758e+00, 1.012487183e+00, + 1.012715661e+00, 1.012944189e+00, 1.013172770e+00, 1.013401401e+00, + 1.013630085e+00, 1.013858820e+00, 1.014087607e+00, 1.014316445e+00, + 1.014545335e+00, 1.014774277e+00, 1.015003270e+00, 1.015232315e+00, + 1.015461411e+00, 1.015690560e+00, 1.015919760e+00, 1.016149011e+00, + 1.016378315e+00, 1.016607670e+00, 1.016837077e+00, 1.017066536e+00, + 1.017296046e+00, 1.017525609e+00, 1.017755223e+00, 1.017984889e+00, + 1.018214607e+00, 1.018444376e+00, 1.018674198e+00, 1.018904071e+00, + 1.019133996e+00, 1.019363973e+00, 1.019594002e+00, 1.019824083e+00, + 1.020054216e+00, 1.020284401e+00, 1.020514637e+00, 1.020744926e+00, + 1.020975266e+00, 1.021205659e+00, 1.021436104e+00, 1.021666600e+00, + 1.021897149e+00, 1.022127749e+00, 1.022358402e+00, 1.022589107e+00, + 1.022819863e+00, 1.023050672e+00, 1.023281533e+00, 1.023512446e+00, + 1.023743411e+00, 1.023974428e+00, 1.024205498e+00, 1.024436619e+00, + 1.024667793e+00, 1.024899019e+00, 1.025130297e+00, 1.025361627e+00, + 1.025593009e+00, 1.025824444e+00, 1.026055931e+00, 1.026287470e+00, + 1.026519061e+00, 1.026750705e+00, 1.026982401e+00, 1.027214149e+00, + 1.027445949e+00, 1.027677802e+00, 1.027909707e+00, 1.028141664e+00, + 1.028373674e+00, 1.028605736e+00, 1.028837851e+00, 1.029070017e+00, + 1.029302237e+00, 1.029534508e+00, 1.029766832e+00, 1.029999209e+00, + 1.030231638e+00, 1.030464119e+00, 1.030696653e+00, 1.030929239e+00, + 1.031161878e+00, 1.031394569e+00, 1.031627313e+00, 1.031860109e+00, + 1.032092958e+00, 1.032325859e+00, 1.032558813e+00, 1.032791820e+00, + 1.033024879e+00, 1.033257991e+00, 1.033491155e+00, 1.033724372e+00, + 1.033957641e+00, 1.034190964e+00, 1.034424338e+00, 1.034657766e+00, + 1.034891246e+00, 1.035124779e+00, 1.035358364e+00, 1.035592003e+00, + 1.035825694e+00, 1.036059437e+00, 1.036293234e+00, 1.036527083e+00, + 1.036760985e+00, 1.036994940e+00, 1.037228947e+00, 1.037463008e+00, + 1.037697121e+00, 1.037931287e+00, 1.038165506e+00, 1.038399777e+00, + 1.038634102e+00, 1.038868479e+00, 1.039102910e+00, 1.039337393e+00, + 1.039571929e+00, 1.039806518e+00, 1.040041160e+00, 1.040275855e+00, + 1.040510603e+00, 1.040745404e+00, 1.040980258e+00, 1.041215165e+00, + 1.041450125e+00, 1.041685138e+00, 1.041920204e+00, 1.042155323e+00, + 1.042390495e+00, 1.042625720e+00, 1.042860998e+00, 1.043096329e+00, + 1.043331714e+00, 1.043567151e+00, 1.043802642e+00, 1.044038185e+00, + 1.044273782e+00, 1.044509433e+00, 1.044745136e+00, 1.044980892e+00, + 1.045216702e+00, 1.045452565e+00, 1.045688481e+00, 1.045924450e+00, + 1.046160473e+00, 1.046396549e+00, 1.046632678e+00, 1.046868860e+00, + 1.047105096e+00, 1.047341385e+00, 1.047577727e+00, 1.047814123e+00, + 1.048050572e+00, 1.048287074e+00, 1.048523630e+00, 1.048760239e+00, + 1.048996902e+00, 1.049233618e+00, 1.049470387e+00, 1.049707210e+00, + 1.049944086e+00, 1.050181015e+00, 1.050417999e+00, 1.050655035e+00, + 1.050892125e+00, 1.051129269e+00, 1.051366466e+00, 1.051603717e+00, + 1.051841021e+00, 1.052078378e+00, 1.052315790e+00, 1.052553255e+00, + 1.052790773e+00, 1.053028345e+00, 1.053265971e+00, 1.053503650e+00, + 1.053741383e+00, 1.053979169e+00, 1.054217010e+00, 1.054454903e+00, + 1.054692851e+00, 1.054930852e+00, 1.055168907e+00, 1.055407016e+00, + 1.055645178e+00, 1.055883395e+00, 1.056121664e+00, 1.056359988e+00, + 1.056598366e+00, 1.056836797e+00, 1.057075282e+00, 1.057313821e+00, + 1.057552413e+00, 1.057791060e+00, 1.058029760e+00, 1.058268515e+00, + 1.058507323e+00, 1.058746185e+00, 1.058985101e+00, 1.059224071e+00, +}; diff --git a/src/fx_shimmer_helper.h b/src/fx_shimmer_helper.h new file mode 100644 index 00000000..582131c8 --- /dev/null +++ b/src/fx_shimmer_helper.h @@ -0,0 +1,34 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_shimmer_helper.h +// +// Helper class for the FX Shimmer FX that is ported from Mutable Instruments +// Ported by: Vincent Gauché +// + +#pragma once + +#include "fx.h" + +extern const float lut_pitch_ratio_high[257]; +extern const float lut_pitch_ratio_low[257]; + +inline float32_t semitoneToRatio(float32_t semitones) +{ + float32_t pitch = semitones + 128.0f; + MAKE_INTEGRAL_FRACTIONAL(pitch); + + return lut_pitch_ratio_high[pitch_integral] * lut_pitch_ratio_low[static_cast(pitch_fractional * 256.0f)]; +} diff --git a/src/fx_shimmer_reverb.cpp b/src/fx_shimmer_reverb.cpp new file mode 100644 index 00000000..cc9ce023 --- /dev/null +++ b/src/fx_shimmer_reverb.cpp @@ -0,0 +1,169 @@ +#include "fx_shimmer_reverb.h" + +#define TAIL , -1 + +ShimmerReverb::ShimmerReverb(float32_t sampling_frequency) : + FXElement(sampling_frequency, 1.2f), + pitch_shifter_(sampling_frequency, PITCH_SHIFTER_TRANSPOSE_BOUNDARY), + lp_filter_(sampling_frequency, SVF::FilterMode::SVF_LP), + hp_filter_(sampling_frequency, SVF::FilterMode::SVF_HP), + reverberator_(sampling_frequency), + texture_(0.0f), + lp_cutoff_(0.0f), + hp_cutoff_(0.0f), + lpq_(0.0f), + amount_(0.0f), + feedback_(0.0f), + cutoff_(0.0f) +{ + this->setInputGain(0.2f); + this->setDiffusion(0.7f); + this->setCutoff(1.0f); + + this->reset(); +} + +ShimmerReverb::~ShimmerReverb() +{ +} + +void ShimmerReverb::reset() +{ + this->pitch_shifter_.reset(); + this->lp_filter_.reset(); + this->hp_filter_.reset(); + this->reverberator_.reset(); +} + +void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + this->pitch_shifter_.processSample(inL, inR, outL, outR); + this->lp_filter_.processSample(outL, outR, outL, outR); + this->hp_filter_.processSample(outL, outR, outL, outR); + this->reverberator_.processSample(outL, outR, outL, outR); + + outL *= this->OutputLevelCorrector; + outR *= this->OutputLevelCorrector; +} + +void ShimmerReverb::setInputGain(float32_t input_gain) +{ + this->reverberator_.setInputGain(input_gain); +} + +float32_t ShimmerReverb::getInputGain() const +{ + return this->reverberator_.getInputGain(); +} + +void ShimmerReverb::setDiffusion(float32_t diffusion) +{ + this->reverberator_.setDiffusion(diffusion); +} + +float32_t ShimmerReverb::getDiffusion() const +{ + return this->reverberator_.getDiffusion(); +} + +void ShimmerReverb::setTime(float32_t time) +{ + this->reverberator_.setTime(time); +} + +float32_t ShimmerReverb::getTime() const +{ + return this->reverberator_.getTime(); +} + +void ShimmerReverb::setReverbAmount(float32_t amount) +{ + amount = constrain(amount, 0.0f, 1.0f); + if(this->amount_ != amount) + { + this->amount_ = amount; + this->updateReverberatorCoefficients(); + } +} + +void ShimmerReverb::setTexture(float32_t texture) +{ + texture = constrain(texture, 0.0f, 1.0f); + if(this->texture_ != texture) + { + this->texture_ = texture; + this->updateFilterCoefficients(); + } +} + +float32_t ShimmerReverb::getTexture() const +{ + return this->texture_; +} + +void ShimmerReverb::setFeedback(float32_t feedback) +{ + feedback = constrain(feedback, 0.0f, 1.0f); + if(this->feedback_ != feedback) + { + this->feedback_ = feedback; + this->updateFilterCoefficients(); + this->updateReverberatorCoefficients(); + } +} + +float32_t ShimmerReverb::getFeedback() const +{ + return this->feedback_; +} + +void ShimmerReverb::setCutoff(float32_t cutoff) +{ + cutoff = constrain(cutoff, 0.0f, 1.0f); + if(this->cutoff_ != cutoff) + { + this->cutoff_ = cutoff; + this->updateFilterCoefficients(); + } +} + +void ShimmerReverb::updateFilterCoefficients() +{ + this->lp_cutoff_ = constrain(0.50f * semitoneToRatio((this->cutoff_ < 0.5f ? this->cutoff_ - 0.5f : 0.0f ) * 216.0f), 0.0f, 0.499f); + this->hp_cutoff_ = constrain(0.25f * semitoneToRatio((this->cutoff_ < 0.5f ? -0.5f : this->cutoff_ - 1.0f) * 216.0f), 0.0f, 0.499f); + this->lpq_ = 1.0f + 3.0f * (1.0f - this->feedback_) * (0.5f - this->lp_cutoff_); + + this->lp_filter_.setFQ(this->lp_cutoff_, this->lpq_); + this->hp_filter_.setFQ(this->hp_cutoff_, 1.0f); + + this->reverberator_.setLP(0.6f + 0.37f * this->feedback_); +} + +void ShimmerReverb::updateReverberatorCoefficients() +{ + float32_t reverb_amount = this->amount_ * 0.95f; + reverb_amount += this->feedback_ * (2.0f - this->feedback_); + reverb_amount = constrain(reverb_amount, 0.0f, 1.0f); + + this->setTime(0.35f + 0.63f * reverb_amount); +} + +void ShimmerReverb::setPitch(float32_t pitch) +{ + this->pitch_shifter_.setTranspose(pitch); +} + +float32_t ShimmerReverb::getPitch() const +{ + return this->pitch_shifter_.getTranspose(); +} + +void ShimmerReverb::setSize(float32_t size) +{ + this->pitch_shifter_.setSize(size); +} + +float32_t ShimmerReverb::getSize() const +{ + return this->pitch_shifter_.getSize(); +} diff --git a/src/fx_shimmer_reverb.h b/src/fx_shimmer_reverb.h new file mode 100644 index 00000000..115ed85a --- /dev/null +++ b/src/fx_shimmer_reverb.h @@ -0,0 +1,111 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +// +// fx_shimmer_reverb.h +// +// Stereo ShimmerReverb Reverb proposed in the context of the MiniDexed project +// It is adapted from the ShimmerReverb Reverb that could be found on Cloud EuroRack module from Mutable Instrruments +// Ported by: Vincent Gauché +// +#pragma once + +#include "fx_components.h" +#include "fx_svf.h" +#include "fx_shimmer_helper.h" +#include "fx_pitch_shifter.h" +#include "fx_reverberator.h" + +class ShimmerReverb : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(ShimmerReverb); + +public: + ShimmerReverb(float32_t sampling_rate); + virtual ~ShimmerReverb(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setInputGain(float32_t input_gain); + float32_t getInputGain() const; + + void setDiffusion(float32_t diffusion); + float32_t getDiffusion() const; + + void setTime(float32_t time); + float32_t getTime() const; + + void setReverbAmount(float32_t amount); + + void setTexture(float32_t texture); + float32_t getTexture() const; + + void setFeedback(float32_t feedback); + float32_t getFeedback() const; + + void setPitch(float32_t pitch); + float32_t getPitch() const; + + void setSize(float32_t size); + float32_t getSize() const; + + void setCutoff(float32_t cutoff); + float32_t getCutoff() const; + +private: + void updateFilterCoefficients(); + void updateReverberatorCoefficients(); + + PitchShifter pitch_shifter_; + SVF lp_filter_; + SVF hp_filter_; + Reverberator reverberator_; + + float32_t texture_; + float32_t lp_cutoff_; + float32_t hp_cutoff_; + float32_t lpq_; + float32_t amount_; + float32_t feedback_; + float32_t cutoff_; + + IMPLEMENT_DUMP( + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".texture_", this->texture_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".lp_cutoff_", this->lp_cutoff_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".hp_cutoff_", this->hp_cutoff_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".lpq_", this->lpq_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".amount_", this->amount_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".feedback_", this->feedback_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".cutoff_", this->cutoff_, 0.0f, 1.0f, deepInspection); + + if(deepInspection) + { + nb_errors += this->pitch_shifter_.inspect(inspector, deepInspection, tag + ".pitch_shifter_"); + nb_errors += this->lp_filter_.inspect(inspector, deepInspection, tag + ".lp_filter_"); + nb_errors += this->hp_filter_.inspect(inspector, deepInspection, tag + ".hp_filter_"); + nb_errors += this->reverberator_.inspect(inspector, deepInspection, tag + ".reverberator_"); + } + + return nb_errors; + ) +}; diff --git a/src/fx_svf.cpp b/src/fx_svf.cpp new file mode 100644 index 00000000..976cad26 --- /dev/null +++ b/src/fx_svf.cpp @@ -0,0 +1,153 @@ +#include "fx_svf.h" + +#include + +StateVariableFilter::StateVariableFilter(float32_t sampling_rate, FilterMode mode, float32_t cutoff) : + FXElement(sampling_rate), + mode_(mode), + gain_(-1.0f), + cutoff_(cutoff), + resonance_(0.0f) +{ + this->setCutoff(cutoff); + this->setResonance(0.0f); + this->setGainDB(0.0f); + + this->reset(); +} + +StateVariableFilter::~StateVariableFilter() +{ +} + +void StateVariableFilter::setFilterMode(FilterMode mode) +{ + if(this->mode_ != mode) + { + this->mode_ = mode; + this->updateCoefficients(); + } +} + +void StateVariableFilter::setCutoff(float32_t cutoff) +{ + static const float32_t max_frequency = 0.45f * this->getSamplingRate(); + + cutoff = constrain(cutoff, 1.0f, max_frequency); + if(this->cutoff_ != cutoff) + { + this->cutoff_ = cutoff; + this->updateCoefficients(); + } +} + +void StateVariableFilter::setResonance(float32_t resonance) +{ + resonance = constrain(resonance, 0.005f, 1.0f); + if(this->resonance_ != resonance) + { + this->resonance_ = resonance; + this->updateCoefficients(); + } +} + +void StateVariableFilter::setGainDB(float32_t gainDB) +{ + gainDB = constrain(gainDB, -1.0f, 1.0f); + if(this->gain_ != gainDB) + { + this->gain_ = gainDB; + this->g_ = std::pow(10.0f, 1.2f * this->gain_); + this->updateCoefficients(); + } +} + +void StateVariableFilter::updateCoefficients() +{ + // Compute the filter coefficients based on the current parameter values + this->w_ = 2.0f * std::tan(PI * this->cutoff_ / this->getSamplingRate()); + this->a_ = this->w_ / this->resonance_; + this->b_ = this->w_ * this->w_; + float32_t a_b = this->a_ + this->b_; + this->c1_ = a_b / (1.0f + 0.5f * this->a_ + 0.25f * this->b_); + this->c2_ = this->b_ / a_b; + + switch(this->mode_) + { + case FilterMode::LPF: + this->d1_ = 0.0f; + this->d0_ = 0.25f * this->c1_ * this->c2_; + break; + + case FilterMode::HPF: + this->d1_ = 0.0f; + this->d0_ = 1.0f - 0.5f * this->c1_ + 0.25f * this->c1_ * this->c2_; + break; + + case FilterMode::BPF: + this->d1_ = 1.0f - this->c2_; + this->d0_ = this->d1_ * this->c1_ * 0.5f; + break; + } + + this->reset(); +} + +void StateVariableFilter::reset() +{ + memset(this->z1_, 0, StereoChannels::kNumChannels * sizeof(float32_t)); + memset(this->z2_, 0, StereoChannels::kNumChannels * sizeof(float32_t)); +} + +void StateVariableFilter::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + const float32_t gain = this->g_; + + switch(this->mode_) + { + case FilterMode::LPF: + { + const float32_t x = inL - this->z1_[StereoChannels::Left] - this->z2_[StereoChannels::Left] + 1e-20f; + this->z2_[StereoChannels::Left] += this->c2_ * this->z1_[StereoChannels::Left]; + outL = gain * (this->d0_ * x + this->z2_[StereoChannels::Left]); + this->z1_[StereoChannels::Left] += this->c1_ * x; + } + { + const float32_t x = inR - this->z1_[StereoChannels::Right] - this->z2_[StereoChannels::Right] + 1e-20f; + this->z2_[StereoChannels::Right] += this->c2_ * this->z1_[StereoChannels::Right]; + outR = gain * (this->d0_ * x + this->z2_[StereoChannels::Right]); + this->z1_[StereoChannels::Right] += this->c1_ * x; + } + break; + + case FilterMode::HPF: + { + const float32_t x = inL - this->z1_[StereoChannels::Left] - this->z2_[StereoChannels::Left] + 1e-20f; + outL = gain * this->d0_ * x; + this->z2_[StereoChannels::Left] += this->c2_ * this->z1_[StereoChannels::Left]; + this->z1_[StereoChannels::Left] += this->c1_ * x; + } + { + const float32_t x = inR - this->z1_[StereoChannels::Right] - this->z2_[StereoChannels::Right] + 1e-20f; + outR = gain * this->d0_ * x; + this->z2_[StereoChannels::Right] += this->c2_ * this->z1_[StereoChannels::Right]; + this->z1_[StereoChannels::Right] += this->c1_ * x; + } + break; + + case FilterMode::BPF: + { + const float32_t x = inL - this->z1_[StereoChannels::Left] - this->z2_[StereoChannels::Left] + 1e-20f; + outL = gain * (this->d0_ * x) + this->d1_ * this->z1_[StereoChannels::Left]; + this->z2_[StereoChannels::Left] += this->c2_ * this->z1_[StereoChannels::Left]; + this->z1_[StereoChannels::Left] += this->c1_ * x; + } + { + const float32_t x = inR - this->z1_[StereoChannels::Right] - this->z2_[StereoChannels::Right] + 1e-20f; + outL = gain * (this->d0_ * x) + this->d1_ * this->z1_[StereoChannels::Right]; + this->z2_[StereoChannels::Right] += this->c2_ * this->z1_[StereoChannels::Right]; + this->z1_[StereoChannels::Right] += this->c1_ * x; + } + break; + } +} diff --git a/src/fx_svf.h b/src/fx_svf.h new file mode 100644 index 00000000..361dc6e1 --- /dev/null +++ b/src/fx_svf.h @@ -0,0 +1,363 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +// +// fx_svf.h +// +// State Variable Filter used in Tape Delay +// Author: Vincent Gauché +// +#pragma once + +#include "fx.h" +#include "fx_components.h" + +class StateVariableFilter : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(StateVariableFilter); + +public: + enum FilterMode + { + LPF, // Low pass filter + HPF, // High pass filter + BPF // Band pass filter + }; + + StateVariableFilter(float32_t sampling_rate, FilterMode mode, float32_t cutoff); + virtual ~StateVariableFilter(); + + void setFilterMode(FilterMode mode); + void setGainDB(float32_t gainDB); + void setCutoff(float32_t cutoff); + void setResonance(float32_t resonance); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + +private: + void updateCoefficients(); + + FilterMode mode_; + float32_t gain_; + float32_t cutoff_; + float32_t resonance_; + float32_t g_; + float32_t w_; + float32_t a_; + float32_t b_; + float32_t c1_; + float32_t c2_; + float32_t d0_; + float32_t d1_; + float32_t z1_[StereoChannels::kNumChannels]; + float32_t z2_[StereoChannels::kNumChannels]; + + IMPLEMENT_DUMP( + const size_t space = 12; + const size_t precision = 6; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "mode_"); + SS__TEXT(ss, ' ', space, std::left, '|', "gain_"); + SS__TEXT(ss, ' ', space, std::left, '|', "cutoff_"); + SS__TEXT(ss, ' ', space, std::left, '|', "resonance_"); + SS__TEXT(ss, ' ', space, std::left, '|', "g_"); + SS__TEXT(ss, ' ', space, std::left, '|', "w_"); + SS__TEXT(ss, ' ', space, std::left, '|', "a_"); + SS__TEXT(ss, ' ', space, std::left, '|', "b_"); + SS__TEXT(ss, ' ', space, std::left, '|', "c1_"); + SS__TEXT(ss, ' ', space, std::left, '|', "c2_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->mode_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->gain_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->cutoff_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->resonance_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->g_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->w_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->a_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->b_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->c1_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->c2_); + out << "\t" << ss.str() << std::endl; + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + "gain_", this->gain_, -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + "cutoff_", this->cutoff_, 1.0f, this->getSamplingRate() / 2.0f, deepInspection); + nb_errors += inspector(tag + "resonance_", this->resonance_, 0.005f, 1.0f, deepInspection); + nb_errors += inspector(tag + "g_", this->g_, 0.0f, 16.0f, deepInspection); + nb_errors += inspector(tag + "w_", this->w_, 0.0f, 13.0f, deepInspection); + nb_errors += inspector(tag + "a_", this->a_, 0.0f, 2526.0f, deepInspection); + nb_errors += inspector(tag + "b_", this->b_, 0.0f, 160.0f, deepInspection); + nb_errors += inspector(tag + "c1_", this->c1_, 0.0f, 2.06f, deepInspection); + nb_errors += inspector(tag + "c2_", this->c2_, 0.0f, 0.06f, deepInspection); + + return nb_errors; + ) +}; + +class SVF : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(SVF); + +public: + enum FrequencyApproximation + { + FrequencyExact, + FrequencyAccurate, + FrequencyFast, + FrequencyDirty + }; + + enum FilterMode + { + SVF_LP, + SVF_BP, + SVF_BP_NORMALIZED, + SVF_HP + }; + + SVF(float32_t sampling_frequency, FilterMode mode = FilterMode::SVF_LP) : + FXElement(sampling_frequency), + Mode(mode), + g_(0.0f), + r_(0.0f), + h_(0.0f) + { + this->reset(); + } + + virtual ~SVF() + { + } + + inline virtual void reset() override + { + memset(this->state1_, 0, StereoChannels::kNumChannels * sizeof(float32_t)); + memset(this->state2_, 0, StereoChannels::kNumChannels * sizeof(float32_t)); + } + + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override + { + float32_t hp, bp, lp; + + { + hp = (inL - this->r_ * this->state1_[StereoChannels::Left ] - this->g_ * this->state1_[StereoChannels::Left ] - this->state2_[StereoChannels::Left ]) * this->h_; + bp = this->g_ * hp + this->state1_[StereoChannels::Left ]; + this->state1_[StereoChannels::Left ] = this->g_ * hp + bp; + lp = this->g_ * bp + this->state2_[StereoChannels::Left ]; + this->state2_[StereoChannels::Left ] = this->g_ * bp + lp; + + switch(this->Mode) + { + case FilterMode::SVF_LP: + outL = lp; + break; + + case FilterMode::SVF_BP: + outL = bp; + break; + + case FilterMode::SVF_BP_NORMALIZED: + outL = bp * this->r_; + break; + + case FilterMode::SVF_HP: + outL = hp; + break; + } + } + + { + hp = (inR - this->r_ * this->state1_[StereoChannels::Right] - this->g_ * this->state1_[StereoChannels::Right] - this->state2_[StereoChannels::Right]) * this->h_; + bp = this->g_ * hp + this->state1_[StereoChannels::Right]; + this->state1_[StereoChannels::Right] = this->g_ * hp + bp; + lp = this->g_ * bp + this->state2_[StereoChannels::Right]; + this->state2_[StereoChannels::Right] = this->g_ * bp + lp; + + switch(this->Mode) + { + case FilterMode::SVF_LP: + outR = lp; + break; + + case FilterMode::SVF_BP: + outR = bp; + break; + + case FilterMode::SVF_BP_NORMALIZED: + outR = bp * this->r_; + break; + + case FilterMode::SVF_HP: + outR = hp; + break; + } + } + } + + inline void setGRH(float32_t g, float32_t r, float32_t h) + { + this->g_ = g; + this->r_ = r; + this->h_ = h; + } + + inline void setGR(float32_t g, float32_t r) + { + this->g_ = g; + this->r_ = r; + this->h_ = 1.0f / (1.0f + this->r_ * this->g_ * this->g_ * this->g_); + } + + template + inline void setFQ(float32_t frequency, float32_t resonance) + { + this->g_ = SVF::tan(frequency); + this->r_ = 1.0f / resonance; + this->h_ = 1.0f / (1.0f + this->r_ * this->g_ * this->g_ * this->g_); + } + +private: + template + static inline float32_t tan(float32_t f) + { + switch(approximation) + { + case FrequencyApproximation::FrequencyExact: + { + // Clip coefficient to about 100. + f = constrain(f, 0.0f, 0.497f); + return ::tan(PI * f); + } + + case FrequencyApproximation::FrequencyDirty: + { + // Optimized for frequencies below 8kHz. + const float32_t a = 3.736e-01 * Constants::M_PI_POW_3; + return f * (PI + a * f * f); + } + + case FrequencyApproximation::FrequencyFast: + { + // The usual tangent approximation uses 3.1755e-01 and 2.033e-01, but + // the coefficients used here are optimized to minimize error for the + // 16Hz to 16kHz range, with a sample rate of 48kHz. + const float a = 3.260e-01 * Constants::M_PI_POW_3; + const float b = 1.823e-01 * Constants::M_PI_POW_5; + float f2 = f * f; + return f * (PI + f2 * (a + b * f2)); + } + + case FrequencyApproximation::FrequencyAccurate: + { + // These coefficients don't need to be tweaked for the audio range. + const float a = 3.333314036e-01 * Constants::M_PI_POW_3; + const float b = 1.333923995e-01 * Constants::M_PI_POW_5; + const float c = 5.33740603e-02 * Constants::M_PI_POW_7; + const float d = 2.900525e-03 * Constants::M_PI_POW_9; + const float e = 9.5168091e-03 * Constants::M_PI_POW_11; + float f2 = f * f; + return f * (PI + f2 * (a + f2 * (b + f2 * (c + f2 * (d + f2 * e))))); + } + } + } + + const FilterMode Mode; + float32_t g_; + float32_t r_; + float32_t h_; + + float32_t state1_[StereoChannels::kNumChannels]; + float32_t state2_[StereoChannels::kNumChannels]; + + IMPLEMENT_DUMP( + const size_t space = 12; + const size_t precision = 6; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "g_"); + SS__TEXT(ss, ' ', space, std::left, '|', "r_"); + SS__TEXT(ss, ' ', space, std::left, '|', "h_"); + SS__TEXT(ss, ' ', space, std::left, '|', "state1_[ L ]"); + SS__TEXT(ss, ' ', space, std::left, '|', "state1_[ R ]"); + SS__TEXT(ss, ' ', space, std::left, '|', "state2_[ L ]"); + SS__TEXT(ss, ' ', space, std::left, '|', "state2_[ R ]"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->g_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->r_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->r_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->state1_[StereoChannels::Left ]); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->state1_[StereoChannels::Right]); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->state2_[StereoChannels::Left ]); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->state2_[StereoChannels::Right]); + out << "\t" << ss.str() << std::endl; + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0u; + + nb_errors += inspector(tag + ".g_", this->g_, 0.0f, 106.11f, deepInspection); + nb_errors += inspector(tag + ".r_", this->r_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".h_", this->h_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".state1_[ L ]", this->state1_[StereoChannels::Left ], -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".state1_[ R ]", this->state1_[StereoChannels::Right], -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".state2_[ L ]", this->state2_[StereoChannels::Left ], -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".state2_[ R ]", this->state2_[StereoChannels::Right], -1.0f, 1.0f, deepInspection); + + return nb_errors; + ) +}; diff --git a/src/fx_tube.cpp b/src/fx_tube.cpp new file mode 100644 index 00000000..2b171d95 --- /dev/null +++ b/src/fx_tube.cpp @@ -0,0 +1,60 @@ +#include "fx_tube.h" + +#include + +Tube::Tube(float32_t samplingRate) : + FXElement(samplingRate), + overdrive_(1.0f), + saturator_factor_(1.0f), + gain_factor_(1.0f) +{ + this->setOverdrive(0.0f); +} + +Tube::~Tube() +{ +} + +void Tube::reset() +{ + // nothing to be done +} + +void Tube::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + if(inL == 0.0f) + { + outL = 0.0f; + } + else + { + outL = std::tanh(this->saturator_factor_ * inL) * this->gain_factor_; + } + + if(inR == 0.0f) + { + outR = 0.0f; + } + else + { + outR = std::tanh(this->saturator_factor_ * inR) * this->gain_factor_; + } +} + +void Tube::setOverdrive(float32_t overdrive) +{ + static const float32_t N = 3.0f; + + overdrive = constrain(overdrive, 0.0f, 1.0f); + if(this->overdrive_ != overdrive) + { + this->overdrive_ = overdrive; + this->saturator_factor_ = 1.0 + N * overdrive; + this->gain_factor_ = this->OutputLevelCorrector / std::tanh(this->saturator_factor_); + } +} + +float32_t Tube::getOverdrive() const +{ + return this->overdrive_; +} diff --git a/src/fx_tube.h b/src/fx_tube.h new file mode 100644 index 00000000..99a163dd --- /dev/null +++ b/src/fx_tube.h @@ -0,0 +1,81 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_tube.h +// +// Stereo Tube overdrive audio effects proposed in the context of the MiniDexed project +// Author: Vincent Gauché +// +#pragma once + +#include "fx.h" + +class Tube : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(Tube); + +public: + Tube(float32_t sampling_rate); + virtual ~Tube(); + + virtual void reset() override; + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setOverdrive(float32_t overdrive); + float32_t getOverdrive() const; + +private: + float32_t overdrive_; + float32_t saturator_factor_; + float32_t gain_factor_; + + IMPLEMENT_DUMP( + const size_t space = 17; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', "overdrive_"); + SS__TEXT(ss, ' ', space, std::left, '|', "saturator_factor_"); + SS__TEXT(ss, ' ', space, std::left, '|', "gain_factor_"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->overdrive_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->saturator_factor_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->gain_factor_); + out << "\t" << ss.str() << std::endl; + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0; + + nb_errors += inspector(tag + ".overdrive_", this->overdrive_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".saturator_factor_", this->saturator_factor_, 1.0f, 201.0f, deepInspection); + nb_errors += inspector(tag + ".gain_factor_", this->gain_factor_, 0.0f, 4.0f, deepInspection); + + return nb_errors; + ) +}; diff --git a/src/fx_unit.hpp b/src/fx_unit.hpp new file mode 100644 index 00000000..344cdc79 --- /dev/null +++ b/src/fx_unit.hpp @@ -0,0 +1,116 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_unit.h +// +// Unit of FX that handle enable and wet parameters +// Author: Vincent Gauché +// +#pragma once + +#include "fx_components.h" + +class FXUnitModule +{ + DISALLOW_COPY_AND_ASSIGN(FXUnitModule); + +public: + FXUnitModule(bool enable = true, float32_t wet_level = 0.5f) + { + this->setEnable(enable); + this->setWetLevel(wet_level); + } + + virtual ~FXUnitModule() + { + } + + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) = 0; + + void setEnable(bool enable = true) + { + this->enable_ = enable; + } + + inline bool isEnable() const + { + return this->enable_; + } + + void setWetLevel(float32_t wet_level) + { + this->wet_level_ = constrain(wet_level, 0.0f, 1.0f); + } + + inline float32_t getWetLevel() const + { + return this->wet_level_; + } + +protected: + bool enable_; + float32_t wet_level_; // How much the signal is affected by the inner FX (0.0 - 1.0) +}; + +template +class FXUnit : public virtual FXUnitModule, public virtual _FXElement +{ + DISALLOW_COPY_AND_ASSIGN(FXUnit); + +public: + FXUnit(float32_t sampling_rate, bool enable = true, float32_t wet_level = 0.5f) : + FXUnitModule(), + _FXElement(sampling_rate), + is_reset_(false) + { + this->setEnable(enable); + this->setWetLevel(wet_level); + } + + virtual ~FXUnit() + { + } + + void reset() override + { + if(!this->is_reset_) + { + _FXElement::reset(); + this->is_reset_ = true; + } + } + + void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override + { + if(!this->isEnable() || this->getWetLevel() == 0.0f) + { + this->reset(); + + outL = inL; + outR = inR; + } + else + { + this->is_reset_ = false; + _FXElement::processSample(inL, inR, outL, outR); + + float32_t dry = 1.0f - this->getWetLevel(); + outL = this->getWetLevel() * outL + dry * inL; + outR = this->getWetLevel() * outR + dry * inR; + } + } + +private: + bool is_reset_; +}; \ No newline at end of file diff --git a/src/fx_unit2.hpp b/src/fx_unit2.hpp new file mode 100644 index 00000000..e26213c0 --- /dev/null +++ b/src/fx_unit2.hpp @@ -0,0 +1,104 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_unit2.h +// +// Unit of FX that handle the mute parameter +// Author: Vincent Gauché +// +#pragma once + +#include "fx_components.h" + +class FXUnitModule2 +{ + DISALLOW_COPY_AND_ASSIGN(FXUnitModule2); + +public: + FXUnitModule2(bool mute = false) + { + this->setMute(mute); + } + + virtual ~FXUnitModule2() + { + } + + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) = 0; + + inline void setMute(bool mute = false) + { + this->mute_ = mute; + } + + inline bool isMute() const + { + return this->mute_; + } + +protected: + bool mute_; +}; + +template +class FXUnit2 : public virtual FXUnitModule2, public virtual _FXElement +{ + DISALLOW_COPY_AND_ASSIGN(FXUnit2); + +public: + FXUnit2(float32_t sampling_rate, bool mute = false) : + FXUnitModule2(mute), + _FXElement(sampling_rate), + is_reset_(false) + { + this->setMute(mute); + } + + virtual ~FXUnit2() + { + } + + inline void reset() override + { + if(!this->is_reset_) + { + _FXElement::reset(); + this->is_reset_ = true; + } + } + + inline void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override + { + if(this->isMute()) + { + this->reset(); + + outL = 0.0f; + outR = 0.0f; + } + else if(this->bypassFXProcess()) + { + outL = inL; + outR = inR; + } + else + { + this->is_reset_ = false; + _FXElement::processSample(inL, inR, outL, outR); + } + } + +private: + bool is_reset_; +}; diff --git a/src/mididevice.cpp b/src/mididevice.cpp index e1f983ca..371a070a 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -31,39 +31,45 @@ LOGMODULE ("mididevice"); -#define MIDI_NOTE_OFF 0b1000 -#define MIDI_NOTE_ON 0b1001 -#define MIDI_AFTERTOUCH 0b1010 // TODO -#define MIDI_CHANNEL_AFTERTOUCH 0b1101 // right now Synth_Dexed just manage Channel Aftertouch not Polyphonic AT -> 0b1010 -#define MIDI_CONTROL_CHANGE 0b1011 - #define MIDI_CC_BANK_SELECT_MSB 0 - #define MIDI_CC_MODULATION 1 - #define MIDI_CC_BREATH_CONTROLLER 2 - #define MIDI_CC_FOOT_PEDAL 4 - #define MIDI_CC_VOLUME 7 - #define MIDI_CC_PAN_POSITION 10 - #define MIDI_CC_BANK_SELECT_LSB 32 - #define MIDI_CC_BANK_SUSTAIN 64 - #define MIDI_CC_RESONANCE 71 - #define MIDI_CC_FREQUENCY_CUTOFF 74 - #define MIDI_CC_REVERB_LEVEL 91 - #define MIDI_CC_DETUNE_LEVEL 94 - #define MIDI_CC_ALL_SOUND_OFF 120 - #define MIDI_CC_ALL_NOTES_OFF 123 -#define MIDI_PROGRAM_CHANGE 0b1100 -#define MIDI_PITCH_BEND 0b1110 - -#define MIDI_SYSTEM_EXCLUSIVE_BEGIN 0xF0 -#define MIDI_SYSTEM_EXCLUSIVE_END 0xF7 -#define MIDI_TIMING_CLOCK 0xF8 -#define MIDI_ACTIVE_SENSING 0xFE +#define MIDI_NOTE_OFF 0b1000 +#define MIDI_NOTE_ON 0b1001 +#define MIDI_AFTERTOUCH 0b1010 // TODO +#define MIDI_CHANNEL_AFTERTOUCH 0b1101 // right now Synth_Dexed just manage Channel Aftertouch not Polyphonic AT -> 0b1010 +#define MIDI_CONTROL_CHANGE 0b1011 +#define MIDI_CC_BANK_SELECT_MSB 0 +#define MIDI_CC_MODULATION 1 +#define MIDI_CC_BREATH_CONTROLLER 2 +#define MIDI_CC_FOOT_PEDAL 4 +#define MIDI_CC_VOLUME 7 +#define MIDI_CC_PAN_POSITION 10 +#define MIDI_CC_BANK_SELECT_LSB 32 +#define MIDI_CC_BANK_SUSTAIN 64 +#define MIDI_CC_RESONANCE 71 +#define MIDI_CC_FREQUENCY_CUTOFF 74 +#define MIDI_CC_REVERB_LEVEL 91 +#define MIDI_CC_DETUNE_LEVEL 94 +#define MIDI_CC_ALL_SOUND_OFF 120 +#define MIDI_CC_ALL_NOTES_OFF 123 +#define MIDI_PROGRAM_CHANGE 0b1100 +#define MIDI_PITCH_BEND 0b1110 + +#if defined(MIXING_CONSOLE_ENABLE) +#define MIDI_CC_ORBITONE_LEVEL 92 // added with mixing console +#define MIDI_CC_CHORUS_LEVEL 93 // added with mixing console +#define MIDI_CC_PHASER_LEVEL 95 // added with mixing console +#endif + +#define MIDI_SYSTEM_EXCLUSIVE_BEGIN 0xF0 +#define MIDI_SYSTEM_EXCLUSIVE_END 0xF7 +#define MIDI_TIMING_CLOCK 0xF8 +#define MIDI_ACTIVE_SENSING 0xFE CMIDIDevice::TDeviceMap CMIDIDevice::s_DeviceMap; -CMIDIDevice::CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI) -: m_pSynthesizer (pSynthesizer), - m_pConfig (pConfig), - m_pUI (pUI) +CMIDIDevice::CMIDIDevice(CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI) + : m_pSynthesizer(pSynthesizer), + m_pConfig(pConfig), + m_pUI(pUI) { for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) { @@ -304,9 +310,25 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign break; case MIDI_CC_REVERB_LEVEL: - m_pSynthesizer->SetReverbSend (maplong (pMessage[2], 0, 127, 0, 99), nTG); +#if defined(MIXING_CONSOLE_ENABLE) + this->m_pSynthesizer->setMixingConsoleSendLevel(nTG, MixerOutput::FX_PlateReverb, maplong(pMessage[2], 0, 127, 0, 99)); +#elif defined(PLATE_REVERB_ENABLE) + m_pSynthesizer->SetReverbSend(maplong(pMessage[2], 0, 127, 0, 99), nTG); +#endif break; - + +#if defined(MIXING_CONSOLE_ENABLE) + case MIDI_CC_ORBITONE_LEVEL: + this->m_pSynthesizer->setMixingConsoleSendLevel(nTG, MixerOutput::FX_Orbitone, maplong(pMessage[2], 0, 127, 0, 99)); + break; + case MIDI_CC_CHORUS_LEVEL: + this->m_pSynthesizer->setMixingConsoleSendLevel(nTG, MixerOutput::FX_Chorus, maplong(pMessage[2], 0, 127, 0, 99)); + break; + case MIDI_CC_PHASER_LEVEL: + this->m_pSynthesizer->setMixingConsoleSendLevel(nTG, MixerOutput::FX_Phaser, maplong(pMessage[2], 0, 127, 0, 99)); + break; +#endif + case MIDI_CC_DETUNE_LEVEL: if (pMessage[2] == 0) { diff --git a/src/minidexed.cpp b/src/minidexed.cpp index fafeff81..fdc2ae27 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -30,9 +30,13 @@ LOGMODULE ("minidexed"); -CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, - CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, FATFS *pFileSystem) -: +CMiniDexed::CMiniDexed ( + CConfig *pConfig, + CInterruptSystem *pInterrupt, + CGPIOManager *pGPIOManager, + CI2CMaster *pI2CMaster, + FATFS *pFileSystem +) : #ifdef ARM_ALLOW_MULTI_CORE CMultiCoreSupport (CMemorySystem::Get ()), #endif @@ -47,8 +51,9 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, #ifdef ARM_ALLOW_MULTI_CORE m_nActiveTGsLog2 (0), #endif - m_GetChunkTimer ("GetChunk", - 1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()), + + + m_GetChunkTimer ("GetChunk", 1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()), m_bProfileEnabled (m_pConfig->GetProfileEnabled ()), m_bSavePerformance (false), m_bSavePerformanceNewFile (false), @@ -79,16 +84,29 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_nNoteLimitHigh[i] = 127; m_nNoteShift[i] = 0; - m_nModulationWheelRange[i]=99; - m_nModulationWheelTarget[i]=7; - m_nFootControlRange[i]=99; - m_nFootControlTarget[i]=0; - m_nBreathControlRange[i]=99; - m_nBreathControlTarget[i]=0; - m_nAftertouchRange[i]=99; - m_nAftertouchTarget[i]=0; - + + + + + + + + + + m_nModulationWheelRange[i] = 99; + m_nModulationWheelTarget[i] = 7; + m_nFootControlRange[i] = 99; + m_nFootControlTarget[i] = 0; + m_nBreathControlRange[i] = 99; + m_nBreathControlTarget[i] = 0; + m_nAftertouchRange[i] = 99; + m_nAftertouchTarget[i] = 0; + +#if defined(MIXING_CONSOLE_ENABLE) + memset(this->m_nTGSendLevel[i], 0, MixerOutput::kFXCount * sizeof(unsigned)); +#elif defined(PLATE_REVERB_ENABLE) m_nReverbSend[i] = 0; +#endif m_uchOPMask[i] = 0b111111; // All operators on m_pTG[i] = new CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()); @@ -97,6 +115,17 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_pTG[i]->activate (); } +#if defined(MIXING_CONSOLE_ENABLE) + for(size_t i = MixerOutput::FX_Tube; i < (MixerOutput::kFXCount - 1); ++i) + { + memset(this->m_nFXSendLevel[i], 0, MixerOutput::kFXCount * sizeof(unsigned)); + } + + this->m_nTGSendLevel[0][MixerOutput::MainOutput] = 99; + this->m_nTGSendLevel[0][MixerOutput::FX_PlateReverb] = 99; + this->m_nFXSendLevel[MixerOutput::FX_PlateReverb][MixerOutput::MainOutput] = 99; +#endif + for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) { m_pMIDIKeyboard[i] = new CMIDIKeyboard (this, pConfig, &m_UI, i); @@ -139,7 +168,69 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, } #endif - setMasterVolume(1.0); + this->setMasterVolume(1.0); + +#if defined(MIXING_CONSOLE_ENABLE) + this->mixing_console_ = new Mixer(static_cast(pConfig->GetSampleRate()), CConfig::MaxChunkSize, this->m_bChannelsSwapped); + for (uint8_t i = 0; i < CConfig::ToneGenerators; i++) + { + memset(this->m_OutputLevel[i], 0, CConfig::MaxChunkSize * sizeof(float32_t)); + this->mixing_console_->setInputSampleBuffer(i, this->m_OutputLevel[i]); + } + + // Tube parameters + this->SetParameter(TParameter::ParameterFXTubeEnable, 1); + this->SetParameter(TParameter::ParameterFXTubeOverdrive, 25); + + // Chorus parameters + this->SetParameter(TParameter::ParameterFXChorusEnable, 1); + this->SetParameter(TParameter::ParameterFXChorusRate, 50); + this->SetParameter(TParameter::ParameterFXChorusDepth, 50); + + // Flanger parameters + this->SetParameter(TParameter::ParameterFXFlangerEnable, 1); + this->SetParameter(TParameter::ParameterFXFlangerRate, 3); + this->SetParameter(TParameter::ParameterFXFlangerDepth, 75); + this->SetParameter(TParameter::ParameterFXFlangerFeedback, 50); + + // Orbitone parameters + this->SetParameter(TParameter::ParameterFXOrbitoneEnable, 1); + this->SetParameter(TParameter::ParameterFXOrbitoneRate, 40); + this->SetParameter(TParameter::ParameterFXOrbitoneDepth, 50); + + // Phaser parameters + this->SetParameter(TParameter::ParameterFXPhaserEnable, 1); + this->SetParameter(TParameter::ParameterFXPhaserRate, 5); + this->SetParameter(TParameter::ParameterFXPhaserDepth, 99); + this->SetParameter(TParameter::ParameterFXPhaserFeedback, 50); + this->SetParameter(TParameter::ParameterFXPhaserNbStages, 12); + + // Delay parameters + this->SetParameter(TParameter::ParameterFXDelayEnable, 1); + this->SetParameter(TParameter::ParameterFXDelayLeftDelayTime, 15); + this->SetParameter(TParameter::ParameterFXDelayRightDelayTime, 22); + this->SetParameter(TParameter::ParameterFXDelayFeedback, 35); + + // AudioEffectPlateReverb parameters + this->SetParameter(TParameter::ParameterReverbEnable, 1); + this->SetParameter(TParameter::ParameterReverbSize, 70); + this->SetParameter(TParameter::ParameterReverbHighDamp, 50); + this->SetParameter(TParameter::ParameterReverbLowDamp, 50); + this->SetParameter(TParameter::ParameterReverbLowPass, 30); + this->SetParameter(TParameter::ParameterReverbDiffusion, 65); + this->SetParameter(TParameter::ParameterReverbLevel, 99); + + // Reverberator parameters + this->SetParameter(TParameter::ParameterFXReverberatorEnable, 1); + this->SetParameter(TParameter::ParameterFXReverberatorInputGain, 99); + this->SetParameter(TParameter::ParameterFXReverberatorTime, 80); + this->SetParameter(TParameter::ParameterFXReverberatorDiffusion, 80); + this->SetParameter(TParameter::ParameterFXReverberatorLP, 70); + + // Bypass + this->SetParameter(TParameter::ParameterFXBypass, 0); + +#elif defined(PLATE_REVERB_ENABLE) // BEGIN setup tg_mixer tg_mixer = new AudioStereoMixer(pConfig->GetChunkSize()/2); @@ -148,16 +239,18 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, // BEGIN setup reverb reverb_send_mixer = new AudioStereoMixer(pConfig->GetChunkSize()/2); reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate()); - SetParameter (ParameterReverbEnable, 1); - SetParameter (ParameterReverbSize, 70); - SetParameter (ParameterReverbHighDamp, 50); - SetParameter (ParameterReverbLowDamp, 50); - SetParameter (ParameterReverbLowPass, 30); - SetParameter (ParameterReverbDiffusion, 65); - SetParameter (ParameterReverbLevel, 99); + SetParameter (TParameter::ParameterReverbEnable, 1); + SetParameter (TParameter::ParameterReverbSize, 70); + SetParameter (TParameter::ParameterReverbHighDamp, 50); + SetParameter (TParameter::ParameterReverbLowDamp, 50); + SetParameter (TParameter::ParameterReverbLowPass, 30); + SetParameter (TParameter::ParameterReverbDiffusion, 65); + SetParameter (TParameter::ParameterReverbLevel, 99); // END setup reverb - SetParameter (ParameterCompressorEnable, 1); +#endif + + this->SetParameter (TParameter::ParameterCompressorEnable, 1); }; bool CMiniDexed::Initialize (void) @@ -195,10 +288,21 @@ bool CMiniDexed::Initialize (void) m_pTG[i]->setBCController (99, 1, 0); m_pTG[i]->setATController (99, 1, 0); +#if defined(MIXING_CONSOLE_ENABLE) + this->mixing_console_->reset(); + this->mixing_console_->setPan(i, this->m_nPan[i] / 127.0f); + + this->mixing_console_->setSendLevel(i, MixerOutput::FX_PlateReverb, this->m_nTGSendLevel[i][MixerOutput::FX_PlateReverb] / 99.0f); + this->mixing_console_->setSendLevel(i, MixerOutput::MainOutput, this->m_nTGSendLevel[i][MixerOutput::MainOutput] / 99.0f); + this->mixing_console_->setFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::MainOutput, this->m_nFXSendLevel[MixerOutput::FX_PlateReverb][MixerOutput::FX_PlateReverb] / 99.0f); + +#elif defined(PLATE_REVERB_ENABLE) + tg_mixer->pan(i,mapfloat(m_nPan[i],0,127,0.0f,1.0f)); tg_mixer->gain(i,1.0f); reverb_send_mixer->pan(i,mapfloat(m_nPan[i],0,127,0.0f,1.0f)); reverb_send_mixer->gain(i,mapfloat(m_nReverbSend[i],0,99,0.0f,1.0f)); +#endif } if (m_PerformanceConfig.Load ()) @@ -209,7 +313,7 @@ bool CMiniDexed::Initialize (void) { SetMIDIChannel (CMIDIDevice::OmniMode, 0); } - + // load performances file list, and attempt to create the performance folder if (!m_PerformanceConfig.ListPerformances()) { @@ -224,24 +328,26 @@ bool CMiniDexed::Initialize (void) return false; } -#ifndef ARM_ALLOW_MULTI_CORE - m_pSoundDevice->SetWriteFormat (SoundFormatSigned16, 1); // 16-bit Mono -#else +#if defined(ARM_ALLOW_MULTI_CORE) + + m_pSoundDevice->SetWriteFormat (SoundFormatSigned16, 2); // 16-bit Stereo +#else + m_pSoundDevice->SetWriteFormat (SoundFormatSigned16, 1); // 16-bit Mono #endif m_nQueueSizeFrames = m_pSoundDevice->GetQueueSizeFrames (); m_pSoundDevice->Start (); -#ifdef ARM_ALLOW_MULTI_CORE +#if defined(ARM_ALLOW_MULTI_CORE) // start secondary cores if (!CMultiCoreSupport::Initialize ()) { return false; } #endif - + return true; } @@ -354,6 +460,10 @@ void CMiniDexed::Run (unsigned nCore) { assert (m_pTG[nTG]); m_pTG[nTG]->getSamples (m_OutputLevel[nTG],m_nFramesToProcess); + +#if defined(MIXING_CONSOLE_ENABLE) + this->mixing_console_->preProcessInputSampleBuffer(nTG, this->m_nFramesToProcess); +#endif } } } @@ -468,6 +578,10 @@ void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG) assert (m_pTG[nTG]); m_pTG[nTG]->setGain (nVolume / 127.0f); +#if defined(MIXING_CONSOLE_ENABLE) + this->mixing_console_->setChannelLevel(nTG, nVolume == 0 ? 0.0f : 1.0f); +#endif + m_UI.ParameterChanged (); } @@ -477,13 +591,52 @@ void CMiniDexed::SetPan (unsigned nPan, unsigned nTG) assert (nTG < CConfig::ToneGenerators); m_nPan[nTG] = nPan; - + +#if defined(MIXING_CONSOLE_ENABLE) + this->mixing_console_->setPan(nTG, nPan / 127.0f); + +#elif defined(PLATE_REVERB_ENABLE) + tg_mixer->pan(nTG,mapfloat(nPan,0,127,0.0f,1.0f)); reverb_send_mixer->pan(nTG,mapfloat(nPan,0,127,0.0f,1.0f)); +#endif m_UI.ParameterChanged (); } +#if defined(MIXING_CONSOLE_ENABLE) + +unsigned CMiniDexed::getMixingConsoleSendLevel(unsigned nTG, MixerOutput fx) const +{ + assert (nTG < CConfig::ToneGenerators); + return this->m_nTGSendLevel[nTG][fx]; +} + +void CMiniDexed::setMixingConsoleSendLevel(unsigned nTG, MixerOutput fx, unsigned nFXSend) +{ + assert (nTG < CConfig::ToneGenerators); + nFXSend = constrain((int)nFXSend, 0, 99); + + this->m_nTGSendLevel[nTG][fx] = nFXSend; + this->mixing_console_->setSendLevel(nTG, fx, nFXSend / 99.0f); + + this->m_UI.ParameterChanged(); +} + +void CMiniDexed::setMixingConsoleFXSendLevel(MixerOutput fromFX, MixerOutput toFX, unsigned nFXSend) +{ + assert(fromFX < (MixerOutput::kFXCount - 1)); + assert(toFX < MixerOutput::kFXCount); + if(fromFX != toFX) + { + nFXSend = constrain((int)nFXSend, 0, 99); + this->m_nFXSendLevel[fromFX][toFX] = nFXSend; + this->mixing_console_->setFXSendLevel(fromFX, toFX, nFXSend / 99.0f); + } +} + +#elif defined(PLATE_REVERB_ENABLE) + void CMiniDexed::SetReverbSend (unsigned nReverbSend, unsigned nTG) { nReverbSend=constrain((int)nReverbSend,0,99); @@ -496,6 +649,8 @@ void CMiniDexed::SetReverbSend (unsigned nReverbSend, unsigned nTG) m_UI.ParameterChanged (); } +#endif + void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG) { nMasterTune=constrain((int)nMasterTune,-99,99); @@ -690,70 +845,689 @@ void CMiniDexed::ControllersRefresh (unsigned nTG) void CMiniDexed::SetParameter (TParameter Parameter, int nValue) { - assert (reverb); + - assert (Parameter < ParameterUnknown); + assert(Parameter < TParameter::ParameterUnknown); + m_nParameter[Parameter] = nValue; switch (Parameter) { - case ParameterCompressorEnable: - for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) + case TParameter::ParameterCompressorEnable: + for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; ++nTG) { - assert (m_pTG[nTG]); + assert(m_pTG[nTG]); m_pTG[nTG]->setCompressor (!!nValue); } break; - case ParameterReverbEnable: +#if defined(MIXING_CONSOLE_ENABLE) + + // Tube parameters + case TParameter::ParameterFXTubeEnable: + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getTube()->setMute(!!!nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXTubeOverdrive: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getTube()->setOverdrive(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + + // Chorus parameters + case TParameter::ParameterFXChorusEnable: + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getChorus()->setMute(!!!nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXChorusRate: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getChorus()->setRate(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXChorusDepth: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getChorus()->setDepth(nValue / 9.9f); + this->m_FXSpinLock.Release(); + break; + + // Flanger parameters + case TParameter::ParameterFXFlangerEnable: + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getFlanger()->setMute(!!!nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXFlangerRate: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getFlanger()->setRate(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXFlangerDepth: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getFlanger()->setDepth(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXFlangerFeedback: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getFlanger()->setFeedback(mapfloat(nValue, 0, 99, 0.0f, 0.97f)); + this->m_FXSpinLock.Release(); + break; + + // Orbitone parameters + case TParameter::ParameterFXOrbitoneEnable: + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getOrbitone()->setMute(!!!nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXOrbitoneRate: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getOrbitone()->setRate(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXOrbitoneDepth: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getOrbitone()->setDepth(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + + // Phaser parameters + case TParameter::ParameterFXPhaserEnable: + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getPhaser()->setMute(!!!nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPhaserRate: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getPhaser()->setRate(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPhaserDepth: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getPhaser()->setDepth(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPhaserFeedback: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getPhaser()->setFeedback(mapfloat(nValue, 0, 99, 0.0f, 0.97f)); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPhaserNbStages: + nValue = constrain((int)nValue, 2, MAX_NB_PHASES); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getPhaser()->setNbStages(nValue); + this->m_FXSpinLock.Release(); + break; + + // Delay parameters + case TParameter::ParameterFXDelayEnable: + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getDelay()->setMute(!!!nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXDelayLeftDelayTime: + nValue = constrain((int)nValue, -99, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getDelay()->setLeftDelayTime(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXDelayRightDelayTime: + nValue = constrain((int)nValue, -99, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getDelay()->setRightDelayTime(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXDelayFeedback: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getDelay()->setFeedback(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + + // AudioEffectPlateReverb parameters + case TParameter::ParameterReverbEnable: + this->m_FXSpinLock.Acquire (); + this->mixing_console_->getPlateReverb()->set_bypass (!!!nValue); + this->m_FXSpinLock.Release (); + break; + case TParameter::ParameterReverbSize: + nValue=constrain((int)nValue,0,99); + this->m_FXSpinLock.Acquire (); + this->mixing_console_->getPlateReverb()->size (nValue / 99.0f); + this->m_FXSpinLock.Release (); + break; + case TParameter::ParameterReverbHighDamp: + nValue=constrain((int)nValue,0,99); + this->m_FXSpinLock.Acquire (); + this->mixing_console_->getPlateReverb()->hidamp (nValue / 99.0f); + this->m_FXSpinLock.Release (); + break; + case TParameter::ParameterReverbLowDamp: + nValue=constrain((int)nValue,0,99); + this->m_FXSpinLock.Acquire (); + this->mixing_console_->getPlateReverb()->lodamp (nValue / 99.0f); + this->m_FXSpinLock.Release (); + break; + case TParameter::ParameterReverbLowPass: + nValue=constrain((int)nValue,0,99); + this->m_FXSpinLock.Acquire (); + this->mixing_console_->getPlateReverb()->lowpass (nValue / 99.0f); + this->m_FXSpinLock.Release (); + break; + case TParameter::ParameterReverbDiffusion: + nValue=constrain((int)nValue,0,99); + this->m_FXSpinLock.Acquire (); + this->mixing_console_->getPlateReverb()->diffusion (nValue / 99.0f); + this->m_FXSpinLock.Release (); + break; + case TParameter::ParameterReverbLevel: + nValue=constrain((int)nValue,0,99); + this->m_FXSpinLock.Acquire (); + this->mixing_console_->getPlateReverb()->level (nValue / 99.0f); + this->m_FXSpinLock.Release (); + break; + + // Reverberator parameters + case TParameter::ParameterFXReverberatorEnable: + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getReverberator()->setMute(!!!nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXReverberatorInputGain: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getReverberator()->setInputGain(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXReverberatorTime: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getReverberator()->setTime(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXReverberatorDiffusion: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getReverberator()->setDiffusion(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXReverberatorLP: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->mixing_console_->getReverberator()->setLP(nValue / 99.0f); + this->m_FXSpinLock.Release(); + break; + + // Tube Send parameters + case TParameter::ParameterFXTube_ChorusSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Chorus, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXTube_FlangerSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Flanger, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXTube_OrbitoneSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Orbitone, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXTube_PhaserSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Phaser, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXTube_DelaySend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Delay, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXTube_PlateReverbSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_PlateReverb, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXTube_ReverberatorSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Reverberator, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXTube_MainOutput: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Tube, MixerOutput::MainOutput, nValue); + this->m_FXSpinLock.Release(); + break; + + // Chorus Send parameters + case TParameter::ParameterFXChorus_TubeSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Tube, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXChorus_FlangerSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Flanger, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXChorus_OrbitoneSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Orbitone, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXChorus_PhaserSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Phaser, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXChorus_DelaySend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Delay, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXChorus_PlateReverbSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_PlateReverb, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXChorus_ReverberatorSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Reverberator, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXChorus_MainOutput: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::MainOutput, nValue); + this->m_FXSpinLock.Release(); + break; + + // Flanger Send parameters + case TParameter::ParameterFXFlanger_TubeSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Tube, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXFlanger_ChorusSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Chorus, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXFlanger_OrbitoneSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Orbitone, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXFlanger_PhaserSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Phaser, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXFlanger_DelaySend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Delay, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXFlanger_PlateReverbSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_PlateReverb, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXFlanger_ReverberatorSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Reverberator, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXFlanger_MainOutput: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::MainOutput, nValue); + this->m_FXSpinLock.Release(); + break; + + // Orbitone Send parameters + case TParameter::ParameterFXOrbitone_TubeSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Tube, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXOrbitone_ChorusSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Chorus, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXOrbitone_FlangerSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Flanger, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXOrbitone_PhaserSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Phaser, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXOrbitone_DelaySend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Delay, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXOrbitone_PlateReverbSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_PlateReverb, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXOrbitone_ReverberatorSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Reverberator, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXOrbitone_MainOutput: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::MainOutput, nValue); + this->m_FXSpinLock.Release(); + break; + + // Phaser Send parameters + case TParameter::ParameterFXPhaser_TubeSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Tube, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPhaser_ChorusSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Chorus, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPhaser_FlangerSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Flanger, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPhaser_OrbitoneSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Orbitone, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPhaser_DelaySend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Delay, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPhaser_PlateReverbSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_PlateReverb, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPhaser_ReverberatorSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Reverberator, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPhaser_MainOutput: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::MainOutput, nValue); + this->m_FXSpinLock.Release(); + break; + + // Delay Send parameters + case TParameter::ParameterFXDelay_TubeSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Tube, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXDelay_ChorusSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Chorus, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXDelay_FlangerSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Flanger, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXDelay_OrbitoneSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Orbitone, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXDelay_PhaserSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Phaser, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXDelay_PlateReverbSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_PlateReverb, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXDelay_ReverberatorSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Reverberator, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXDelay_MainOutput: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Delay, MixerOutput::MainOutput, nValue); + this->m_FXSpinLock.Release(); + break; + + // Reverb Send parameters + case TParameter::ParameterFXPlateReverb_TubeSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Tube, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPlateReverb_ChorusSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Chorus, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPlateReverb_FlangerSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Flanger, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPlateReverb_OrbitoneSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Orbitone, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPlateReverb_PhaserSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Phaser, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPlateReverb_DelaySend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Delay, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPlateReverb_ReverberatorSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Reverberator, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXPlateReverb_MainOutput: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::MainOutput, nValue); + this->m_FXSpinLock.Release(); + break; + + // Reverberator Send parameters + case TParameter::ParameterFXReverberator_TubeSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Tube, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXReverberator_ChorusSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Chorus, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXReverberator_FlangerSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Flanger, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXReverberator_OrbitoneSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Orbitone, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXReverberator_PhaserSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Phaser, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXReverberator_DelaySend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Delay, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXReverberator_PlateReverbSend: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_PlateReverb, nValue); + this->m_FXSpinLock.Release(); + break; + case TParameter::ParameterFXReverberator_MainOutput: + nValue = constrain((int)nValue, 0, 99); + this->m_FXSpinLock.Acquire(); + this->setMixingConsoleFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput, nValue); + this->m_FXSpinLock.Release(); + break; + + case TParameter::ParameterFXBypass: + this->m_FXSpinLock.Acquire(); + this->mixing_console_->bypass(!!nValue); + this->m_FXSpinLock.Release(); + break; + +#elif defined(PLATE_REVERB_ENABLE) + + case TParameter::ParameterReverbEnable: nValue=constrain((int)nValue,0,1); - m_ReverbSpinLock.Acquire (); + m_FXSpinLock.Acquire (); reverb->set_bypass (!nValue); - m_ReverbSpinLock.Release (); + m_FXSpinLock.Release (); break; - case ParameterReverbSize: + case TParameter::ParameterReverbSize: nValue=constrain((int)nValue,0,99); - m_ReverbSpinLock.Acquire (); + m_FXSpinLock.Acquire (); reverb->size (nValue / 99.0f); - m_ReverbSpinLock.Release (); + m_FXSpinLock.Release (); break; - case ParameterReverbHighDamp: + case TParameter::ParameterReverbHighDamp: nValue=constrain((int)nValue,0,99); - m_ReverbSpinLock.Acquire (); + m_FXSpinLock.Acquire (); reverb->hidamp (nValue / 99.0f); - m_ReverbSpinLock.Release (); + m_FXSpinLock.Release (); break; - case ParameterReverbLowDamp: + case TParameter::ParameterReverbLowDamp: nValue=constrain((int)nValue,0,99); - m_ReverbSpinLock.Acquire (); + m_FXSpinLock.Acquire (); reverb->lodamp (nValue / 99.0f); - m_ReverbSpinLock.Release (); + m_FXSpinLock.Release (); break; - case ParameterReverbLowPass: + case TParameter::ParameterReverbLowPass: nValue=constrain((int)nValue,0,99); - m_ReverbSpinLock.Acquire (); + m_FXSpinLock.Acquire (); reverb->lowpass (nValue / 99.0f); - m_ReverbSpinLock.Release (); + m_FXSpinLock.Release (); break; - case ParameterReverbDiffusion: + case TParameter::ParameterReverbDiffusion: nValue=constrain((int)nValue,0,99); - m_ReverbSpinLock.Acquire (); + m_FXSpinLock.Acquire (); reverb->diffusion (nValue / 99.0f); - m_ReverbSpinLock.Release (); + m_FXSpinLock.Release (); break; - case ParameterReverbLevel: + case TParameter::ParameterReverbLevel: nValue=constrain((int)nValue,0,99); - m_ReverbSpinLock.Acquire (); + m_FXSpinLock.Acquire (); reverb->level (nValue / 99.0f); - m_ReverbSpinLock.Release (); + m_FXSpinLock.Release (); break; +#endif + default: assert (0); break; @@ -762,7 +1536,8 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue) int CMiniDexed::GetParameter (TParameter Parameter) { - assert (Parameter < ParameterUnknown); + assert(Parameter < TParameter::ParameterUnknown); + return m_nParameter[Parameter]; } @@ -772,53 +1547,67 @@ void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nT switch (Parameter) { - case TGParameterVoiceBank: BankSelect (nValue, nTG); break; - case TGParameterVoiceBankMSB: BankSelectMSB (nValue, nTG); break; - case TGParameterVoiceBankLSB: BankSelectLSB (nValue, nTG); break; - case TGParameterProgram: ProgramChange (nValue, nTG); break; - case TGParameterVolume: SetVolume (nValue, nTG); break; - case TGParameterPan: SetPan (nValue, nTG); break; - case TGParameterMasterTune: SetMasterTune (nValue, nTG); break; - case TGParameterCutoff: SetCutoff (nValue, nTG); break; - case TGParameterResonance: SetResonance (nValue, nTG); break; - case TGParameterPitchBendRange: setPitchbendRange (nValue, nTG); break; - case TGParameterPitchBendStep: setPitchbendStep (nValue, nTG); break; - case TGParameterPortamentoMode: setPortamentoMode (nValue, nTG); break; - case TGParameterPortamentoGlissando: setPortamentoGlissando (nValue, nTG); break; - case TGParameterPortamentoTime: setPortamentoTime (nValue, nTG); break; - case TGParameterMonoMode: setMonoMode (nValue , nTG); break; + case TTGParameter::TGParameterVoiceBank: this->BankSelect (nValue, nTG); break; + case TTGParameter::TGParameterVoiceBankMSB: this->BankSelectMSB (nValue, nTG); break; + case TTGParameter::TGParameterVoiceBankLSB: this->BankSelectLSB (nValue, nTG); break; + case TTGParameter::TGParameterProgram: this->ProgramChange (nValue, nTG); break; + case TTGParameter::TGParameterVolume: this->SetVolume (nValue, nTG); break; + case TTGParameter::TGParameterPan: this->SetPan (nValue, nTG); break; + case TTGParameter::TGParameterMasterTune: this->SetMasterTune (nValue, nTG); break; + case TTGParameter::TGParameterCutoff: this->SetCutoff (nValue, nTG); break; + case TTGParameter::TGParameterResonance: this->SetResonance (nValue, nTG); break; + case TTGParameter::TGParameterPitchBendRange: this->setPitchbendRange (nValue, nTG); break; + case TTGParameter::TGParameterPitchBendStep: this->setPitchbendStep (nValue, nTG); break; + case TTGParameter::TGParameterPortamentoMode: this->setPortamentoMode (nValue, nTG); break; + case TTGParameter::TGParameterPortamentoGlissando: this->setPortamentoGlissando (nValue, nTG); break; + case TTGParameter::TGParameterPortamentoTime: this->setPortamentoTime (nValue, nTG); break; + case TTGParameter::TGParameterMonoMode: this->setMonoMode (nValue , nTG); break; - case TGParameterMWRange: setModController(0, 0, nValue, nTG); break; - case TGParameterMWPitch: setModController(0, 1, nValue, nTG); break; - case TGParameterMWAmplitude: setModController(0, 2, nValue, nTG); break; - case TGParameterMWEGBias: setModController(0, 3, nValue, nTG); break; + case TTGParameter::TGParameterMWRange: this->setModController(0, 0, nValue, nTG); break; + case TTGParameter::TGParameterMWPitch: this->setModController(0, 1, nValue, nTG); break; + case TTGParameter::TGParameterMWAmplitude: this->setModController(0, 2, nValue, nTG); break; + case TTGParameter::TGParameterMWEGBias: this->setModController(0, 3, nValue, nTG); break; - case TGParameterFCRange: setModController(1, 0, nValue, nTG); break; - case TGParameterFCPitch: setModController(1, 1, nValue, nTG); break; - case TGParameterFCAmplitude: setModController(1, 2, nValue, nTG); break; - case TGParameterFCEGBias: setModController(1, 3, nValue, nTG); break; + case TTGParameter::TGParameterFCRange: this->setModController(1, 0, nValue, nTG); break; + case TTGParameter::TGParameterFCPitch: this->setModController(1, 1, nValue, nTG); break; + case TTGParameter::TGParameterFCAmplitude: this->setModController(1, 2, nValue, nTG); break; + case TTGParameter::TGParameterFCEGBias: this->setModController(1, 3, nValue, nTG); break; - case TGParameterBCRange: setModController(2, 0, nValue, nTG); break; - case TGParameterBCPitch: setModController(2, 1, nValue, nTG); break; - case TGParameterBCAmplitude: setModController(2, 2, nValue, nTG); break; - case TGParameterBCEGBias: setModController(2, 3, nValue, nTG); break; + case TTGParameter::TGParameterBCRange: this->setModController(2, 0, nValue, nTG); break; + case TTGParameter::TGParameterBCPitch: this->setModController(2, 1, nValue, nTG); break; + case TTGParameter::TGParameterBCAmplitude: this->setModController(2, 2, nValue, nTG); break; + case TTGParameter::TGParameterBCEGBias: this->setModController(2, 3, nValue, nTG); break; - case TGParameterATRange: setModController(3, 0, nValue, nTG); break; - case TGParameterATPitch: setModController(3, 1, nValue, nTG); break; - case TGParameterATAmplitude: setModController(3, 2, nValue, nTG); break; - case TGParameterATEGBias: setModController(3, 3, nValue, nTG); break; + case TTGParameter::TGParameterATRange: this->setModController(3, 0, nValue, nTG); break; + case TTGParameter::TGParameterATPitch: this->setModController(3, 1, nValue, nTG); break; + case TTGParameter::TGParameterATAmplitude: this->setModController(3, 2, nValue, nTG); break; + case TTGParameter::TGParameterATEGBias: this->setModController(3, 3, nValue, nTG); break; - case TGParameterMIDIChannel: + case TTGParameter::TGParameterMIDIChannel: assert (0 <= nValue && nValue <= 255); SetMIDIChannel ((uint8_t) nValue, nTG); break; - case TGParameterReverbSend: SetReverbSend (nValue, nTG); break; +#if defined(MIXING_CONSOLE_ENABLE) + case TTGParameter::TGParameterMixingSendFXTube: this->setMixingConsoleSendLevel(nTG, MixerOutput::FX_Tube, nValue); break; + case TTGParameter::TGParameterMixingSendFXChorus: this->setMixingConsoleSendLevel(nTG, MixerOutput::FX_Chorus, nValue); break; + case TTGParameter::TGParameterMixingSendFXFlanger: this->setMixingConsoleSendLevel(nTG, MixerOutput::FX_Flanger, nValue); break; + case TTGParameter::TGParameterMixingSendFXOrbitone: this->setMixingConsoleSendLevel(nTG, MixerOutput::FX_Orbitone, nValue); break; + case TTGParameter::TGParameterMixingSendFXPhaser: this->setMixingConsoleSendLevel(nTG, MixerOutput::FX_Phaser, nValue); break; + case TTGParameter::TGParameterMixingSendFXDelay: this->setMixingConsoleSendLevel(nTG, MixerOutput::FX_Delay, nValue); break; + case TTGParameter::TGParameterMixingSendFXPlateReverb: this->setMixingConsoleSendLevel(nTG, MixerOutput::FX_PlateReverb, nValue); break; + case TTGParameter::TGParameterMixingSendFXReverberator: this->setMixingConsoleSendLevel(nTG, MixerOutput::FX_Reverberator, nValue); break; + case TTGParameter::TGParameterMixingSendFXMainOutput: this->setMixingConsoleSendLevel(nTG, MixerOutput::MainOutput, nValue); break; +#elif defined(PLATE_REVERB_ENABLE) + case TTGParameter::TGParameterReverbSend: this->SetReverbSend (nValue, nTG); break; +#endif // MIXING_CONSOLE_ENABLE default: assert (0); break; } + + this->m_UI.ParameterChanged(); } int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG) @@ -827,45 +1616,56 @@ int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG) switch (Parameter) { - case TGParameterVoiceBank: return m_nVoiceBankID[nTG]; - case TGParameterVoiceBankMSB: return m_nVoiceBankID[nTG] >> 7; - case TGParameterVoiceBankLSB: return m_nVoiceBankID[nTG] & 0x7F; - case TGParameterProgram: return m_nProgram[nTG]; - case TGParameterVolume: return m_nVolume[nTG]; - case TGParameterPan: return m_nPan[nTG]; - case TGParameterMasterTune: return m_nMasterTune[nTG]; - case TGParameterCutoff: return m_nCutoff[nTG]; - case TGParameterResonance: return m_nResonance[nTG]; - case TGParameterMIDIChannel: return m_nMIDIChannel[nTG]; - case TGParameterReverbSend: return m_nReverbSend[nTG]; - case TGParameterPitchBendRange: return m_nPitchBendRange[nTG]; - case TGParameterPitchBendStep: return m_nPitchBendStep[nTG]; - case TGParameterPortamentoMode: return m_nPortamentoMode[nTG]; - case TGParameterPortamentoGlissando: return m_nPortamentoGlissando[nTG]; - case TGParameterPortamentoTime: return m_nPortamentoTime[nTG]; - case TGParameterMonoMode: return m_bMonoMode[nTG] ? 1 : 0; - - case TGParameterMWRange: return getModController(0, 0, nTG); - case TGParameterMWPitch: return getModController(0, 1, nTG); - case TGParameterMWAmplitude: return getModController(0, 2, nTG); - case TGParameterMWEGBias: return getModController(0, 3, nTG); - - case TGParameterFCRange: return getModController(1, 0, nTG); - case TGParameterFCPitch: return getModController(1, 1, nTG); - case TGParameterFCAmplitude: return getModController(1, 2, nTG); - case TGParameterFCEGBias: return getModController(1, 3, nTG); + case TTGParameter::TGParameterVoiceBank: return m_nVoiceBankID[nTG]; + case TTGParameter::TGParameterVoiceBankMSB: return m_nVoiceBankID[nTG] >> 7; + case TTGParameter::TGParameterVoiceBankLSB: return m_nVoiceBankID[nTG] & 0x7F; + case TTGParameter::TGParameterProgram: return m_nProgram[nTG]; + case TTGParameter::TGParameterVolume: return m_nVolume[nTG]; + case TTGParameter::TGParameterPan: return m_nPan[nTG]; + case TTGParameter::TGParameterMasterTune: return m_nMasterTune[nTG]; + case TTGParameter::TGParameterCutoff: return m_nCutoff[nTG]; + case TTGParameter::TGParameterResonance: return m_nResonance[nTG]; + case TTGParameter::TGParameterMIDIChannel: return m_nMIDIChannel[nTG]; +#if defined(MIXING_CONSOLE_ENABLE) + case TTGParameter::TGParameterMixingSendFXTube: return this->getMixingConsoleSendLevel(nTG, MixerOutput::FX_Tube); + case TTGParameter::TGParameterMixingSendFXChorus: return this->getMixingConsoleSendLevel(nTG, MixerOutput::FX_Chorus); + case TTGParameter::TGParameterMixingSendFXFlanger: return this->getMixingConsoleSendLevel(nTG, MixerOutput::FX_Flanger); + case TTGParameter::TGParameterMixingSendFXOrbitone: return this->getMixingConsoleSendLevel(nTG, MixerOutput::FX_Orbitone); + case TTGParameter::TGParameterMixingSendFXPhaser: return this->getMixingConsoleSendLevel(nTG, MixerOutput::FX_Phaser); + case TTGParameter::TGParameterMixingSendFXDelay: return this->getMixingConsoleSendLevel(nTG, MixerOutput::FX_Delay); + case TTGParameter::TGParameterMixingSendFXPlateReverb: return this->getMixingConsoleSendLevel(nTG, MixerOutput::FX_PlateReverb); + case TTGParameter::TGParameterMixingSendFXReverberator: return this->getMixingConsoleSendLevel(nTG, MixerOutput::FX_Reverberator); + case TTGParameter::TGParameterMixingSendFXMainOutput: return this->getMixingConsoleSendLevel(nTG, MixerOutput::MainOutput); +#elif defined(PLATE_REVERB_ENABLE) + case TTGParameter::TGParameterReverbSend: return m_nReverbSend[nTG]; +#endif + case TTGParameter::TGParameterPitchBendRange: return m_nPitchBendRange[nTG]; + case TTGParameter::TGParameterPitchBendStep: return m_nPitchBendStep[nTG]; + case TTGParameter::TGParameterPortamentoMode: return m_nPortamentoMode[nTG]; + case TTGParameter::TGParameterPortamentoGlissando: return m_nPortamentoGlissando[nTG]; + case TTGParameter::TGParameterPortamentoTime: return m_nPortamentoTime[nTG]; + case TTGParameter::TGParameterMonoMode: return m_bMonoMode[nTG] ? 1 : 0; - case TGParameterBCRange: return getModController(2, 0, nTG); - case TGParameterBCPitch: return getModController(2, 1, nTG); - case TGParameterBCAmplitude: return getModController(2, 2, nTG); - case TGParameterBCEGBias: return getModController(2, 3, nTG); + case TTGParameter::TGParameterMWRange: return getModController(0, 0, nTG); + case TTGParameter::TGParameterMWPitch: return getModController(0, 1, nTG); + case TTGParameter::TGParameterMWAmplitude: return getModController(0, 2, nTG); + case TTGParameter::TGParameterMWEGBias: return getModController(0, 3, nTG); - case TGParameterATRange: return getModController(3, 0, nTG); - case TGParameterATPitch: return getModController(3, 1, nTG); - case TGParameterATAmplitude: return getModController(3, 2, nTG); - case TGParameterATEGBias: return getModController(3, 3, nTG); + case TTGParameter::TGParameterFCRange: return getModController(1, 0, nTG); + case TTGParameter::TGParameterFCPitch: return getModController(1, 1, nTG); + case TTGParameter::TGParameterFCAmplitude: return getModController(1, 2, nTG); + case TTGParameter::TGParameterFCEGBias: return getModController(1, 3, nTG); + case TTGParameter::TGParameterBCRange: return getModController(2, 0, nTG); + case TTGParameter::TGParameterBCPitch: return getModController(2, 1, nTG); + case TTGParameter::TGParameterBCAmplitude: return getModController(2, 2, nTG); + case TTGParameter::TGParameterBCEGBias: return getModController(2, 3, nTG); + case TTGParameter::TGParameterATRange: return getModController(3, 0, nTG); + case TTGParameter::TGParameterATPitch: return getModController(3, 1, nTG); + case TTGParameter::TGParameterATAmplitude: return getModController(3, 2, nTG); + case TTGParameter::TGParameterATEGBias: return getModController(3, 3, nTG); + default: assert (0); return 0; @@ -1003,6 +1803,10 @@ void CMiniDexed::ProcessSound (void) { assert (m_pTG[i]); m_pTG[i]->getSamples (m_OutputLevel[i], nFrames); + +#if defined(MIXING_CONSOLE_ENABLE) + this->mixing_console_->preProcessInputSampleBuffer(i, nFrames); +#endif } // wait for cores 2 and 3 to complete their work @@ -1016,17 +1820,40 @@ void CMiniDexed::ProcessSound (void) // // Audio signal path after tone generators starts here - // + int16_t tmp_int[nFrames * 2]; - assert (CConfig::ToneGenerators == 8); +#if defined(MIXING_CONSOLE_ENABLE) + // BEGIN mixing + if(this->nMasterVolume > 0.0f) + { + // temp buffering and channel indexing + float32_t interlacedSampleBuffer[nFrames << 1]; + + this->m_FXSpinLock.Acquire(); + this->mixing_console_->process(interlacedSampleBuffer); + this->m_FXSpinLock.Release(); + + if(this->nMasterVolume < 1.0f) + { + arm_scale_f32(interlacedSampleBuffer, this->nMasterVolume, interlacedSampleBuffer, nFrames << 1); + } + + // Convert float array (left, right) to single int16 array (left/right) + arm_float_to_q15(interlacedSampleBuffer, tmp_int, nFrames << 1); + } + else // this->nMasterVolume == 0.0f + { + arm_fill_q15(0, tmp_int, nFrames << 1); + } + +#elif defined(PLATE_REVERB_ENABLE) uint8_t indexL=0, indexR=1; // BEGIN TG mixing float32_t tmp_float[nFrames*2]; - int16_t tmp_int[nFrames*2]; - if(nMasterVolume > 0.0) + if(nMasterVolume > 0.0f) { for (uint8_t i = 0; i < CConfig::ToneGenerators; i++) { @@ -1053,9 +1880,9 @@ void CMiniDexed::ProcessSound (void) arm_fill_f32(0.0f, ReverbSendBuffer[indexR], nFrames); arm_fill_f32(0.0f, ReverbSendBuffer[indexL], nFrames); - m_ReverbSpinLock.Acquire (); + m_FXSpinLock.Acquire (); - reverb_send_mixer->getMix(ReverbSendBuffer[indexL], ReverbSendBuffer[indexR]); + reverb_send_mixer->getMix(ReverbSendBuffer[indexL], ReverbSendBuffer[indexR]); reverb->doReverb(ReverbSendBuffer[indexL],ReverbSendBuffer[indexR],ReverbBuffer[indexL], ReverbBuffer[indexR],nFrames); // scale down and add left reverb buffer by reverb level @@ -1065,7 +1892,7 @@ void CMiniDexed::ProcessSound (void) arm_scale_f32(ReverbBuffer[indexR], reverb->get_level(), ReverbBuffer[indexR], nFrames); arm_add_f32(SampleBuffer[indexR], ReverbBuffer[indexR], SampleBuffer[indexR], nFrames); - m_ReverbSpinLock.Release (); + m_FXSpinLock.Release (); } // END adding reverb @@ -1094,15 +1921,16 @@ void CMiniDexed::ProcessSound (void) } else arm_fill_q15(0, tmp_int, nFrames * 2); +#endif - if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int)) + if(this->m_pSoundDevice->Write(tmp_int, sizeof(tmp_int)) != (int)sizeof(tmp_int)) { LOGERR ("Sound data dropped"); } - if (m_bProfileEnabled) + if(this->m_bProfileEnabled) { - m_GetChunkTimer.Stop (); + this->m_GetChunkTimer.Stop (); } } } @@ -1150,18 +1978,139 @@ bool CMiniDexed::DoSavePerformance (void) m_PerformanceConfig.SetBreathControlTarget (m_nBreathControlTarget[nTG], nTG); m_PerformanceConfig.SetAftertouchRange (m_nAftertouchRange[nTG], nTG); m_PerformanceConfig.SetAftertouchTarget (m_nAftertouchTarget[nTG], nTG); - + +#if defined(MIXING_CONSOLE_ENABLE) + for(size_t fx = 0; fx < MixerOutput::kFXCount; ++fx) + { + this->m_PerformanceConfig.SetTGSendLevel(nTG, static_cast(fx), this->m_nTGSendLevel[nTG][fx]); + } +#endif + +#if defined(PLATE_REVERB_ENABLE) m_PerformanceConfig.SetReverbSend (m_nReverbSend[nTG], nTG); +#endif } - m_PerformanceConfig.SetCompressorEnable (!!m_nParameter[ParameterCompressorEnable]); - m_PerformanceConfig.SetReverbEnable (!!m_nParameter[ParameterReverbEnable]); - m_PerformanceConfig.SetReverbSize (m_nParameter[ParameterReverbSize]); - m_PerformanceConfig.SetReverbHighDamp (m_nParameter[ParameterReverbHighDamp]); - m_PerformanceConfig.SetReverbLowDamp (m_nParameter[ParameterReverbLowDamp]); - m_PerformanceConfig.SetReverbLowPass (m_nParameter[ParameterReverbLowPass]); - m_PerformanceConfig.SetReverbDiffusion (m_nParameter[ParameterReverbDiffusion]); - m_PerformanceConfig.SetReverbLevel (m_nParameter[ParameterReverbLevel]); + m_PerformanceConfig.SetCompressorEnable (!!m_nParameter[TParameter::ParameterCompressorEnable]); +#if defined(MIXING_CONSOLE_ENABLE) || defined(PLATE_REVERB_ENABLE) + m_PerformanceConfig.SetReverbEnable (!!m_nParameter[TParameter::ParameterReverbEnable]); + m_PerformanceConfig.SetReverbSize (m_nParameter[TParameter::ParameterReverbSize]); + m_PerformanceConfig.SetReverbHighDamp (m_nParameter[TParameter::ParameterReverbHighDamp]); + m_PerformanceConfig.SetReverbLowDamp (m_nParameter[TParameter::ParameterReverbLowDamp]); + m_PerformanceConfig.SetReverbLowPass (m_nParameter[TParameter::ParameterReverbLowPass]); + m_PerformanceConfig.SetReverbDiffusion (m_nParameter[TParameter::ParameterReverbDiffusion]); + m_PerformanceConfig.SetReverbLevel (m_nParameter[TParameter::ParameterReverbLevel]); +#endif + +#ifdef MIXING_CONSOLE_ENABLE + this->m_PerformanceConfig.SetFXTubeEnable(!!this->m_nParameter[TParameter::ParameterFXTubeEnable]); + this->m_PerformanceConfig.SetFXTubeOverdrive(this->m_nParameter[TParameter::ParameterFXTubeOverdrive]); + + this->m_PerformanceConfig.SetFXChorusEnable(!!this->m_nParameter[TParameter::ParameterFXChorusEnable]); + this->m_PerformanceConfig.SetFXChorusRate(this->m_nParameter[TParameter::ParameterFXChorusRate]); + this->m_PerformanceConfig.SetFXChorusDepth(this->m_nParameter[TParameter::ParameterFXChorusDepth]); + + this->m_PerformanceConfig.SetFXFlangerEnable(!!this->m_nParameter[TParameter::ParameterFXFlangerEnable]); + this->m_PerformanceConfig.SetFXFlangerRate(this->m_nParameter[TParameter::ParameterFXFlangerRate]); + this->m_PerformanceConfig.SetFXFlangerDepth(this->m_nParameter[TParameter::ParameterFXFlangerDepth]); + this->m_PerformanceConfig.SetFXFlangerFeedback(this->m_nParameter[TParameter::ParameterFXFlangerFeedback]); + + this->m_PerformanceConfig.SetFXOrbitoneEnable(!!this->m_nParameter[TParameter::ParameterFXOrbitoneEnable]); + this->m_PerformanceConfig.SetFXOrbitoneRate(this->m_nParameter[TParameter::ParameterFXOrbitoneRate]); + this->m_PerformanceConfig.SetFXOrbitoneDepth(this->m_nParameter[TParameter::ParameterFXOrbitoneDepth]); + + this->m_PerformanceConfig.SetFXPhaserEnable(!!this->m_nParameter[TParameter::ParameterFXPhaserEnable]); + this->m_PerformanceConfig.SetFXPhaserRate(this->m_nParameter[TParameter::ParameterFXPhaserRate]); + this->m_PerformanceConfig.SetFXPhaserDepth(this->m_nParameter[TParameter::ParameterFXPhaserDepth]); + this->m_PerformanceConfig.SetFXPhaserFeedback(this->m_nParameter[TParameter::ParameterFXPhaserFeedback]); + this->m_PerformanceConfig.SetFXPhaserNbStages(this->m_nParameter[TParameter::ParameterFXPhaserNbStages]); + + this->m_PerformanceConfig.SetFXDelayEnable(!!this->m_nParameter[TParameter::ParameterFXDelayEnable]); + this->m_PerformanceConfig.SetFXDelayLeftDelayTime(this->m_nParameter[TParameter::ParameterFXDelayLeftDelayTime]); + this->m_PerformanceConfig.SetFXDelayRightDelayTime(this->m_nParameter[TParameter::ParameterFXDelayRightDelayTime]); + this->m_PerformanceConfig.SetFXDelayFeedback(this->m_nParameter[TParameter::ParameterFXDelayFeedback]); + + this->m_PerformanceConfig.SetFXReverberatorEnable(!!this->m_nParameter[TParameter::ParameterFXReverberatorEnable]); + this->m_PerformanceConfig.SetFXReverberatorInputGain(this->m_nParameter[TParameter::ParameterFXReverberatorInputGain]); + this->m_PerformanceConfig.SetFXReverberatorTime(this->m_nParameter[TParameter::ParameterFXReverberatorTime]); + this->m_PerformanceConfig.SetFXReverberatorDiffusion(this->m_nParameter[TParameter::ParameterFXReverberatorDiffusion]); + this->m_PerformanceConfig.SetFXReverberatorLP(this->m_nParameter[TParameter::ParameterFXReverberatorLP]); + + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Chorus, this->m_nParameter[TParameter::ParameterFXTube_ChorusSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Flanger, this->m_nParameter[TParameter::ParameterFXTube_FlangerSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Orbitone, this->m_nParameter[TParameter::ParameterFXTube_OrbitoneSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Phaser, this->m_nParameter[TParameter::ParameterFXTube_PhaserSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Delay, this->m_nParameter[TParameter::ParameterFXTube_DelaySend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_PlateReverb, this->m_nParameter[TParameter::ParameterFXTube_PlateReverbSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Reverberator, this->m_nParameter[TParameter::ParameterFXTube_ReverberatorSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::MainOutput, this->m_nParameter[TParameter::ParameterFXTube_MainOutput]); + + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Tube, this->m_nParameter[TParameter::ParameterFXChorus_TubeSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Flanger, this->m_nParameter[TParameter::ParameterFXChorus_FlangerSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Orbitone, this->m_nParameter[TParameter::ParameterFXChorus_OrbitoneSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Phaser, this->m_nParameter[TParameter::ParameterFXChorus_PhaserSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Delay, this->m_nParameter[TParameter::ParameterFXChorus_DelaySend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_PlateReverb, this->m_nParameter[TParameter::ParameterFXChorus_PlateReverbSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Reverberator, this->m_nParameter[TParameter::ParameterFXChorus_ReverberatorSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::MainOutput, this->m_nParameter[TParameter::ParameterFXChorus_MainOutput]); + + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Tube, this->m_nParameter[TParameter::ParameterFXFlanger_TubeSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Chorus, this->m_nParameter[TParameter::ParameterFXFlanger_ChorusSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Orbitone, this->m_nParameter[TParameter::ParameterFXFlanger_OrbitoneSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Phaser, this->m_nParameter[TParameter::ParameterFXFlanger_PhaserSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Delay, this->m_nParameter[TParameter::ParameterFXFlanger_DelaySend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_PlateReverb, this->m_nParameter[TParameter::ParameterFXFlanger_PlateReverbSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Reverberator, this->m_nParameter[TParameter::ParameterFXFlanger_ReverberatorSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::MainOutput, this->m_nParameter[TParameter::ParameterFXFlanger_MainOutput]); + + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Tube, this->m_nParameter[TParameter::ParameterFXOrbitone_TubeSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Chorus, this->m_nParameter[TParameter::ParameterFXOrbitone_ChorusSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Flanger, this->m_nParameter[TParameter::ParameterFXOrbitone_FlangerSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Phaser, this->m_nParameter[TParameter::ParameterFXOrbitone_PhaserSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Delay, this->m_nParameter[TParameter::ParameterFXOrbitone_DelaySend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_PlateReverb, this->m_nParameter[TParameter::ParameterFXOrbitone_PlateReverbSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Reverberator, this->m_nParameter[TParameter::ParameterFXOrbitone_ReverberatorSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::MainOutput, this->m_nParameter[TParameter::ParameterFXOrbitone_MainOutput]); + + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Tube, this->m_nParameter[TParameter::ParameterFXPhaser_TubeSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Chorus, this->m_nParameter[TParameter::ParameterFXPhaser_ChorusSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Flanger, this->m_nParameter[TParameter::ParameterFXPhaser_FlangerSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Orbitone, this->m_nParameter[TParameter::ParameterFXPhaser_OrbitoneSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Delay, this->m_nParameter[TParameter::ParameterFXPhaser_DelaySend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_PlateReverb, this->m_nParameter[TParameter::ParameterFXPhaser_PlateReverbSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Reverberator, this->m_nParameter[TParameter::ParameterFXPhaser_ReverberatorSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::MainOutput, this->m_nParameter[TParameter::ParameterFXPhaser_MainOutput]); + + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Tube, this->m_nParameter[TParameter::ParameterFXDelay_TubeSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Chorus, this->m_nParameter[TParameter::ParameterFXDelay_ChorusSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Flanger, this->m_nParameter[TParameter::ParameterFXDelay_FlangerSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Orbitone, this->m_nParameter[TParameter::ParameterFXDelay_OrbitoneSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Phaser, this->m_nParameter[TParameter::ParameterFXDelay_PhaserSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_PlateReverb, this->m_nParameter[TParameter::ParameterFXDelay_PlateReverbSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Reverberator, this->m_nParameter[TParameter::ParameterFXDelay_ReverberatorSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::MainOutput, this->m_nParameter[TParameter::ParameterFXDelay_MainOutput]); + + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Tube, this->m_nParameter[TParameter::ParameterFXPlateReverb_TubeSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Chorus, this->m_nParameter[TParameter::ParameterFXPlateReverb_ChorusSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Flanger, this->m_nParameter[TParameter::ParameterFXPlateReverb_FlangerSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Orbitone, this->m_nParameter[TParameter::ParameterFXPlateReverb_OrbitoneSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Phaser, this->m_nParameter[TParameter::ParameterFXPlateReverb_PhaserSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Delay, this->m_nParameter[TParameter::ParameterFXPlateReverb_DelaySend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Reverberator, this->m_nParameter[TParameter::ParameterFXPlateReverb_ReverberatorSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::MainOutput, this->m_nParameter[TParameter::ParameterFXPlateReverb_MainOutput]); + + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Tube, this->m_nParameter[TParameter::ParameterFXReverberator_TubeSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Chorus, this->m_nParameter[TParameter::ParameterFXReverberator_ChorusSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Flanger, this->m_nParameter[TParameter::ParameterFXReverberator_FlangerSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Orbitone, this->m_nParameter[TParameter::ParameterFXReverberator_OrbitoneSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Phaser, this->m_nParameter[TParameter::ParameterFXReverberator_PhaserSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Delay, this->m_nParameter[TParameter::ParameterFXReverberator_DelaySend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_PlateReverb, this->m_nParameter[TParameter::ParameterFXReverberator_PlateReverbSend]); + this->m_PerformanceConfig.SetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput, this->m_nParameter[TParameter::ParameterFXReverberator_MainOutput]); + + this->m_PerformanceConfig.SetFXBypass(this->mixing_console_->bypass()); + +#endif if(m_bSaveAsDeault) { @@ -1408,12 +2357,12 @@ void CMiniDexed::getSysExVoiceDump(uint8_t* dest, uint8_t nTG) void CMiniDexed::setMasterVolume (float32_t vol) { - if(vol < 0.0) - vol = 0.0; - else if(vol > 1.0) - vol = 1.0; + this->nMasterVolume = constrain(vol, 0.0f, 1.0f); + + + - nMasterVolume=vol; + } std::string CMiniDexed::GetPerformanceFileName(unsigned nID) @@ -1502,55 +2451,179 @@ bool CMiniDexed::DoSavePerformanceNewFile (void) void CMiniDexed::LoadPerformanceParameters(void) { for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) + { + + BankSelect (m_PerformanceConfig.GetBankNumber (nTG), nTG); + ProgramChange (m_PerformanceConfig.GetVoiceNumber (nTG), nTG); + SetMIDIChannel (m_PerformanceConfig.GetMIDIChannel (nTG), nTG); + SetVolume (m_PerformanceConfig.GetVolume (nTG), nTG); + SetPan (m_PerformanceConfig.GetPan (nTG), nTG); + SetMasterTune (m_PerformanceConfig.GetDetune (nTG), nTG); + SetCutoff (m_PerformanceConfig.GetCutoff (nTG), nTG); + SetResonance (m_PerformanceConfig.GetResonance (nTG), nTG); + setPitchbendRange (m_PerformanceConfig.GetPitchBendRange (nTG), nTG); + setPitchbendStep (m_PerformanceConfig.GetPitchBendStep (nTG), nTG); + setPortamentoMode (m_PerformanceConfig.GetPortamentoMode (nTG), nTG); + setPortamentoGlissando (m_PerformanceConfig.GetPortamentoGlissando (nTG), nTG); + setPortamentoTime (m_PerformanceConfig.GetPortamentoTime (nTG), nTG); + + m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG); + m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG); + m_nNoteShift[nTG] = m_PerformanceConfig.GetNoteShift (nTG); + + + + + + + + + + + + + + + + + + + + if(m_PerformanceConfig.VoiceDataFilled(nTG)) { + uint8_t* tVoiceData = m_PerformanceConfig.GetVoiceDataFromTxt(nTG); + m_pTG[nTG]->loadVoiceParameters(tVoiceData); + } + setMonoMode(m_PerformanceConfig.GetMonoMode(nTG) ? 1 : 0, nTG); - BankSelect (m_PerformanceConfig.GetBankNumber (nTG), nTG); - ProgramChange (m_PerformanceConfig.GetVoiceNumber (nTG), nTG); - SetMIDIChannel (m_PerformanceConfig.GetMIDIChannel (nTG), nTG); - SetVolume (m_PerformanceConfig.GetVolume (nTG), nTG); - SetPan (m_PerformanceConfig.GetPan (nTG), nTG); - SetMasterTune (m_PerformanceConfig.GetDetune (nTG), nTG); - SetCutoff (m_PerformanceConfig.GetCutoff (nTG), nTG); - SetResonance (m_PerformanceConfig.GetResonance (nTG), nTG); - setPitchbendRange (m_PerformanceConfig.GetPitchBendRange (nTG), nTG); - setPitchbendStep (m_PerformanceConfig.GetPitchBendStep (nTG), nTG); - setPortamentoMode (m_PerformanceConfig.GetPortamentoMode (nTG), nTG); - setPortamentoGlissando (m_PerformanceConfig.GetPortamentoGlissando (nTG), nTG); - setPortamentoTime (m_PerformanceConfig.GetPortamentoTime (nTG), nTG); - - m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG); - m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG); - m_nNoteShift[nTG] = m_PerformanceConfig.GetNoteShift (nTG); - - if(m_PerformanceConfig.VoiceDataFilled(nTG)) - { - uint8_t* tVoiceData = m_PerformanceConfig.GetVoiceDataFromTxt(nTG); - m_pTG[nTG]->loadVoiceParameters(tVoiceData); - } - setMonoMode(m_PerformanceConfig.GetMonoMode(nTG) ? 1 : 0, nTG); - SetReverbSend (m_PerformanceConfig.GetReverbSend (nTG), nTG); - - setModWheelRange (m_PerformanceConfig.GetModulationWheelRange (nTG), nTG); - setModWheelTarget (m_PerformanceConfig.GetModulationWheelTarget (nTG), nTG); - setFootControllerRange (m_PerformanceConfig.GetFootControlRange (nTG), nTG); - setFootControllerTarget (m_PerformanceConfig.GetFootControlTarget (nTG), nTG); - setBreathControllerRange (m_PerformanceConfig.GetBreathControlRange (nTG), nTG); - setBreathControllerTarget (m_PerformanceConfig.GetBreathControlTarget (nTG), nTG); - setAftertouchRange (m_PerformanceConfig.GetAftertouchRange (nTG), nTG); - setAftertouchTarget (m_PerformanceConfig.GetAftertouchTarget (nTG), nTG); - - + this->SetParameter(TParameter::ParameterCompressorEnable, this->m_PerformanceConfig.GetCompressorEnable()); +#if defined(MIXING_CONSOLE_ENABLE) + for(size_t fx = 0; fx < MixerOutput::kFXCount; ++fx) + { + this->setMixingConsoleSendLevel(nTG, static_cast(fx), this->m_PerformanceConfig.GetTGSendLevel(nTG, static_cast(fx))); } +#elif defined(PLATE_REVERB_ENABLE) + SetReverbSend (m_PerformanceConfig.GetReverbSend (nTG), nTG); +#endif - // Effects - SetParameter (ParameterCompressorEnable, m_PerformanceConfig.GetCompressorEnable () ? 1 : 0); - SetParameter (ParameterReverbEnable, m_PerformanceConfig.GetReverbEnable () ? 1 : 0); - SetParameter (ParameterReverbSize, m_PerformanceConfig.GetReverbSize ()); - SetParameter (ParameterReverbHighDamp, m_PerformanceConfig.GetReverbHighDamp ()); - SetParameter (ParameterReverbLowDamp, m_PerformanceConfig.GetReverbLowDamp ()); - SetParameter (ParameterReverbLowPass, m_PerformanceConfig.GetReverbLowPass ()); - SetParameter (ParameterReverbDiffusion, m_PerformanceConfig.GetReverbDiffusion ()); - SetParameter (ParameterReverbLevel, m_PerformanceConfig.GetReverbLevel ()); + setModWheelRange (m_PerformanceConfig.GetModulationWheelRange (nTG), nTG); + setModWheelTarget (m_PerformanceConfig.GetModulationWheelTarget (nTG), nTG); + setFootControllerRange (m_PerformanceConfig.GetFootControlRange (nTG), nTG); + setFootControllerTarget (m_PerformanceConfig.GetFootControlTarget (nTG), nTG); + setBreathControllerRange (m_PerformanceConfig.GetBreathControlRange (nTG), nTG); + setBreathControllerTarget (m_PerformanceConfig.GetBreathControlTarget (nTG), nTG); + setAftertouchRange (m_PerformanceConfig.GetAftertouchRange (nTG), nTG); + setAftertouchTarget (m_PerformanceConfig.GetAftertouchTarget (nTG), nTG); + } + +#ifdef MIXING_CONSOLE_ENABLE + this->SetParameter(TParameter::ParameterFXTubeEnable, this->m_PerformanceConfig.GetFXTubeEnable()); + this->SetParameter(TParameter::ParameterFXTubeOverdrive, this->m_PerformanceConfig.GetFXTubeOverdrive()); + + this->SetParameter(TParameter::ParameterFXChorusEnable, this->m_PerformanceConfig.GetFXChorusEnable()); + this->SetParameter(TParameter::ParameterFXChorusRate, this->m_PerformanceConfig.GetFXChorusRate()); + this->SetParameter(TParameter::ParameterFXChorusDepth, this->m_PerformanceConfig.GetFXChorusDepth()); + + this->SetParameter(TParameter::ParameterFXFlangerEnable, this->m_PerformanceConfig.GetFXFlangerEnable()); + this->SetParameter(TParameter::ParameterFXFlangerRate, this->m_PerformanceConfig.GetFXFlangerRate()); + this->SetParameter(TParameter::ParameterFXFlangerDepth, this->m_PerformanceConfig.GetFXFlangerDepth()); + this->SetParameter(TParameter::ParameterFXFlangerFeedback, this->m_PerformanceConfig.GetFXFlangerFeedback()); + + this->SetParameter(TParameter::ParameterFXOrbitoneEnable, this->m_PerformanceConfig.GetFXOrbitoneEnable()); + this->SetParameter(TParameter::ParameterFXOrbitoneRate, this->m_PerformanceConfig.GetFXOrbitoneRate()); + this->SetParameter(TParameter::ParameterFXOrbitoneDepth, this->m_PerformanceConfig.GetFXOrbitoneDepth()); + + this->SetParameter(TParameter::ParameterFXPhaserEnable, this->m_PerformanceConfig.GetFXPhaserEnable()); + this->SetParameter(TParameter::ParameterFXPhaserRate, this->m_PerformanceConfig.GetFXPhaserRate()); + this->SetParameter(TParameter::ParameterFXPhaserDepth, this->m_PerformanceConfig.GetFXPhaserDepth()); + this->SetParameter(TParameter::ParameterFXPhaserFeedback, this->m_PerformanceConfig.GetFXPhaserFeedback()); + this->SetParameter(TParameter::ParameterFXPhaserNbStages, this->m_PerformanceConfig.GetFXPhaserNbStages()); + + this->SetParameter(TParameter::ParameterFXDelayEnable, this->m_PerformanceConfig.GetFXDelayEnable()); + this->SetParameter(TParameter::ParameterFXDelayLeftDelayTime, this->m_PerformanceConfig.GetFXDelayLeftDelayTime()); + this->SetParameter(TParameter::ParameterFXDelayRightDelayTime, this->m_PerformanceConfig.GetFXDelayRightDelayTime()); + this->SetParameter(TParameter::ParameterFXDelayFeedback, this->m_PerformanceConfig.GetFXDelayFeedback()); + + this->SetParameter(TParameter::ParameterFXReverberatorEnable, this->m_PerformanceConfig.GetFXReverberatorEnable()); + this->SetParameter(TParameter::ParameterFXReverberatorInputGain, this->m_PerformanceConfig.GetFXReverberatorInputGain()); + this->SetParameter(TParameter::ParameterFXReverberatorTime, this->m_PerformanceConfig.GetFXReverberatorTime()); + this->SetParameter(TParameter::ParameterFXReverberatorDiffusion, this->m_PerformanceConfig.GetFXReverberatorDiffusion()); + this->SetParameter(TParameter::ParameterFXReverberatorLP, this->m_PerformanceConfig.GetFXReverberatorLP()); + + this->SetParameter(TParameter::ParameterFXTube_ChorusSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Chorus)); + this->SetParameter(TParameter::ParameterFXTube_FlangerSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Flanger)); + this->SetParameter(TParameter::ParameterFXTube_OrbitoneSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Orbitone)); + this->SetParameter(TParameter::ParameterFXTube_PhaserSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Phaser)); + this->SetParameter(TParameter::ParameterFXTube_DelaySend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Delay)); + this->SetParameter(TParameter::ParameterFXTube_PlateReverbSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_PlateReverb)); + this->SetParameter(TParameter::ParameterFXTube_ReverberatorSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Reverberator)); + this->SetParameter(TParameter::ParameterFXTube_MainOutput, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Tube, MixerOutput::MainOutput)); + + this->SetParameter(TParameter::ParameterFXChorus_TubeSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Tube)); + this->SetParameter(TParameter::ParameterFXChorus_FlangerSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Flanger)); + this->SetParameter(TParameter::ParameterFXChorus_OrbitoneSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Orbitone)); + this->SetParameter(TParameter::ParameterFXChorus_PhaserSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Phaser)); + this->SetParameter(TParameter::ParameterFXChorus_DelaySend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Delay)); + this->SetParameter(TParameter::ParameterFXChorus_PlateReverbSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_PlateReverb)); + this->SetParameter(TParameter::ParameterFXChorus_ReverberatorSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Reverberator)); + this->SetParameter(TParameter::ParameterFXChorus_MainOutput, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::MainOutput)); + + this->SetParameter(TParameter::ParameterFXFlanger_TubeSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Tube)); + this->SetParameter(TParameter::ParameterFXFlanger_ChorusSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Chorus)); + this->SetParameter(TParameter::ParameterFXFlanger_OrbitoneSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Orbitone)); + this->SetParameter(TParameter::ParameterFXFlanger_PhaserSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Phaser)); + this->SetParameter(TParameter::ParameterFXFlanger_DelaySend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Delay)); + this->SetParameter(TParameter::ParameterFXFlanger_PlateReverbSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_PlateReverb)); + this->SetParameter(TParameter::ParameterFXFlanger_ReverberatorSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::FX_Reverberator)); + this->SetParameter(TParameter::ParameterFXFlanger_MainOutput, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Flanger, MixerOutput::MainOutput)); + + this->SetParameter(TParameter::ParameterFXOrbitone_TubeSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Tube)); + this->SetParameter(TParameter::ParameterFXOrbitone_ChorusSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Chorus)); + this->SetParameter(TParameter::ParameterFXOrbitone_FlangerSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Flanger)); + this->SetParameter(TParameter::ParameterFXOrbitone_PhaserSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Phaser)); + this->SetParameter(TParameter::ParameterFXOrbitone_DelaySend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Delay)); + this->SetParameter(TParameter::ParameterFXOrbitone_PlateReverbSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_PlateReverb)); + this->SetParameter(TParameter::ParameterFXOrbitone_ReverberatorSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_Reverberator)); + this->SetParameter(TParameter::ParameterFXOrbitone_MainOutput, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::MainOutput)); + + this->SetParameter(TParameter::ParameterFXPhaser_TubeSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Tube)); + this->SetParameter(TParameter::ParameterFXPhaser_ChorusSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Chorus)); + this->SetParameter(TParameter::ParameterFXPhaser_FlangerSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Flanger)); + this->SetParameter(TParameter::ParameterFXPhaser_OrbitoneSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Orbitone)); + this->SetParameter(TParameter::ParameterFXPhaser_DelaySend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Delay)); + this->SetParameter(TParameter::ParameterFXPhaser_PlateReverbSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_PlateReverb)); + this->SetParameter(TParameter::ParameterFXPhaser_ReverberatorSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Reverberator)); + this->SetParameter(TParameter::ParameterFXPhaser_MainOutput, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::MainOutput)); + + this->SetParameter(TParameter::ParameterFXDelay_TubeSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Tube)); + this->SetParameter(TParameter::ParameterFXDelay_ChorusSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Chorus)); + this->SetParameter(TParameter::ParameterFXDelay_FlangerSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Flanger)); + this->SetParameter(TParameter::ParameterFXDelay_OrbitoneSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Orbitone)); + this->SetParameter(TParameter::ParameterFXDelay_PhaserSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Phaser)); + this->SetParameter(TParameter::ParameterFXDelay_PlateReverbSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_PlateReverb)); + this->SetParameter(TParameter::ParameterFXDelay_ReverberatorSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::FX_Reverberator)); + this->SetParameter(TParameter::ParameterFXDelay_MainOutput, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Delay, MixerOutput::MainOutput)); + + this->SetParameter(TParameter::ParameterFXPlateReverb_TubeSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Tube)); + this->SetParameter(TParameter::ParameterFXPlateReverb_ChorusSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Chorus)); + this->SetParameter(TParameter::ParameterFXPlateReverb_FlangerSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Flanger)); + this->SetParameter(TParameter::ParameterFXPlateReverb_OrbitoneSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Orbitone)); + this->SetParameter(TParameter::ParameterFXPlateReverb_PhaserSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Phaser)); + this->SetParameter(TParameter::ParameterFXPlateReverb_DelaySend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Delay)); + this->SetParameter(TParameter::ParameterFXPlateReverb_ReverberatorSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::FX_Reverberator)); + this->SetParameter(TParameter::ParameterFXPlateReverb_MainOutput, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::MainOutput)); + + this->SetParameter(TParameter::ParameterFXReverberator_TubeSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Tube)); + this->SetParameter(TParameter::ParameterFXReverberator_ChorusSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Chorus)); + this->SetParameter(TParameter::ParameterFXReverberator_FlangerSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Flanger)); + this->SetParameter(TParameter::ParameterFXReverberator_OrbitoneSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Orbitone)); + this->SetParameter(TParameter::ParameterFXReverberator_PhaserSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Phaser)); + this->SetParameter(TParameter::ParameterFXReverberator_DelaySend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_Delay)); + this->SetParameter(TParameter::ParameterFXReverberator_PlateReverbSend, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::FX_PlateReverb)); + this->SetParameter(TParameter::ParameterFXReverberator_MainOutput, this->m_PerformanceConfig.GetFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput)); + + this->mixing_console_->bypass(this->m_PerformanceConfig.IsFXBypass()); +#endif } std::string CMiniDexed::GetNewPerformanceDefaultName(void) @@ -1732,5 +2805,4 @@ unsigned CMiniDexed::getModController (unsigned controller, unsigned parameter, return 0; break; } - } diff --git a/src/minidexed.h b/src/minidexed.h index e49e7dea..ed6e84d1 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -20,6 +20,7 @@ #ifndef _minidexed_h #define _minidexed_h +#include "extra_features.h" #include "dexedadapter.h" #include "config.h" #include "userinterface.h" @@ -44,14 +45,25 @@ #include "effect_platervbstereo.h" #include "effect_compressor.h" +#if defined(MIXING_CONSOLE_ENABLE) +#include "mixing_console.hpp" + +typedef MixingConsole Mixer; +#endif + class CMiniDexed #ifdef ARM_ALLOW_MULTI_CORE : public CMultiCoreSupport #endif { public: - CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, - CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, FATFS *pFileSystem); + CMiniDexed( + CConfig *pConfig, + CInterruptSystem *pInterrupt, + CGPIOManager *pGPIOManager, + CI2CMaster *pI2CMaster, + FATFS *pFileSystem + ); bool Initialize (void); @@ -88,7 +100,13 @@ class CMiniDexed void setBreathController (uint8_t value, unsigned nTG); void setAftertouch (uint8_t value, unsigned nTG); +#if defined(MIXING_CONSOLE_ENABLE) + unsigned getMixingConsoleSendLevel(unsigned nTG, MixerOutput fx) const; + void setMixingConsoleSendLevel(unsigned nTG, MixerOutput fx, unsigned nFXSend); + void setMixingConsoleFXSendLevel(MixerOutput fromFX, MixerOutput toFX, unsigned nFXReturn); +#elif defined(PLATE_REVERB_ENABLE) void SetReverbSend (unsigned nReverbSend, unsigned nTG); // 0 .. 127 +#endif void setMonoMode(uint8_t mono, uint8_t nTG); void setPitchbendRange(uint8_t range, uint8_t nTG); @@ -130,6 +148,9 @@ class CMiniDexed enum TParameter { ParameterCompressorEnable, + + #if defined(PLATE_REVERB_ENABLE) || defined(MIXING_CONSOLE_ENABLE) + // Plate Reverb parameters ParameterReverbEnable, ParameterReverbSize, ParameterReverbHighDamp, @@ -137,6 +158,137 @@ class CMiniDexed ParameterReverbLowPass, ParameterReverbDiffusion, ParameterReverbLevel, + #endif + + // BEGIN FX global parameters definition + #if defined(MIXING_CONSOLE_ENABLE) + + // Tube parameters + ParameterFXTubeEnable, + ParameterFXTubeOverdrive, + + // Chorus parameters + ParameterFXChorusEnable, + ParameterFXChorusRate, + ParameterFXChorusDepth, + + // Flanger parameters + ParameterFXFlangerEnable, + ParameterFXFlangerRate, + ParameterFXFlangerDepth, + ParameterFXFlangerFeedback, + + // Orbitone parameters + ParameterFXOrbitoneEnable, + ParameterFXOrbitoneRate, + ParameterFXOrbitoneDepth, + + // Phaser parameters + ParameterFXPhaserEnable, + ParameterFXPhaserRate, + ParameterFXPhaserDepth, + ParameterFXPhaserFeedback, + ParameterFXPhaserNbStages, + + // Delay parameters + ParameterFXDelayEnable, + ParameterFXDelayLeftDelayTime, + ParameterFXDelayRightDelayTime, + ParameterFXDelayFeedback, + + // Reverberator parameters + ParameterFXReverberatorEnable, + ParameterFXReverberatorInputGain, + ParameterFXReverberatorTime, + ParameterFXReverberatorDiffusion, + ParameterFXReverberatorLP, + + // Tube Return parameters + ParameterFXTube_ChorusSend, + ParameterFXTube_FlangerSend, + ParameterFXTube_OrbitoneSend, + ParameterFXTube_PhaserSend, + ParameterFXTube_DelaySend, + ParameterFXTube_PlateReverbSend, + ParameterFXTube_ReverberatorSend, + ParameterFXTube_MainOutput, + + // Chorus Return parameters + ParameterFXChorus_TubeSend, + ParameterFXChorus_FlangerSend, + ParameterFXChorus_OrbitoneSend, + ParameterFXChorus_PhaserSend, + ParameterFXChorus_DelaySend, + ParameterFXChorus_PlateReverbSend, + ParameterFXChorus_ReverberatorSend, + ParameterFXChorus_MainOutput, + + // Flanger Return parameters + ParameterFXFlanger_TubeSend, + ParameterFXFlanger_ChorusSend, + ParameterFXFlanger_OrbitoneSend, + ParameterFXFlanger_PhaserSend, + ParameterFXFlanger_DelaySend, + ParameterFXFlanger_PlateReverbSend, + ParameterFXFlanger_ReverberatorSend, + ParameterFXFlanger_MainOutput, + + // Orbitone Return parameters + ParameterFXOrbitone_TubeSend, + ParameterFXOrbitone_ChorusSend, + ParameterFXOrbitone_FlangerSend, + ParameterFXOrbitone_PhaserSend, + ParameterFXOrbitone_DelaySend, + ParameterFXOrbitone_PlateReverbSend, + ParameterFXOrbitone_ReverberatorSend, + ParameterFXOrbitone_MainOutput, + + // Phaser Return parameters + ParameterFXPhaser_TubeSend, + ParameterFXPhaser_ChorusSend, + ParameterFXPhaser_FlangerSend, + ParameterFXPhaser_OrbitoneSend, + ParameterFXPhaser_DelaySend, + ParameterFXPhaser_PlateReverbSend, + ParameterFXPhaser_ReverberatorSend, + ParameterFXPhaser_MainOutput, + + // Delay Return parameters + ParameterFXDelay_TubeSend, + ParameterFXDelay_ChorusSend, + ParameterFXDelay_FlangerSend, + ParameterFXDelay_OrbitoneSend, + ParameterFXDelay_PhaserSend, + ParameterFXDelay_PlateReverbSend, + ParameterFXDelay_ReverberatorSend, + ParameterFXDelay_MainOutput, + + // Plate Reverb Return parameters + ParameterFXPlateReverb_TubeSend, + ParameterFXPlateReverb_ChorusSend, + ParameterFXPlateReverb_FlangerSend, + ParameterFXPlateReverb_OrbitoneSend, + ParameterFXPlateReverb_PhaserSend, + ParameterFXPlateReverb_DelaySend, + ParameterFXPlateReverb_ReverberatorSend, + ParameterFXPlateReverb_MainOutput, + + // Reverberator Return parameters + ParameterFXReverberator_TubeSend, + ParameterFXReverberator_ChorusSend, + ParameterFXReverberator_FlangerSend, + ParameterFXReverberator_OrbitoneSend, + ParameterFXReverberator_PhaserSend, + ParameterFXReverberator_DelaySend, + ParameterFXReverberator_PlateReverbSend, + ParameterFXReverberator_MainOutput, + + // Bypass FX + ParameterFXBypass, + + #endif + // END FX global parameters definition + ParameterUnknown }; @@ -162,7 +314,9 @@ class CMiniDexed TGParameterCutoff, TGParameterResonance, TGParameterMIDIChannel, +#if defined(PLATE_REVERB_ENABLE) TGParameterReverbSend, +#endif TGParameterPitchBendRange, TGParameterPitchBendStep, TGParameterPortamentoMode, @@ -190,6 +344,18 @@ class CMiniDexed TGParameterATAmplitude, TGParameterATEGBias, +#if defined(MIXING_CONSOLE_ENABLE) + TGParameterMixingSendFXTube, + TGParameterMixingSendFXChorus, + TGParameterMixingSendFXFlanger, + TGParameterMixingSendFXOrbitone, + TGParameterMixingSendFXPhaser, + TGParameterMixingSendFXDelay, + TGParameterMixingSendFXPlateReverb, + TGParameterMixingSendFXReverberator, + TGParameterMixingSendFXMainOutput, +#endif // MIXING_CONSOLE_ENABLE + TGParameterUnknown }; @@ -261,8 +427,13 @@ class CMiniDexed unsigned m_nNoteLimitHigh[CConfig::ToneGenerators]; int m_nNoteShift[CConfig::ToneGenerators]; +#ifdef MIXING_CONSOLE_ENABLE + unsigned m_nTGSendLevel[CConfig::ToneGenerators][MixerOutput::kFXCount]; + unsigned m_nFXSendLevel[MixerOutput::kFXCount - 1][MixerOutput::kFXCount]; +#elif defined(PLATE_REVERB_ENABLE) unsigned m_nReverbSend[CConfig::ToneGenerators]; - +#endif + uint8_t m_nRawVoiceData[156]; @@ -291,11 +462,16 @@ class CMiniDexed CPerformanceTimer m_GetChunkTimer; bool m_bProfileEnabled; +#if defined(MIXING_CONSOLE_ENABLE) + Mixer* mixing_console_; +#elif defined(PLATE_REVERB_ENABLE) AudioEffectPlateReverb* reverb; AudioStereoMixer* tg_mixer; AudioStereoMixer* reverb_send_mixer; +#endif + + CSpinLock m_FXSpinLock; - CSpinLock m_ReverbSpinLock; bool m_bSavePerformance; bool m_bSavePerformanceNewFile; diff --git a/src/mixing_console.hpp b/src/mixing_console.hpp new file mode 100644 index 00000000..af679fc0 --- /dev/null +++ b/src/mixing_console.hpp @@ -0,0 +1,793 @@ +// +// mixing_console.hpp +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Author: Vincent Gauché +// Copyright (C) 2022 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +// Implementation of the MixingConsole class defined in mixing_console.h +#pragma once + +#include "mixing_console_constants.h" +#include "fx_tube.h" +#include "fx_chorus.h" +#include "fx_flanger.h" +#include "fx_orbitone.h" +#include "fx_phaser.h" +#include "fx_delay.h" +#include "effect_platervbstereo.h" +#include "fx_reverberator.h" +#include "fx_dry.h" +#include "fx_unit2.hpp" + +template +class MixingConsole : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(MixingConsole); + +public: + MixingConsole(float32_t sampling_rate, size_t buffer_size, bool swapStereoImage = false); + ~MixingConsole(); + + inline size_t getChannelNumber() const; + + inline void bypass(bool bypass); + inline bool bypass() const; + + // Send section + inline void setChannelLevel(size_t in, float32_t lvl); + inline void setPan(size_t in, float32_t pan); + inline void swapStereoImage(bool swap); + inline void setSendLevel(size_t in, MixerOutput fx, float32_t lvl); + inline void setInputSample(size_t in, float32_t sampleL, float32_t sampleR); + inline void setInputSampleBuffer(size_t in, float32_t* samples); + + // Return section + inline void setFXSendLevel(MixerOutput fromFX, MixerOutput toFX, float32_t lvl); + inline void setReturnSample(MixerOutput ret, float32_t sampleL, float32_t sampleR); + + // Get FX + inline FXElement* getFX(size_t fx); + inline FXUnit2* getTube(); + inline FXUnit2* getChorus(); + inline FXUnit2* getFlanger(); + inline FXUnit2* getOrbitone(); + inline FXUnit2* getPhaser(); + inline FXUnit2* getDelay(); + inline FXUnit2* getPlateReverb(); + inline FXUnit2* getReverberator(); + inline FXUnit2* getDry(); + + // Processing + inline void init(); + inline void reset() override; + inline void preProcessInputSampleBuffer(size_t in, size_t nSamples); + inline void injectInputSamples(size_t in, float32_t* samplesL, float32_t* samplesR, size_t nSamples); + inline void processSample(float32_t& outL, float32_t& outR); + void process(float32_t* outL, float32_t* outR); + void process(float32_t* outLR); + +protected: + inline void updatePan(size_t in); + inline void setLevel(size_t in, MixerOutput fx, float32_t lvl); + inline void setSample(size_t in, float32_t sampleL, float32_t sampleR); + +private: + static inline float32_t weighted_sum(const float32_t* data, const float32_t* weights, size_t size); + + const size_t BufferSize; + + bool bypass_; + + float32_t channel_level_[nb_inputs]; + float32_t pan_[StereoChannels::kNumChannels + 1][nb_inputs]; + bool swap_stereo_image_; + float32_t* tg_input_sample_buffer_[nb_inputs]; + float32_t* input_sample_buffer_[StereoChannels::kNumChannels][nb_inputs]; + float32_t input_samples_[StereoChannels::kNumChannels][nb_inputs + MixerOutput::kFXCount - 1]; + float32_t levels_[MixerOutput::kFXCount][nb_inputs + MixerOutput::kFXCount - 1]; + volatile size_t m_nSamples; + + FXElement* fx_[MixerOutput::kFXCount]; + FXUnit2* tube_; + FXUnit2* chorus_; + FXUnit2* flanger_; + FXUnit2* orbitone_; + FXUnit2* phaser_; + FXUnit2* delay_; + FXUnit2* plate_reverb_; + FXUnit2* reverberator_; + FXUnit2* dry_; + + IMPLEMENT_DUMP( + const size_t space = 9; + const size_t precision = 5; + + std::stringstream ss; + + out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + + out << "\t" << "Input levels & Pan:" << std::endl; + { + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, ' ', space, std::left, '|'); + SS__TEXT(ss, ' ', space, std::left, '|', "Level"); + SS__TEXT(ss, ' ', space, std::left, '|', "Pan L"); + SS__TEXT(ss, ' ', space, std::left, '|', "Pan R"); + SS__TEXT(ss, ' ', space, std::left, '|', "Pan"); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); + out << "\t" << ss.str() << std::endl; + + for(size_t i = 0; i < nb_inputs; ++i) + { + std::stringstream s; + s << "* Input "; + s << (i + 1); + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', s.str()); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->channel_level_[i]); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->pan_[StereoChannels::Left][i]); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->pan_[StereoChannels::Right][i]); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->pan_[StereoChannels::kNumChannels][i]); + + out << "\t" << ss.str() << std::endl; + } + } + out << std::endl; + + out << "\t" << "Mixing Console input samples:" << std::endl; + { + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, ' ', space, std::left, '|'); + for(size_t i = 0; i < nb_inputs; ++i) + { + std::stringstream s; + s << "Input "; + s << (i + 1); + + SS__TEXT(ss, ' ', space, std::left, '|', s.str()); + } + for(size_t i = 0; i < (MixerOutput::kFXCount - 1); ++i) + { + std::string s = toString(static_cast(i)); + s.resize(space); + SS__TEXT(ss, ' ', space, std::left, '|', s.c_str()); + } + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + for(size_t i = 0; i < nb_inputs; ++i) + { + SS_SPACE(ss, '-', space, std::left, '+'); + } + for(size_t i = 0; i < (MixerOutput::kFXCount - 1); ++i) + { + SS_SPACE(ss, '-', space, std::left, '+'); + } + out << "\t" << ss.str() << std::endl; + + const char* LR = "LR"; + for(size_t c = 0; c < StereoChannels::kNumChannels; ++c) + { + std::stringstream s; + s << "* Input "; + s << LR[c]; + + SS_RESET(ss, precision, std::left); + SS__TEXT(ss, ' ', space, std::left, '|', s.str()); + for(size_t i = 0; i < (nb_inputs + MixerOutput::kFXCount - 1); ++i) + { + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->input_samples_[c][i]); + } + out << "\t" << ss.str() << std::endl; + } + } + out << std::endl; + + out << "\t" << "Mixing Console levels:" << std::endl; + { + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, ' ', space, std::left, '|'); + for(size_t i = 0; i < nb_inputs; ++i) + { + std::stringstream s; + s << "Input "; + s << (i + 1); + + SS__TEXT(ss, ' ', space, std::left, '|', s.str()); + } + for(size_t i = 0; i < (MixerOutput::kFXCount - 1); ++i) + { + std::string s = toString(static_cast(i)); + s.resize(space); + SS__TEXT(ss, ' ', space, std::left, '|', s.c_str()); + } + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + SS_SPACE(ss, '-', space, std::left, '+'); + for(size_t i = 0; i < nb_inputs; ++i) + { + SS_SPACE(ss, '-', space, std::left, '+'); + } + for(size_t i = 0; i < (MixerOutput::kFXCount - 1); ++i) + { + SS_SPACE(ss, '-', space, std::left, '+'); + } + out << "\t" << ss.str() << std::endl; + + for(size_t c = 0; c < MixerOutput::kFXCount; ++c) + { + SS_RESET(ss, precision, std::left); + std::string s = toString(static_cast(c)); + s.resize(space); + SS__TEXT(ss, ' ', space, std::left, '|', s.c_str()); + for(size_t i = 0; i < (nb_inputs + MixerOutput::kFXCount - 1); ++i) + { + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->levels_[c][i]); + } + out << "\t" << ss.str() << std::endl; + } + } + out << std::endl; + + if(deepInspection) + { + this->tube_->dump(out, deepInspection, tag + ".tube_"); + this->chorus_->dump(out, deepInspection, tag + ".chorus_"); + this->flanger_->dump(out, deepInspection, tag + ".flanger_"); + this->orbitone_->dump(out, deepInspection, tag + ".orbitone_"); + this->phaser_->dump(out, deepInspection, tag + ".phaser_"); + this->delay_->dump(out, deepInspection, tag + ".delay_"); + this->plate_reverb_->dump(out, deepInspection, tag + ".plate_reverb_"); + this->reverberator_->dump(out, deepInspection, tag + ".reverberator_"); + this->dry_->dump(out, deepInspection, tag + ".dry_"); + } + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0; + + for(size_t i = 0; i < nb_inputs; ++i) + { + nb_errors += inspector(tag + ".level[ input #" + std::to_string(i) + " ]" , this->channel_level_[i], -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".pan[ L ][ input #" + std::to_string(i) + " ]", this->pan_[StereoChannels::Left][i], -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".pan[ R ][ input #" + std::to_string(i) + " ]", this->pan_[StereoChannels::Right][i], -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".pan[ input #" + std::to_string(i) + " ]", this->pan_[StereoChannels::kNumChannels][i], -1.0f, 1.0f, deepInspection); + } + + for(size_t i = 0; i < nb_inputs; ++i) + { + nb_errors += inspector(tag + ".input[ L ][ input #" + std::to_string(i) + " ]", this->input_samples_[StereoChannels::Left ][i], -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".input[ R ][ input #" + std::to_string(i) + " ]", this->input_samples_[StereoChannels::Right][i], -1.0f, 1.0f, deepInspection); + } + + for(size_t i = nb_inputs; i < (nb_inputs + MixerOutput::kFXCount - 1); ++i) + { + nb_errors += inspector(tag + ".input[ L ][ input " + toString(static_cast(i - nb_inputs)) + " ]", this->input_samples_[StereoChannels::Left ][i], -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".input[ R ][ input " + toString(static_cast(i - nb_inputs)) + " ]", this->input_samples_[StereoChannels::Right][i], -1.0f, 1.0f, deepInspection); + } + + for(size_t c = 0; c < MixerOutput::kFXCount; ++c) + { + for(size_t i = 0; i < (nb_inputs + MixerOutput::kFXCount - 1); ++i) + { + nb_errors += inspector(tag + ".levels[ " + std::to_string(c) + " ][ " + std::to_string(i) + " ]", this->levels_[c][i], -1.0f, 1.0f, deepInspection); + } + } + + if(deepInspection) + { + for(size_t i = 0; i < nb_inputs; ++i) + { + for(size_t k = 0; k < this->m_nSamples; ++k) + { + nb_errors += inspector(tag + ".input_sample_buffer_[ L ][ " + std::to_string(i) + " ][ " + std::to_string(k) +" ] ", this->input_sample_buffer_[StereoChannels::Left ][i][k], -1.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".input_sample_buffer_[ R ][ " + std::to_string(i) + " ][ " + std::to_string(k) +" ] ", this->input_sample_buffer_[StereoChannels::Right][i][k], -1.0f, 1.0f, deepInspection); + } + } + + nb_errors += this->tube_->inspect(inspector, deepInspection, tag + ".tube_"); + nb_errors += this->chorus_->inspect(inspector, deepInspection, tag + ".chorus_"); + nb_errors += this->flanger_->inspect(inspector, deepInspection, tag + ".flanger_"); + nb_errors += this->orbitone_->inspect(inspector, deepInspection, tag + ".orbitone_"); + nb_errors += this->phaser_->inspect(inspector, deepInspection, tag + ".phaser_"); + nb_errors += this->delay_->inspect(inspector, deepInspection, tag + ".delay_"); + nb_errors += this->plate_reverb_->inspect(inspector, deepInspection, tag + ".plate_reverb_"); + nb_errors += this->reverberator_->inspect(inspector, deepInspection, tag + ".reverberator_"); + nb_errors += this->dry_->inspect(inspector, deepInspection, tag + ".dry_"); + } + + return nb_errors; + ) +}; + +template +float32_t MixingConsole::weighted_sum(const float32_t* data, const float32_t* weights, size_t size) +{ + float32_t res = arm_weighted_sum_f32(data, weights, size); + + return std::isnan(res) ? 0.0f : res; +} + +template +MixingConsole::MixingConsole(float32_t sampling_rate, size_t buffer_size, bool swapStereoImage) : + FXBase(sampling_rate), + BufferSize(buffer_size), + bypass_(true), + swap_stereo_image_(swapStereoImage), + m_nSamples(0) +{ + for(size_t i = 0; i < nb_inputs; ++i) + { + this->tg_input_sample_buffer_[i] = nullptr; + this->input_sample_buffer_[StereoChannels::Left ][i] = new float32_t[this->BufferSize]; + this->input_sample_buffer_[StereoChannels::Right][i] = new float32_t[this->BufferSize]; + memset(this->input_sample_buffer_[StereoChannels::Left ][i], 0, sizeof(float32_t) * this->BufferSize); + memset(this->input_sample_buffer_[StereoChannels::Right][i], 0, sizeof(float32_t) * this->BufferSize); + } + + this->fx_[MixerOutput::FX_Tube] = this->tube_ = new FXUnit2(sampling_rate); + this->fx_[MixerOutput::FX_Chorus] = this->chorus_ = new FXUnit2(sampling_rate); + this->fx_[MixerOutput::FX_Flanger] = this->flanger_ = new FXUnit2(sampling_rate); + this->fx_[MixerOutput::FX_Orbitone] = this->orbitone_ = new FXUnit2(sampling_rate); + this->fx_[MixerOutput::FX_Phaser] = this->phaser_ = new FXUnit2(sampling_rate); + this->fx_[MixerOutput::FX_Delay] = this->delay_ = new FXUnit2(sampling_rate); + this->fx_[MixerOutput::FX_PlateReverb] = this->plate_reverb_ = new FXUnit2(sampling_rate); + this->fx_[MixerOutput::FX_Reverberator] = this->reverberator_ = new FXUnit2(sampling_rate); + this->fx_[MixerOutput::MainOutput] = this->dry_ = new FXUnit2(sampling_rate); + + this->bypass(false); + + this->init(); +} + +template +MixingConsole::~MixingConsole() +{ + for(size_t i = 0; i < MixerOutput::kFXCount; ++i) + { + delete this->fx_[i]; + } + + for(size_t i = 0; i < nb_inputs; ++i) + { + delete[] this->input_sample_buffer_[StereoChannels::Left ][i]; + delete[] this->input_sample_buffer_[StereoChannels::Right][i]; + + // The tg_input_sample_buffer_ buffers are not freed as MixingConsole is not the creator + // They must be freed by the creator of the buffers + this->tg_input_sample_buffer_[i] = nullptr; + } +} + +template +void MixingConsole::bypass(bool bypass) +{ + if(this->bypass_ != bypass) + { + this->bypass_ = bypass; + + for(size_t fx = MixerOutput::FX_Tube; fx < MixerOutput::kFXCount; ++fx) + { + this->getFX(fx)->bypassFXProcess(bypass); + } + + if(!bypass) + { + this->reset(); + } + } +} + +template +bool MixingConsole::bypass() const +{ + return this->bypass_; +} + +template +size_t MixingConsole::getChannelNumber() const +{ + return nb_inputs; +} + +// Send section +template +void MixingConsole::setChannelLevel(size_t in, float32_t lvl) +{ + assert(in < nb_inputs); + + lvl = constrain(lvl, 0.0f, 1.0f); + if(lvl == this->channel_level_[in]) return; + + this->channel_level_[in] = lvl; + this->updatePan(in); +} + +template +void MixingConsole::setPan(size_t in, float32_t pan) +{ + assert(in < nb_inputs); + + pan = constrain(pan, 0.0f, 1.0f); + + if(pan == this->pan_[StereoChannels::kNumChannels][in]) return; + + this->pan_[StereoChannels::kNumChannels][in] = pan; + this->updatePan(in); +} + +template +void MixingConsole::swapStereoImage(bool swap) +{ + this->swap_stereo_image_ = swap; +} + +template +void MixingConsole::setSendLevel(size_t in, MixerOutput fx, float32_t lvl) +{ + assert(in < nb_inputs); + assert(fx < MixerOutput::kFXCount); + + this->setLevel(in, fx, lvl); +} + +template +void MixingConsole::setInputSample(size_t in, float32_t sampleL, float32_t sampleR) +{ + assert(in < nb_inputs); + + this->setSample(in, sampleL, sampleR); +} + +template +void MixingConsole::setInputSampleBuffer(size_t in, float32_t* samples) +{ + assert(in < nb_inputs); + + this->tg_input_sample_buffer_[in] = samples; +} + +// Return section +template +void MixingConsole::setFXSendLevel(MixerOutput fromFX, MixerOutput toFX, float32_t lvl) +{ + assert(fromFX < (MixerOutput::kFXCount - 1)); + assert(toFX < MixerOutput::kFXCount); + + if(fromFX == toFX) + { + // An FX cannot feedback on itself + return; + } + + this->setLevel(nb_inputs + fromFX, toFX, lvl); +} + +template +void MixingConsole::setReturnSample(MixerOutput ret, float32_t sampleL, float32_t sampleR) +{ + assert(ret < (MixerOutput::kFXCount - 1)); + + this->setSample(nb_inputs + ret, sampleL, sampleR); +} + +// Get FX +template +FXElement* MixingConsole::getFX(size_t fx) +{ + assert(fx < MixerOutput::kFXCount); + return this->fx_[fx]; +} + +template +FXUnit2* MixingConsole::getTube() +{ + return this->tube_; +} + +template +FXUnit2* MixingConsole::getChorus() +{ + return this->chorus_; +} + +template +FXUnit2* MixingConsole::getFlanger() +{ + return this->flanger_; +} + +template +FXUnit2* MixingConsole::getOrbitone() +{ + return this->orbitone_; +} + +template +FXUnit2* MixingConsole::getPhaser() +{ + return this->phaser_; +} + +template +FXUnit2* MixingConsole::getDelay() +{ + return this->delay_; +} + +template +FXUnit2* MixingConsole::getPlateReverb() +{ + return this->plate_reverb_; +} + +template +FXUnit2* MixingConsole::getReverberator() +{ + return this->reverberator_; +} + +template +FXUnit2* MixingConsole::getDry() +{ + return this->dry_; +} + +// Processing +template +void MixingConsole::init() +{ + memset(this->channel_level_, 0, nb_inputs * sizeof(float32_t)); + for(size_t i = 0; i <= StereoChannels::kNumChannels; ++i) memset(this->pan_[i], 0, nb_inputs * sizeof(float32_t)); + + for(size_t i = 0; i < MixerOutput::kFXCount; ++i) + memset(this->levels_[i], 0, (nb_inputs + MixerOutput::kFXCount - 1) * sizeof(float32_t)); + + for(size_t i = 0; i < StereoChannels::kNumChannels; ++i) + memset(this->input_samples_[i], 0, (nb_inputs + MixerOutput::kFXCount - 1) * sizeof(float32_t)); + + this->reset(); +} + +template +void MixingConsole::reset() +{ + for(size_t i = 0; i < nb_inputs; ++i) + { + memset(this->input_sample_buffer_[StereoChannels::Left ][i], 0, this->BufferSize * sizeof(float32_t)); + memset(this->input_sample_buffer_[StereoChannels::Right][i], 0, this->BufferSize * sizeof(float32_t)); + } + + for(size_t i = 0; i < MixerOutput::kFXCount; ++i) + { + this->fx_[i]->reset(); + + if(i != MixerOutput::MainOutput) + { + this->setReturnSample(static_cast(i), 0.0f, 0.0f); + } + } +} + +template +void MixingConsole::injectInputSamples(size_t in, float32_t* samplesL, float32_t* samplesR, size_t nSamples) +{ + // Only used to input stereo samples + assert(in < nb_inputs); + this->m_nSamples = std::min(nSamples, this->BufferSize); + if(samplesL != nullptr) + { + memcpy(this->input_sample_buffer_[StereoChannels::Left ][in], samplesL, this->m_nSamples * sizeof(float32_t)); + } + else + { + memset(this->input_sample_buffer_[StereoChannels::Left ][in], 0, this->m_nSamples * sizeof(float32_t)); + } + + if(samplesR != nullptr) + { + memcpy(this->input_sample_buffer_[StereoChannels::Right][in], samplesR, this->m_nSamples * sizeof(float32_t)); + } + else + { + memset(this->input_sample_buffer_[StereoChannels::Right][in], 0, this->m_nSamples * sizeof(float32_t)); + } +} + +template +void MixingConsole::preProcessInputSampleBuffer(size_t in, size_t nSamples) +{ + assert(in < nb_inputs); + assert(nSamples <= this->BufferSize); + + float32_t* samples = this->tg_input_sample_buffer_[in]; + if(samples == nullptr) return; + + this->m_nSamples = nSamples; + if(nSamples > 0) + { + if(this->pan_[StereoChannels::Left ][in] != 0.0f) + { + arm_scale_f32(samples, this->pan_[StereoChannels::Left ][in], this->input_sample_buffer_[StereoChannels::Left ][in], nSamples); + } + else + { + memset(this->input_sample_buffer_[StereoChannels::Left ][in], 0, nSamples * sizeof(float32_t)); + } + + if(this->pan_[StereoChannels::Right][in] != 0.0f) + { + arm_scale_f32(samples, this->pan_[StereoChannels::Right][in], this->input_sample_buffer_[StereoChannels::Right][in], nSamples); + } + else + { + memset(this->input_sample_buffer_[StereoChannels::Right][in], 0, nSamples * sizeof(float32_t)); + } + } + else + { + memset(this->input_sample_buffer_[StereoChannels::Left ][in], 0, this->BufferSize * sizeof(float32_t)); + memset(this->input_sample_buffer_[StereoChannels::Right][in], 0, this->BufferSize * sizeof(float32_t)); + } +} + +template +void MixingConsole::processSample(float32_t& outL, float32_t& outR) +{ + const size_t nBuffers = nb_inputs + MixerOutput::kFXCount - 1; + + float32_t fx_input_[StereoChannels::kNumChannels]; + float32_t fx_output_[StereoChannels::kNumChannels]; + for(size_t fxId = 0; fxId < MixerOutput::kFXCount; ++fxId) + { + // Compute the samples that will feed the FX + fx_input_[StereoChannels::Left ] = MixingConsole::weighted_sum(this->input_samples_[StereoChannels::Left ], this->levels_[fxId], nBuffers); + fx_input_[StereoChannels::Right] = MixingConsole::weighted_sum(this->input_samples_[StereoChannels::Right], this->levels_[fxId], nBuffers); + + // Process the FX + this->fx_[fxId]->processSample( + fx_input_[StereoChannels::Left ], + fx_input_[StereoChannels::Right], + fx_output_[StereoChannels::Left ], + fx_output_[StereoChannels::Right] + ); + + if(fxId != MixerOutput::MainOutput) + { + // Feedback the processed samples except for the main output + this->setReturnSample( + static_cast(fxId), + fx_output_[StereoChannels::Left], + fx_output_[StereoChannels::Right] + ); + } + else + { + // Returns the main output sample + outL = fx_output_[StereoChannels::Left]; + outR = fx_output_[StereoChannels::Right]; + } + } +} + +template +void MixingConsole::process(float32_t* outL, float32_t* outR) +{ + size_t nSamples = this->m_nSamples; + for(size_t s = 0; s < nSamples; ++s) + { + for(size_t in = 0; in < nb_inputs; ++in) + { + this->setSample( + in, + this->input_sample_buffer_[StereoChannels::Left ][in][s], + this->input_sample_buffer_[StereoChannels::Right][in][s] + ); + } + + if(this->swap_stereo_image_) + { + this->processSample(*outR, *outL); + } + else + { + this->processSample(*outL, *outR); + } + + ++outL; + ++outR; + } + + this->m_nSamples = 0; +} + +template +void MixingConsole::process(float32_t* outLR) +{ + size_t nSamples = this->m_nSamples; + for(size_t s = 0; s < nSamples; ++s) + { + for(size_t in = 0; in < nb_inputs; ++in) + { + this->setSample( + in, + this->input_sample_buffer_[StereoChannels::Left ][in][s], + this->input_sample_buffer_[StereoChannels::Right][in][s] + ); + } + + if(this->swap_stereo_image_) + { + this->processSample(*(outLR + 1), *outLR); + } + else + { + this->processSample(*outLR, *(outLR + 1)); + + } + + outLR += 2; + } + + this->m_nSamples = 0; +} + +template +void MixingConsole::updatePan(size_t in) +{ + float32_t pan = this->pan_[StereoChannels::kNumChannels][in] * Constants::MPI_2; + if(this->channel_level_[in] != 0.0f) + { + this->pan_[StereoChannels::Left ][in] = InterpolatedSineOscillator::Cos(pan) * this->channel_level_[in]; + this->pan_[StereoChannels::Right][in] = InterpolatedSineOscillator::Sin(pan) * this->channel_level_[in]; + } + else + { + this->pan_[StereoChannels::Left ][in] = + this->pan_[StereoChannels::Right][in] = 0.0f; + } +} + +template +void MixingConsole::setLevel(size_t in, MixerOutput fx, float32_t lvl) +{ + assert(in < (nb_inputs + MixerOutput::kFXCount - 1)); + assert(fx < MixerOutput::kFXCount); + + this->levels_[fx][in] = constrain(lvl, 0.0f, 1.0f); +} + +template +void MixingConsole::setSample(size_t in, float32_t sampleL, float32_t sampleR) +{ + assert(in < (nb_inputs + MixerOutput::kFXCount - 1)); + this->input_samples_[StereoChannels::Left ][in] = sampleL; + this->input_samples_[StereoChannels::Right][in] = sampleR; +} diff --git a/src/mixing_console_constants.h b/src/mixing_console_constants.h new file mode 100644 index 00000000..0257733a --- /dev/null +++ b/src/mixing_console_constants.h @@ -0,0 +1,58 @@ +#pragma once + +#include "extra_features.h" + +#include +#include +#include + +enum MixerOutput +{ + OutputStart = 0, + FX_Tube = 0, + FX_Chorus, + FX_Flanger, + FX_Orbitone, + FX_Phaser, + FX_Delay, + FX_PlateReverb, + FX_Reverberator, + MainOutput, + kFXCount +}; + +inline std::string toString(MixerOutput enum_val) +{ + static const std::array names + { + "Tube", + "Chorus", + "Flanger", + "Orbitone", + "Phaser", + "Delay", + "PlateReverb", + "Reverberator", + "MainOutput" + }; + static_assert(names.size() == MixerOutput::kFXCount, "Enum MixerOutput and string array size mismatch"); + + return std::string(names[static_cast(enum_val)]); +} + +#define TO_INDEX_CHECK(str, fx) if(std::strcmp(str, toString(fx).c_str()) == 0) return fx; + +inline MixerOutput toIndex(const char* str) +{ + TO_INDEX_CHECK(str, MixerOutput::FX_Tube); + TO_INDEX_CHECK(str, MixerOutput::FX_Chorus); + TO_INDEX_CHECK(str, MixerOutput::FX_Flanger); + TO_INDEX_CHECK(str, MixerOutput::FX_Orbitone); + TO_INDEX_CHECK(str, MixerOutput::FX_Phaser); + TO_INDEX_CHECK(str, MixerOutput::FX_Delay); + TO_INDEX_CHECK(str, MixerOutput::FX_PlateReverb); + TO_INDEX_CHECK(str, MixerOutput::FX_Reverberator); + TO_INDEX_CHECK(str, MixerOutput::MainOutput); + + throw std::invalid_argument("Invalid MixerOutput string"); +} diff --git a/src/performance.ini b/src/performance.ini index ba7d0284..cfa6f6ba 100644 --- a/src/performance.ini +++ b/src/performance.ini @@ -36,8 +36,8 @@ BankNumber1=0 VoiceNumber1=1 MIDIChannel1=255 Volume1=100 -Pan1=0 -Detune1=-11 +Pan1=64 +Detune1=0 Cutoff1=99 Resonance1=0 NoteLimitLow1=0 @@ -64,9 +64,9 @@ AftertouchTarget1=0 BankNumber2=0 VoiceNumber2=1 MIDIChannel2=255 -Volume2=100 +Volume2=0 Pan2=127 -Detune2=11 +Detune2=0 Cutoff2=99 Resonance2=0 NoteLimitLow2=0 @@ -93,7 +93,7 @@ AftertouchTarget2=0 BankNumber3=0 VoiceNumber3=1 MIDIChannel3=255 -Volume3=100 +Volume3=0 Pan3=48 Detune3=-7 Cutoff3=99 @@ -122,7 +122,7 @@ AftertouchTarget3=0 BankNumber4=0 VoiceNumber4=1 MIDIChannel4=255 -Volume4=100 +Volume4=0 Pan4=80 Detune4=7 Cutoff4=99 @@ -151,7 +151,7 @@ AftertouchTarget4=0 BankNumber5=0 VoiceNumber5=1 MIDIChannel5=0 -Volume5=100 +Volume5=0 Pan5=64 Detune5=0 Cutoff5=99 @@ -180,7 +180,7 @@ AftertouchTarget5=0 BankNumber6=0 VoiceNumber6=1 MIDIChannel6=0 -Volume6=100 +Volume6=0 Pan6=64 Detune6=0 Cutoff6=99 @@ -209,7 +209,7 @@ AftertouchTarget6=0 BankNumber7=0 VoiceNumber7=1 MIDIChannel7=0 -Volume7=100 +Volume7=0 Pan7=64 Detune7=0 Cutoff7=99 @@ -238,7 +238,7 @@ AftertouchTarget7=0 BankNumber8=0 VoiceNumber8=1 MIDIChannel8=0 -Volume8=100 +Volume8=0 Pan8=64 Detune8=0 Cutoff8=99 @@ -275,6 +275,7 @@ AftertouchTarget8=0 # Effects CompressorEnable=1 + ReverbEnable=1 ReverbSize=70 ReverbHighDamp=50 @@ -282,3 +283,189 @@ ReverbLowDamp=50 ReverbLowPass=30 ReverbDiffusion=65 ReverbLevel=99 + +# Mixing Console +FXTubeEnable=1 +FXTubeOverdrive=10 + +FXChorusEnable=1 +FXChorusRate=40 +FXChorusDepth=50 + +FXFlangerEnable=1 +FXFlangerRate=3 +FXFlangerDepth=75 +FXFlangerFeedback=50 + +FXOrbitoneEnable=1 +FXOrbitoneRate=40 +FXOrbitoneDepth=50 + +FXPhaserEnable=1 +FXPhaserRate=5 +FXPhaserDepth=99 +FXPhaserFeedback=70 +FXPhaserNbStages=12 + +FXDelayEnable=1 +FXDelayLeftDelayTime=15 +FXDelayRightDelayTime=22 +FXDelayFeedback=35 + +FXReverberatorEnable=1 +FXReverberatorInputGain=55 +FXReverberatorTime=75 +FXReverberatorDiffusion=75 +FXReverberatorLP=80 + +FXSend_TG0_to_Tube=0 +FXSend_TG0_to_Chorus=0 +FXSend_TG0_to_Flanger=0 +FXSend_TG0_to_Orbitone=0 +FXSend_TG0_to_Phaser=0 +FXSend_TG0_to_Delay=0 +FXSend_TG0_to_PlateReverb=99 +FXSend_TG0_to_Reverberator=0 +FXSend_TG0_to_MainOutput=99 + +FXSend_TG1_to_Tube=0 +FXSend_TG1_to_Chorus=0 +FXSend_TG1_to_Flanger=0 +FXSend_TG1_to_Orbitone=0 +FXSend_TG1_to_Phaser=0 +FXSend_TG1_to_Delay=0 +FXSend_TG1_to_PlateReverb=0 +FXSend_TG1_to_Reverberator=0 +FXSend_TG1_to_MainOutput=0 + +FXSend_TG2_to_Tube=0 +FXSend_TG2_to_Chorus=0 +FXSend_TG2_to_Flanger=0 +FXSend_TG2_to_Orbitone=0 +FXSend_TG2_to_Phaser=0 +FXSend_TG2_to_Delay=0 +FXSend_TG2_to_PlateReverb=0 +FXSend_TG2_to_Reverberator=0 +FXSend_TG2_to_MainOutput=0 + +FXSend_TG3_to_Tube=0 +FXSend_TG3_to_Chorus=0 +FXSend_TG3_to_Flanger=0 +FXSend_TG3_to_Orbitone=0 +FXSend_TG3_to_Phaser=0 +FXSend_TG3_to_Delay=0 +FXSend_TG3_to_PlateReverb=0 +FXSend_TG3_to_Reverberator=0 +FXSend_TG3_to_MainOutput=0 + +FXSend_TG4_to_Tube=0 +FXSend_TG4_to_Chorus=0 +FXSend_TG4_to_Flanger=0 +FXSend_TG4_to_Orbitone=0 +FXSend_TG4_to_Phaser=0 +FXSend_TG4_to_Delay=0 +FXSend_TG4_to_PlateReverb=0 +FXSend_TG4_to_Reverberator=0 +FXSend_TG4_to_MainOutput=0 + +FXSend_TG5_to_Tube=0 +FXSend_TG5_to_Chorus=0 +FXSend_TG5_to_Flanger=0 +FXSend_TG5_to_Orbitone=0 +FXSend_TG5_to_Phaser=0 +FXSend_TG5_to_Delay=0 +FXSend_TG5_to_PlateReverb=0 +FXSend_TG5_to_Reverberator=0 +FXSend_TG5_to_MainOutput=0 + +FXSend_TG6_to_Tube=0 +FXSend_TG6_to_Chorus=0 +FXSend_TG6_to_Flanger=0 +FXSend_TG6_to_Orbitone=0 +FXSend_TG6_to_Phaser=0 +FXSend_TG6_to_Delay=0 +FXSend_TG6_to_PlateReverb=0 +FXSend_TG6_to_Reverberator=0 +FXSend_TG6_to_MainOutput=0 + +FXSend_TG7_to_Tube=0 +FXSend_TG7_to_Chorus=0 +FXSend_TG7_to_Flanger=0 +FXSend_TG7_to_Orbitone=0 +FXSend_TG7_to_Phaser=0 +FXSend_TG7_to_Delay=0 +FXSend_TG7_to_PlateReverb=0 +FXSend_TG7_to_Reverberator=0 +FXSend_TG7_to_MainOutput=0 + +FXSend_Tube_to_Chorus=0 +FXSend_Tube_to_Flanger=0 +FXSend_Tube_to_Orbitone=0 +FXSend_Tube_to_Phaser=0 +FXSend_Tube_to_Delay=0 +FXSend_Tube_to_PlateReverb=0 +FXSend_Tube_to_Reverberator=0 +FXSend_Tube_to_MainOutput=0 + +FXSend_Chorus_to_Tube=0 +FXSend_Chorus_to_Flanger=0 +FXSend_Chorus_to_Orbitone=0 +FXSend_Chorus_to_Phaser=0 +FXSend_Chorus_to_Delay=0 +FXSend_Chorus_to_PlateReverb=0 +FXSend_Chorus_to_Reverberator=0 +FXSend_Chorus_to_MainOutput=0 + +FXSend_Flanger_to_Tube=0 +FXSend_Flanger_to_Chorus=0 +FXSend_Flanger_to_Orbitone=0 +FXSend_Flanger_to_Phaser=0 +FXSend_Flanger_to_Delay=0 +FXSend_Flanger_to_PlateReverb=0 +FXSend_Flanger_to_Reverberator=0 +FXSend_Flanger_to_MainOutput=0 + +FXSend_Orbitone_to_Tube=0 +FXSend_Orbitone_to_Chorus=0 +FXSend_Orbitone_to_Flanger=0 +FXSend_Orbitone_to_Phaser=0 +FXSend_Orbitone_to_Delay=0 +FXSend_Orbitone_to_PlateReverb=0 +FXSend_Orbitone_to_Reverberator=0 +FXSend_Orbitone_to_MainOutput=0 + +FXSend_Phaser_to_Tube=0 +FXSend_Phaser_to_Chorus=0 +FXSend_Phaser_to_Flanger=0 +FXSend_Phaser_to_Orbitone=0 +FXSend_Phaser_to_Delay=0 +FXSend_Phaser_to_PlateReverb=0 +FXSend_Phaser_to_Reverberator=0 +FXSend_Phaser_to_MainOutput=0 + +FXSend_Delay_to_Tube=0 +FXSend_Delay_to_Chorus=0 +FXSend_Delay_to_Flanger=0 +FXSend_Delay_to_Orbitone=0 +FXSend_Delay_to_Phaser=0 +FXSend_Delay_to_PlateReverb=0 +FXSend_Delay_to_Reverberator=0 +FXSend_Delay_to_MainOutput=0 + +FXSend_PlateReverb_to_Tube=0 +FXSend_PlateReverb_to_Chorus=0 +FXSend_PlateReverb_to_Flanger=0 +FXSend_PlateReverb_to_Orbitone=0 +FXSend_PlateReverb_to_Phaser=0 +FXSend_PlateReverb_to_Delay=0 +FXSend_PlateReverb_to_Reverberator=0 +FXSend_PlateReverb_to_MainOutput=99 + +FXSend_Reverberator_to_Tube=0 +FXSend_Reverberator_to_Chorus=0 +FXSend_Reverberator_to_Flanger=0 +FXSend_Reverberator_to_Orbitone=0 +FXSend_Reverberator_to_Phaser=0 +FXSend_Reverberator_to_Delay=0 +FXSend_Reverberator_to_PlateReverb=0 +FXSend_Reverberator_to_MainOutput=0 diff --git a/src/performanceconfig.cpp b/src/performanceconfig.cpp index 853ccf51..574a6641 100644 --- a/src/performanceconfig.cpp +++ b/src/performanceconfig.cpp @@ -102,9 +102,11 @@ bool CPerformanceConfig::Load (void) PropertyName.Format ("NoteShift%u", nTG+1); m_nNoteShift[nTG] = m_Properties.GetSignedNumber (PropertyName, 0); +#if defined(PLATE_REVERB_ENABLE) PropertyName.Format ("ReverbSend%u", nTG+1); m_nReverbSend[nTG] = m_Properties.GetNumber (PropertyName, 50); - +#endif + PropertyName.Format ("PitchBendRange%u", nTG+1); m_nPitchBendRange[nTG] = m_Properties.GetNumber (PropertyName, 2); @@ -150,17 +152,91 @@ bool CPerformanceConfig::Load (void) PropertyName.Format ("AftertouchTarget%u", nTG+1); m_nAftertouchTarget[nTG] = m_Properties.GetNumber (PropertyName, 0); - } + } m_bCompressorEnable = m_Properties.GetNumber ("CompressorEnable", 1) != 0; - m_bReverbEnable = m_Properties.GetNumber ("ReverbEnable", 1) != 0; - m_nReverbSize = m_Properties.GetNumber ("ReverbSize", 70); - m_nReverbHighDamp = m_Properties.GetNumber ("ReverbHighDamp", 50); - m_nReverbLowDamp = m_Properties.GetNumber ("ReverbLowDamp", 50); - m_nReverbLowPass = m_Properties.GetNumber ("ReverbLowPass", 30); - m_nReverbDiffusion = m_Properties.GetNumber ("ReverbDiffusion", 65); - m_nReverbLevel = m_Properties.GetNumber ("ReverbLevel", 99); +#if defined(PLATE_REVERB_ENABLE) || defined(MIXING_CONSOLE_ENABLE) + this->m_bReverbEnable = this->m_Properties.GetNumber ("ReverbEnable", 1) != 0; + this->m_nReverbSize = this->m_Properties.GetNumber ("ReverbSize", 70); + this->m_nReverbHighDamp = this->m_Properties.GetNumber ("ReverbHighDamp", 50); + this->m_nReverbLowDamp = this->m_Properties.GetNumber ("ReverbLowDamp", 50); + this->m_nReverbLowPass = this->m_Properties.GetNumber ("ReverbLowPass", 30); + this->m_nReverbDiffusion = this->m_Properties.GetNumber ("ReverbDiffusion", 65); + this->m_nReverbLevel = this->m_Properties.GetNumber ("ReverbLevel", 99); +#endif + +#if defined(MIXING_CONSOLE_ENABLE) + this->m_bFXTubeEnable = this->m_Properties.GetNumber("FXTubeEnable", 1); + this->m_nFXTubeOverdrive = this->m_Properties.GetNumber("FXTubeOverdrive", 10); + + this->m_bFXChorusEnable = this->m_Properties.GetNumber("FXChorusEnable", 1); + this->m_nFXChorusRate = this->m_Properties.GetNumber("FXChorusRate", 50); + this->m_nFXChorusDepth = this->m_Properties.GetNumber("FXChorusDepth", 50); + + this->m_bFXFlangerEnable = this->m_Properties.GetNumber("FXFlangerEnable", 1); + this->m_nFXFlangerRate = this->m_Properties.GetNumber("FXFlangerRate", 15); + this->m_nFXFlangerDepth = this->m_Properties.GetNumber("FXFlangerDepth", 10); + this->m_nFXFlangerFeedback = this->m_Properties.GetNumber("FXFlangerFeedback", 20); + + this->m_bFXOrbitoneEnable = this->m_Properties.GetNumber("FXOrbitoneEnable", 1); + this->m_nFXOrbitoneRate = this->m_Properties.GetNumber("FXOrbitoneRate", 40); + this->m_nFXOrbitoneDepth = this->m_Properties.GetNumber("FXOrbitoneDepth", 50); + + this->m_bFXPhaserEnable = this->m_Properties.GetNumber("FXPhaserEnable", 1); + this->m_nFXPhaserRate = this->m_Properties.GetNumber("FXPhaserRate", 5); + this->m_nFXPhaserDepth = this->m_Properties.GetNumber("FXPhaserDepth", 99); + this->m_nFXPhaserFeedback = this->m_Properties.GetNumber("FXPhaserFeedback", 50); + this->m_nFXPhaserNbStages = this->m_Properties.GetNumber("FXPhaserNbStages", 12); + + this->m_bFXDelayEnable = this->m_Properties.GetNumber("FXDelayEnable", 1); + this->m_nFXDelayLeftDelayTime = this->m_Properties.GetNumber("FXDelayLeftDelayTime", 15); + this->m_nFXDelayRightDelayTime = this->m_Properties.GetNumber("FXDelayRightDelayTime", 22); + this->m_nFXDelayFeedback = this->m_Properties.GetNumber("FXDelayFeedback", 35); + + this->m_bFXReverberatorEnable = this->m_Properties.GetNumber("FXReverberatorEnable", 1); + this->m_nFXReverberatorInputGain = this->m_Properties.GetNumber("FXReverberatorInputGain", 30); + this->m_nFXReverberatorTime = this->m_Properties.GetNumber("FXReverberatorTime", 30); + this->m_nFXReverberatorDiffusion = this->m_Properties.GetNumber("FXReverberatorDiffusion", 30); + this->m_nFXReverberatorLP = this->m_Properties.GetNumber("FXReverberatorLP", 99); + + bool revUsed = false; + for(unsigned nTG = 0; nTG < CConfig::ToneGenerators; ++nTG) + { + CString reverbSendProp; + reverbSendProp.Format ("ReverbSend%u", nTG + 1); + unsigned reverbSend = m_Properties.GetNumber(reverbSendProp, 50); + revUsed |= (reverbSend > 0); + + for(unsigned toFX = 0; toFX < MixerOutput::kFXCount; ++toFX) + { + CString propertyName; + propertyName.Format("FXSend_TG%u_to_%s", nTG + 1, toString(static_cast(toFX)).c_str()); + unsigned defaultLevel = 0; + if(nTG == 0) + { + if(toFX == MixerOutput::FX_PlateReverb) defaultLevel = reverbSend; + else if(toFX == MixerOutput::MainOutput) defaultLevel = 99 - reverbSend; + } + this->m_nTGSendLevel[nTG][toFX] = this->m_Properties.GetNumber(propertyName, defaultLevel); + } + } + + size_t end = MixerOutput::kFXCount - 1; + for(size_t fromFX = 0; fromFX < end; ++fromFX) + { + for(size_t toFX = 0; toFX < MixerOutput::kFXCount; ++toFX) + { + CString propertyName; + propertyName.Format("FXSend_%s_to_%s", toString(static_cast(fromFX)).c_str(), toString(static_cast(toFX)).c_str()); + unsigned defaultLevel = 0; + if(fromFX == MixerOutput::FX_PlateReverb && toFX == MixerOutput::MainOutput) defaultLevel = revUsed ? 99 : 0; + this->m_nFXSendLevel[fromFX][toFX] = this->m_Properties.GetNumber(propertyName, defaultLevel); + } + } + + this->m_bFXBypass = this->m_Properties.GetNumber("FXBypass", 0); +#endif return bResult; } @@ -219,9 +295,11 @@ bool CPerformanceConfig::Save (void) PropertyName.Format ("NoteShift%u", nTG+1); m_Properties.SetSignedNumber (PropertyName, m_nNoteShift[nTG]); +#if defined(PLATE_REVERB_ENABLE) PropertyName.Format ("ReverbSend%u", nTG+1); m_Properties.SetNumber (PropertyName, m_nReverbSend[nTG]); - +#endif + PropertyName.Format ("PitchBendRange%u", nTG+1); m_Properties.SetNumber (PropertyName, m_nPitchBendRange[nTG]); @@ -272,6 +350,7 @@ bool CPerformanceConfig::Save (void) m_Properties.SetNumber ("CompressorEnable", m_bCompressorEnable ? 1 : 0); +#if defined(PLATE_REVERB_ENABLE) || defined(MIXING_CONSOLE_ENABLE) m_Properties.SetNumber ("ReverbEnable", m_bReverbEnable ? 1 : 0); m_Properties.SetNumber ("ReverbSize", m_nReverbSize); m_Properties.SetNumber ("ReverbHighDamp", m_nReverbHighDamp); @@ -279,6 +358,65 @@ bool CPerformanceConfig::Save (void) m_Properties.SetNumber ("ReverbLowPass", m_nReverbLowPass); m_Properties.SetNumber ("ReverbDiffusion", m_nReverbDiffusion); m_Properties.SetNumber ("ReverbLevel", m_nReverbLevel); +#endif + +#if defined(MIXING_CONSOLE_ENABLE) + this->m_Properties.SetNumber("FXTubeEnable", this->m_bFXTubeEnable ? 1 : 0); + this->m_Properties.SetNumber("FXTubeOverdrive", this->m_nFXTubeOverdrive); + + this->m_Properties.SetNumber("FXChorusEnable", this->m_bFXChorusEnable ? 1 : 0); + this->m_Properties.SetNumber("FXChorusRate", this->m_nFXChorusRate); + this->m_Properties.SetNumber("FXChorusDepth", this->m_nFXChorusDepth); + + this->m_Properties.SetNumber("FXFlangerEnable", this->m_bFXFlangerEnable ? 1 : 0); + this->m_Properties.SetNumber("FXFlangerRate", this->m_nFXFlangerRate); + this->m_Properties.SetNumber("FXFlangerDepth", this->m_nFXFlangerDepth); + this->m_Properties.SetNumber("FXFlangerFeedback", this->m_nFXFlangerFeedback); + + this->m_Properties.SetNumber("FXOrbitoneEnable", this->m_bFXOrbitoneEnable ? 1 : 0); + this->m_Properties.SetNumber("FXOrbitoneRate", this->m_nFXOrbitoneRate); + this->m_Properties.SetNumber("FXOrbitoneDepth", this->m_nFXOrbitoneDepth); + + this->m_Properties.SetNumber("FXPhaserEnable", this->m_bFXPhaserEnable ? 1 : 0); + this->m_Properties.SetNumber("FXPhaserRate", this->m_nFXPhaserRate); + this->m_Properties.SetNumber("FXPhaserDepth", this->m_nFXPhaserDepth); + this->m_Properties.SetNumber("FXPhaserFeedback", this->m_nFXPhaserFeedback); + this->m_Properties.SetNumber("FXPhaserNbStages", this->m_nFXPhaserNbStages); + + this->m_Properties.SetNumber("FXDelayEnable", this->m_bFXDelayEnable ? 1 : 0); + this->m_Properties.SetNumber("FXDelayLeftDelayTime", this->m_nFXDelayLeftDelayTime); + this->m_Properties.SetNumber("FXDelayRightDelayTime", this->m_nFXDelayRightDelayTime); + this->m_Properties.SetNumber("FXDelayFeedback", this->m_nFXDelayFeedback); + + this->m_Properties.SetNumber("FXReverberatorEnable", this->m_bFXReverberatorEnable ? 1 : 0); + this->m_Properties.SetNumber("FXReverberatorInputGain", this->m_nFXReverberatorInputGain); + this->m_Properties.SetNumber("FXReverberatorTime", this->m_nFXReverberatorTime); + this->m_Properties.SetNumber("FXReverberatorDiffusion", this->m_nFXReverberatorDiffusion); + this->m_Properties.SetNumber("FXReverberatorLP", this->m_nFXReverberatorLP); + + for(unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) + { + for(size_t toFX = 0; toFX < MixerOutput::kFXCount; ++toFX) + { + CString propertyName; + propertyName.Format("FXSend_TG%u_to_%s", nTG + 1, toString(static_cast(toFX)).c_str()); + this->m_Properties.SetNumber(propertyName, this->m_nTGSendLevel[nTG][toFX]); + } + } + + size_t end = MixerOutput::kFXCount - 1; + for(size_t fromFX = 0; fromFX < end; ++fromFX) + { + for(size_t toFX = 0; toFX < MixerOutput::kFXCount; ++toFX) + { + CString propertyName; + propertyName.Format("FXSend_%s_to_%s", toString(static_cast(fromFX)).c_str(), toString(static_cast(toFX)).c_str()); + this->m_Properties.SetNumber(propertyName, this->m_nFXSendLevel[fromFX][toFX]); + } + } + + this->m_Properties.SetNumber("FXBypass", this->m_bFXBypass ? 1 : 0); +#endif return m_Properties.Save (); } @@ -349,11 +487,13 @@ int CPerformanceConfig::GetNoteShift (unsigned nTG) const return m_nNoteShift[nTG]; } +#if defined(PLATE_REVERB_ENABLE) unsigned CPerformanceConfig::GetReverbSend (unsigned nTG) const { assert (nTG < CConfig::ToneGenerators); return m_nReverbSend[nTG]; } +#endif void CPerformanceConfig::SetBankNumber (unsigned nValue, unsigned nTG) { @@ -421,11 +561,13 @@ void CPerformanceConfig::SetNoteShift (int nValue, unsigned nTG) m_nNoteShift[nTG] = nValue; } +#if defined(PLATE_REVERB_ENABLE) void CPerformanceConfig::SetReverbSend (unsigned nValue, unsigned nTG) { assert (nTG < CConfig::ToneGenerators); m_nReverbSend[nTG] = nValue; } +#endif bool CPerformanceConfig::GetCompressorEnable (void) const { @@ -851,9 +993,12 @@ bool CPerformanceConfig::ListPerformances() Result = f_findfirst (&Directory, &FileInfo, "SD:/" PERFORMANCE_DIR, "*.ini"); for (unsigned i = 0; Result == FR_OK && FileInfo.fname[0]; i++) { - if (nLastPerformance >= NUM_PERFORMANCES) { + if (nLastPerformance >= NUM_PERFORMANCES) + { LOGNOTE ("Skipping performance %s", FileInfo.fname); - } else { + } + else + { if (!(FileInfo.fattrib & (AM_HID | AM_SYS))) { std::string FileName = FileInfo.fname; @@ -865,9 +1010,9 @@ bool CPerformanceConfig::ListPerformances() { nLastFileIndex=nPIndex; } - + m_nPerformanceFileName[nLastPerformance++]= FileName; - } + } } } @@ -876,15 +1021,14 @@ bool CPerformanceConfig::ListPerformances() // sort by performance number-name if (nLastPerformance > 2) { - sort (m_nPerformanceFileName+1, m_nPerformanceFileName + nLastPerformance); // default is always on first place. %%%%%%%%%%%%%%%% + sort (m_nPerformanceFileName+1, m_nPerformanceFileName + nLastPerformance); // default is always on first place. %%%%%%%%%%%%%%%% } } LOGNOTE ("Number of Performances: %d", nLastPerformance); - + return nInternalFolderOk; } - void CPerformanceConfig::SetNewPerformance (unsigned nID) { @@ -951,3 +1095,305 @@ bool CPerformanceConfig::DeletePerformance(unsigned nID) } return bOK; } + +#ifdef MIXING_CONSOLE_ENABLE + +bool CPerformanceConfig::GetFXTubeEnable(void) const +{ + return this->m_bFXTubeEnable; +} + +unsigned CPerformanceConfig::GetFXTubeOverdrive(void) const +{ + return this->m_nFXTubeOverdrive; +} + +bool CPerformanceConfig::GetFXChorusEnable(void) const +{ + return this->m_bFXChorusEnable; +} + +unsigned CPerformanceConfig::GetFXChorusRate(void) const +{ + return this->m_nFXChorusRate; +} + +unsigned CPerformanceConfig::GetFXChorusDepth(void) const +{ + return this->m_nFXChorusDepth; +} + +bool CPerformanceConfig::GetFXFlangerEnable(void) const +{ + return this->m_bFXFlangerEnable; +} + +unsigned CPerformanceConfig::GetFXFlangerRate(void) const +{ + return this->m_nFXFlangerRate; +} + +unsigned CPerformanceConfig::GetFXFlangerDepth(void) const +{ + return this->m_nFXFlangerDepth; +} + +unsigned CPerformanceConfig::GetFXFlangerFeedback(void) const +{ + return this->m_nFXFlangerFeedback; +} + +bool CPerformanceConfig::GetFXOrbitoneEnable(void) const +{ + return this->m_bFXOrbitoneEnable; +} + +unsigned CPerformanceConfig::GetFXOrbitoneRate(void) const +{ + return this->m_nFXOrbitoneRate; +} + +unsigned CPerformanceConfig::GetFXOrbitoneDepth(void) const +{ + return this->m_nFXOrbitoneDepth; +} + +bool CPerformanceConfig::GetFXPhaserEnable(void) const +{ + return this->m_bFXPhaserEnable; +} + +unsigned CPerformanceConfig::GetFXPhaserRate(void) const +{ + return this->m_nFXPhaserRate; +} + +unsigned CPerformanceConfig::GetFXPhaserDepth(void) const +{ + return this->m_nFXPhaserDepth; +} + +unsigned CPerformanceConfig::GetFXPhaserFeedback(void) const +{ + return this->m_nFXPhaserFeedback; +} + +unsigned CPerformanceConfig::GetFXPhaserNbStages(void) const +{ + return this->m_nFXPhaserNbStages; +} + +bool CPerformanceConfig::GetFXDelayEnable(void) const +{ + return this->m_bFXDelayEnable; +} + +unsigned CPerformanceConfig::GetFXDelayLeftDelayTime(void) const +{ + return this->m_nFXDelayLeftDelayTime; +} + +unsigned CPerformanceConfig::GetFXDelayRightDelayTime(void) const +{ + return this->m_nFXDelayRightDelayTime; +} + +unsigned CPerformanceConfig::GetFXDelayFeedback(void) const +{ + return this->m_nFXDelayFeedback; +} + +bool CPerformanceConfig::GetFXReverberatorEnable(void) const +{ + return this->m_bFXReverberatorEnable; +} + +unsigned CPerformanceConfig::GetFXReverberatorInputGain(void) const +{ + return this->m_nFXReverberatorInputGain; +} + +unsigned CPerformanceConfig::GetFXReverberatorTime(void) const +{ + return this->m_nFXReverberatorTime; +} + +unsigned CPerformanceConfig::GetFXReverberatorDiffusion(void) const +{ + return this->m_nFXReverberatorDiffusion; +} + +unsigned CPerformanceConfig::GetFXReverberatorLP(void) const +{ + return this->m_nFXReverberatorLP; +} + +unsigned CPerformanceConfig::GetTGSendLevel(unsigned in, MixerOutput fx) const +{ + assert(in < CConfig::ToneGenerators); + assert(fx < MixerOutput::kFXCount); + return this->m_nTGSendLevel[in][fx]; +} + +unsigned CPerformanceConfig::GetFXSendLevel(MixerOutput fromFX, MixerOutput toFX) const +{ + assert(fromFX < (MixerOutput::kFXCount - 1)); + assert(toFX < MixerOutput::kFXCount); + return (fromFX == toFX) ? 0 : this->m_nFXSendLevel[fromFX][toFX]; +} + +void CPerformanceConfig::SetFXTubeEnable(bool bValue) +{ + this->m_bFXTubeEnable = bValue; +} + +void CPerformanceConfig::SetFXTubeOverdrive(unsigned nValue) +{ + this->m_nFXTubeOverdrive = nValue; +} + +void CPerformanceConfig::SetFXChorusEnable(bool bValue) +{ + this->m_bFXChorusEnable = bValue; +} + +void CPerformanceConfig::SetFXChorusRate(unsigned nValue) +{ + this->m_nFXChorusRate = nValue; +} + +void CPerformanceConfig::SetFXChorusDepth(unsigned nValue) +{ + this->m_nFXChorusDepth = nValue; +} + +void CPerformanceConfig::SetFXFlangerEnable(bool bValue) +{ + this->m_bFXFlangerEnable = bValue; +} + +void CPerformanceConfig::SetFXFlangerRate(unsigned nValue) +{ + this->m_nFXFlangerRate = nValue; +} + +void CPerformanceConfig::SetFXFlangerDepth(unsigned nValue) +{ + this->m_nFXFlangerDepth = nValue; +} + +void CPerformanceConfig::SetFXFlangerFeedback(unsigned nValue) +{ + this->m_nFXFlangerFeedback = nValue; +} + +void CPerformanceConfig::SetFXOrbitoneEnable(bool bValue) +{ + this->m_bFXOrbitoneEnable = bValue; +} + +void CPerformanceConfig::SetFXOrbitoneRate(unsigned nValue) +{ + this->m_nFXOrbitoneRate = nValue; +} + +void CPerformanceConfig::SetFXOrbitoneDepth(unsigned nValue) +{ + this->m_nFXOrbitoneDepth = nValue; +} + +void CPerformanceConfig::SetFXPhaserEnable(bool bValue) +{ + this->m_bFXPhaserEnable = bValue; +} + +void CPerformanceConfig::SetFXPhaserRate(unsigned nValue) +{ + this->m_nFXPhaserRate = nValue; +} + +void CPerformanceConfig::SetFXPhaserDepth(unsigned nValue) +{ + this->m_nFXPhaserDepth = nValue; +} + +void CPerformanceConfig::SetFXPhaserFeedback(unsigned nValue) +{ + this->m_nFXPhaserFeedback = nValue; +} + +void CPerformanceConfig::SetFXPhaserNbStages(unsigned nValue) +{ + this->m_nFXPhaserNbStages = nValue; +} + +void CPerformanceConfig::SetFXDelayEnable(unsigned bValue) +{ + this->m_bFXDelayEnable = bValue; +} + +void CPerformanceConfig::SetFXDelayLeftDelayTime(unsigned nValue) +{ + this->m_nFXDelayLeftDelayTime = nValue; +} + +void CPerformanceConfig::SetFXDelayRightDelayTime(unsigned nValue) +{ + this->m_nFXDelayRightDelayTime = nValue; +} + +void CPerformanceConfig::SetFXDelayFeedback(unsigned nValue) +{ + this->m_nFXDelayFeedback = nValue; +} + +void CPerformanceConfig::SetFXReverberatorEnable(unsigned bValue) +{ + this->m_bFXReverberatorEnable = bValue; +} + +void CPerformanceConfig::SetFXReverberatorInputGain(unsigned nValue) +{ + this->m_nFXReverberatorInputGain = nValue; +} + +void CPerformanceConfig::SetFXReverberatorTime(unsigned nValue) +{ + this->m_nFXReverberatorTime = nValue; +} + +void CPerformanceConfig::SetFXReverberatorDiffusion(unsigned nValue) +{ + this->m_nFXReverberatorDiffusion = nValue; +} + +void CPerformanceConfig::SetFXReverberatorLP(unsigned nValue) +{ + this->m_nFXReverberatorLP = nValue; +} + +void CPerformanceConfig::SetTGSendLevel(unsigned in, MixerOutput fx, unsigned nValue) +{ + assert(in < CConfig::ToneGenerators); + assert(fx < MixerOutput::kFXCount); + this->m_nTGSendLevel[in][fx] = nValue; +} + +void CPerformanceConfig::SetFXSendLevel(MixerOutput fromFX, MixerOutput toFX, unsigned nValue) +{ + assert(fromFX < (MixerOutput::kFXCount - 1)); + assert(toFX < MixerOutput::kFXCount); + this->m_nFXSendLevel[fromFX][toFX] = (fromFX == toFX) ? 0 : nValue; +} + +void CPerformanceConfig::SetFXBypass(bool bypass) +{ + this->m_bFXBypass = bypass; +} + +bool CPerformanceConfig::IsFXBypass() const +{ + return this->m_bFXBypass; +} + +#endif diff --git a/src/performanceconfig.h b/src/performanceconfig.h index c151c7aa..5e308f3d 100644 --- a/src/performanceconfig.h +++ b/src/performanceconfig.h @@ -24,6 +24,7 @@ #define _performanceconfig_h #include "config.h" +#include "mixing_console_constants.h" #include #include #define NUM_VOICE_PARAM 156 @@ -52,7 +53,9 @@ class CPerformanceConfig // Performance configuration unsigned GetNoteLimitLow (unsigned nTG) const; // 0 .. 127 unsigned GetNoteLimitHigh (unsigned nTG) const; // 0 .. 127 int GetNoteShift (unsigned nTG) const; // -24 .. 24 +#if defined(PLATE_REVERB_ENABLE) unsigned GetReverbSend (unsigned nTG) const; // 0 .. 127 +#endif unsigned GetPitchBendRange (unsigned nTG) const; // 0 .. 12 unsigned GetPitchBendStep (unsigned nTG) const; // 0 .. 12 unsigned GetPortamentoMode (unsigned nTG) const; // 0 .. 1 @@ -80,7 +83,9 @@ class CPerformanceConfig // Performance configuration void SetNoteLimitLow (unsigned nValue, unsigned nTG); void SetNoteLimitHigh (unsigned nValue, unsigned nTG); void SetNoteShift (int nValue, unsigned nTG); +#if defined(PLATE_REVERB_ENABLE) void SetReverbSend (unsigned nValue, unsigned nTG); +#endif void SetPitchBendRange (unsigned nValue, unsigned nTG); void SetPitchBendStep (unsigned nValue, unsigned nTG); void SetPortamentoMode (unsigned nValue, unsigned nTG); @@ -118,6 +123,82 @@ class CPerformanceConfig // Performance configuration void SetReverbDiffusion (unsigned nValue); void SetReverbLevel (unsigned nValue); +#ifdef MIXING_CONSOLE_ENABLE + bool GetFXTubeEnable(void) const; + unsigned GetFXTubeOverdrive(void) const; + + bool GetFXChorusEnable(void) const; + unsigned GetFXChorusRate(void) const; + unsigned GetFXChorusDepth(void) const; + + bool GetFXFlangerEnable(void) const; + unsigned GetFXFlangerRate(void) const; + unsigned GetFXFlangerDepth(void) const; + unsigned GetFXFlangerFeedback(void) const; + + bool GetFXOrbitoneEnable(void) const; + unsigned GetFXOrbitoneRate(void) const; + unsigned GetFXOrbitoneDepth(void) const; + + bool GetFXPhaserEnable(void) const; + unsigned GetFXPhaserRate(void) const; + unsigned GetFXPhaserDepth(void) const; + unsigned GetFXPhaserFeedback(void) const; + unsigned GetFXPhaserNbStages(void) const; + + bool GetFXDelayEnable(void) const; + unsigned GetFXDelayLeftDelayTime(void) const; + unsigned GetFXDelayRightDelayTime(void) const; + unsigned GetFXDelayFeedback(void) const; + + bool GetFXReverberatorEnable(void) const; + unsigned GetFXReverberatorInputGain(void) const; + unsigned GetFXReverberatorTime(void) const; + unsigned GetFXReverberatorDiffusion(void) const; + unsigned GetFXReverberatorLP(void) const; + unsigned GetTGSendLevel(unsigned in, MixerOutput fx) const; + unsigned GetFXSendLevel(MixerOutput ret, MixerOutput fx) const; + + void SetFXTubeEnable(bool bValue); + void SetFXTubeOverdrive(unsigned nValue); + + void SetFXChorusEnable(bool bValue); + void SetFXChorusRate(unsigned nValue); + void SetFXChorusDepth(unsigned nValue); + + void SetFXFlangerEnable(bool bValue); + void SetFXFlangerRate(unsigned nValue); + void SetFXFlangerDepth(unsigned nValue); + void SetFXFlangerFeedback(unsigned nValue); + + void SetFXOrbitoneEnable(bool bValue); + void SetFXOrbitoneRate(unsigned nValue); + void SetFXOrbitoneDepth(unsigned nValue); + + void SetFXPhaserEnable(bool bValue); + void SetFXPhaserRate(unsigned nValue); + void SetFXPhaserDepth(unsigned nValue); + void SetFXPhaserFeedback(unsigned nValue); + void SetFXPhaserNbStages(unsigned nValue); + + void SetFXDelayEnable(unsigned nValue); + void SetFXDelayLeftDelayTime(unsigned nValue); + void SetFXDelayRightDelayTime(unsigned nValue); + void SetFXDelayFeedback(unsigned nValue); + + void SetFXReverberatorEnable(unsigned nValue); + void SetFXReverberatorInputGain(unsigned nValue); + void SetFXReverberatorTime(unsigned nValue); + void SetFXReverberatorDiffusion(unsigned nValue); + void SetFXReverberatorLP(unsigned nValue); + + void SetTGSendLevel(unsigned in, MixerOutput fx, unsigned nValue); + void SetFXSendLevel(MixerOutput fromFX, MixerOutput toFX, unsigned nValue); + + void SetFXBypass(bool bypass); + bool IsFXBypass() const; +#endif + bool VoiceDataFilled(unsigned nTG); bool ListPerformances(); //std::string m_DirName; @@ -148,7 +229,9 @@ class CPerformanceConfig // Performance configuration unsigned m_nNoteLimitLow[CConfig::ToneGenerators]; unsigned m_nNoteLimitHigh[CConfig::ToneGenerators]; int m_nNoteShift[CConfig::ToneGenerators]; +#if defined(PLATE_REVERB_ENABLE) int m_nReverbSend[CConfig::ToneGenerators]; +#endif unsigned m_nPitchBendRange[CConfig::ToneGenerators]; unsigned m_nPitchBendStep[CConfig::ToneGenerators]; unsigned m_nPortamentoMode[CConfig::ToneGenerators]; @@ -185,6 +268,48 @@ class CPerformanceConfig // Performance configuration unsigned m_nReverbLowPass; unsigned m_nReverbDiffusion; unsigned m_nReverbLevel; + +#if defined(MIXING_CONSOLE_ENABLE) + bool m_bFXTubeEnable; + unsigned m_nFXTubeWet; + unsigned m_nFXTubeOverdrive; + + bool m_bFXChorusEnable; + unsigned m_nFXChorusRate; + unsigned m_nFXChorusDepth; + + bool m_bFXFlangerEnable; + unsigned m_nFXFlangerRate; + unsigned m_nFXFlangerDepth; + unsigned m_nFXFlangerFeedback; + + bool m_bFXOrbitoneEnable; + unsigned m_nFXOrbitoneRate; + unsigned m_nFXOrbitoneDepth; + + bool m_bFXPhaserEnable; + unsigned m_nFXPhaserRate; + unsigned m_nFXPhaserDepth; + unsigned m_nFXPhaserFeedback; + unsigned m_nFXPhaserNbStages; + + bool m_bFXDelayEnable; + unsigned m_nFXDelayLeftDelayTime; + unsigned m_nFXDelayRightDelayTime; + unsigned m_nFXDelayFeedback; + + bool m_bFXReverberatorEnable; + unsigned m_nFXReverberatorInputGain; + unsigned m_nFXReverberatorTime; + unsigned m_nFXReverberatorDiffusion; + unsigned m_nFXReverberatorLP; + + unsigned m_nTGSendLevel[CConfig::ToneGenerators + MixerOutput::kFXCount - 1][MixerOutput::kFXCount]; + unsigned m_nFXSendLevel[MixerOutput::kFXCount - 1][MixerOutput::kFXCount]; + + bool m_bFXBypass; + +#endif }; #endif diff --git a/src/test/Makefile b/src/test/Makefile new file mode 100644 index 00000000..e308e226 --- /dev/null +++ b/src/test/Makefile @@ -0,0 +1,110 @@ +BINDIR := bin +OBJDIR := objects +OUTPUT_FOLDER := results + +EXE := $(BINDIR)/all_tests.bin +BETA := $(BINDIR)/beta.bin + +CXX = g++ +CXXFLAGS = -g -std=c++20 -MMD -MP -Wall -Wextra -Wformat-nonliteral -Wcast-align -Wpointer-arith -Wmissing-declarations -Winline -Wundef -Wno-unused-parameter +DEFINES = -DCPU=x86 -DOUTPUT_FOLDER="\"$(OUTPUT_FOLDER)\"" + +ifeq ($(shell echo $(MODE) | tr '[:upper:]' '[:lower:]'), release) + +# RELEASE +CXXFLAGS += -O3 +DEFINES += -DNDEBUG + +else + +# DEBUG +CXXFLAGS += -g3 +DEFINES += -DDEBUG + +endif + +INCLUDES = -I../../CMSIS_5/CMSIS/DSP/Include/ \ + -I../../CMSIS_5/CMSIS/Core/Include/ \ + -I../../Synth_Dexed/src/ + +CPPCHECK = cppcheck +CHECKFLAGS = -q -j 8 --enable=all --force --language=c++ \ + $(INCLUDES) --platform=unix64 \ + --error-exitcode=0 \ + --suppressions-list=cppcheck-suppression-list.txt + +-include $(TST_OBJS:.o=.d) +-include $(FX__OBJS:.o=.d) + +LD := g++ +LIBS := -lm -lstdc++ -lgtest -lpthread + +FX__SRCS := ../fx.cpp +FX__SRCS += ../fx_components.cpp +FX__SRCS += ../fx_svf.cpp +FX__SRCS += ../fx_tube.cpp +FX__SRCS += ../fx_chorus.cpp +FX__SRCS += ../fx_phaser.cpp +FX__SRCS += ../fx_orbitone.cpp +FX__SRCS += ../fx_flanger.cpp +FX__SRCS += ../fx_delay.cpp +FX__SRCS += ../effect_platervbstereo.cpp +FX__SRCS += ../fx_shimmer_helper.cpp +FX__SRCS += ../fx_diffuser.cpp +FX__SRCS += ../fx_pitch_shifter.cpp +FX__SRCS += ../fx_reverberator.cpp +FX__SRCS += ../fx_shimmer_reverb.cpp +FX__SRCS += ../fx_dry.cpp +FX__SRCS += ../fx_rack.cpp + +TST_SRCS := $(filter-out waveplay.cpp $(wildcard beta*.cpp), $(wildcard *.cpp)) + +BETASRCS := $(wildcard beta*.cpp) +BETASRCS += arm_functions.cpp +BETASRCS += wavein.cpp +BETASRCS += waveout.cpp +BETASRCS += test_fx_helper.cpp + +FX__OBJS = $(patsubst ../%, $(OBJDIR)/%, $(FX__SRCS:.cpp=.o)) +TST_OBJS = $(TST_SRCS:%.cpp=$(OBJDIR)/%.o) +BETAOBJS = $(BETASRCS:%.cpp=$(OBJDIR)/%.o) + +all: $(EXE) test + +build: $(EXE) + +cpp-check: $(FX__SRCS) $(TST_SRCS) $(BETASRCS) + flawfinder ../../src + $(CPPCHECK) $(CHECKFLAGS) $^ + +test: $(EXE) $(OUTPUT_FOLDER) + rm -rf $(OUTPUT_FOLDER)/* + ./$(EXE) + +test-debug: $(EXE) $(OUTPUT_FOLDER) + rm -rf $(OUTPUT_FOLDER)/* + valgrind --leak-check=full --leak-resolution=high --show-leak-kinds=all --xtree-leak=yes --show-mismatched-frees=yes --error-limit=no --log-file=$(OUTPUT_FOLDER)/valgrind-analysis-results.txt ./$(EXE) + +$(BINDIR): + mkdir -p $@ + +$(OBJDIR): + mkdir -p $@ + +$(OUTPUT_FOLDER): + mkdir -p $@ + +$(OBJDIR)/%.o: %.cpp $(OBJDIR) + $(CXX) $(CXXFLAGS) $(DEFINES) $(INCLUDES) -c $< -o $@ + +$(OBJDIR)/%.o: ../%.cpp $(OBJDIR) + $(CXX) $(CXXFLAGS) $(DEFINES) $(INCLUDES) -c $< -o $@ + +$(EXE): $(BINDIR) $(TST_OBJS) $(FX__OBJS) + $(LD) $(CXXFLAGS) $(call wildcard,$(TST_OBJS)) $(call wildcard,$(FX__OBJS)) -o $@ $(LIBS) + +$(BETA): $(BINDIR) $(BETAOBJS) + $(LD) $(CXXFLAGS) $(BETAOBJS) -o $@ $(LIBS) + +clean: + rm -rf $(OBJDIR) $(BINDIR) $(OUTPUT_FOLDER) diff --git a/src/test/all_tests.cpp b/src/test/all_tests.cpp new file mode 100644 index 00000000..4c994ef4 --- /dev/null +++ b/src/test/all_tests.cpp @@ -0,0 +1,7 @@ +#include + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/src/test/arm_functions.cpp b/src/test/arm_functions.cpp new file mode 100644 index 00000000..61a5f0dc --- /dev/null +++ b/src/test/arm_functions.cpp @@ -0,0 +1,53 @@ +#include + +float32_t arm_sin_f32(float32_t phase) +{ + return sin(phase); +} + +float32_t arm_cos_f32(float32_t phase) +{ + return cos(phase); +} + +void arm_scale_f32(const float32_t *pSrc, float32_t scale, float32_t *pDst, uint32_t blockSize) +{ + for(unsigned i = 0; i < blockSize; ++i) + { + pDst[i] = scale * pSrc[i]; + } +} + +void arm_copy_f32(const float32_t *pSrc, float32_t *pDst, uint32_t blockSize) +{ + memcpy(pDst, pSrc, blockSize * sizeof(float32_t)); +} + +void arm_add_f32(const float32_t *pSrcA, const float32_t *pSrcB, float32_t *pDst, uint32_t blockSize) +{ + for(size_t i = 0; i < blockSize; ++i) pDst[i] = pSrcA[i] + pSrcB[i]; +} + +void arm_fill_f32(float32_t value, float32_t *pDst, uint32_t blockSize) +{ + for(size_t i = 0; i < blockSize; ++i) pDst[i] = value; +} + +float32_t arm_weighted_sum_f32(const float32_t *in, const float32_t *weights, uint32_t blockSize) +{ + float32_t s = 0.0f; + float32_t w = 0.0f; + + for(size_t i = 0; i < blockSize; ++i) + { + s += in[i] * weights[i]; + w += weights[i]; + } + + return s / w; +} + +void arm_clip_f32(const float32_t *pSrc, float32_t *pDst, float32_t low, float32_t high, uint32_t numSamples) +{ + for(size_t i = 0; i < numSamples; ++i) pDst[i] = (pSrc[i] < low) ? low : (pSrc[i] > high) ? high : pSrc[i]; +} \ No newline at end of file diff --git a/src/test/beta.cpp b/src/test/beta.cpp new file mode 100644 index 00000000..3adea7e9 --- /dev/null +++ b/src/test/beta.cpp @@ -0,0 +1,390 @@ +#include "test_fx_helper.h" + +#include +#include +#include + +class FastLFODebugger +{ +public: + FastLFODebugger(float32_t sampling_rate, float32_t min_frequency, float32_t max_frequency, float32_t initial_phase, bool centered) : + SamplingFrequency(sampling_rate), + InitialPhase(initial_phase), + min_frequency_(min_frequency), + max_frequency_(max_frequency), + centered_(centered), + frequency_(0.0f), + nb_sub_increment_(1), + sub_increment_(0), + y0_(0.0f), + y1_(0.0f), + iir_coefficient_(0.0f), + initial_amplitude_(0.0f) + { + assert(this->min_frequency_ <= this->max_frequency_); + assert(this->max_frequency_ < sampling_rate / 2.0f); + + this->setFrequency(this->min_frequency_); + } + + ~FastLFODebugger() + { + } + + inline float32_t getSamplingRate() const + { + return this->SamplingFrequency; + } + + void setNormalizedFrequency(float32_t normalized_frequency) + { + normalized_frequency = constrain(normalized_frequency, 0.0f, 1.0f); + if(this->normalized_frequency_ != normalized_frequency) + { + float32_t frequency = mapfloat(normalized_frequency, 0.0f, 1.0f, this->min_frequency_, this->max_frequency_); + this->normalized_frequency_ = normalized_frequency; + this->frequency_ = frequency; + this->unitary_frequency_ = this->frequency_ / this->getSamplingRate(); + + this->nb_sub_increment_ = (frequency >= 3.0f ? 1 : 300); + this->unitary_frequency_ *= this->nb_sub_increment_; + + this->updateCoefficient(1.0f); + } + } + + float32_t getNormalizedFrequency() const + { + return this->normalized_frequency_; + } + + void setFrequency(float32_t frequency) + { + frequency = constrain(frequency, this->min_frequency_, this->max_frequency_); + if(this->frequency_ != frequency) + { + float32_t normalized_frequency = mapfloat(frequency, this->min_frequency_, this->max_frequency_, 0.0f, 1.0f); + this->normalized_frequency_ = normalized_frequency; + this->frequency_ = frequency; + this->unitary_frequency_ = this->frequency_ / this->getSamplingRate(); + + this->nb_sub_increment_ = (frequency >= 3.0f ? 1 : 300); + this->unitary_frequency_ *= this->nb_sub_increment_; + + this->updateCoefficient(1.0f); + } + } + + float32_t getFrequency() const + { + return this->frequency_; + } + + void updateCoefficient(float32_t correction_ratio) + { + float32_t frequency = this->unitary_frequency_ * correction_ratio; + + float32_t sign = 16.0f; + frequency -= 0.25f; + if(frequency < 0.0f) + { + frequency = -frequency; + } + else + { + if(frequency > 0.5f) + { + frequency -= 0.5f; + } + else + { + sign = -16.0f; + } + } + + this->iir_coefficient_ = sign * frequency * (1.0f - 2.0f * frequency); + this->initial_amplitude_ = this->iir_coefficient_ * 0.25f; + + this->reset(correction_ratio); + } + + void reset(float32_t correction_ratio = 1.0f) + { + // static const float32_t epsilon = 1e-7; + + this->sub_increment_ = 0.0f; + + // computing cos(0) = sin(-PI/2) + this->y1_ = this->initial_amplitude_; + this->y0_ = 0.5f; + + // if(this->unitary_frequency_ == 0.0f) + // { + // return; + // } + + // float32_t p_i = 2.0f * PI * this->unitary_frequency_ * correction_ratio / static_cast(this->nb_sub_increment_); + // float32_t p = PI / 2.0f; + // float32_t t_p = this->InitialPhase; + // if(t_p < p) + // { + // p -= 2.0f* PI; + // } + // float32_t current = 3.0f; + // while(abs(current, sin(this->InitialPhase)) > epsilon) + // { + // std::cout << "phase: " << p << std::endl; + // this->process(); + // p += p_i; + // if(p > (6.0f * PI)) + // cout << "ERROR: FLO is not precise enough" << + // return; + // } + } + + float32_t process() + { + float32_t temp = this->y0_; + float32_t current = temp + 0.5f; + if(this->centered_) + { + current = current * 2.0f - 1.0f; + } + + if(this->sub_increment_ == 0) + { + this->y0_ = this->iir_coefficient_ * this->y0_ - this->y1_; + this->y1_ = temp; + this->current_ = current; + return current; + } + + this->sub_increment_++; + if(this->sub_increment_ >= this->nb_sub_increment_) + { + this->sub_increment_ = 0; + } + + return mapfloat(this->sub_increment_, 0, this->nb_sub_increment_, this->current_, current); + } + + float32_t current() const + { + return this->current_; + } + +private: + const float32_t SamplingFrequency; + const float32_t InitialPhase; + const float32_t min_frequency_; + const float32_t max_frequency_; + const bool centered_; + float32_t frequency_; + float32_t normalized_frequency_; + float32_t unitary_frequency_; + size_t nb_sub_increment_; + size_t sub_increment_; + + float32_t y0_; + float32_t y1_; + float32_t iir_coefficient_; + float32_t initial_amplitude_; + float32_t current_; +}; + +void updateCorrectionStep(float32_t& sign, float32_t& correction_ratio_step, int direction) +{ + if(sign == direction) + { + // does nothing + } + else if(sign == -direction) + { + sign = static_cast(direction); + correction_ratio_step /= 2.0f; + } + else + { + sign = static_cast(direction); + } + + if(direction > 0) + { + std::cout << "LFO is too slow - correction ratio step becomes: " << (sign * correction_ratio_step); + } + else if(direction < 0) + { + std::cout << "LFO is too fast - correction ratio step becomes: " << (sign * correction_ratio_step); + } +} + +TEST(BetaTesta, FastLFO) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + size_t NB = static_cast(1.0f * SAMPLING_FREQUENCY); + const float32_t freq = 1.5f; + const float32_t init_phase = PI / 2.0f; + float32_t correction_ratio = 1.0f; + float32_t correction_ratio_step = 8.0f/ SAMPLING_FREQUENCY; + + FastLFODebugger lfo1(SAMPLING_FREQUENCY, freq, 440.0f, init_phase, true); + lfo1.setFrequency(freq); + + const float32_t epsilon = 1e-3; + + int nbTrials = 100000; + float32_t maxDiff; + float32_t sign = 0.0f; + float32_t phase; + float32_t phase_increment; + size_t maxOK = 0; + float32_t best_correction = correction_ratio; + while(nbTrials > 0) + { + maxDiff = 0.0f; + phase = init_phase; + correction_ratio += sign * correction_ratio_step; + std::cout << std::setprecision(9) << std::fixed << " - Testing correction_ratio: " << correction_ratio << std::endl; + lfo1.updateCoefficient(correction_ratio); + phase_increment = freq / SAMPLING_FREQUENCY; + + for(size_t i = 0; i < NB; ++i) + { + float32_t v1 = lfo1.process(); + float32_t v2 = sin(phase); + // std::cout << std::setprecision(9) << std::fixed << " + phase: " << phase << " // v1: " << v1 << " / v2: " << v2 << " => diff: " << (v2 - v1); + + float32_t diff = abs(v1 - v2); + if(diff > maxDiff) maxDiff = diff; + + // std::cout << " - OK: " << ((diff < epsilon) ? "Yes" : "No") << std::endl; + + if(diff > epsilon) + { + if(maxOK < i) + { + maxOK = i + 1; + best_correction = correction_ratio; + } + + int quater = 0; + if(phase > (PI / 2.0f)) ++quater; + if(phase > PI) ++quater; + if(phase > (3.0f * PI / 2.0f)) ++quater; + + if(v1 < v2) + { + switch (quater) + { + case 0: + case 4: + // Sinus phase [0, PI / 2] => [0.00, 1.00] + // Sinus phase [3 * PI / 2, 2 * PI] => [-1.00, 0.00] + // lfo1 is too slow + updateCorrectionStep(sign, correction_ratio_step, +1); + break; + + case 1: + case 3: + // Sinus phase [PI / 2, PI] => [1.00, 0.00] + // Sinus phase [PI, 3 * PI / 2] => [0.00, -1.00] + // lfo1 is too fast + updateCorrectionStep(sign, correction_ratio_step, -1); + break; + + default: + FAIL() << "Issue on phase: " << phase; + break; + } + break; + } + else + { + switch (quater) + { + case 0: + case 4: + // Sinus phase [0, PI / 2] => [0.00, 1.00] + // Sinus phase [3 * PI / 2, 2 * PI] => [-1.00, 0.00] + // lfo1 is too fast + updateCorrectionStep(sign, correction_ratio_step, -1); + break; + + case 1: + case 3: + // Sinus phase [PI / 2, PI] => [1.00, 0.00] + // Sinus phase [PI, 3 * PI / 2] => [0.00, -1.00] + // lfo1 is too slow + updateCorrectionStep(sign, correction_ratio_step, +1); + break; + + default: + FAIL() << "Issue on phase: " << phase; + break; + } + break; + } + } + + if(correction_ratio_step < 1e-9) FAIL() << "correction_ratio_step became too small. maxOK = " << maxOK << " with best_correction = " << best_correction; + + phase += phase_increment; + if(phase > 2.0f * PI) phase -= 2.0f * PI; + } + + --nbTrials; + } + if(nbTrials > -2) + std::cout << "Correct correction ratio = " << correction_ratio << " with maxDiff = " << maxDiff << std::endl; + else + std::cout << "No matching correction ratio" << std::endl; + + std::cout << "maxOK = " << maxOK << " with best_correction = " << best_correction << std::endl;; + + + // std::stringstream ssFst; + // std::stringstream ssSin; + + // for(size_t i = 0; i < NB; ++i) + // { + // ssFst << lfo1.process() << (i == (NB - 1) ? "" : ", "); + // ssSin << sin(2.0f * PI * freq * i / SAMPLING_FREQUENCY + init_phase) << (i == (NB - 1) ? "" : ", "); + // } + + // std::ofstream _fst(getResultFile(full_test_name + ".fst.data", true)); + // _fst << ssFst.str(); + // _fst.close(); + + // std::ofstream _sin(getResultFile(full_test_name + ".sin.data", true)); + // _sin << ssSin.str(); + // _sin.close(); + + + // std::ofstream out(getResultFile(full_test_name + ".data.m", true)); + // out << "# m file to tune FastLFO component" << std::endl << std::endl; + // out << "# Parameters:" << std::endl + // << "# + frequency: " << freq << "Hz" << std::endl + // << "# + # samples: " << NB << std::endl << std::endl; + + // out << "time = 0 : " << (NB - 1) << ";" << std::endl; + // out << "fst_lfo = load(\"-ascii\", \"" << full_test_name << ".fst.data\");" << std::endl; + // out << "sin_lfo = load(\"-ascii\", \"" << full_test_name << ".sin.data\");" << std::endl; + + // out << std::endl << std::endl; + + // out << "plot(time, fst_lfo, '-', time, sin_lfo, '-');" << std::endl; + // out << "title('LFO tuning');" << std::endl; + // out << "legend('FastLFODebugger', 'Sinus');" << std::endl; + + // out.close(); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/test/cppcheck-suppression-list.txt b/src/test/cppcheck-suppression-list.txt new file mode 100644 index 00000000..6ad34ba2 --- /dev/null +++ b/src/test/cppcheck-suppression-list.txt @@ -0,0 +1,13 @@ +*:../../CMSIS_5/* +toomanyconfigs:* +noExplicitConstructor:* +unusedFunction:* +missingIncludeSystem:* +unmatchedSuppression:* +unreadVariable:test*.cpp + +// unexplained exceptions +syntaxError:beta.cpp:52 +syntaxError:test_fx_mixing_console.cpp:207 +internalAstError:test_cpp_performance.cpp:22 +unpreciseMathCall:* diff --git a/src/test/test.wav b/src/test/test.wav new file mode 100644 index 00000000..84fe1bc4 Binary files /dev/null and b/src/test/test.wav differ diff --git a/src/test/test2.wav b/src/test/test2.wav new file mode 100644 index 00000000..dc25bc59 Binary files /dev/null and b/src/test/test2.wav differ diff --git a/src/test/test3.wav b/src/test/test3.wav new file mode 100644 index 00000000..bf118f04 Binary files /dev/null and b/src/test/test3.wav differ diff --git a/src/test/test_cpp_performance.cpp b/src/test/test_cpp_performance.cpp new file mode 100644 index 00000000..3c16317c --- /dev/null +++ b/src/test/test_cpp_performance.cpp @@ -0,0 +1,255 @@ +#include + +#include + +#include "test_fx_helper.h" +#include "../fx_components.h" + +TEST(CppPerformance, LFOPerformance_ComplexLFO_InterpolatedSineOscillator) +{ + const size_t NB = 10000000; + float32_t freq = 0.1f; + + ComplexLFO lfo1(SAMPLING_FREQUENCY, 0.0f, 10.0f); + InterpolatedSineOscillator lfo2(SAMPLING_FREQUENCY, 0.0f, 10.0f); + + lfo1.setFrequency(freq); + LAP_TIME("lfo1"); + for(size_t i = 0; i < NB; ++i) + { + lfo1.process(); + } + auto d1 = LAP_TIME("lfo1"); + + lfo2.setFrequency(freq); + LAP_TIME("lfo2"); + for(size_t i = 0; i < NB; ++i) + { + lfo2.process(); + } + auto d2 = LAP_TIME("lfo2"); + + EXPECT_GE(d1, d2); +} + +TEST(CppPerformance, LFOPerformance_ComplexLFO_FastLFO) +{ + const size_t NB = 10000000; + float32_t freq = 0.1f; + + ComplexLFO lfo1(SAMPLING_FREQUENCY, 0.0f, 10.0f, Constants::MPI_2); + FastLFO lfo2(SAMPLING_FREQUENCY, 0.0f, 10.0f); + + lfo1.setFrequency(freq); + LAP_TIME("lfo1"); + for(size_t i = 0; i < NB; ++i) + { + lfo1.process(); + } + auto d1 = LAP_TIME("lfo1"); + + lfo2.setFrequency(freq); + LAP_TIME("lfo2"); + for(size_t i = 0; i < NB; ++i) + { + lfo2.process(); + } + auto d2 = LAP_TIME("lfo2"); + + EXPECT_GE(d1, d2); +} + +TEST(CppPerformance, LFOPerformance_InterpolatedSineOscillator_FastLFO) +{ + const size_t NB = 10000000; + float32_t freq = 0.1f; + + InterpolatedSineOscillator lfo1(SAMPLING_FREQUENCY, 0.0f, 10.0f, Constants::MPI_2); + FastLFO lfo2(SAMPLING_FREQUENCY, 0.0f, 10.0f); + + lfo1.setFrequency(freq); + LAP_TIME("lfo1"); + for(size_t i = 0; i < NB; ++i) + { + lfo1.process(); + } + auto d1 = LAP_TIME("lfo1"); + + lfo2.setFrequency(freq); + LAP_TIME("lfo2"); + for(size_t i = 0; i < NB; ++i) + { + lfo2.process(); + } + auto d2 = LAP_TIME("lfo2"); + + EXPECT_GE(d1, d2); +} + +TEST(CppPerformance, LFOPerformance_FastLFO_FastLFO2) +{ + const size_t NB = 10000000; + float32_t freq = 0.1f; + + FastLFO lfo1(SAMPLING_FREQUENCY, 0.0f, 10.0f, Constants::MPI_2); + FastLFO2 lfo2(SAMPLING_FREQUENCY, 0.0f, 10.0f); + + lfo1.setFrequency(freq); + lfo1.reset(); + LAP_TIME("lfo1"); + for(size_t i = 0; i < NB; ++i) + { + lfo1.process(); + } + auto d1 = LAP_TIME("lfo1"); + + lfo2.setFrequency(freq); + lfo2.reset(); + LAP_TIME("lfo2"); + for(size_t i = 0; i < NB; ++i) + { + lfo2.process(); + } + auto d2 = LAP_TIME("lfo2"); + + EXPECT_GE(d1, d2); +} + +TEST(CppPerformance, FastLFOTuning) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + size_t NB = static_cast(1.0f * SAMPLING_FREQUENCY); + float32_t freq = 1.5f; + + FastLFO lfo1(SAMPLING_FREQUENCY, freq, 440.0f); + lfo1.setFrequency(freq); + + InterpolatedSineOscillator lfo2(SAMPLING_FREQUENCY, freq, 440.0f); + lfo2.setFrequency(freq); + + ComplexLFO lfo3(SAMPLING_FREQUENCY, freq, 440.0f); + lfo3.setFrequency(freq); + + std::ofstream out(getResultFile(full_test_name + ".data.csv", true)); + setupOuputStreamForCSV(out); + out << "index;FastLFO;InterpolatedSineOscillator;ComplexLFO" << std::endl; + for(size_t i = 0; i < NB; ++i) + { + out + << i << ";" + << lfo1.process() << ";" + << lfo2.process() << ";" + << lfo3.process() << std::endl; + } + out.close(); +} + + +TEST(CppPerformance, FastLFO2Tuning) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + size_t NB = static_cast(1.0f * SAMPLING_FREQUENCY); + float32_t freq = 1.5f; + + FastLFO2 lfo1(SAMPLING_FREQUENCY, freq, 440.0f); + lfo1.setFrequency(freq); + + InterpolatedSineOscillator lfo2(SAMPLING_FREQUENCY, freq, 440.0f); + lfo2.setFrequency(freq); + + ComplexLFO lfo3(SAMPLING_FREQUENCY, freq, 440.0f); + lfo3.setFrequency(freq); + + std::ofstream out(getResultFile(full_test_name + ".data.csv", true)); + setupOuputStreamForCSV(out); + out << "index;FastLFO;InterpolatedSineOscillator;ComplexLFO" << std::endl; + for(size_t i = 0; i < NB; ++i) + { + out + << i << ";" + << lfo1.process() << ";" + << lfo2.process() << ";" + << lfo3.process() << std::endl; + } + out.close(); +} + +TEST(CppPerformance, FastLFOsTuningOctave) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + size_t NB = static_cast(1.0f * SAMPLING_FREQUENCY); + float32_t freq = 1.5f; + + FastLFO lfo1(SAMPLING_FREQUENCY, freq, 440.0f); + lfo1.setFrequency(freq); + + FastLFO2 lfo2(SAMPLING_FREQUENCY, freq, 440.0f); + lfo2.setFrequency(freq); + + InterpolatedSineOscillator lfo3(SAMPLING_FREQUENCY, freq, 440.0f); + lfo3.setFrequency(freq); + + ComplexLFO lfo4(SAMPLING_FREQUENCY, freq, 440.0f); + lfo4.setFrequency(freq); + + std::stringstream ssLFO1; + std::stringstream ssLFO2; + std::stringstream ssLFO3; + std::stringstream ssLFO4; + + for(size_t i = 0; i < NB; ++i) + { + ssLFO1 << lfo1.process() << (i == (NB - 1) ? "" : ", "); + ssLFO2 << lfo2.process() << (i == (NB - 1) ? "" : ", "); + ssLFO3 << lfo3.process() << (i == (NB - 1) ? "" : ", "); + ssLFO4 << lfo4.process() << (i == (NB - 1) ? "" : ", "); + } + + std::ofstream _lfo1(getResultFile(std::string("data/") + full_test_name + ".fast.data", true)); + _lfo1 << ssLFO1.str(); + _lfo1.close(); + + std::ofstream _lfo2(getResultFile(std::string("data/") + full_test_name + ".fst2.data", true)); + _lfo2 << ssLFO2.str(); + _lfo2.close(); + + std::ofstream _lfo3(getResultFile(std::string("data/") + full_test_name + ".intr.data", true)); + _lfo3 << ssLFO3.str(); + _lfo3.close(); + + std::ofstream _lfo4(getResultFile(std::string("data/") + full_test_name + ".cplx.data", true)); + _lfo4 << ssLFO4.str(); + _lfo4.close(); + + std::ofstream out(getResultFile(full_test_name + ".data.m", true)); + out << "# m file to tune FastLFO component" << std::endl << std::endl; + out << "# Parameters:" << std::endl + << "# + frequency: " << freq << "Hz" << std::endl + << "# + # samples: " << NB << std::endl << std::endl; + + out << "time = 0 : " << (NB - 1) << ";" << std::endl; + out << "fast_lfo = load(\"-ascii\", \"data/" << full_test_name << ".fast.data\");" << std::endl; + out << "fst2_lfo = load(\"-ascii\", \"data/" << full_test_name << ".fst2.data\");" << std::endl; + out << "intr_lfo = load(\"-ascii\", \"data/" << full_test_name << ".intr.data\");" << std::endl; + out << "cplx_lfo = load(\"-ascii\", \"data/" << full_test_name << ".cplx.data\");" << std::endl; + + out << std::endl << std::endl; + + out << "plot(time, fast_lfo, '-r', 'LineWidth', 6, time, fst2_lfo, '-o', 'LineWidth', 6, time, intr_lfo, '-b', 'LineWidth', 4, cplx_lfo, '-g', 'LineWidth', 4);" << std::endl; + out << "title('LFO tuning');" << std::endl; + out << "legend('FastLFO2', 'InterpolatedSineOscillator', 'ComplexLFO');" << std::endl; + + out.close(); +} diff --git a/src/test/test_framework.cpp b/src/test/test_framework.cpp new file mode 100644 index 00000000..bdcd4169 --- /dev/null +++ b/src/test/test_framework.cpp @@ -0,0 +1,21 @@ +#include + +#include "test_fx_helper.h" + +#include "../debug.hpp" +#include "../fx_base.h" + +TEST(Framework, TestWaveIn) +{ + size_t size; + float32_t** samples = readWaveFile(AUDIO_SOURCE_FILE, size); + + size_t nb_errors = 0; + for(size_t i = 0; i < size; ++i) + { + nb_errors += fullInspector("L", samples[StereoChannels::Left ][i], -1.0f, 1.0f, true); + nb_errors += fullInspector("R", samples[StereoChannels::Right][i], -1.0f, 1.0f, true); + } + + ASSERT_EQ(nb_errors, 0) << "readWaveFile returns NaN of out of bounds samples: " << nb_errors << " out of " << size; +} diff --git a/src/test/test_fxLevelTuning.cpp b/src/test/test_fxLevelTuning.cpp new file mode 100644 index 00000000..90f23282 --- /dev/null +++ b/src/test/test_fxLevelTuning.cpp @@ -0,0 +1,273 @@ +#include "test_fx_helper.h" + +#include "../fx_tube.h" +#include "../fx_chorus.h" +#include "../fx_flanger.h" +#include "../fx_orbitone.h" +#include "../fx_phaser.h" +#include "../fx_delay.h" +#include "../effect_platervbstereo.h" +#include "../fx_reverberator.h" + +TEST(LevelTuning, Tube) +{ + Tube fx(SAMPLING_FREQUENCY); + fx.reset(); + fx.setOverdrive(0.75f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + float32_t sumIn = 0.0f; + float32_t sumOut = 0.0f; + size_t nb_errors = 0; + AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, + sumIn += inL * inL; + + fx.processSample(inL, inR, outL, outR); + + sumOut += outL * outL; + + nb_errors += std::abs(outL) > 1.0f ? 1 : 0; + nb_errors += std::abs(outR) > 1.0f ? 1 : 0; + ); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); + + float32_t ratio = std::sqrt(sumOut / sumIn); + + ASSERT_EQ(nb_errors, 0) << "Sample value error for Tube"; + EXPECT_GE(ratio, 0.9f); + EXPECT_LE(1.0f / ratio, 1.1f); +} + +TEST(LevelTuning, Chorus) +{ + Chorus fx(SAMPLING_FREQUENCY); + fx.reset(); + fx.setRate(0.4f); + fx.setDepth(0.5f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + float32_t sumIn = 0.0f; + float32_t sumOut = 0.0f; + size_t nb_errors = 0; + AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, + sumIn += inL * inL; + + fx.processSample(inL, inR, outL, outR); + + sumOut += outL * outL; + + nb_errors += std::abs(outL) > 1.0f ? 1 : 0; + nb_errors += std::abs(outR) > 1.0f ? 1 : 0; + ); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); + + float32_t ratio = std::sqrt(sumOut / sumIn); + + ASSERT_EQ(nb_errors, 0) << "Sample value error for Chorus"; + EXPECT_LE(ratio, 1.0f); + EXPECT_GE(ratio, 0.9f); + EXPECT_LE(1.0f / ratio, 1.1f); +} + +TEST(LevelTuning, Flanger) +{ + Flanger fx(SAMPLING_FREQUENCY); + fx.reset(); + fx.setRate(0.03f); + fx.setDepth(0.75f); + fx.setFeedback(0.5f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + float32_t sumIn = 0.0f; + float32_t sumOut = 0.0f; + size_t nb_errors = 0; + AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, + sumIn += inL * inL; + + fx.processSample(inL, inR, outL, outR); + + sumOut += outL * outL; + + nb_errors += std::abs(outL) > 1.0f ? 1 : 0; + nb_errors += std::abs(outR) > 1.0f ? 1 : 0; + ); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); + + float32_t ratio = std::sqrt(sumOut / sumIn); + + ASSERT_EQ(nb_errors, 0) << "Sample value error for Flanger"; + EXPECT_LE(ratio, 1.0f); + EXPECT_GE(ratio, 0.9f); + EXPECT_LE(1.0f / ratio, 1.1f); +} + +TEST(LevelTuning, Orbitone) +{ + Orbitone fx(SAMPLING_FREQUENCY); + fx.reset(); + fx.setRate(0.4f); + fx.setDepth(0.5f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + float32_t sumIn = 0.0f; + float32_t sumOut = 0.0f; + size_t nb_errors = 0; + AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, + sumIn += inL * inL; + + fx.processSample(inL, inR, outL, outR); + + sumOut += outL * outL; + + nb_errors += std::abs(outL) > 1.0f ? 1 : 0; + nb_errors += std::abs(outR) > 1.0f ? 1 : 0; + ); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); + + float32_t ratio = std::sqrt(sumOut / sumIn); + + ASSERT_EQ(nb_errors, 0) << "Sample value error for Orbitone"; + EXPECT_LE(ratio, 1.0f); + EXPECT_GE(ratio, 0.9f); + EXPECT_LE(1.0f / ratio, 1.1f); +} + +TEST(LevelTuning, Phaser) +{ + Phaser fx(SAMPLING_FREQUENCY); + fx.reset(); + fx.setRate(0.1f); + fx.setDepth(1.0f); + fx.setFeedback(0.5f); + fx.setNbStages(12); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + float32_t sumIn = 0.0f; + float32_t sumOut = 0.0f; + size_t nb_errors = 0; + AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, + sumIn += inL * inL; + + fx.processSample(inL, inR, outL, outR); + + sumOut += outL * outL; + + nb_errors += std::abs(outL) > 1.0f ? 1 : 0; + nb_errors += std::abs(outR) > 1.0f ? 1 : 0; + ); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); + + float32_t ratio = std::sqrt(sumOut / sumIn); + + ASSERT_EQ(nb_errors, 0) << "Sample value error for Phaser"; + EXPECT_LE(ratio, 1.0f); + EXPECT_GE(ratio, 0.9f); + EXPECT_LE(1.0f / ratio, 1.1f); +} + +TEST(LevelTuning, Delay) +{ + Delay fx(SAMPLING_FREQUENCY); + fx.reset(); + fx.setLeftDelayTime(0.15f); + fx.setLeftDelayTime(0.2f); + fx.setFeedback(0.35f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + float32_t sumIn = 0.0f; + float32_t sumOut = 0.0f; + size_t nb_errors = 0; + AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, + sumIn += inL * inL; + + fx.processSample(inL, inR, outL, outR); + + sumOut += outL * outL; + + nb_errors += std::abs(outL) > 1.0f ? 1 : 0; + nb_errors += std::abs(outR) > 1.0f ? 1 : 0; + ); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); + + float32_t ratio = std::sqrt(sumOut / sumIn); + + ASSERT_EQ(nb_errors, 0) << "Sample value error for Delay"; + EXPECT_LE(ratio, 1.0f); + EXPECT_GE(ratio, 0.9f); + EXPECT_LE(1.0f / ratio, 1.1f); +} + +TEST(LevelTuning, PlateReverb) +{ + AudioEffectPlateReverb fx(SAMPLING_FREQUENCY); + fx.reset(); + fx.set_bypass(false); + fx.size(0.7f); + fx.hidamp(0.5f); + fx.lodamp(0.5f); + fx.lowpass(0.3f); + fx.diffusion(0.65f); + fx.level(1.0f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + float32_t sumIn = 0.0f; + float32_t sumOut = 0.0f; + size_t nb_errors = 0; + AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, + sumIn += inL * inL; + + fx.processSample(inL, inR, outL, outR); + + sumOut += outL * outL; + + nb_errors += std::abs(outL) > 1.0f ? 1 : 0; + nb_errors += std::abs(outR) > 1.0f ? 1 : 0; + ); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); + + float32_t ratio = std::sqrt(sumOut / sumIn); + + ASSERT_EQ(nb_errors, 0) << "Sample value error for PlateReverb"; + EXPECT_GE(ratio, 0.9f); + EXPECT_LE(1.0f / ratio, 1.1f); +} + +TEST(LevelTuning, Reverberator) +{ + Reverberator fx(SAMPLING_FREQUENCY); + fx.reset(); + fx.setInputGain(0.35f); + fx.setTime(0.89f); + fx.setDiffusion(0.75f); + fx.setLP(0.8f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + float32_t sumIn = 0.0f; + float32_t sumOut = 0.0f; + size_t nb_errors = 0; + AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, + sumIn += inL * inL; + + fx.processSample(inL, inR, outL, outR); + + sumOut += outL * outL; + + nb_errors += std::abs(outL) > 1.0f ? 1 : 0; + nb_errors += std::abs(outR) > 1.0f ? 1 : 0; + ); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); + + float32_t ratio = std::sqrt(sumOut / sumIn); + + ASSERT_EQ(nb_errors, 0) << "Sample value error for Reverberator"; + EXPECT_GE(ratio, 0.9f); + EXPECT_LE(1.0f / ratio, 1.1f); +} diff --git a/src/test/test_fx_components.cpp b/src/test/test_fx_components.cpp new file mode 100644 index 00000000..e812a195 --- /dev/null +++ b/src/test/test_fx_components.cpp @@ -0,0 +1,361 @@ +#include +#include +#include +#include +#include + +#include "test_fx_helper.h" + +#include "../fx_rack.h" +#include "../effect_platervbstereo.h" + +#define MAX_SVF_SAMPLES 10000000 +#define MAX_NB_ERRORS 100 + +void testFastLFOPrecision(std::string file, float32_t freq, float32_t init_phase); + +TEST(FXComponent, LFO) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const float32_t freq = 10.0f; + + LFO lfo(SAMPLING_FREQUENCY, 0.0f, freq); + unsigned size = static_cast(8.0f * SAMPLING_FREQUENCY / freq); + float32_t rate = 0.0f; + float32_t rate_increment = freq / 2.0f / SAMPLING_FREQUENCY; + + std::ofstream out(getResultFile(full_test_name + ".FXComponent.LFO.csv", true)); + setupOuputStreamForCSV(out); + out << std::fixed << std::showpoint; + + out << "index;LFO" << std::endl; + for(unsigned i = 0; i < size; ++i) + { + lfo.setNormalizedFrequency(rate); + out << i << ";" << lfo.process() << std::endl; + rate += rate_increment; + + if(rate >= 1.0f || rate <= 0.0f) + { + rate_increment *= -1.0f; + } + } +} + +TEST(FXComponent, Flutter) +{ + JitterGenerator jg(SAMPLING_FREQUENCY); + jg.setSpeed(1.0f); + jg.setMagnitude(0.1f); + + for (unsigned i = 0; i < 1000; ++i) + { + jg.process(); + } +} + +TEST(FXComponent, SVF) +{ + float32_t inL, inR; + float32_t outL, outR; + StateVariableFilter svf(SAMPLING_FREQUENCY, StateVariableFilter::FilterMode::LPF, 12000.0f); + + { + svf.setFilterMode(StateVariableFilter::FilterMode::LPF); + svf.setCutoff(12000.0f); + svf.setResonance(0.0f); + unsigned nbSamples = 0; + unsigned nbErrors = 0; + while(nbErrors < MAX_NB_ERRORS && nbSamples < MAX_SVF_SAMPLES) + { + nbSamples++; + inL = getRandomValue(); + inR = getRandomValue(); + svf.processSample(inL, inR, outL, outR); + + if(std::abs(outL) > 1.0f) + nbErrors++; + if(std::abs(outR) > 1.0f) + nbErrors++; + } + EXPECT_LT(nbErrors, MAX_NB_ERRORS); + } + + { + svf.setFilterMode(StateVariableFilter::FilterMode::LPF); + svf.setCutoff(60.0f); + svf.setResonance(0.0f); + unsigned nbSamples = 0; + unsigned nbErrors = 0; + while(nbErrors < MAX_NB_ERRORS && nbSamples < MAX_SVF_SAMPLES) + { + nbSamples++; + inL = getRandomValue(); + inR = getRandomValue(); + svf.processSample(inL, inR, outL, outR); + + if(std::abs(outL) > 1.0f) + nbErrors++; + if(std::abs(outR) > 1.0f) + nbErrors++; + } + EXPECT_LT(nbErrors, MAX_NB_ERRORS); + } +} + +TEST(CppOptimization, InterpolatedSineOscillatorPrecisionTest) +{ + const float32_t freq = 0.15f; + const size_t NB = static_cast(2.0f * SAMPLING_FREQUENCY); + + const float32_t epsilon = 1e-3; + + ComplexLFO lfo1(SAMPLING_FREQUENCY, 0.0f, 10.0f); + InterpolatedSineOscillator lfo2(SAMPLING_FREQUENCY, 0.0f, 10.0f); + lfo1.setFrequency(freq); + lfo2.setFrequency(freq); + float32_t max_delta = 0.0f; + for(size_t i = 0; i < NB; ++i) + { + float32_t v1 = lfo1.process(); + float32_t v2 = lfo2.process(); + + max_delta = std::max(max_delta, std::abs(v1 - v2)); + } + EXPECT_GT(epsilon, max_delta); +} + +void testFastLFOPrecision(std::string file, float32_t freq, float32_t init_phase) +{ + const size_t NB = static_cast(2.0f * SAMPLING_FREQUENCY); + const float32_t init_phase_deg = init_phase * 180.0f / PI; + + const float32_t epsilon = 40e-3; + + ComplexLFO lfo0(SAMPLING_FREQUENCY, 0.0f, 220.0f, init_phase, true); + FastLFO lfo1(SAMPLING_FREQUENCY, 0.0f, 220.0f, init_phase, true); + FastLFO2 lfo2(SAMPLING_FREQUENCY, 0.0f, 220.0f, init_phase, true); + lfo0.setFrequency(freq); + lfo1.setFrequency(freq); + lfo2.setFrequency(freq); + + std::string file0 = string("data/") + file + ".ComplexLFO." + std::to_string(freq) + "Hz-" + std::to_string(init_phase_deg) + ".data"; + std::string file1 = string("data/") + file + ".FastLFO." + std::to_string(freq) + "Hz-" + std::to_string(init_phase_deg) + ".data"; + std::string file2 = string("data/") + file + ".FastLFO2." + std::to_string(freq) + "Hz-" + std::to_string(init_phase_deg) + ".data"; + std::string file3 = string(file + ".") + std::to_string(freq) + "Hz-" + std::to_string(init_phase_deg) + ".data.m"; + + std::ofstream _lfo0(getResultFile(file0, true)); + std::ofstream _lfo1(getResultFile(file1, true)); + std::ofstream _lfo2(getResultFile(file2, true)); + std::ofstream _m(getResultFile(file3, true)); + + float32_t max_delta1 = 0.0f; + float32_t max_delta2 = 0.0f; + for(size_t i = 0; i < NB; ++i) + { + float32_t v0 = lfo0.process(); + float32_t v1 = lfo1.process(); + float32_t v2 = lfo2.process(); + + _lfo0 << std::setprecision(6) << std::fixed << v0 << ((i == (NB - 1)) ? "" : ", "); + _lfo1 << std::setprecision(6) << std::fixed << v1 << ((i == (NB - 1)) ? "" : ", "); + _lfo2 << std::setprecision(6) << std::fixed << v2 << ((i == (NB - 1)) ? "" : ", "); + + max_delta1 = std::max(max_delta1, std::abs(v1 - v0)); + max_delta2 = std::max(max_delta2, std::abs(v2 - v0)); + } + + _lfo0.close(); + _lfo1.close(); + _lfo2.close(); + + _m << "# Tests of FastLFO precision:" << std::endl; + _m << std::setprecision(2) << std::fixed << "# + frequency: " << freq << " Hz" << std::endl; + _m << std::setprecision(2) << std::fixed << "# + initial phase: " << init_phase << std::endl; + _m << std::endl; + _m << "time = 0 : " << (NB - 1) << ";" << std::endl; + _m << "cplx_lfo = load(\"-ascii\", \"" << file0 << "\");" << std::endl; + _m << "fast_lfo = load(\"-ascii\", \"" << file1 << "\");" << std::endl; + _m << "fast_lfo2 = load(\"-ascii\", \"" << file2 << "\");" << std::endl; + _m << "plot(time, cplx_lfo, '-b', 'LineWidth', 6, time, fast_lfo, '-r', 'LineWidth', 4, time, fast_lfo2, '-o', 'LineWidth', 4);" << std::endl; + _m << "title('LFO tuning @ " << freq << "Hz / " << init_phase_deg << "°');" << std::endl; + _m << "legend('ComplexLFO', 'FastLFO');" << std::endl; + _m.close(); + + EXPECT_GT(epsilon, max_delta1) << "FastLFO is not precise enough for " << freq << " Hz starting with phase: " << (init_phase * 180.0f / PI); + EXPECT_GT(epsilon, max_delta2) << "FastLFO2 is not precise enough for " << freq << " Hz starting with phase: " << (init_phase * 180.0f / PI); +} + +TEST(CppOptimization, FastLFOPrecisionTest0_01Hz) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const float32_t freq = 0.01f; + testFastLFOPrecision(full_test_name, freq, 0.0f); + testFastLFOPrecision(full_test_name, freq, PI / 6.0f); + testFastLFOPrecision(full_test_name, freq, PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, PI / 2.0f); + testFastLFOPrecision(full_test_name, freq, 2.0f * PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 4.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 2.0f); +} + +TEST(CppOptimization, FastLFOPrecisionTest0_15Hz) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const float32_t freq = 0.15f; + testFastLFOPrecision(full_test_name, freq, 0.0f); + testFastLFOPrecision(full_test_name, freq, PI / 6.0f); + testFastLFOPrecision(full_test_name, freq, PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, PI / 2.0f); + testFastLFOPrecision(full_test_name, freq, 2.0f * PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 4.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 2.0f); +} + +TEST(CppOptimization, FastLFOPrecisionTest0_2Hz) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const float32_t freq = 0.2f; + testFastLFOPrecision(full_test_name, freq, 0.0f); + testFastLFOPrecision(full_test_name, freq, PI / 6.0f); + testFastLFOPrecision(full_test_name, freq, PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, PI / 2.0f); + testFastLFOPrecision(full_test_name, freq, 2.0f * PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 4.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 2.0f); +} + +TEST(CppOptimization, FastLFOPrecisionTest0_3Hz) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const float32_t freq = 0.3f; + testFastLFOPrecision(full_test_name, freq, 0.0f); + testFastLFOPrecision(full_test_name, freq, PI / 6.0f); + testFastLFOPrecision(full_test_name, freq, PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, PI / 2.0f); + testFastLFOPrecision(full_test_name, freq, 2.0f * PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 4.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 2.0f); +} + +TEST(CppOptimization, FastLFOPrecisionTest0_5Hz) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const float32_t freq = 0.5f; + testFastLFOPrecision(full_test_name, freq, 0.0f); + testFastLFOPrecision(full_test_name, freq, PI / 6.0f); + testFastLFOPrecision(full_test_name, freq, PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, PI / 2.0f); + testFastLFOPrecision(full_test_name, freq, 2.0f * PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 4.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 2.0f); +} + +TEST(CppOptimization, FastLFOPrecisionTest1Hz) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const float32_t freq = 1.0f; + testFastLFOPrecision(full_test_name, freq, 0.0f); + testFastLFOPrecision(full_test_name, freq, PI / 6.0f); + testFastLFOPrecision(full_test_name, freq, PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, PI / 2.0f); + testFastLFOPrecision(full_test_name, freq, 2.0f * PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 4.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 2.0f); +} + +TEST(CppOptimization, FastLFOPrecisionTest2_15Hz) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const float32_t freq = 2.15f; + testFastLFOPrecision(full_test_name, freq, 0.0f); + testFastLFOPrecision(full_test_name, freq, PI / 6.0f); + testFastLFOPrecision(full_test_name, freq, PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, PI / 2.0f); + testFastLFOPrecision(full_test_name, freq, 2.0f * PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 4.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 2.0f); +} + +TEST(CppOptimization, FastLFOPrecisionTest5Hz) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const float32_t freq = 5.0f; + testFastLFOPrecision(full_test_name, freq, 0.0f); + testFastLFOPrecision(full_test_name, freq, PI / 6.0f); + testFastLFOPrecision(full_test_name, freq, PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, PI / 2.0f); + testFastLFOPrecision(full_test_name, freq, 2.0f * PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 4.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 2.0f); +} + +TEST(CppOptimization, FastLFOPrecisionTest10_5Hz) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const float32_t freq = 10.5f; + testFastLFOPrecision(full_test_name, freq, 0.0f); + testFastLFOPrecision(full_test_name, freq, PI / 6.0f); + testFastLFOPrecision(full_test_name, freq, PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, PI / 2.0f); + testFastLFOPrecision(full_test_name, freq, 2.0f * PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 4.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 2.0f); +} + +TEST(CppOptimization, FastLFOPrecisionTest120_5Hz) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const float32_t freq = 120.5f; + testFastLFOPrecision(full_test_name, freq, 0.0f); + testFastLFOPrecision(full_test_name, freq, PI / 6.0f); + // testFastLFOPrecision(full_test_name, freq, PI / 3.0f); + testFastLFOPrecision(full_test_name, freq, PI / 2.0f); + testFastLFOPrecision(full_test_name, freq, 2.0f * PI / 3.0f); + // testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 4.0f); + testFastLFOPrecision(full_test_name, freq, 3.0f * PI / 2.0f); +} diff --git a/src/test/test_fx_helper.cpp b/src/test/test_fx_helper.cpp new file mode 100644 index 00000000..98bfb856 --- /dev/null +++ b/src/test/test_fx_helper.cpp @@ -0,0 +1,150 @@ +#include "test_fx_helper.h" + +#include + +std::string getScenarioName(int scenario) +{ + std::stringstream ss; + + bool fxTube = Active(scenario, FXSwitch::FX__Tube); + bool fxChorus = Active(scenario, FXSwitch::FX__Chorus); + bool fxPhaser = Active(scenario, FXSwitch::FX__Phaser); + bool fxOrbitone = Active(scenario, FXSwitch::FX__Orbitone); + bool fxFlanger = Active(scenario, FXSwitch::FX__Flanger); + bool fxDelay = Active(scenario, FXSwitch::FX__Delay); + bool fxReverberator = Active(scenario, FXSwitch::FX__Reverberator); + bool fxReverb = Active(scenario, FXSwitch::FX__PlateReverb); + bool first = true; + + ss << "[ "; + + if(fxTube) + { + // if(!first) ss << ", "; + ss << "Tube"; + first = false; + } + + if(fxChorus) + { + if(!first) ss << ", "; + ss << "Chrs"; + first = false; + } + + if(fxPhaser) + { + if(!first) ss << ", "; + ss << "Phsr"; + first = false; + } + + if(fxOrbitone) + { + if(!first) ss << ", "; + ss << "Orbt"; + first = false; + } + + if(fxFlanger) + { + if(!first) ss << ", "; + ss << "Flgr"; + first = false; + } + + if(fxDelay) + { + if(!first) ss << ", "; + ss << "Dely"; + first = false; + } + + if(fxReverb) + { + if(!first) ss << ", "; + ss << "Revb"; + first = false; + } + + if(fxReverberator) + { + if(!first) ss << ", "; + ss << "Shim"; + // first = false; + } + + ss << " ]"; + + return ss.str(); +} + + +void setupOuputStreamForCSV(std::ostream& out) +{ + struct comma_separator : std::numpunct + { + virtual char do_decimal_point() const override { return ','; } + }; + + out.imbue(std::locale(out.getloc(), new comma_separator)); + out << std::fixed << std::showpoint; +} + +bool createFolderStructure(std::string& path) +{ + try + { + std::filesystem::path file_path(path); + if(!std::filesystem::exists(file_path.parent_path())) + { + std::filesystem::create_directories(file_path.parent_path()); + } + + return true; + } + catch(const std::exception& e) + { + std::cerr << e.what() << '\n'; + return false; + } +} + +std::string getResultFile(const std::string& filename, bool createPath) +{ + std::string f = std::string(OUTPUT_FOLDER) + "/" + filename; + if(createPath) + { + createFolderStructure(f); + } + + return f; +} + +float32_t getRandomValue() +{ + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_real_distribution dist(-1.0f, 1.0f); + + return dist(gen); +} + +float32_t** loadAudioTest(size_t& size, WaveHeader* hdr) +{ + float32_t** samples = readWaveFile(AUDIO_SOURCE_FILE, size, hdr); + assert(samples != nullptr); + + return samples; +} + +void freeAudioSamples(float32_t** samples, size_t size) +{ + assert(samples != nullptr); + + for(size_t i = 0; i < size; ++i) + { + delete[] samples[i]; + } + delete[] samples; +} diff --git a/src/test/test_fx_helper.h b/src/test/test_fx_helper.h new file mode 100644 index 00000000..b574fc8e --- /dev/null +++ b/src/test/test_fx_helper.h @@ -0,0 +1,95 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// test_fx_helper.h +// +// Set og helpers dedicated to code rationalization for the elaboration on unit tests. +// Author: Vincent Gauché +// +#pragma once + +#include +#include +#include + +#include "wave.h" +#include "../fx.h" + +#define AUDIO_SOURCE_FILE "test3.wav" + +#define SAMPLING_FREQUENCY 44100.0f + +#define PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name)\ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info();\ + std::string full_test_name = test_info->test_case_name();\ + full_test_name += ".";\ + full_test_name += test_info->name();\ + WaveHeader hdr;\ + size_t size;\ + float32_t** inSamples = loadAudioTest(size, &hdr);\ + float32_t** outSamples = new float32_t*[2];\ + outSamples[0] = new float32_t[size]; memset(outSamples[0], 0, size * sizeof(float32_t));\ + outSamples[1] = new float32_t[size]; memset(outSamples[1], 0, size * sizeof(float32_t)) + +#define CLEANUP_AUDIO_TEST(inSamples, outSamples)\ + freeAudioSamples(inSamples, 2);\ + freeAudioSamples(outSamples, 2) + +#define AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, code)\ + for(size_t i = 0; i < size; ++i)\ + {\ + float32_t inL = inSamples[0][i];\ + float32_t inR = inSamples[1][i];\ + float32_t outL;\ + float32_t outR;\ + code\ + outSamples[0][i] = outL;\ + outSamples[1][i] = outR;\ + } // + +#define SIMPLE_AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx)\ + AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx.processSample(inL, inR, outL, outR);) + +#define SAVE_AUDIO_RESULTS(filename, samples, size)\ + saveWaveFile(getResultFile(filename + ".wav", true), samples[0], samples[1], size, static_cast(SAMPLING_FREQUENCY), 16) + +#define Active(scenarioKey, FxID) ((scenarioKey & (1 << FxID)) == (1 << FxID)) + +std::string getScenarioName(int scenario); + +enum FXSwitch +{ + FX__Tube = 0, + FX__Chorus, + FX__Flanger, + FX__Orbitone, + FX__Phaser, + FX__Delay, + FX__Reverberator, + FX__PlateReverb, + __kFXCount +}; + +void setupOuputStreamForCSV(std::ostream& out); + +bool createFolderStructure(std::string& path); + +std::string getResultFile(const std::string& filename, bool createPath); + +float32_t getRandomValue(); + +class FXScenarioTest : public testing::TestWithParam {}; + +float32_t** loadAudioTest(size_t& size, WaveHeader* hdr); +void freeAudioSamples(float32_t** samples, size_t size); \ No newline at end of file diff --git a/src/test/test_fx_mixing_console.cpp b/src/test/test_fx_mixing_console.cpp new file mode 100644 index 00000000..d08debad --- /dev/null +++ b/src/test/test_fx_mixing_console.cpp @@ -0,0 +1,565 @@ +#include +#include + +#include "test_fx_helper.h" +#include "wave.h" + +#include "../mixing_console.hpp" + +#define NB_MIXER_CHANNELS 8 + +class MixingConsoleScenarioTest : public testing::TestWithParam {}; + +typedef MixingConsole Mixer; + +void setupMixingConsoleFX(Mixer* mixer); +void setupMixingConsoleFX(Mixer* mixer, int scenarioId, size_t channel = 0); + +TEST_P(MixingConsoleScenarioTest, MixerOutputTest) +{ + MixerOutput v = this->GetParam(); + std::string str = toString(v); + MixerOutput mo = toIndex(str.c_str()); + ASSERT_EQ(v, mo); +} + +INSTANTIATE_TEST_SUITE_P(MixerOutputTest, MixingConsoleScenarioTest, testing::Range(MixerOutput::OutputStart, MixerOutput::kFXCount)); + +void setupMixingConsoleFX(Mixer* mixer) +{ + mixer->setChannelLevel(0, 1.0f); + mixer->setPan(0, 0.5f); + + mixer->getTube()->setMute(false); + mixer->getTube()->setOverdrive(0.85f); + + mixer->getChorus()->setMute(false); + mixer->getChorus()->setRate(0.4f); + mixer->getChorus()->setDepth(0.7f); + + mixer->getFlanger()->setMute(false); + mixer->getFlanger()->setRate(0.03f); + mixer->getFlanger()->setDepth(0.75f); + mixer->getFlanger()->setFeedback(0.7f); + + mixer->getOrbitone()->setMute(false); + mixer->getOrbitone()->setRate(0.4f); + mixer->getOrbitone()->setDepth(0.8f); + + mixer->getPhaser()->setMute(false); + mixer->getPhaser()->setRate(0.1f); + mixer->getPhaser()->setDepth(1.0f); + mixer->getPhaser()->setFeedback(0.7f); + mixer->getPhaser()->setNbStages(12); + + mixer->getDelay()->setMute(false); + mixer->getDelay()->setLeftDelayTime(0.15f); + mixer->getDelay()->setLeftDelayTime(0.20f); + mixer->getDelay()->setFeedback(0.7f); + + mixer->getPlateReverb()->setMute(false); + mixer->getPlateReverb()->set_bypass(false); + mixer->getPlateReverb()->size(0.7f); + mixer->getPlateReverb()->hidamp(0.5f); + mixer->getPlateReverb()->lodamp(0.5f); + mixer->getPlateReverb()->lowpass(0.3f); + mixer->getPlateReverb()->diffusion(0.65f); + mixer->getPlateReverb()->level(1.0f); + + mixer->getReverberator()->setMute(false); + mixer->getReverberator()->setInputGain(0.65f); + mixer->getReverberator()->setTime(0.89f); + mixer->getReverberator()->setDiffusion(0.75f); + mixer->getReverberator()->setLP(0.8f); +} + +#define ACTIVE_FX(activity, scenarioId, fx) activity[MixerOutput::fx] = ((scenarioId & (1 << MixerOutput::fx)) == (1 << MixerOutput::fx)) + +void setupMixingConsoleFX(Mixer* mixer, int scenarioId, size_t channel) +{ + mixer->setChannelLevel(channel, 1.0f); + mixer->setPan(channel, 0.5f); + + bool fxActivity[MixerOutput::kFXCount - 1]; + ACTIVE_FX(fxActivity, scenarioId, FX_Tube); + ACTIVE_FX(fxActivity, scenarioId, FX_Chorus); + ACTIVE_FX(fxActivity, scenarioId, FX_Flanger); + ACTIVE_FX(fxActivity, scenarioId, FX_Orbitone); + ACTIVE_FX(fxActivity, scenarioId, FX_Phaser); + ACTIVE_FX(fxActivity, scenarioId, FX_Delay); + ACTIVE_FX(fxActivity, scenarioId, FX_PlateReverb); + ACTIVE_FX(fxActivity, scenarioId, FX_Reverberator); + + size_t nbActiveFX = 0; + MixerOutput previousActivatedFX = MixerOutput::MainOutput; + + for(size_t i = 0; i < (MixerOutput::kFXCount - 1); ++i) + { + if(fxActivity[i]) + { + nbActiveFX++; + if(nbActiveFX == 1) + { + mixer->setSendLevel(channel, static_cast(i), 1.0f); + } + else + { + mixer->setFXSendLevel(previousActivatedFX, static_cast(i), 1.0f); + } + previousActivatedFX = static_cast(i); + } + } + + if(previousActivatedFX == MixerOutput::MainOutput) + { + mixer->setSendLevel(channel, MixerOutput::MainOutput, 1.0f); + } + else + { + mixer->setSendLevel(channel, MixerOutput::MainOutput, 0.25f); + mixer->setFXSendLevel(previousActivatedFX, MixerOutput::MainOutput, 0.75f); + } +} + +TEST(MixingConsole, ZeroSamplesTest) +{ + static const size_t length = 4; + + Mixer mixer(SAMPLING_FREQUENCY, length); + ASSERT_EQ(0, FULL_INSPECT((&mixer), true)); + + mixer.reset(); + ASSERT_EQ(0, FULL_INSPECT((&mixer), true)); + + setupMixingConsoleFX(&mixer); + ASSERT_EQ(0, FULL_INSPECT((&mixer), true)); + + mixer.setChannelLevel(0, 1.0f); + ASSERT_EQ(0, FULL_INSPECT((&mixer), true)); + + mixer.setPan(0, 0.5f); + ASSERT_EQ(0, FULL_INSPECT((&mixer), true)); + + float32_t samples[] = {0.0f, 0.0f, 0.0f, 0.0f}; + mixer.setInputSampleBuffer(0, samples); + mixer.preProcessInputSampleBuffer(0, 4); + ASSERT_EQ(0, FULL_INSPECT((&mixer), true)); + + mixer.setSendLevel(0, MixerOutput::MainOutput, 1.0f); + ASSERT_EQ(0, FULL_INSPECT((&mixer), true)); + + float32_t outL[4]; + float32_t outR[4]; + mixer.process(outL, outR); + ASSERT_EQ(0, FULL_INSPECT((&mixer), true)); + + mixer.setSendLevel(0, MixerOutput::FX_Tube, 1.0f); + mixer.setSendLevel(0, MixerOutput::FX_Delay, 1.0f); + mixer.setSendLevel(0, MixerOutput::FX_PlateReverb, 1.0f); + + mixer.setFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Orbitone, 1.0f); + mixer.setFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::MainOutput, 0.5f); + mixer.setFXSendLevel(MixerOutput::FX_Orbitone, MixerOutput::FX_PlateReverb, 1.0f); + mixer.setFXSendLevel(MixerOutput::FX_Delay, MixerOutput::MainOutput, 0.5f); + mixer.setFXSendLevel(MixerOutput::FX_PlateReverb, MixerOutput::MainOutput, 0.5f); + ASSERT_EQ(0, FULL_INSPECT((&mixer), true)); +} + +TEST(MixingConsole, DryProcessing) +{ + static const float32_t epsilon = 1e-4; + static const size_t length = 2; + + Mixer mixer(SAMPLING_FREQUENCY, length); + mixer.reset(); + mixer.setChannelLevel(0, 1.0f); + mixer.setPan(0, 0.5f); + + mixer.setSendLevel(0, MixerOutput::FX_Tube, 0.0f); + mixer.setSendLevel(0, MixerOutput::FX_Chorus, 0.0f); + mixer.setSendLevel(0, MixerOutput::FX_Flanger, 0.0f); + mixer.setSendLevel(0, MixerOutput::FX_Orbitone, 0.0f); + mixer.setSendLevel(0, MixerOutput::FX_Phaser, 0.0f); + mixer.setSendLevel(0, MixerOutput::FX_Delay, 0.0f); + mixer.setSendLevel(0, MixerOutput::FX_PlateReverb, 0.0f); + mixer.setSendLevel(0, MixerOutput::FX_Reverberator, 0.0f); + + for(size_t i = MixerOutput::OutputStart; i < (MixerOutput::kFXCount - 1); ++i) + { + mixer.setFXSendLevel(static_cast(i), MixerOutput::MainOutput, 0.0f); + } + + mixer.setSendLevel(0, MixerOutput::MainOutput, 1.0f); + ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + + float32_t in[length] = {0.1, 0.2}; + float32_t out[StereoChannels::kNumChannels][length]; + for(size_t i = 0; i < StereoChannels::kNumChannels; ++i) memset(out[i], 0, length * sizeof(float32_t)); + + mixer.setInputSampleBuffer(0, in); + mixer.preProcessInputSampleBuffer(0, 2); + ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + + mixer.process( + out[StereoChannels::Left ], + out[StereoChannels::Right] + ); + ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + + EXPECT_FLOAT_EQ(out[StereoChannels::Left ][0], out[StereoChannels::Right][0]); + EXPECT_FLOAT_EQ(out[StereoChannels::Left ][1], out[StereoChannels::Right][1]); + EXPECT_NEAR(out[StereoChannels::Left ][0], sqrt(2.0f) / 20.0f, epsilon); + EXPECT_NEAR(out[StereoChannels::Left ][1], sqrt(2.0f) / 10.0f, epsilon); +} + +TEST(MixingConsole, ReverberatorProcessing) +{ + static const float32_t epsilon = 1e-7; + static const size_t length = 2; + + Mixer mixer(SAMPLING_FREQUENCY, length); + mixer.reset(); + mixer.setChannelLevel(0, 1.0f); + mixer.setPan(0, 0.5f); + + mixer.setSendLevel(0, MixerOutput::MainOutput, 0.0f); + mixer.setSendLevel(0, MixerOutput::FX_Reverberator, 1.0f); + mixer.setFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput, 1.0f); + ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + + float32_t in[length] = {0.1, 0.2}; + float32_t out1[StereoChannels::kNumChannels][length]; + for(size_t i = 0; i < StereoChannels::kNumChannels; ++i) memset(out1[i], 0, length * sizeof(float32_t)); + + mixer.setInputSampleBuffer(0, in); + ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + + mixer.process( + out1[StereoChannels::Left ], + out1[StereoChannels::Right] + ); + ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + + mixer.reset(); + ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + + float32_t out2[StereoChannels::kNumChannels][length]; + mixer.setInputSampleBuffer(0, in); + ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + + mixer.process( + out2[StereoChannels::Left ], + out2[StereoChannels::Right] + ); + ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + + EXPECT_NEAR(out1[StereoChannels::Left ][0], out2[StereoChannels::Left ][0], epsilon); + EXPECT_NEAR(out1[StereoChannels::Right][0], out2[StereoChannels::Right][0], epsilon); + EXPECT_NEAR(out1[StereoChannels::Left ][1], out2[StereoChannels::Left ][1], epsilon); + EXPECT_NEAR(out1[StereoChannels::Right][1], out2[StereoChannels::Right][1], epsilon); +} + +TEST(MixingConsole, ReverberatorNoiseProcessing) +{ + static const size_t length = 1024; + + Mixer mixer(SAMPLING_FREQUENCY, length); + mixer.reset(); + mixer.setChannelLevel(0, 1.0f); + mixer.setPan(0, 0.5f); + + mixer.setSendLevel(0, MixerOutput::MainOutput, 0.0f); + mixer.setSendLevel(0, MixerOutput::FX_Reverberator, 1.0f); + mixer.setFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput, 1.0f); + ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + + float32_t in[length]; + for(size_t i = 0; i < length; ++i) in[i] = getRandomValue(); + + float32_t out[StereoChannels::kNumChannels][length]; + for(size_t i = 0; i < StereoChannels::kNumChannels; ++i) memset(out[i], 0, length * sizeof(float32_t)); + + mixer.setInputSampleBuffer(0, in); + ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + + mixer.process( + out[StereoChannels::Left ], + out[StereoChannels::Right] + ); + ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); +} + +TEST(MixingConsole, StandardUsageProcessingByInjection) +{ + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + + Mixer mixer(SAMPLING_FREQUENCY, size); + setupMixingConsoleFX(&mixer); + + mixer.getTube()->setOverdrive(0.15f); + + mixer.setSendLevel(0, MixerOutput::FX_Tube, 1.0f); + mixer.setSendLevel(0, MixerOutput::FX_Phaser, 1.0f); + // mixer.setFXSendLevel(MixerOutput::FX_Tube, MixerOutput::MainOutput, 1.0f); + // mixer.setSendLevel(0, MixerOutput::FX_Chorus, 1.0f); + // mixer.setSendLevel(0, MixerOutput::FX_Reverberator, 1.0f); + mixer.setFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Chorus, 1.0f); + mixer.setFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Reverberator, 1.0f); + mixer.setFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Delay, 1.0f); + + mixer.setSendLevel(0, MixerOutput::MainOutput, 0.25f); + mixer.setFXSendLevel(MixerOutput::FX_Tube, MixerOutput::MainOutput, 0.1f); + mixer.setFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::MainOutput, 0.15f); + mixer.setFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput, 0.3f); + mixer.setFXSendLevel(MixerOutput::FX_Delay, MixerOutput::MainOutput, 0.3f); + + mixer.injectInputSamples(0, inSamples[StereoChannels::Left], inSamples[StereoChannels::Right], size); + mixer.process(outSamples[0], outSamples[1]); + ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamples[0], outSamples[1], size, static_cast(SAMPLING_FREQUENCY), 16); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(MixingConsole, StandardUsageProcessing) +{ + static const size_t MAX_BUFFER_SIZE = 4096; + static const size_t BUFFER_SIZE = 256; + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + + Mixer mixer(SAMPLING_FREQUENCY, MAX_BUFFER_SIZE); + + float32_t channelBuffer[MAX_BUFFER_SIZE]; + memset(channelBuffer, 0, MAX_BUFFER_SIZE * sizeof(float32_t)); + mixer.setInputSampleBuffer(0, channelBuffer); + + setupMixingConsoleFX(&mixer); + + mixer.getTube()->setOverdrive(0.15f); + + mixer.setSendLevel(0, MixerOutput::FX_Tube, 1.0f); + mixer.setSendLevel(0, MixerOutput::FX_Phaser, 1.0f); + // mixer.setFXSendLevel(MixerOutput::FX_Tube, MixerOutput::MainOutput, 1.0f); + // mixer.setSendLevel(0, MixerOutput::FX_Chorus, 1.0f); + // mixer.setSendLevel(0, MixerOutput::FX_Reverberator, 1.0f); + mixer.setFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Chorus, 1.0f); + mixer.setFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Reverberator, 1.0f); + mixer.setFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Delay, 1.0f); + + mixer.setSendLevel(0, MixerOutput::MainOutput, 0.25f); + mixer.setFXSendLevel(MixerOutput::FX_Tube, MixerOutput::MainOutput, 0.1f); + mixer.setFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::MainOutput, 0.15f); + mixer.setFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput, 0.3f); + mixer.setFXSendLevel(MixerOutput::FX_Delay, MixerOutput::MainOutput, 0.3f); + + float32_t* inS = inSamples[StereoChannels::Left]; + float32_t* outS[StereoChannels::kNumChannels]; + outS[StereoChannels::Left ] = outSamples[StereoChannels::Left ]; + outS[StereoChannels::Right] = outSamples[StereoChannels::Right]; + size_t s = size; + + while(s > 0) + { + size_t nb = (s > BUFFER_SIZE) ? BUFFER_SIZE : s; + + memcpy(channelBuffer, inS, nb * sizeof(float32_t)); + + mixer.preProcessInputSampleBuffer(0, nb); + mixer.process(outS[StereoChannels::Left ], outS[StereoChannels::Right]); + // ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + + inS += nb; + outS[StereoChannels::Left ] += nb; + outS[StereoChannels::Right] += nb; + s -= nb; + } + + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamples[0], outSamples[1], size, static_cast(SAMPLING_FREQUENCY), 16); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(MixingConsole, StandardUsageProcessingAllMixerChannels) +{ + static const size_t MAX_BUFFER_SIZE = 4096; + static const size_t BUFFER_SIZE = 256; + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + + Mixer mixer(SAMPLING_FREQUENCY, MAX_BUFFER_SIZE); + + float32_t channelBuffer[NB_MIXER_CHANNELS][MAX_BUFFER_SIZE]; + for(size_t i = 0; i < NB_MIXER_CHANNELS; ++i) + { + memset(channelBuffer[i], 0, MAX_BUFFER_SIZE * sizeof(float32_t)); + mixer.setInputSampleBuffer(i, channelBuffer[i]); + } + + setupMixingConsoleFX(&mixer); + + mixer.getTube()->setOverdrive(0.15f); + + mixer.setSendLevel(0, MixerOutput::FX_Tube, 1.0f); + mixer.setSendLevel(0, MixerOutput::FX_Phaser, 1.0f); + // mixer.setFXSendLevel(MixerOutput::FX_Tube, MixerOutput::MainOutput, 1.0f); + // mixer.setSendLevel(0, MixerOutput::FX_Chorus, 1.0f); + // mixer.setSendLevel(0, MixerOutput::FX_Reverberator, 1.0f); + mixer.setFXSendLevel(MixerOutput::FX_Tube, MixerOutput::FX_Chorus, 1.0f); + mixer.setFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Reverberator, 1.0f); + mixer.setFXSendLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Delay, 1.0f); + + mixer.setSendLevel(0, MixerOutput::MainOutput, 0.25f); + mixer.setFXSendLevel(MixerOutput::FX_Tube, MixerOutput::MainOutput, 0.1f); + mixer.setFXSendLevel(MixerOutput::FX_Chorus, MixerOutput::MainOutput, 0.15f); + mixer.setFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput, 0.3f); + mixer.setFXSendLevel(MixerOutput::FX_Delay, MixerOutput::MainOutput, 0.3f); + + float32_t* inS = inSamples[StereoChannels::Left]; + float32_t* outS[StereoChannels::kNumChannels]; + outS[StereoChannels::Left ] = outSamples[StereoChannels::Left ]; + outS[StereoChannels::Right] = outSamples[StereoChannels::Right]; + size_t s = size; + + while(s > 0) + { + size_t nb = (s > BUFFER_SIZE) ? BUFFER_SIZE : s; + + memcpy(channelBuffer[0], inS, nb * sizeof(float32_t)); + + for(size_t i = 0; i < mixer.getChannelNumber(); ++i) + { + mixer.preProcessInputSampleBuffer(i, nb); + } + mixer.process(outS[StereoChannels::Left ], outS[StereoChannels::Right]); + + inS += nb; + outS[StereoChannels::Left ] += nb; + outS[StereoChannels::Right] += nb; + s -= nb; + } + + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamples[0], outSamples[1], size, static_cast(SAMPLING_FREQUENCY), 16); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(MixingConsole, StandardUsageProcessingAllMixerChannels2) +{ + static const size_t MAX_BUFFER_SIZE = 4096; + static const size_t BUFFER_SIZE = 256; + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + + Mixer mixer(SAMPLING_FREQUENCY, MAX_BUFFER_SIZE); + + float32_t channelBuffer[NB_MIXER_CHANNELS][MAX_BUFFER_SIZE]; + for(size_t i = 0; i < NB_MIXER_CHANNELS; ++i) + { + memset(channelBuffer[i], 0, MAX_BUFFER_SIZE * sizeof(float32_t)); + mixer.setInputSampleBuffer(i, channelBuffer[i]); + } + + setupMixingConsoleFX(&mixer); + + for(size_t i = 0; i < NB_MIXER_CHANNELS; ++i) + { + mixer.setSendLevel(i, static_cast(i), 1.0f); + mixer.setFXSendLevel(static_cast(i), MixerOutput::MainOutput, 0.5f); + mixer.setSendLevel(i, MixerOutput::MainOutput, 0.5f); + } + + float32_t* inS = inSamples[StereoChannels::Left]; + float32_t* outS[StereoChannels::kNumChannels]; + outS[StereoChannels::Left ] = outSamples[StereoChannels::Left ]; + outS[StereoChannels::Right] = outSamples[StereoChannels::Right]; + size_t s = size; + + while(s > 0) + { + size_t nb = (s > BUFFER_SIZE) ? BUFFER_SIZE : s; + + for(size_t i = 0; i < mixer.getChannelNumber(); ++i) + { + memcpy(channelBuffer[i], inS, nb * sizeof(float32_t)); + mixer.preProcessInputSampleBuffer(i, nb); + } + mixer.process(outS[StereoChannels::Left ], outS[StereoChannels::Right]); + + inS += nb; + outS[StereoChannels::Left ] += nb; + outS[StereoChannels::Right] += nb; + s -= nb; + } + + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamples[0], outSamples[1], size, static_cast(SAMPLING_FREQUENCY), 16); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST_P(FXScenarioTest, FXProcessingScenario) +{ + static const size_t MAX_BUFFER_SIZE = 4096; + static const size_t BUFFER_SIZE = 256; + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + + Mixer mixer(SAMPLING_FREQUENCY, MAX_BUFFER_SIZE); + + float32_t channelBuffer[NB_MIXER_CHANNELS][MAX_BUFFER_SIZE]; + for(size_t i = 0; i < NB_MIXER_CHANNELS; ++i) + { + memset(channelBuffer[i], 0, MAX_BUFFER_SIZE * sizeof(float32_t)); + mixer.setInputSampleBuffer(i, channelBuffer[i]); + } + + setupMixingConsoleFX(&mixer); + + int scenarioId = this->GetParam(); + setupMixingConsoleFX((&mixer), scenarioId); + + float32_t* inS = inSamples[StereoChannels::Left]; + float32_t* outS[StereoChannels::kNumChannels]; + outS[StereoChannels::Left ] = outSamples[StereoChannels::Left ]; + outS[StereoChannels::Right] = outSamples[StereoChannels::Right]; + size_t s = size; + + while(s > 0) + { + size_t nb = (s > BUFFER_SIZE) ? BUFFER_SIZE : s; + + for(size_t i = 0; i < mixer.getChannelNumber(); ++i) + { + memcpy(channelBuffer[i], inS, nb * sizeof(float32_t)); + mixer.preProcessInputSampleBuffer(i, nb); + } + mixer.process(outS[StereoChannels::Left ], outS[StereoChannels::Right]); + + inS += nb; + outS[StereoChannels::Left ] += nb; + outS[StereoChannels::Right] += nb; + s -= nb; + } + + std::string filename = ""; + for(size_t i = 0; i < (MixerOutput::kFXCount - 1); ++i) + { + int k = 1 << i; + if((scenarioId & k) == 0) + { + continue; + } + + if(filename.size() > 0) + { + filename += ", "; + } + filename += toString(static_cast(i)); + } + + saveWaveFile(getResultFile(full_test_name + " mixing-console[ " + filename + " ].wav", true), outSamples[0], outSamples[1], size, static_cast(SAMPLING_FREQUENCY), 16); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +INSTANTIATE_TEST_SUITE_P(MixingConsole, FXScenarioTest, testing::Range(0, 1 << (MixerOutput::kFXCount - 1))); diff --git a/src/test/test_fx_mixing_console_unitary.cpp b/src/test/test_fx_mixing_console_unitary.cpp new file mode 100644 index 00000000..313d0407 --- /dev/null +++ b/src/test/test_fx_mixing_console_unitary.cpp @@ -0,0 +1,187 @@ +#include "test_fx_helper.h" + +#include "../mixing_console.hpp" + +typedef MixingConsole<1> Mixer; + +TEST(MixingConsole, ShortBuffer) +{ + static const float32_t SINPI_4 = std::sqrt(2.0f) / 2.0f; + static const float32_t epsilon = 1e-4; + + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const size_t size = 10; + Mixer* mixer = new Mixer(SAMPLING_FREQUENCY, size); + mixer->reset(); + mixer->setChannelLevel(0, 1.0f); + mixer->setPan(0, 0.5f); + + mixer->setSendLevel(0, MixerOutput::MainOutput, 1.0f); + + float32_t inSamples[size]; + for(size_t s = 0; s < size; ++s) inSamples[s] = getRandomValue(); + float32_t outSamples[2][size]; + memset(outSamples[0], 0, size * sizeof(float32_t)); + memset(outSamples[1], 0, size * sizeof(float32_t)); + + mixer->setInputSampleBuffer(0, inSamples); + mixer->preProcessInputSampleBuffer(0, size); + ASSERT_EQ(0, FULL_INSPECT(mixer, true)) << full_test_name << " Mixer.setInputSampleBuffer"; + + mixer->process(outSamples[0], outSamples[1]); + ASSERT_EQ(0, FULL_INSPECT(mixer, true)) << full_test_name << " Mixer.process"; + for(size_t s = 0; s < size; ++s) + { + EXPECT_NEAR(outSamples[0][s], outSamples[1][s], epsilon); + EXPECT_NEAR(outSamples[0][s], inSamples[s] * SINPI_4, epsilon); + } + + delete mixer; +} + +TEST(MixingConsole, ReverberatorShortBuffer) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const size_t size = 10; + Mixer* mixer = new Mixer(SAMPLING_FREQUENCY, size); + mixer->reset(); + mixer->setChannelLevel(0, 1.0f); + mixer->setPan(0, 0.5f); + + mixer->getReverberator()->setInputGain(0.35f); + mixer->getReverberator()->setTime(0.69f); + mixer->getReverberator()->setDiffusion(0.7f); + mixer->getReverberator()->setLP(0.8f); + + mixer->setSendLevel(0, MixerOutput::MainOutput, 0.4f); + mixer->setSendLevel(0, MixerOutput::FX_Reverberator, 1.0f); + mixer->setFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput, 0.6f); + + float32_t inSamples[size]; + for(size_t s = 0; s < size; ++s) inSamples[s] = getRandomValue(); + float32_t outSamples[2][size]; + memset(outSamples[0], 0, size * sizeof(float32_t)); + memset(outSamples[1], 0, size * sizeof(float32_t)); + + mixer->setInputSampleBuffer(0, inSamples); + mixer->preProcessInputSampleBuffer(0, size); + ASSERT_EQ(0, FULL_INSPECT(mixer, true)) << full_test_name << " Mixer.setInputSampleBuffer"; + + mixer->process(outSamples[0], outSamples[1]); + ASSERT_EQ(0, FULL_INSPECT(mixer, true)) << full_test_name << " Mixer.process"; + + delete mixer; +} + +TEST(MixingConsole, DrySamplesBoundariesTest) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + size_t size; + float32_t** inSamples = readWaveFile(AUDIO_SOURCE_FILE, size); + + Mixer* mixer = new Mixer(SAMPLING_FREQUENCY, size); + mixer->reset(); + FULL_INSPECT2(mixer, true, "Mixer.reset"); + + mixer->setChannelLevel(0, 1.0f); + mixer->setPan(0, 0.5f); + mixer->setSendLevel(0, MixerOutput::MainOutput, 1.0f); + + mixer->setInputSampleBuffer(0, inSamples[0]); + mixer->preProcessInputSampleBuffer(0, size); + + float32_t** outSamples = new float32_t*[2]; + outSamples[0] = new float32_t[size]; + outSamples[1] = new float32_t[size]; + memset(outSamples[0], 0, size * sizeof(float32_t)); + memset(outSamples[1], 0, size * sizeof(float32_t)); + + mixer->process(outSamples[0], outSamples[1]); + + size_t nb_errors = 0; + for(size_t i = 0; i < size; ++i) + { + nb_errors += fullInspector(full_test_name + ".outputSampleTest", inSamples[0][i], -1.0f, 1.0f, true); + nb_errors += fullInspector(full_test_name + ".outputSampleTest", inSamples[1][i], -1.0f, 1.0f, true); + } + ASSERT_EQ(0, nb_errors) << full_test_name << ".outputSampleTest"; + + delete[] inSamples[0]; + delete[] inSamples[1]; + delete[] inSamples; + + delete[] outSamples[0]; + delete[] outSamples[1]; + delete[] outSamples; + + delete mixer; +} + +TEST(MixingConsole, ReverberatorSamplesBoundariesTest) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + size_t size; + float32_t** inSamples = readWaveFile(AUDIO_SOURCE_FILE, size); + + float32_t** outSamples = new float32_t*[2]; + outSamples[0] = new float32_t[size]; + outSamples[1] = new float32_t[size]; + memset(outSamples[0], 0, size * sizeof(float32_t)); + memset(outSamples[1], 0, size * sizeof(float32_t)); + + Mixer* mixer = new Mixer(SAMPLING_FREQUENCY, size); + mixer->reset(); + mixer->setChannelLevel(0, 1.0f); + mixer->setPan(0, 0.5f); + + mixer->setSendLevel(0, MixerOutput::MainOutput, 0.4f); + mixer->setSendLevel(0, MixerOutput::FX_Reverberator, 1.0f); + mixer->setFXSendLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput, 0.6f); + + mixer->getReverberator()->setMute(false); + mixer->getReverberator()->setInputGain(0.35); + mixer->getReverberator()->setTime(0.65); + mixer->getReverberator()->setDiffusion(0.8); + mixer->getReverberator()->setLP(0.7f); + + mixer->setInputSampleBuffer(0, inSamples[0]); + mixer->preProcessInputSampleBuffer(0, size); + mixer->process(outSamples[0], outSamples[1]); + ASSERT_EQ(0, FULL_INSPECT2(mixer, true, full_test_name + "Mixer.process")) << full_test_name << " Mixer.process"; + + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamples[0], outSamples[1], size, static_cast(SAMPLING_FREQUENCY), 16); + + size_t nb_errors = 0; + for(size_t i = 0; i < size; ++i) + { + nb_errors += fullInspector(full_test_name + ".outputSampleTest", inSamples[0][i], -1.0f, 1.0f, true); + nb_errors += fullInspector(full_test_name + ".outputSampleTest", inSamples[1][i], -1.0f, 1.0f, true); + } + ASSERT_EQ(0, nb_errors) << full_test_name << ".outputSampleTest"; + + delete[] inSamples[0]; + delete[] inSamples[1]; + delete[] inSamples; + + delete[] outSamples[0]; + delete[] outSamples[1]; + delete[] outSamples; + + delete mixer; +} diff --git a/src/test/test_fx_plate_reverb.cpp b/src/test/test_fx_plate_reverb.cpp new file mode 100644 index 00000000..20c149a1 --- /dev/null +++ b/src/test/test_fx_plate_reverb.cpp @@ -0,0 +1,39 @@ +#include + +#include "test_fx_helper.h" + +#include "../effect_platervbstereo.h" + +TEST(FXPlateReverb, Migration) +{ + AudioEffectPlateReverb reverb(SAMPLING_FREQUENCY); + reverb.set_bypass(false); + reverb.size(0.7f); + reverb.hidamp(0.5f); + reverb.lodamp(0.5f); + reverb.lowpass(0.3f); + reverb.diffusion(0.65f); + reverb.level(1.0f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + for(size_t i = 0; i < size; ++i) + { + reverb.processSample(inSamples[0][i], inSamples[1][i], outSamples[0][i], outSamples[1][i]); + } + saveWaveFile(getResultFile(full_test_name + ".PlateReverb-new.wav", true), outSamples[0], outSamples[1], size, static_cast(SAMPLING_FREQUENCY), 16); + + size_t index = 0; + size_t len = size; + while(len > 0) + { + size_t grainSize = (len < 1024 ? len : 1024); + + reverb.doReverb(inSamples[0] + index, inSamples[1] + index, outSamples[0] + index, outSamples[1] + index, grainSize); + + index += grainSize; + len -= grainSize; + } + saveWaveFile(getResultFile(full_test_name + ".PlateReverb-legacy.wav", true), outSamples[0], outSamples[1], size, static_cast(SAMPLING_FREQUENCY), 16); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} diff --git a/src/test/test_fx_rack.cpp b/src/test/test_fx_rack.cpp new file mode 100644 index 00000000..53306a97 --- /dev/null +++ b/src/test/test_fx_rack.cpp @@ -0,0 +1,87 @@ +#include "test_fx_helper.h" + +#include "../fx_rack.h" + +using namespace std; + +#define MAX_SVF_SAMPLES 10000000 +#define MAX_NB_ERRORS 100 + +void setupRack(FXRack* rack, int scenario); + +void setupRack(FXRack* rack, int scenario) +{ + rack->setWetLevel(1.0f); + + rack->getTube()->setEnable(Active(scenario, FXSwitch::FX__Tube)); + rack->getTube()->setWetLevel(0.25f); + rack->getTube()->setOverdrive(0.5f); + + rack->getChorus()->setEnable(Active(scenario, FXSwitch::FX__Chorus)); + rack->getChorus()->setWetLevel(0.5f); + rack->getChorus()->setRate(0.4f); + rack->getChorus()->setDepth(0.5f); + + rack->getFlanger()->setEnable(Active(scenario, FXSwitch::FX__Flanger)); + rack->getFlanger()->setWetLevel(0.5f); + rack->getFlanger()->setRate(0.03f); + rack->getFlanger()->setDepth(0.75f); + rack->getFlanger()->setFeedback(0.5f); + + rack->getOrbitone()->setEnable(Active(scenario, FXSwitch::FX__Orbitone)); + rack->getOrbitone()->setWetLevel(0.8f); + rack->getOrbitone()->setRate(0.4f); + rack->getOrbitone()->setDepth(0.7f); + + rack->getPhaser()->setEnable(Active(scenario, FXSwitch::FX__Phaser)); + rack->getPhaser()->setWetLevel(1.0f); + rack->getPhaser()->setRate(0.1f); + rack->getPhaser()->setDepth(1.0f); + rack->getPhaser()->setFeedback(0.5f); + rack->getPhaser()->setNbStages(12); + + rack->getDelay()->setEnable(Active(scenario, FXSwitch::FX__Delay)); + rack->getDelay()->setWetLevel(0.6f); + rack->getDelay()->setLeftDelayTime(0.05f); + rack->getDelay()->setLeftDelayTime(0.07f); + rack->getDelay()->setFeedback(0.35f); + + rack->getReverberator()->setEnable(Active(scenario, FXSwitch::FX__Reverberator)); + rack->getReverberator()->setWetLevel(0.5f); + rack->getReverberator()->setInputGain(0.35f); + rack->getReverberator()->setTime(0.89f); + rack->getReverberator()->setDiffusion(0.75f); + rack->getReverberator()->setLP(0.8f); +} + +TEST_P(FXScenarioTest, FXRackResetAllScenarios) +{ + FXRack rack(SAMPLING_FREQUENCY); + + int fxSwitch = this->GetParam(); + rack.setEnable(true); + setupRack(&rack, fxSwitch); + rack.reset(); +} + +TEST_P(FXScenarioTest, ScenarioProcessing) +{ + int fxSwitch = this->GetParam(); + string name = getScenarioName(fxSwitch); + + FXRack rack(SAMPLING_FREQUENCY); + rack.setEnable(true); + setupRack(&rack, fxSwitch); + rack.reset(); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + rack.process(inSamples[0], inSamples[1], outSamples[0], outSamples[1], size); + + stringstream ss; + ss << full_test_name << "-fx-rack" << name << ".wav"; + saveWaveFile(getResultFile(ss.str(), true), outSamples[0], outSamples[1], size, static_cast(SAMPLING_FREQUENCY), 16); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +INSTANTIATE_TEST_SUITE_P(FXRack, FXScenarioTest, testing::Range(0, 1 << (FXSwitch::FX__Reverberator + 1))); diff --git a/src/test/test_fx_reverberator.cpp b/src/test/test_fx_reverberator.cpp new file mode 100644 index 00000000..171b6e9f --- /dev/null +++ b/src/test/test_fx_reverberator.cpp @@ -0,0 +1,174 @@ +#include + +#include "test_fx_helper.h" +#include "../fx_reverberator.h" + +TEST(FXReverberator, TransientSilence) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const size_t size = static_cast(SAMPLING_FREQUENCY); + float32_t* inSamples = new float32_t[size]; + memset(inSamples, 0, size * sizeof(float32_t)); + + float32_t* outSamplesL = new float32_t[size]; + float32_t* outSamplesR = new float32_t[size]; + memset(outSamplesL, 0, size * sizeof(float32_t)); + memset(outSamplesR, 0, size * sizeof(float32_t)); + + Reverberator fx(SAMPLING_FREQUENCY); + + fx.setInputGain(0.55f); + fx.setTime(0.75f); + fx.setDiffusion(0.8f); + fx.setLP(0.7f); + + fx.reset(); + for(size_t i = 0; i < size; ++i) + { + fx.processSample( + inSamples[i], + inSamples[i], + outSamplesL[i], + outSamplesR[i] + ); + } + + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamplesL, outSamplesR, size, SAMPLING_FREQUENCY, 16); + + delete[] inSamples; + + delete[] outSamplesL; + delete[] outSamplesR; +} + +TEST(FXReverberator, TransientSilenceWithDirac) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const size_t size = 4 * static_cast(SAMPLING_FREQUENCY); + float32_t* inSamples = new float32_t[size]; + memset(inSamples, 0, size * sizeof(float32_t)); + inSamples[0] = 1.0f; + + float32_t* outSamplesL = new float32_t[size]; + float32_t* outSamplesR = new float32_t[size]; + memset(outSamplesL, 0, size * sizeof(float32_t)); + memset(outSamplesR, 0, size * sizeof(float32_t)); + + Reverberator fx(SAMPLING_FREQUENCY); + + fx.setInputGain(0.55f); + fx.setTime(0.75f); + fx.setDiffusion(0.8f); + fx.setLP(0.7f); + + fx.reset(); + for(size_t i = 0; i < size; ++i) + { + fx.processSample( + inSamples[i], + inSamples[i], + outSamplesL[i], + outSamplesR[i] + ); + } + + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamplesL, outSamplesR, size, SAMPLING_FREQUENCY, 16); + + delete[] inSamples; + + delete[] outSamplesL; + delete[] outSamplesR; +} + +TEST(FXReverberator, TransientNoise) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const size_t size = static_cast(SAMPLING_FREQUENCY); + float32_t* inSamples = new float32_t[size]; + for(size_t i = 0; i < size; ++i) inSamples[i] = getRandomValue(); + + float32_t* outSamplesL = new float32_t[size]; + float32_t* outSamplesR = new float32_t[size]; + memset(outSamplesL, 0, size * sizeof(float32_t)); + memset(outSamplesR, 0, size * sizeof(float32_t)); + + Reverberator fx(SAMPLING_FREQUENCY); + + fx.setInputGain(0.55f); + fx.setTime(0.75f); + fx.setDiffusion(0.8f); + fx.setLP(0.7f); + + fx.reset(); + for(size_t i = 0; i < size; ++i) + { + fx.processSample( + inSamples[i], + inSamples[i], + outSamplesL[i], + outSamplesR[i] + ); + } + + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamplesL, outSamplesR, size, SAMPLING_FREQUENCY, 16); + + delete[] inSamples; + + delete[] outSamplesL; + delete[] outSamplesR; +} + +TEST(FXReverberator, TransientMusic) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + size_t size; + float32_t** inSamples = readWaveFile(AUDIO_SOURCE_FILE, size); + + float32_t* outSamplesL = new float32_t[size]; + float32_t* outSamplesR = new float32_t[size]; + memset(outSamplesL, 0, size * sizeof(float32_t)); + memset(outSamplesR, 0, size * sizeof(float32_t)); + + Reverberator fx(SAMPLING_FREQUENCY); + + fx.setInputGain(0.55f); + fx.setTime(0.75f); + fx.setDiffusion(0.8f); + fx.setLP(0.7f); + + fx.reset(); + for(size_t i = 0; i < size; ++i) + { + fx.processSample( + inSamples[0][i], + inSamples[1][i], + outSamplesL[i], + outSamplesR[i] + ); + } + + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamplesL, outSamplesR, size, SAMPLING_FREQUENCY, 16); + + delete[] inSamples[0]; + delete[] inSamples[1]; + delete[] inSamples; + + delete[] outSamplesL; + delete[] outSamplesR; +} diff --git a/src/test/test_fx_shimmer_reverb.cpp b/src/test/test_fx_shimmer_reverb.cpp new file mode 100644 index 00000000..272ce949 --- /dev/null +++ b/src/test/test_fx_shimmer_reverb.cpp @@ -0,0 +1,182 @@ +#include + +#include "test_fx_helper.h" +#include "../fx_reverberator.h" + +TEST(FXShimmer, TransientSilence) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const size_t size = static_cast(SAMPLING_FREQUENCY); + float32_t* inSamples = new float32_t[size]; + memset(inSamples, 0, size * sizeof(float32_t)); + + float32_t* outSamplesL = new float32_t[size]; + float32_t* outSamplesR = new float32_t[size]; + memset(outSamplesL, 0, size * sizeof(float32_t)); + memset(outSamplesR, 0, size * sizeof(float32_t)); + + Reverberator* shimmer = new Reverberator(SAMPLING_FREQUENCY); + + shimmer->setInputGain(0.55f); + shimmer->setTime(0.75f); + shimmer->setDiffusion(0.8f); + shimmer->setLP(0.7f); + + shimmer->reset(); + for(size_t i = 0; i < size; ++i) + { + shimmer->processSample( + inSamples[i], + inSamples[i], + outSamplesL[i], + outSamplesR[i] + ); + } + + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamplesL, outSamplesR, size, SAMPLING_FREQUENCY, 16); + + delete shimmer; + + delete[] inSamples; + + delete[] outSamplesL; + delete[] outSamplesR; +} + +TEST(FXShimmer, TransientSilenceWithDirac) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const size_t size = 4 * static_cast(SAMPLING_FREQUENCY); + float32_t* inSamples = new float32_t[size]; + memset(inSamples, 0, size * sizeof(float32_t)); + inSamples[0] = 1.0f; + + float32_t* outSamplesL = new float32_t[size]; + float32_t* outSamplesR = new float32_t[size]; + memset(outSamplesL, 0, size * sizeof(float32_t)); + memset(outSamplesR, 0, size * sizeof(float32_t)); + + Reverberator* shimmer = new Reverberator(SAMPLING_FREQUENCY); + + shimmer->setInputGain(0.55f); + shimmer->setTime(0.75f); + shimmer->setDiffusion(0.8f); + shimmer->setLP(0.7f); + + shimmer->reset(); + for(size_t i = 0; i < size; ++i) + { + shimmer->processSample( + inSamples[i], + inSamples[i], + outSamplesL[i], + outSamplesR[i] + ); + } + + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamplesL, outSamplesR, size, SAMPLING_FREQUENCY, 16); + + delete shimmer; + + delete[] inSamples; + + delete[] outSamplesL; + delete[] outSamplesR; +} + +TEST(FXShimmer, TransientNoise) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + const size_t size = static_cast(SAMPLING_FREQUENCY); + float32_t* inSamples = new float32_t[size]; + for(size_t i = 0; i < size; ++i) inSamples[i] = getRandomValue(); + + float32_t* outSamplesL = new float32_t[size]; + float32_t* outSamplesR = new float32_t[size]; + memset(outSamplesL, 0, size * sizeof(float32_t)); + memset(outSamplesR, 0, size * sizeof(float32_t)); + + Reverberator* shimmer = new Reverberator(SAMPLING_FREQUENCY); + + shimmer->setInputGain(0.55f); + shimmer->setTime(0.75f); + shimmer->setDiffusion(0.8f); + shimmer->setLP(0.7f); + + shimmer->reset(); + for(size_t i = 0; i < size; ++i) + { + shimmer->processSample( + inSamples[i], + inSamples[i], + outSamplesL[i], + outSamplesR[i] + ); + } + + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamplesL, outSamplesR, size, SAMPLING_FREQUENCY, 16); + + delete shimmer; + + delete[] inSamples; + + delete[] outSamplesL; + delete[] outSamplesR; +} + +TEST(FXShimmer, TransientMusic) +{ + const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); + std::string full_test_name = test_info->test_case_name(); + full_test_name += "."; + full_test_name += test_info->name(); + + size_t size; + float32_t** inSamples = readWaveFile(AUDIO_SOURCE_FILE, size); + + float32_t* outSamplesL = new float32_t[size]; + float32_t* outSamplesR = new float32_t[size]; + memset(outSamplesL, 0, size * sizeof(float32_t)); + memset(outSamplesR, 0, size * sizeof(float32_t)); + + Reverberator* shimmer = new Reverberator(SAMPLING_FREQUENCY); + + shimmer->setInputGain(0.55f); + shimmer->setTime(0.75f); + shimmer->setDiffusion(0.8f); + shimmer->setLP(0.7f); + + shimmer->reset(); + for(size_t i = 0; i < size; ++i) + { + shimmer->processSample( + inSamples[0][i], + inSamples[1][i], + outSamplesL[i], + outSamplesR[i] + ); + } + + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamplesL, outSamplesR, size, SAMPLING_FREQUENCY, 16); + + delete shimmer; + + delete[] inSamples[0]; + delete[] inSamples[1]; + delete[] inSamples; + + delete[] outSamplesL; + delete[] outSamplesR; +} diff --git a/src/test/test_unitFXTuning.cpp b/src/test/test_unitFXTuning.cpp new file mode 100644 index 00000000..f6e87b96 --- /dev/null +++ b/src/test/test_unitFXTuning.cpp @@ -0,0 +1,169 @@ +#include "test_fx_helper.h" + +#include "../fx_dry.h" +#include "../fx_tube.h" +#include "../fx_chorus.h" +#include "../fx_flanger.h" +#include "../fx_orbitone.h" +#include "../fx_phaser.h" +#include "../fx_delay.h" +#include "../effect_platervbstereo.h" +#include "../fx_diffuser.h" +#include "../fx_pitch_shifter.h" +#include "../fx_reverberator.h" +#include "../fx_shimmer_reverb.h" + +TEST(UnitFXTuning, Dry) +{ + Dry fx(SAMPLING_FREQUENCY); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + SIMPLE_AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx); + SAVE_AUDIO_RESULTS(full_test_name, outSamples, size); + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(UnitFXTuning, Tube) +{ + Tube fx(SAMPLING_FREQUENCY); + fx.setOverdrive(0.5f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + SIMPLE_AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx); + SAVE_AUDIO_RESULTS(full_test_name, outSamples, size); + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(UnitFXTuning, Chorus) +{ + Chorus fx(SAMPLING_FREQUENCY); + fx.setRate(0.4f); + fx.setDepth(0.7f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + SIMPLE_AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx); + SAVE_AUDIO_RESULTS(full_test_name, outSamples, size); + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(UnitFXTuning, Flanger) +{ + Flanger fx(SAMPLING_FREQUENCY); + fx.setRate(0.03f); + fx.setDepth(0.75f); + fx.setFeedback(0.5f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + SIMPLE_AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx); + SAVE_AUDIO_RESULTS(full_test_name, outSamples, size); + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(UnitFXTuning, Orbitone) +{ + Orbitone fx(SAMPLING_FREQUENCY); + fx.setRate(0.4f); + fx.setDepth(0.7f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + SIMPLE_AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx); + SAVE_AUDIO_RESULTS(full_test_name, outSamples, size); + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(UnitFXTuning, Phaser) +{ + Phaser fx(SAMPLING_FREQUENCY); + fx.setRate(0.1f); + fx.setDepth(1.0f); + fx.setFeedback(0.5f); + fx.setNbStages(12); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + SIMPLE_AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx); + SAVE_AUDIO_RESULTS(full_test_name, outSamples, size); + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(UnitFXTuning, Delay) +{ + Delay fx(SAMPLING_FREQUENCY); + fx.setLeftDelayTime(0.25f); + fx.setLeftDelayTime(0.40f); + fx.setFeedback(0.55f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + SIMPLE_AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx); + SAVE_AUDIO_RESULTS(full_test_name, outSamples, size); + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(UnitFXTuning, PlateReverb) +{ + AudioEffectPlateReverb fx(SAMPLING_FREQUENCY); + fx.set_bypass(false); + fx.size(0.7f); + fx.hidamp(0.5f); + fx.lodamp(0.5f); + fx.lowpass(0.3f); + fx.diffusion(0.65f); + fx.level(1.0f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + SIMPLE_AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx); + SAVE_AUDIO_RESULTS(full_test_name, outSamples, size); + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(UnitFXTuning, Diffuser) +{ + Diffuser fx(SAMPLING_FREQUENCY); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + SIMPLE_AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx); + SAVE_AUDIO_RESULTS(full_test_name, outSamples, size); + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(UnitFXTuning, PitchShifter) +{ + PitchShifter fx(SAMPLING_FREQUENCY); + fx.setSize(0.5f); + fx.setTranspose(12.0f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + SIMPLE_AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx); + SAVE_AUDIO_RESULTS(full_test_name, outSamples, size); + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(UnitFXTuning, Reverberator) +{ + Reverberator fx(SAMPLING_FREQUENCY); + fx.setInputGain(0.65f); + fx.setTime(0.89f); + fx.setDiffusion(0.75f); + fx.setLP(0.8f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + SIMPLE_AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx); + SAVE_AUDIO_RESULTS(full_test_name, outSamples, size); + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + +TEST(UnitFXTuning, ShimmerReverb) +{ + const float32_t amount = 0.6f; + + ShimmerReverb fx(SAMPLING_FREQUENCY); + fx.setInputGain(0.2f); + fx.setReverbAmount(amount); + fx.setDiffusion(0.7f); + fx.setFeedback(0.8f); + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + SIMPLE_AUDIO_LOOP(inSamples, outSamples, size, inL, inR, outL, outR, fx); + SAVE_AUDIO_RESULTS(full_test_name, outSamples, size); + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + diff --git a/src/test/wave.h b/src/test/wave.h new file mode 100644 index 00000000..08834b7a --- /dev/null +++ b/src/test/wave.h @@ -0,0 +1,103 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// wave.h +// +// Set of helpers to manipulate RIFF Wave files. These helpers are used in the unit tests. +// Author: Vincent Gauché +// +#pragma once + +#include +#include +#include +#include + +inline uint32_t id2int(const char id[4]) +{ + uint32_t v = id[3]; + v <<= 8; + v += id[2]; + v <<= 8; + v += id[1]; + v <<= 8; + v += id[0]; + + return v; +} + +union ChunkID +{ + char ID[4]; + uint32_t Value; +}; + +struct WaveHeader { + ChunkID chunkId; + uint32_t chunkSize; + ChunkID format; + ChunkID subchunk1Id; + uint32_t subchunk1Size; + uint16_t audioFormat; + uint16_t numChannels; + uint32_t sampleRate; + uint32_t byteRate; + uint16_t blockAlign; + uint16_t bitsPerSample; + ChunkID subchunk2Id; + uint32_t subchunk2Size; +}; + +struct WaveHeaderRIFF { + ChunkID chunkId; + uint32_t chunkSize; + ChunkID format; +}; + +struct WaveHeaderFMT { + ChunkID subchunk1Id; + uint32_t subchunk1Size; + uint16_t audioFormat; + uint16_t numChannels; + uint32_t sampleRate; + uint32_t byteRate; + uint16_t blockAlign; + uint16_t bitsPerSample; +}; + +struct WaveHeaderDATA { + ChunkID subchunk2Id; + uint32_t subchunk2Size; +}; + +std::ostream& operator<<(std::ostream& out, const ChunkID& id); +std::ostream& operator<<(std::ostream& out, const WaveHeader& hdr); +std::ostream& operator<<(std::ostream& out, const WaveHeaderRIFF& riff); +std::ostream& operator<<(std::ostream& out, const WaveHeaderFMT& fmt); +std::ostream& operator<<(std::ostream& out, const WaveHeaderDATA& data); + +float32_t** readWaveFile(const std::string& fileName, size_t& size, WaveHeader* hdr = nullptr); + +void saveWaveFile(const std::string& fileName, + const float32_t* LChannel, + const float32_t* RChannel, + size_t size, + int sampleRate, + int bitsPerSample); + +// void playSound(float32_t* LChannel, +// float32_t* RChannel, +// unsigned size, +// int sampleRate, +// int bitsPerSample); diff --git a/src/test/wavein.cpp b/src/test/wavein.cpp new file mode 100644 index 00000000..29267a53 --- /dev/null +++ b/src/test/wavein.cpp @@ -0,0 +1,247 @@ +#include "wave.h" + +#include +#include +#include + +#if defined(DEBUG) +#define ASSERT_NORMALIZED(x) assert(x <= 1.0f && x >= -1.0f) +#else +#define ASSERT_NORMALIZED(x) +#endif + +std::ostream& operator<<(std::ostream& out, const WaveHeader& hdr) +{ + out << "WaveHeader" << std::endl; + out << " + chunkId : " << hdr.chunkId << std::endl; + out << " + chunkSize : " << hdr.chunkSize << std::endl; + out << " + format : " << hdr.format << std::endl; + out << " + subchunk1Id : " << hdr.subchunk1Id << std::endl; + out << " + subchunk1Size : " << hdr.subchunk1Size << std::endl; + out << " + audioFormat : " << hdr.audioFormat << std::endl; + out << " + numChannels : " << hdr.numChannels << std::endl; + out << " + sampleRate : " << hdr.sampleRate << std::endl; + out << " + byteRate : " << hdr.byteRate << std::endl; + out << " + blockAlign : " << hdr.blockAlign << std::endl; + out << " + bitsPerSample : " << hdr.bitsPerSample << std::endl; + out << " + subchunk2Id : " << hdr.subchunk2Id << std::endl; + out << " + subchunk2Size : " << hdr.subchunk2Size << std::endl; + + return out; +} + +std::ostream& operator<<(std::ostream& out, const ChunkID& id) +{ + out << "'" + << id.ID[0] + << id.ID[1] + << id.ID[2] + << id.ID[3] + << "'"; + + return out; +} + +std::ostream& operator<<(std::ostream& out, const WaveHeaderRIFF& riff) +{ + out << "WaveHeaderRIFF" << std::endl; + out << " + chunkId : " << riff.chunkId << std::endl; + out << " + chunkSize : " << riff.chunkSize << std::endl; + out << " + format : " << riff.format << std::endl; + + return out; +} + +std::ostream& operator<<(std::ostream& out, const WaveHeaderFMT& fmt) +{ + out << "WaveHeaderFMT" << std::endl; + out << " + subchunk1Id : " << fmt.subchunk1Id << std::endl; + out << " + subchunk1Size : " << fmt.subchunk1Size << std::endl; + out << " + audioFormat : " << fmt.audioFormat << std::endl; + out << " + numChannels : " << fmt.numChannels << std::endl; + out << " + sampleRate : " << fmt.sampleRate << std::endl; + out << " + byteRate : " << fmt.byteRate << std::endl; + out << " + blockAlign : " << fmt.blockAlign << std::endl; + out << " + bitsPerSample : " << fmt.bitsPerSample << std::endl; + + return out; +} + +std::ostream& operator<<(std::ostream& out, const WaveHeaderDATA& data) +{ + out << "WaveHeaderDATA" << std::endl; + out << " + subchunk2Id : " << data.subchunk2Id << std::endl; + out << " + subchunk2Size : " << data.subchunk2Size << std::endl; + + return out; +} + +template +bool readChunk(std::ifstream& in, uint32_t id, T& chunk) +{ + ChunkID chunkID; + while(!in.eof()) + { + in.read((char*)&chunkID.Value, sizeof(chunkID.Value)); + if(chunkID.Value == id) + { + in.seekg(-sizeof(chunkID.Value), in.cur); + in.read((char*)&chunk, sizeof(chunk)); + return true; + } + else + { + in.read((char*)&chunkID.Value, sizeof(chunkID.Value)); + in.seekg(chunkID.Value, in.cur); + } + } + + return false; +} + +float32_t** readWaveFile(const std::string& fileName, size_t& size, WaveHeader* hdr) +{ + std::ifstream file(fileName, std::ios::binary); + if(!file) + { + std::cerr << "Error opening file: " << fileName << std::endl; + return nullptr; + } + + WaveHeaderRIFF riff; + if(!readChunk(file, id2int("RIFF"), riff)) + { + std::cerr << "The file " << fileName << " does not contain any 'RIFF' chunk" << std::endl; + return nullptr; + } + + if(riff.format.Value != id2int("WAVE")) + { + std::cerr << "The file " << fileName << " is not a 'WAVE' file but a '" << riff.format.ID << "'" << std::endl; + return nullptr; + } + + WaveHeaderFMT fmt; + if(!readChunk(file, id2int("fmt "), fmt)) + { + std::cerr << "The file " << fileName << " does not contain any 'fmt ' chunk" << std::endl; + return nullptr; + } + + WaveHeaderDATA data; + if(!readChunk(file, id2int("data"), data)) + { + std::cerr << "The file " << fileName << " does not contain any 'data' chunk" << std::endl; + return nullptr; + } + + if(fmt.audioFormat != 1) + { + std::cerr << "Error: only support PCM format" << std::endl; + return nullptr; + } + + if(hdr != nullptr) + { + hdr->chunkId = riff.chunkId; + hdr->chunkSize = riff.chunkSize; + hdr->format = riff.format; + hdr->subchunk1Id = fmt.subchunk1Id; + hdr->subchunk1Size = fmt.subchunk1Size; + hdr->audioFormat = fmt.audioFormat; + hdr->numChannels = fmt.numChannels; + hdr->sampleRate = fmt.sampleRate; + hdr->byteRate = fmt.byteRate; + hdr->blockAlign = fmt.blockAlign; + hdr->bitsPerSample = fmt.bitsPerSample; + hdr->subchunk2Id = data.subchunk2Id; + hdr->subchunk2Size = data.subchunk2Size; + } + + size = data.subchunk2Size / fmt.blockAlign; + float32_t* LChannel = new float32_t[size]; + float32_t* RChannel = new float32_t[size]; + + size_t i = 0; + while(!file.eof() && i < size) + { + if(fmt.bitsPerSample == 8) + { + uint8_t LSample; + file.read((char*)&LSample, 1); + LChannel[i] = LSample / 128.0f - 1.0f; + if(fmt.numChannels == 2) + { + uint8_t RSample; + file.read((char*)&RSample, 1); + RChannel[i] = RSample / 128.0f - 1.0f; + } + else + { + RChannel[i] = LChannel[i]; + } + } + else if(fmt.bitsPerSample == 16) + { + int16_t LSample; + file.read((char*)&LSample, 2); + LChannel[i] = LSample / 32768.0f; + if(fmt.numChannels == 2) + { + int16_t RSample; + file.read((char*)&RSample, 2); + RChannel[i] = RSample / 32768.0f; + } + else + { + RChannel[i] = LChannel[i]; + } + } + else if(fmt.bitsPerSample == 24) + { + int32_t LSample; + file.read((char*)&LSample, 3); + LChannel[i] = LSample / 8388608.0f; + if(fmt.numChannels == 2) + { + int32_t RSample; + file.read((char*)&RSample, 3); + RChannel[i] = RSample / 8388608.0f; + } + else + { + RChannel[i] = LChannel[i]; + } + } + else if(fmt.bitsPerSample == 32) + { + int32_t LSample; + file.read((char*)&LSample, 4); + LChannel[i] = LSample / 2147483648.0f; + if(fmt.numChannels == 2) + { + int32_t RSample; + file.read((char*)&RSample, 4); + RChannel[i] = RSample / 2147483648.0f; + } + else + { + RChannel[i] = LChannel[i]; + } + } + else + { + std::cerr << "Error: unsupported bit depth: " << fmt.bitsPerSample << std::endl; + return nullptr; + } + + ++i; + } + assert(i == size); + + float32_t** result = new float32_t*[2]; + result[0] = LChannel; + result[1] = RChannel; + + return result; +} diff --git a/src/test/waveout.cpp b/src/test/waveout.cpp new file mode 100644 index 00000000..86710a4f --- /dev/null +++ b/src/test/waveout.cpp @@ -0,0 +1,91 @@ +#include "wave.h" + +#include +#include +#include + +void saveWaveFile(const std::string& fileName, + const float32_t* LChannel, + const float32_t* RChannel, + size_t size, + int sampleRate, + int bitsPerSample) +{ + std::ofstream file(fileName, std::ios::binary); + if(!file) + { + std::cerr << "Error opening file: " << fileName << std::endl; + return; + } + + WaveHeader header; + std::memset(&header, 0, sizeof(header)); + header.sampleRate = sampleRate; + header.numChannels = 2; + header.bitsPerSample = bitsPerSample; + header.byteRate = header.sampleRate * header.numChannels * header.bitsPerSample / 8; + header.blockAlign = header.numChannels * header.bitsPerSample / 8; + header.subchunk2Size = size * header.blockAlign; + header.chunkSize = 36 + header.subchunk2Size; + header.subchunk1Size = 16; + header.audioFormat = 1; + + header.chunkId.Value = id2int("RIFF"); + header.format.Value = id2int("WAVE"); + header.subchunk1Id.Value = id2int("fmt "); + header.subchunk2Id.Value = id2int("data"); + // std::strncpy(header.chunkId, "RIFF", 4); + // std::strncpy(header.format, "WAVE", 4); + // std::strncpy(header.subchunk1Id, "fmt ", 4); + // std::strncpy(header.subchunk2Id, "data", 4); + + file.write((char*)&header, sizeof(header)); + + if(bitsPerSample == 8) + { + for (size_t i = 0; i < size; i++) + { + int8_t leftSample = (int8_t)(LChannel[i] * 128.0f + 128.0f); + int8_t rightSample = (int8_t)(RChannel[i] * 128.0f + 128.0f); + file.write((char*)&leftSample, 1); + file.write((char*)&rightSample, 1); + } + } + else if(bitsPerSample == 16) + { + for (size_t i = 0; i < size; i++) + { + int16_t leftSample = (int16_t)(LChannel[i] * 32768.0f); + int16_t rightSample = (int16_t)(RChannel[i] * 32768.0f); + file.write((char*)&leftSample, 2); + file.write((char*)&rightSample, 2); + } + } + else if(bitsPerSample == 24) + { + for(size_t i = 0; i < size; i++) + { + int32_t leftSample = (int32_t)(LChannel[i] * 8388608.0f); + int32_t rightSample = (int32_t)(RChannel[i] * 8388608.0f); + file.write((char*)&leftSample, 3); + file.write((char*)&rightSample, 3); + } + } + else if(bitsPerSample == 32) + { + for (size_t i = 0; i < size; i++) + { + int32_t leftSample = (int32_t)(LChannel[i] * 2147483648.0f); + int32_t rightSample = (int32_t)(RChannel[i] * 2147483648.0f); + file.write((char*)&leftSample, 4); + file.write((char*)&rightSample, 4); + } + } + else + { + std::cerr << "Error: unsupported bit depth: " << bitsPerSample << std::endl; + return; + } + + file.close(); +} diff --git a/src/test/waveplay.cpp b/src/test/waveplay.cpp new file mode 100644 index 00000000..f11a3a99 --- /dev/null +++ b/src/test/waveplay.cpp @@ -0,0 +1,116 @@ +#include "wave.h" + +#include +#include +#include + +void playSound(float32_t* LChannel, + float32_t* RChannel, + unsigned size, + int sampleRate, + int bitsPerSample) +{ + // Calculate the number of samples and the size of the audio buffer + int numSamples = size; + int bufferSize = numSamples * bitsPerSample / 8 * 2; + + // Create an audio buffer + std::vector buffer(bufferSize); + + // Fill the audio buffer with the sample data + for(int i = 0; i < numSamples; i++) + { + if(bitsPerSample == 8) + { + // 8-bit samples are unsigned and range from 0 to 255 + uint8_t leftSample = (uint8_t)(LChannel[i] * 128.0f + 128.0f); + uint8_t rightSample = (uint8_t)(RChannel[i] * 128.0f + 128.0f); + buffer[i * 2 + 0] = leftSample; + buffer[i * 2 + 1] = rightSample; + } + else if(bitsPerSample == 16) + { + // 16-bit samples are signed and range from -32768 to 32767 + int16_t leftSample = (int16_t)(LChannel[i] * 32768.0f); + int16_t rightSample = (int16_t)(RChannel[i] * 32768.0f); + buffer[i * 2 + 0] = (uint8_t)leftSample; + buffer[i * 2 + 1] = (uint8_t)(leftSample >> 8); + buffer[i * 2 + 2] = (uint8_t)rightSample; + buffer[i * 2 + 3] = (uint8_t)(rightSample >> 8); + } + else if(bitsPerSample == 24) + { + // 24-bit samples are signed and range from -32768 to 32767 + int32_t leftSample = (int16_t)(LChannel[i] * 8388608.0f); + int32_t rightSample = (int16_t)(RChannel[i] * 8388608.0f); + buffer[i * 3 + 0] = (uint8_t)leftSample; + buffer[i * 3 + 1] = (uint8_t)(leftSample >> 8); + buffer[i * 3 + 2] = (uint8_t)(leftSample >> 16); + buffer[i * 3 + 3] = (uint8_t)rightSample; + buffer[i * 3 + 4] = (uint8_t)(rightSample >> 8); + buffer[i * 3 + 5] = (uint8_t)(rightSample >> 16); + } + else if (bitsPerSample == 32) + { + // 32-bit samples are signed and range from -32768 to 32767 + int32_t leftSample = (int16_t)(LChannel[i] * 2147483648.0f); + int32_t rightSample = (int16_t)(RChannel[i] * 2147483648.0f); + buffer[i * 3 + 0] = (uint8_t)leftSample; + buffer[i * 3 + 1] = (uint8_t)(leftSample >> 8); + buffer[i * 3 + 2] = (uint8_t)(leftSample >> 16); + buffer[i * 3 + 3] = (uint8_t)(leftSample >> 24); + buffer[i * 3 + 4] = (uint8_t)rightSample; + buffer[i * 3 + 5] = (uint8_t)(rightSample >> 8); + buffer[i * 3 + 6] = (uint8_t)(rightSample >> 16); + buffer[i * 3 + 7] = (uint8_t)(rightSample >> 24); + } + else + { + std::cerr << "Error: unsupported bit depth: " << bitsPerSample << std::endl; + return; + } + } + + // Set up the WAVEFORMATEX structure + WAVEFORMATEX waveFormat; + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nChannels = 2; + waveFormat.nSamplesPerSec = sampleRate; + waveFormat.nAvgBytesPerSec = sampleRate * bitsPerSample / 8 * 2; + waveFormat.nBlockAlign = bitsPerSample / 8 * 2; + waveFormat.wBitsPerSample = bitsPerSample; + waveFormat.cbSize = 0; + + // Set up the WAVEHDR structure + WAVEHDR waveHeader; + waveHeader.lpData = (LPSTR)buffer.data(); + waveHeader.dwBufferLength = bufferSize; + waveHeader.dwBytesRecorded = 0; + waveHeader.dwUser = 0; + waveHeader.dwFlags = 0; + waveHeader.dwLoops = 0; + waveHeader.lpNext = nullptr; + waveHeader.reserved = 0; + + // Open the audio device + HWAVEOUT audioDevice; + MMRESULT result = waveOutOpen(&audioDevice, WAVE_MAPPER, &waveFormat, 0, 0, WAVE_FORMAT_QUERY); + if (result != MMSYSERR_NOERROR) { + std::cerr << "Error opening audio device" << std::endl; + return; + } + + // Prepare the audio buffer for playback + result = waveOutPrepareHeader(audioDevice, &waveHeader, sizeof(WAVEHDR)); + if (result != MMSYSERR_NOERROR) { + std::cerr << "Error preparing audio buffer" << std::endl; + return; + } + + // Play the audio + waveOutWrite(audioDevice, &waveHeader, sizeof(WAVEHDR)); + + // Cleanup + waveOutUnprepareHeader(audioDevice, &waveHeader, sizeof(WAVEHDR)); + waveOutClose(audioDevice); +} \ No newline at end of file diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 360a5b0b..0a2a221b 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -56,32 +56,65 @@ const CUIMenu::TMenuItem CUIMenu::s_MainMenu[] = {0} }; +#if defined(MIXING_CONSOLE_ENABLE) +const CUIMenu::TMenuItem CUIMenu::s_TGFXMenu[] = +{ + {"> Tub", CUIMenu::EditTGParameter2, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXTube}, + {"> ChR", CUIMenu::EditTGParameter2, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXChorus}, + {"> Flg", CUIMenu::EditTGParameter2, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXFlanger}, + {"> Orb", CUIMenu::EditTGParameter2, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXOrbitone}, + {"> PhR", CUIMenu::EditTGParameter2, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPhaser}, + {"> Dly", CUIMenu::EditTGParameter2, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXDelay}, + {"> Plt", CUIMenu::EditTGParameter2, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPlateReverb}, + {"> Rev", CUIMenu::EditTGParameter2, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXReverberator}, + {"> Main", CUIMenu::EditTGParameter2, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXMainOutput}, + {0} +}; +#endif + const CUIMenu::TMenuItem CUIMenu::s_TGMenu[] = { - {"Voice", EditProgramNumber}, - {"Bank", EditVoiceBankNumber}, - {"Volume", EditTGParameter, 0, CMiniDexed::TGParameterVolume}, + {"Voice", CUIMenu::EditProgramNumber}, + {"Bank", CUIMenu::EditVoiceBankNumber}, + {"Volume", CUIMenu::EditTGParameter, 0, CMiniDexed::TTGParameter::TGParameterVolume}, #ifdef ARM_ALLOW_MULTI_CORE - {"Pan", EditTGParameter, 0, CMiniDexed::TGParameterPan}, + {"Pan", CUIMenu::EditTGParameter, 0, CMiniDexed::TTGParameter::TGParameterPan}, +#endif +#if defined(MIXING_CONSOLE_ENABLE) + {"FX-Send", CUIMenu::MenuHandler, CUIMenu::s_TGFXMenu}, +#elif defined(PLATE_REVERB_ENABLE) + {"Reverb-Send", CUIMenu::EditTGParameter, 0, CMiniDexed::TTGParameter::TGParameterReverbSend}, #endif - {"Reverb-Send", EditTGParameter, 0, CMiniDexed::TGParameterReverbSend}, - {"Detune", EditTGParameter, 0, CMiniDexed::TGParameterMasterTune}, - {"Cutoff", EditTGParameter, 0, CMiniDexed::TGParameterCutoff}, - {"Resonance", EditTGParameter, 0, CMiniDexed::TGParameterResonance}, - {"Pitch Bend", MenuHandler, s_EditPitchBendMenu}, - {"Portamento", MenuHandler, s_EditPortamentoMenu}, - {"Poly/Mono", EditTGParameter, 0, CMiniDexed::TGParameterMonoMode}, - {"Modulation", MenuHandler, s_ModulationMenu}, - {"Channel", EditTGParameter, 0, CMiniDexed::TGParameterMIDIChannel}, - {"Edit Voice", MenuHandler, s_EditVoiceMenu}, + {"Detune", CUIMenu::EditTGParameter, 0, CMiniDexed::TTGParameter::TGParameterMasterTune}, + {"Cutoff", CUIMenu::EditTGParameter, 0, CMiniDexed::TTGParameter::TGParameterCutoff}, + {"Resonance", CUIMenu::EditTGParameter, 0, CMiniDexed::TTGParameter::TGParameterResonance}, + {"Pitch Bend", CUIMenu::MenuHandler, CUIMenu::s_EditPitchBendMenu}, + {"Portamento", CUIMenu::MenuHandler, CUIMenu::s_EditPortamentoMenu}, + {"Poly/Mono", CUIMenu::EditTGParameter, 0, CMiniDexed::TTGParameter::TGParameterMonoMode}, + {"Modulation", CUIMenu::MenuHandler, CUIMenu::s_ModulationMenu}, + {"Channel", CUIMenu::EditTGParameter, 0, CMiniDexed::TTGParameter::TGParameterMIDIChannel}, + {"Edit Voice", CUIMenu::MenuHandler, CUIMenu::s_EditVoiceMenu}, {0} }; const CUIMenu::TMenuItem CUIMenu::s_EffectsMenu[] = { - {"Compress", EditGlobalParameter, 0, CMiniDexed::ParameterCompressorEnable}, -#ifdef ARM_ALLOW_MULTI_CORE - {"Reverb", MenuHandler, s_ReverbMenu}, + {"Compress",CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterCompressorEnable}, +#if defined(MIXING_CONSOLE_ENABLE) + {"Tube", CUIMenu::MenuHandler, CUIMenu::s_FXTube}, + {"Chorus", CUIMenu::MenuHandler, CUIMenu::s_FXChorus}, + {"FlangR", CUIMenu::MenuHandler, CUIMenu::s_FXFlanger}, + {"Orb", CUIMenu::MenuHandler, CUIMenu::s_FXOrbitone}, + {"PhasR", CUIMenu::MenuHandler, CUIMenu::s_FXPhaser}, + {"Delay", CUIMenu::MenuHandler, CUIMenu::s_FXDelay}, +#endif +#if defined(PLATE_REVERB_ENABLE) + {"Reverb", CUIMenu::MenuHandler, CUIMenu::s_FXPlateReverb}, +#elif defined(MIXING_CONSOLE_ENABLE) + {"Plt Rvb", CUIMenu::MenuHandler, CUIMenu::s_FXPlateReverb}, + {"Rvbrtor", CUIMenu::MenuHandler, CUIMenu::s_FXReverberator}, + {"MainOut", CUIMenu::MenuHandler, CUIMenu::s_FXMainOutputLevels}, + {"Bypass", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXBypass}, #endif {0} }; @@ -119,17 +152,403 @@ const CUIMenu::TMenuItem CUIMenu::s_ModulationMenuParameters[] = {0} }; -#ifdef ARM_ALLOW_MULTI_CORE +#if defined(PLATE_REVERB_ENABLE) || defined(MIXING_CONSOLE_ENABLE) -const CUIMenu::TMenuItem CUIMenu::s_ReverbMenu[] = +const CUIMenu::TMenuItem CUIMenu::s_FXPlateReverb[] = { - {"Enable", EditGlobalParameter, 0, CMiniDexed::ParameterReverbEnable}, - {"Size", EditGlobalParameter, 0, CMiniDexed::ParameterReverbSize}, - {"High damp", EditGlobalParameter, 0, CMiniDexed::ParameterReverbHighDamp}, - {"Low damp", EditGlobalParameter, 0, CMiniDexed::ParameterReverbLowDamp}, - {"Low pass", EditGlobalParameter, 0, CMiniDexed::ParameterReverbLowPass}, - {"Diffusion", EditGlobalParameter, 0, CMiniDexed::ParameterReverbDiffusion}, - {"Level", EditGlobalParameter, 0, CMiniDexed::ParameterReverbLevel}, + {"Enable", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterReverbEnable}, + {"Size", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterReverbSize}, + {"High damp", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterReverbHighDamp}, + {"Low damp", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterReverbLowDamp}, + {"Low pass", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterReverbLowPass}, + {"Diffusion", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterReverbDiffusion}, + {"Level", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterReverbLevel}, +#if defined(MIXING_CONSOLE_ENABLE) + {"Levels", CUIMenu::MenuHandler, CUIMenu::s_FXPlateReverbLevels}, + {"FX-Send", CUIMenu::MenuHandler, CUIMenu::s_FXPlateReverbSend}, +#endif + {0} +}; + +#endif + +#if defined(MIXING_CONSOLE_ENABLE) + +const CUIMenu::TMenuItem CUIMenu::s_FXTube[] = +{ + {"Enable", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTubeEnable}, + {"Overdrv", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTubeOverdrive}, + {"Levels", CUIMenu::MenuHandler, CUIMenu::s_FXTubeLevels}, + {"FX-Send", CUIMenu::MenuHandler, CUIMenu::s_FXTubeSend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXChorus[] = +{ + {"Enable", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorusEnable}, + {"Rate", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorusRate}, + {"Depth", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorusDepth}, + {"Levels", CUIMenu::MenuHandler, CUIMenu::s_FXChorusLevels}, + {"FX-Send", CUIMenu::MenuHandler, CUIMenu::s_FXChorusSend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXFlanger[] = +{ + {"Enable", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlangerEnable}, + {"Rate", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlangerRate}, + {"Depth", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlangerDepth}, + {"Feedbck", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlangerFeedback}, + {"Levels", CUIMenu::MenuHandler, CUIMenu::s_FXFlangerLevels}, + {"FX-Send", CUIMenu::MenuHandler, CUIMenu::s_FXFlangerSend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXOrbitone[] = +{ + {"Enable", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitoneEnable}, + {"Rate", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitoneRate}, + {"Depth", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitoneDepth}, + {"Levels", CUIMenu::MenuHandler, CUIMenu::s_FXOrbitoneLevels}, + {"FX-Send", CUIMenu::MenuHandler, CUIMenu::s_FXOrbitoneSend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXPhaser[] = +{ + {"Enable", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaserEnable}, + {"Rate", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaserRate}, + {"Depth", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaserDepth}, + {"Feedbck", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaserFeedback}, + {"Stages", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaserNbStages}, + {"Levels", CUIMenu::MenuHandler, CUIMenu::s_FXPhaserLevels}, + {"FX-Send", CUIMenu::MenuHandler, CUIMenu::s_FXPhaserSend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXDelay[] = +{ + {"Enable", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelayEnable}, + {"L Delay", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelayLeftDelayTime}, + {"R Delay", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelayRightDelayTime}, + {"Feedbck", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelayFeedback}, + {"Levels", CUIMenu::MenuHandler, CUIMenu::s_FXDelayLevels}, + {"FX-Send", CUIMenu::MenuHandler, CUIMenu::s_FXDelaySend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXReverberator[] = +{ + {"Enable", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberatorEnable}, + {"Gain", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberatorInputGain}, + {"Time", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberatorTime}, + {"Diffusion", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberatorDiffusion}, + {"Low pass", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberatorLP}, + {"Levels", CUIMenu::MenuHandler, CUIMenu::s_FXReverberatorLevels}, + {"FX-Send", CUIMenu::MenuHandler, CUIMenu::s_FXReverberatorSend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXTubeLevels[] = +{ + {"TG1 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXTube}, +#if defined(ARM_ALLOW_MULTI_CORE) + {"TG2 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXTube}, + {"TG3 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXTube}, + {"TG4 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXTube}, + {"TG5 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXTube}, + {"TG6 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXTube}, + {"TG7 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXTube}, + {"TG8 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXTube}, +#endif + {"ChR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_TubeSend}, + {"Flg >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_TubeSend}, + {"Orb >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_TubeSend}, + {"PhR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_TubeSend}, + {"Dly >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_TubeSend}, + {"Plt >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_TubeSend}, + {"Rev >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_TubeSend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXTubeSend[] = +{ + {"> ChR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_ChorusSend}, + {"> Flg", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_FlangerSend}, + {"> Orb", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_OrbitoneSend}, + {"> PhR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_PhaserSend}, + {"> Dly", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_DelaySend}, + {"> Plt", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_PlateReverbSend}, + {"> Rev", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_ReverberatorSend}, + {"> Main", CUIMenu::EditGlobalParameter, 0, CMiniDexed::ParameterFXTube_MainOutput}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXChorusLevels[] = +{ + {"TG1 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXChorus}, +#if defined(ARM_ALLOW_MULTI_CORE) + {"TG2 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXChorus}, + {"TG3 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXChorus}, + {"TG4 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXChorus}, + {"TG5 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXChorus}, + {"TG6 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXChorus}, + {"TG7 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXChorus}, + {"TG8 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXChorus}, +#endif + {"Tub >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_ChorusSend}, + {"Flg >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_ChorusSend}, + {"Orb >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_ChorusSend}, + {"PhR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_ChorusSend}, + {"Dly >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_ChorusSend}, + {"Plt >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_ChorusSend}, + {"Rev >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_ChorusSend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXChorusSend[] = +{ + {"> Tub", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_TubeSend}, + {"> Flg", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_FlangerSend}, + {"> Orb", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_OrbitoneSend}, + {"> PhR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_PhaserSend}, + {"> Dly", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_DelaySend}, + {"> Plt", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_PlateReverbSend}, + {"> Rev", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_ReverberatorSend}, + {"> Main", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_MainOutput}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXFlangerLevels[] = +{ + {"TG1 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXFlanger}, +#if defined(ARM_ALLOW_MULTI_CORE) + {"TG2 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXFlanger}, + {"TG3 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXFlanger}, + {"TG4 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXFlanger}, + {"TG5 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXFlanger}, + {"TG6 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXFlanger}, + {"TG7 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXFlanger}, + {"TG8 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXFlanger}, +#endif + {"Tub >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_FlangerSend}, + {"ChR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_FlangerSend}, + {"Orb >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_FlangerSend}, + {"PhR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_FlangerSend}, + {"Dly >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_FlangerSend}, + {"Plt >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_FlangerSend}, + {"Rev >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_FlangerSend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXFlangerSend[] = +{ + {"> Tub", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_TubeSend}, + {"> ChR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_ChorusSend}, + {"> Orb", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_OrbitoneSend}, + {"> PhR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_PhaserSend}, + {"> Dly", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_DelaySend}, + {"> Plt", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_PlateReverbSend}, + {"> Rev", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_ReverberatorSend}, + {"> Main", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_MainOutput}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXOrbitoneLevels[] = +{ + {"TG1 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXOrbitone}, +#if defined(ARM_ALLOW_MULTI_CORE) + {"TG2 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXOrbitone}, + {"TG3 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXOrbitone}, + {"TG4 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXOrbitone}, + {"TG5 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXOrbitone}, + {"TG6 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXOrbitone}, + {"TG7 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXOrbitone}, + {"TG8 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXOrbitone}, +#endif + {"Tub >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_OrbitoneSend}, + {"ChR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_OrbitoneSend}, + {"Flg >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_OrbitoneSend}, + {"PhR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_OrbitoneSend}, + {"Dly >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_OrbitoneSend}, + {"Plt >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_OrbitoneSend}, + {"Rev >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_OrbitoneSend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXOrbitoneSend[] = +{ + {"> Tub", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_TubeSend}, + {"> ChR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_ChorusSend}, + {"> Flg", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_FlangerSend}, + {"> PhR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_PhaserSend}, + {"> Dly", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_DelaySend}, + {"> Plt", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_PlateReverbSend}, + {"> Rev", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_ReverberatorSend}, + {"> Main", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_MainOutput}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXPhaserLevels[] = +{ + {"TG1 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPhaser}, +#if defined(ARM_ALLOW_MULTI_CORE) + {"TG2 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPhaser}, + {"TG3 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPhaser}, + {"TG4 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPhaser}, + {"TG5 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPhaser}, + {"TG6 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPhaser}, + {"TG7 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPhaser}, + {"TG8 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPhaser}, +#endif + {"Tub >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_PhaserSend}, + {"ChR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_PhaserSend}, + {"Flg >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_PhaserSend}, + {"Orb >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_PhaserSend}, + {"Dly >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_PhaserSend}, + {"Plt >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_PhaserSend}, + {"Rev >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_PhaserSend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXPhaserSend[] = +{ + {"> Tub", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_TubeSend}, + {"> ChR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_ChorusSend}, + {"> Flg", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_FlangerSend}, + {"> Orb", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_OrbitoneSend}, + {"> Dly", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_DelaySend}, + {"> Plt", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_PlateReverbSend}, + {"> Rev", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_ReverberatorSend}, + {"> Main", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_MainOutput}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXDelayLevels[] = +{ + {"TG1 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXDelay}, +#if defined(ARM_ALLOW_MULTI_CORE) + {"TG2 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXDelay}, + {"TG3 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXDelay}, + {"TG4 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXDelay}, + {"TG5 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXDelay}, + {"TG6 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXDelay}, + {"TG7 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXDelay}, + {"TG8 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXDelay}, +#endif + {"Tub >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_DelaySend}, + {"ChR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_DelaySend}, + {"Flg >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_DelaySend}, + {"Orb >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_DelaySend}, + {"PhR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_DelaySend}, + {"Plt >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_DelaySend}, + {"Rev >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_DelaySend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXDelaySend[] = +{ + {"> Tub", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_TubeSend}, + {"> ChR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_ChorusSend}, + {"> Flg", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_FlangerSend}, + {"> Orb", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_OrbitoneSend}, + {"> PhR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_PhaserSend}, + {"> Plt", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_PlateReverbSend}, + {"> Rev", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_ReverberatorSend}, + {"> Main", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_MainOutput}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXPlateReverbLevels[] = +{ + {"TG1 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPlateReverb}, +#if defined(ARM_ALLOW_MULTI_CORE) + {"TG2 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPlateReverb}, + {"TG3 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPlateReverb}, + {"TG4 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPlateReverb}, + {"TG5 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPlateReverb}, + {"TG6 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPlateReverb}, + {"TG7 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPlateReverb}, + {"TG8 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXPlateReverb}, +#endif + {"Tub >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_PlateReverbSend}, + {"ChR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_PlateReverbSend}, + {"Flg >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_PlateReverbSend}, + {"Orb >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_PlateReverbSend}, + {"PhR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_PlateReverbSend}, + {"Dly >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_PlateReverbSend}, + {"Rev >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_PlateReverbSend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXPlateReverbSend[] = +{ + {"> Tub", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_TubeSend}, + {"> ChR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_ChorusSend}, + {"> Flg", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_FlangerSend}, + {"> Orb", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_OrbitoneSend}, + {"> PhR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_PhaserSend}, + {"> Dly", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_DelaySend}, + {"> Rev", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_ReverberatorSend}, + {"> Main", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_MainOutput}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXReverberatorLevels[] = +{ + {"TG1 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXReverberator}, +#if defined(ARM_ALLOW_MULTI_CORE) + {"TG2 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXReverberator}, + {"TG3 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXReverberator}, + {"TG4 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXReverberator}, + {"TG5 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXReverberator}, + {"TG6 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXReverberator}, + {"TG7 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXReverberator}, + {"TG8 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXReverberator}, +#endif + {"Tub >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_ReverberatorSend}, + {"ChR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_ReverberatorSend}, + {"Flg >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_ReverberatorSend}, + {"Orb >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_ReverberatorSend}, + {"PhR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_ReverberatorSend}, + {"Dly >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_ReverberatorSend}, + {"Plt >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_ReverberatorSend}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXReverberatorSend[] = +{ + {"> Tub", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_TubeSend}, + {"> ChR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_ChorusSend}, + {"> Flg", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_FlangerSend}, + {"> Orb", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_OrbitoneSend}, + {"> PhR", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_PhaserSend}, + {"> Dly", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_DelaySend}, + {"> Plt", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_PlateReverbSend}, + {"> Main", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_MainOutput}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXMainOutputLevels[] = +{ + {"TG1 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXMainOutput}, +#if defined(ARM_ALLOW_MULTI_CORE) + {"TG2 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXMainOutput}, + {"TG3 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXMainOutput}, + {"TG4 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXMainOutput}, + {"TG5 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXMainOutput}, + {"TG6 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXMainOutput}, + {"TG7 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXMainOutput}, + {"TG8 >", CUIMenu::EditTGParameter3, 0, CMiniDexed::TTGParameter::TGParameterMixingSendFXMainOutput}, +#endif + {"Tub >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXTube_MainOutput}, + {"ChR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXChorus_MainOutput}, + {"Flg >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXFlanger_MainOutput}, + {"Orb >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXOrbitone_MainOutput}, + {"PhR >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPhaser_MainOutput}, + {"Dly >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXDelay_MainOutput}, + {"Plt >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXPlateReverb_MainOutput}, + {"Rev >", CUIMenu::EditGlobalParameter, 0, CMiniDexed::TParameter::ParameterFXReverberator_MainOutput}, {0} }; @@ -203,106 +622,253 @@ const CUIMenu::TMenuItem CUIMenu::s_SaveMenu[] = }; // must match CMiniDexed::TParameter -const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::ParameterUnknown] = +const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::TParameter::ParameterUnknown] = { - {0, 1, 1, ToOnOff}, // ParameterCompessorEnable - {0, 1, 1, ToOnOff}, // ParameterReverbEnable + {0, 1, 1, ToOnOff} // ParameterCompressorEnable + +#if defined(PLATE_REVERB_ENABLE) || defined(MIXING_CONSOLE_ENABLE) + , + {0, 1, 1, ToOnOff}, // ParameterReverbEnable {0, 99, 1}, // ParameterReverbSize {0, 99, 1}, // ParameterReverbHighDamp {0, 99, 1}, // ParameterReverbLowDamp {0, 99, 1}, // ParameterReverbLowPass {0, 99, 1}, // ParameterReverbDiffusion {0, 99, 1} // ParameterReverbLevel +#endif + +// BEGIN FX global parameters mapping definition +#if defined(MIXING_CONSOLE_ENABLE) + , + // FX > Tube parameters + {0, 1, 1, ToOnOff}, // ParameterFXTubeEnable + {0, 99 ,1}, // ParameterFXTubeOverdrive + + // FX > Chorus parameters + {0, 1, 1, ToOnOff}, // ParameterFXChorusEnable + {0, 99, 1}, // ParameterFXChorusRate + {0, 99, 1}, // ParameterFXChorusDepth + + // FX > Flanger parameters + {0, 1, 1, ToOnOff}, // ParameterFXFlangerEnable + {0, 99, 1}, // ParameterFXFlangerRate + {0, 99, 1}, // ParameterFXFlangerDepth + {0, 99, 1}, // ParameterFXFlangerFeedback + + // FX > Orbitone parameters + {0, 1, 1, ToOnOff}, // ParameterFXOrbitoneEnable + {0, 99, 1}, // ParameterFXOrbitoneRate + {0, 99, 1}, // ParameterFXOrbitoneDepth + + // FX > Phaser parameters + {0, 1, 1, ToOnOff}, // ParameterFXPhaserEnable + {0, 99, 1}, // ParameterFXPhaserRate + {0, 99, 1}, // ParameterFXPhaserDepth + {0, 99, 1}, // ParameterFXPhaserFeedback + {2, MAX_NB_PHASES, 1}, // ParameterFXPhaserNbStages + + // FX > Delay parameters + {0, 1, 1, ToOnOff}, // ParameterFXDelayEnable + {-99, 99, 1}, // ParameterFXDelayLeftDelayTime + {-99, 99, 1}, // ParameterFXDelayRightDelayTime + {0, 99, 1}, // ParameterFXDelayFeedback + + // FX > Reverberator parameters + {0, 1, 1, ToOnOff}, // ParameterFXReverberatorEnable + {0, 99, 1}, // ParameterFXReverberatorInputGain + {0, 99, 1}, // ParameterFXReverberatorTime + {0, 99, 1}, // ParameterFXReverberatorDiffusion + {0, 99, 1}, // ParameterFXReverberatorLP + + // FX > Tube Return parameters + {0, 99, 1}, // ParameterFXTube_ChorusSend + {0, 99, 1}, // ParameterFXTube_FlangerSend + {0, 99, 1}, // ParameterFXTube_OrbitoneSend + {0, 99, 1}, // ParameterFXTube_PhaserSend + {0, 99, 1}, // ParameterFXTube_DelaySend + {0, 99, 1}, // ParameterFXTube_PlateReverbSend + {0, 99, 1}, // ParameterFXTube_ReverberatorSend + {0, 99, 1}, // ParameterFXTube_MainOutput + + // FX > Chorus Return parameters + {0, 99, 1}, // ParameterFXChorus_TubeSend + {0, 99, 1}, // ParameterFXChorus_FlangerSend + {0, 99, 1}, // ParameterFXChorus_OrbitoneSend + {0, 99, 1}, // ParameterFXChorus_PhaserSend + {0, 99, 1}, // ParameterFXChorus_DelaySend + {0, 99, 1}, // ParameterFXChorus_PlateReverbSend + {0, 99, 1}, // ParameterFXChorus_ReverberatorSend + {0, 99, 1}, // ParameterFXChorus_MainOutput + + // FX > Flanger Return parameters + {0, 99, 1}, // ParameterFXFlanger_TubeSend + {0, 99, 1}, // ParameterFXFlanger_ChorusSend + {0, 99, 1}, // ParameterFXFlanger_OrbitoneSend + {0, 99, 1}, // ParameterFXFlanger_PhaserSend + {0, 99, 1}, // ParameterFXFlanger_DelaySend + {0, 99, 1}, // ParameterFXFlanger_PlateReverbSend + {0, 99, 1}, // ParameterFXFlanger_ReverberatorSend + {0, 99, 1}, // ParameterFXFlanger_MainOutput + + // FX > Orbitone Return parameters + {0, 99, 1}, // ParameterFXOrbitone_TubeSend + {0, 99, 1}, // ParameterFXOrbitone_ChorusSend + {0, 99, 1}, // ParameterFXOrbitone_FlangerSend + {0, 99, 1}, // ParameterFXOrbitone_PhaserSend + {0, 99, 1}, // ParameterFXOrbitone_DelaySend + {0, 99, 1}, // ParameterFXOrbitone_PlateReverbSend + {0, 99, 1}, // ParameterFXOrbitone_ReverberatorSend + {0, 99, 1}, // ParameterFXOrbitone_MainOutput + + // FX > Phaser Return parameters + {0, 99, 1}, // ParameterFXPhaser_TubeSend + {0, 99, 1}, // ParameterFXPhaser_ChorusSend + {0, 99, 1}, // ParameterFXPhaser_FlangerSend + {0, 99, 1}, // ParameterFXPhaser_OrbitoneSend + {0, 99, 1}, // ParameterFXPhaser_DelaySend + {0, 99, 1}, // ParameterFXPhaser_PlateReverbSend + {0, 99, 1}, // ParameterFXPhaser_ReverberatorSend + {0, 99, 1}, // ParameterFXPhaser_MainOutput + + // FX > Delay Return parameters + {0, 99, 1}, // ParameterFXDelay_TubeSend + {0, 99, 1}, // ParameterFXDelay_ChorusSend + {0, 99, 1}, // ParameterFXDelay_FlangerSend + {0, 99, 1}, // ParameterFXDelay_OrbitoneSend + {0, 99, 1}, // ParameterFXDelay_PhaserSend + {0, 99, 1}, // ParameterFXDelay_PlateReverbSend + {0, 99, 1}, // ParameterFXDelay_ReverberatorSend + {0, 99, 1}, // ParameterFXDelay_MainOutput + + // FX > Reverb Return parameters + {0, 99, 1}, // ParameterFXPlateReverb_TubeSend + {0, 99, 1}, // ParameterFXPlateReverb_ChorusSend + {0, 99, 1}, // ParameterFXPlateReverb_FlangerSend + {0, 99, 1}, // ParameterFXPlateReverb_OrbitoneSend + {0, 99, 1}, // ParameterFXPlateReverb_PhaserSend + {0, 99, 1}, // ParameterFXPlateReverb_DelaySend + {0, 99, 1}, // ParameterFXPlateReverb_ReverberatorSend + {0, 99, 1}, // ParameterFXPlateReverb_MainOutput + + // FX > Reverberator Return parameters + {0, 99, 1}, // ParameterFXReverberator_TubeSend + {0, 99, 1}, // ParameterFXReverberator_ChorusSend + {0, 99, 1}, // ParameterFXReverberator_FlangerSend + {0, 99, 1}, // ParameterFXReverberator_OrbitoneSend + {0, 99, 1}, // ParameterFXReverberator_PhaserSend + {0, 99, 1}, // ParameterFXReverberator_DelaySend + {0, 99, 1}, // ParameterFXReverberator_PlateReverbSend + {0, 99, 1}, // ParameterFXReverberator_MainOutput + + {0, 1, 1, ToOnOff} // ParameterFXBypass +#endif +// END FX global parameters mapping definition }; // must match CMiniDexed::TTGParameter -const CUIMenu::TParameter CUIMenu::s_TGParameter[CMiniDexed::TGParameterUnknown] = -{ - {0, CSysExFileLoader::MaxVoiceBankID, 1}, // TGParameterVoiceBank - {0, 0, 0}, // TGParameterVoiceBankMSB (not used in menus) - {0, 0, 0}, // TGParameterVoiceBankLSB (not used in menus) - {0, CSysExFileLoader::VoicesPerBank-1, 1}, // TGParameterProgram - {0, 127, 8, ToVolume}, // TGParameterVolume - {0, 127, 8, ToPan}, // TGParameterPan - {-99, 99, 1}, // TGParameterMasterTune - {0, 99, 1}, // TGParameterCutoff - {0, 99, 1}, // TGParameterResonance - {0, CMIDIDevice::ChannelUnknown-1, 1, ToMIDIChannel}, // TGParameterMIDIChannel - {0, 99, 1}, // TGParameterReverbSend - {0, 12, 1}, // TGParameterPitchBendRange - {0, 12, 1}, // TGParameterPitchBendStep - {0, 1, 1, ToPortaMode}, // TGParameterPortamentoMode - {0, 1, 1, ToPortaGlissando}, // TGParameterPortamentoGlissando - {0, 99, 1}, // TGParameterPortamentoTime - {0, 1, 1, ToPolyMono}, // TGParameterMonoMode - {0, 99, 1}, //MW Range - {0, 1, 1, ToOnOff}, //MW Pitch - {0, 1, 1, ToOnOff}, //MW Amp - {0, 1, 1, ToOnOff}, //MW EGBias - {0, 99, 1}, //FC Range - {0, 1, 1, ToOnOff}, //FC Pitch - {0, 1, 1, ToOnOff}, //FC Amp - {0, 1, 1, ToOnOff}, //FC EGBias - {0, 99, 1}, //BC Range - {0, 1, 1, ToOnOff}, //BC Pitch - {0, 1, 1, ToOnOff}, //BC Amp - {0, 1, 1, ToOnOff}, //BC EGBias - {0, 99, 1}, //AT Range - {0, 1, 1, ToOnOff}, //AT Pitch - {0, 1, 1, ToOnOff}, //AT Amp - {0, 1, 1, ToOnOff} //AT EGBias +const CUIMenu::TParameter CUIMenu::s_TGParameter[CMiniDexed::TTGParameter::TGParameterUnknown] = +{ + {0, CSysExFileLoader::MaxVoiceBankID, 1}, // TGParameterVoiceBank + {0, 0, 0}, // TGParameterVoiceBankMSB (not used in menus) + {0, 0, 0}, // TGParameterVoiceBankLSB (not used in menus) + {0, CSysExFileLoader::VoicesPerBank-1, 1}, // TGParameterProgram + {0, 127, 8, ToVolume}, // TGParameterVolume + {0, 127, 8, ToPan}, // TGParameterPan + {-99, 99, 1}, // TGParameterMasterTune + {0, 99, 1}, // TGParameterCutoff + {0, 99, 1}, // TGParameterResonance + {0, CMIDIDevice::ChannelUnknown-1, 1, ToMIDIChannel}, // TGParameterMIDIChannel +#if defined(PLATE_REVERB_ENABLE) + {0, 99, 1}, // TGParameterReverbSend +#endif + {0, 12, 1}, // TGParameterPitchBendRange + {0, 12, 1}, // TGParameterPitchBendStep + {0, 1, 1, ToPortaMode}, // TGParameterPortamentoMode + {0, 1, 1, ToPortaGlissando}, // TGParameterPortamentoGlissando + {0, 99, 1}, // TGParameterPortamentoTime + {0, 1, 1, ToPolyMono}, // TGParameterMonoMode + {0, 99, 1}, // MW Range + {0, 1, 1, ToOnOff}, // MW Pitch + {0, 1, 1, ToOnOff}, // MW Amp + {0, 1, 1, ToOnOff}, // MW EGBias + {0, 99, 1}, // FC Range + {0, 1, 1, ToOnOff}, // FC Pitch + {0, 1, 1, ToOnOff}, // FC Amp + {0, 1, 1, ToOnOff}, // FC EGBias + {0, 99, 1}, // BC Range + {0, 1, 1, ToOnOff}, // BC Pitch + {0, 1, 1, ToOnOff}, // BC Amp + {0, 1, 1, ToOnOff}, // BC EGBias + {0, 99, 1}, // AT Range + {0, 1, 1, ToOnOff}, // AT Pitch + {0, 1, 1, ToOnOff}, // AT Amp + {0, 1, 1, ToOnOff} // AT EGBias + +#if defined(MIXING_CONSOLE_ENABLE) + , + {0, 99, 1}, // TGParameterMixingSendFXTube + {0, 99, 1}, // TGParameterMixingSendFXChorus + {0, 99, 1}, // TGParameterMixingSendFXFlanger + {0, 99, 1}, // TGParameterMixingSendFXOrbittone + {0, 99, 1}, // TGParameterMixingSendFXPhaser + {0, 99, 1}, // TGParameterMixingSendFXDelay + {0, 99, 1}, // TGParameterMixingSendFXPlateReverb + {0, 99, 1}, // TGParameterMixingSendFXReverberator + {0, 99, 1} // TGParameterMixingSendFXMainOutput + +#endif // MIXING_CONSOLE_ENABLE }; // must match DexedVoiceParameters in Synth_Dexed const CUIMenu::TParameter CUIMenu::s_VoiceParameter[] = { - {0, 99, 1}, // DEXED_PITCH_EG_R1 - {0, 99, 1}, // DEXED_PITCH_EG_R2 - {0, 99, 1}, // DEXED_PITCH_EG_R3 - {0, 99, 1}, // DEXED_PITCH_EG_R4 - {0, 99, 1}, // DEXED_PITCH_EG_L1 - {0, 99, 1}, // DEXED_PITCH_EG_L2 - {0, 99, 1}, // DEXED_PITCH_EG_L3 - {0, 99, 1}, // DEXED_PITCH_EG_L4 - {0, 31, 1, ToAlgorithm}, // DEXED_ALGORITHM - {0, 7, 1}, // DEXED_FEEDBACK - {0, 1, 1, ToOnOff}, // DEXED_OSC_KEY_SYNC - {0, 99, 1}, // DEXED_LFO_SPEED - {0, 99, 1}, // DEXED_LFO_DELAY - {0, 99, 1}, // DEXED_LFO_PITCH_MOD_DEP - {0, 99, 1}, // DEXED_LFO_AMP_MOD_DEP - {0, 1, 1, ToOnOff}, // DEXED_LFO_SYNC - {0, 5, 1, ToLFOWaveform}, // DEXED_LFO_WAVE - {0, 7, 1}, // DEXED_LFO_PITCH_MOD_SENS - {0, 48, 1, ToTransposeNote}, // DEXED_TRANSPOSE - {0, 1, 1} // Voice Name - Dummy parameters for in case new item would be added in future + {0, 99, 1}, // DEXED_PITCH_EG_R1 + {0, 99, 1}, // DEXED_PITCH_EG_R2 + {0, 99, 1}, // DEXED_PITCH_EG_R3 + {0, 99, 1}, // DEXED_PITCH_EG_R4 + {0, 99, 1}, // DEXED_PITCH_EG_L1 + {0, 99, 1}, // DEXED_PITCH_EG_L2 + {0, 99, 1}, // DEXED_PITCH_EG_L3 + {0, 99, 1}, // DEXED_PITCH_EG_L4 + {0, 31, 1, ToAlgorithm}, // DEXED_ALGORITHM + {0, 7, 1}, // DEXED_FEEDBACK + {0, 1, 1, ToOnOff}, // DEXED_OSC_KEY_SYNC + {0, 99, 1}, // DEXED_LFO_SPEED + {0, 99, 1}, // DEXED_LFO_DELAY + {0, 99, 1}, // DEXED_LFO_PITCH_MOD_DEP + {0, 99, 1}, // DEXED_LFO_AMP_MOD_DEP + {0, 1, 1, ToOnOff}, // DEXED_LFO_SYNC + {0, 5, 1, ToLFOWaveform}, // DEXED_LFO_WAVE + {0, 7, 1}, // DEXED_LFO_PITCH_MOD_SENS + {0, 48, 1, ToTransposeNote}, // DEXED_TRANSPOSE + {0, 1, 1} // Voice Name - Dummy parameters for in case new item would be added in future }; // must match DexedVoiceOPParameters in Synth_Dexed const CUIMenu::TParameter CUIMenu::s_OPParameter[] = { - {0, 99, 1}, // DEXED_OP_EG_R1 - {0, 99, 1}, // DEXED_OP_EG_R2 - {0, 99, 1}, // DEXED_OP_EG_R3 - {0, 99, 1}, // DEXED_OP_EG_R4 - {0, 99, 1}, // DEXED_OP_EG_L1 - {0, 99, 1}, // DEXED_OP_EG_L2 - {0, 99, 1}, // DEXED_OP_EG_L3 - {0, 99, 1}, // DEXED_OP_EG_L4 - {0, 99, 1, ToBreakpointNote}, // DEXED_OP_LEV_SCL_BRK_PT - {0, 99, 1}, // DEXED_OP_SCL_LEFT_DEPTH - {0, 99, 1}, // DEXED_OP_SCL_RGHT_DEPTH - {0, 3, 1, ToKeyboardCurve}, // DEXED_OP_SCL_LEFT_CURVE - {0, 3, 1, ToKeyboardCurve}, // DEXED_OP_SCL_RGHT_CURVE - {0, 7, 1}, // DEXED_OP_OSC_RATE_SCALE - {0, 3, 1}, // DEXED_OP_AMP_MOD_SENS - {0, 7, 1}, // DEXED_OP_KEY_VEL_SENS - {0, 99, 1}, // DEXED_OP_OUTPUT_LEV - {0, 1, 1, ToOscillatorMode}, // DEXED_OP_OSC_MODE - {0, 31, 1}, // DEXED_OP_FREQ_COARSE - {0, 99, 1}, // DEXED_OP_FREQ_FINE - {0, 14, 1, ToOscillatorDetune}, // DEXED_OP_OSC_DETUNE - {0, 1, 1, ToOnOff} // DEXED_OP_ENABLE + {0, 99, 1}, // DEXED_OP_EG_R1 + {0, 99, 1}, // DEXED_OP_EG_R2 + {0, 99, 1}, // DEXED_OP_EG_R3 + {0, 99, 1}, // DEXED_OP_EG_R4 + {0, 99, 1}, // DEXED_OP_EG_L1 + {0, 99, 1}, // DEXED_OP_EG_L2 + {0, 99, 1}, // DEXED_OP_EG_L3 + {0, 99, 1}, // DEXED_OP_EG_L4 + {0, 99, 1, ToBreakpointNote}, // DEXED_OP_LEV_SCL_BRK_PT + {0, 99, 1}, // DEXED_OP_SCL_LEFT_DEPTH + {0, 99, 1}, // DEXED_OP_SCL_RGHT_DEPTH + {0, 3, 1, ToKeyboardCurve}, // DEXED_OP_SCL_LEFT_CURVE + {0, 3, 1, ToKeyboardCurve}, // DEXED_OP_SCL_RGHT_CURVE + {0, 7, 1}, // DEXED_OP_OSC_RATE_SCALE + {0, 3, 1}, // DEXED_OP_AMP_MOD_SENS + {0, 7, 1}, // DEXED_OP_KEY_VEL_SENS + {0, 99, 1}, // DEXED_OP_OUTPUT_LEV + {0, 1, 1, ToOscillatorMode}, // DEXED_OP_OSC_MODE + {0, 31, 1}, // DEXED_OP_FREQ_COARSE + {0, 99, 1}, // DEXED_OP_FREQ_FINE + {0, 14, 1, ToOscillatorDetune}, // DEXED_OP_OSC_DETUNE + {0, 1, 1, ToOnOff} // DEXED_OP_ENABLE }; const char CUIMenu::s_NoteName[100][4] = @@ -328,8 +894,8 @@ const CUIMenu::TMenuItem CUIMenu::s_PerformanceMenu[] = }; -CUIMenu::CUIMenu (CUserInterface *pUI, CMiniDexed *pMiniDexed) -: m_pUI (pUI), +CUIMenu::CUIMenu (CUserInterface *pUI, CMiniDexed *pMiniDexed) : + m_pUI (pUI), m_pMiniDexed (pMiniDexed), m_pParentMenu (s_MenuRoot), m_pCurrentMenu (s_MainMenu), @@ -338,6 +904,7 @@ CUIMenu::CUIMenu (CUserInterface *pUI, CMiniDexed *pMiniDexed) m_nCurrentParameter (0), m_nCurrentMenuDepth (0) { + assert(pMiniDexed); #ifndef ARM_ALLOW_MULTI_CORE // If there is just one core, then there is only a single // tone generator so start on the TG1 menu... @@ -417,23 +984,23 @@ void CUIMenu::MenuHandler (CUIMenu *pUIMenu, TMenuEvent Event) case MenuEventSelect: // push menu assert (pUIMenu->m_nCurrentMenuDepth < MaxMenuDepth); - pUIMenu->m_MenuStackParent[pUIMenu->m_nCurrentMenuDepth] = pUIMenu->m_pParentMenu; - pUIMenu->m_MenuStackMenu[pUIMenu->m_nCurrentMenuDepth] = pUIMenu->m_pCurrentMenu; - pUIMenu->m_nMenuStackItem[pUIMenu->m_nCurrentMenuDepth] - = pUIMenu->m_nCurrentMenuItem; - pUIMenu->m_nMenuStackSelection[pUIMenu->m_nCurrentMenuDepth] - = pUIMenu->m_nCurrentSelection; - pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth] - = pUIMenu->m_nCurrentParameter; + pUIMenu->m_MenuStackParent[pUIMenu->m_nCurrentMenuDepth] = pUIMenu->m_pParentMenu; + pUIMenu->m_MenuStackMenu[pUIMenu->m_nCurrentMenuDepth] = pUIMenu->m_pCurrentMenu; + pUIMenu->m_nMenuStackItem[pUIMenu->m_nCurrentMenuDepth] = pUIMenu->m_nCurrentMenuItem; + + pUIMenu->m_nMenuStackSelection[pUIMenu->m_nCurrentMenuDepth] = pUIMenu->m_nCurrentSelection; + + pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth] = pUIMenu->m_nCurrentParameter; + pUIMenu->m_nCurrentMenuDepth++; - pUIMenu->m_pParentMenu = pUIMenu->m_pCurrentMenu; - pUIMenu->m_nCurrentParameter = - pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Parameter; - pUIMenu->m_pCurrentMenu = - pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].MenuItem; - pUIMenu->m_nCurrentMenuItem = pUIMenu->m_nCurrentSelection; - pUIMenu->m_nCurrentSelection = 0; + pUIMenu->m_pParentMenu = pUIMenu->m_pCurrentMenu; + + pUIMenu->m_nCurrentParameter = pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Parameter; + + pUIMenu->m_pCurrentMenu = pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].MenuItem; + pUIMenu->m_nCurrentMenuItem = pUIMenu->m_nCurrentSelection; + pUIMenu->m_nCurrentSelection = 0; break; case MenuEventStepDown: @@ -520,7 +1087,7 @@ void CUIMenu::EditVoiceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event) { unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-1]; - int nValue = pUIMenu->m_pMiniDexed->GetTGParameter (CMiniDexed::TGParameterVoiceBank, nTG); + int nValue = pUIMenu->m_pMiniDexed->GetTGParameter (CMiniDexed::TTGParameter::TGParameterVoiceBank, nTG); switch (Event) { @@ -530,13 +1097,13 @@ void CUIMenu::EditVoiceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event) case MenuEventStepDown: nValue = pUIMenu->m_pMiniDexed->GetSysExFileLoader ()->GetNextBankDown(nValue); pUIMenu->m_pMiniDexed->SetTGParameter ( - CMiniDexed::TGParameterVoiceBank, nValue, nTG); + CMiniDexed::TTGParameter::TGParameterVoiceBank, nValue, nTG); break; case MenuEventStepUp: nValue = pUIMenu->m_pMiniDexed->GetSysExFileLoader ()->GetNextBankUp(nValue); pUIMenu->m_pMiniDexed->SetTGParameter ( - CMiniDexed::TGParameterVoiceBank, nValue, nTG); + CMiniDexed::TTGParameter::TGParameterVoiceBank, nValue, nTG); break; case MenuEventPressAndStepDown: @@ -564,7 +1131,7 @@ void CUIMenu::EditProgramNumber (CUIMenu *pUIMenu, TMenuEvent Event) { unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-1]; - int nValue = pUIMenu->m_pMiniDexed->GetTGParameter (CMiniDexed::TGParameterProgram, nTG); + int nValue = pUIMenu->m_pMiniDexed->GetTGParameter (CMiniDexed::TTGParameter::TGParameterProgram, nTG); switch (Event) { @@ -576,11 +1143,11 @@ void CUIMenu::EditProgramNumber (CUIMenu *pUIMenu, TMenuEvent Event) { // Switch down a voice bank and set to the last voice nValue = CSysExFileLoader::VoicesPerBank-1; - int nVB = pUIMenu->m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterVoiceBank, nTG); + int nVB = pUIMenu->m_pMiniDexed->GetTGParameter(CMiniDexed::TTGParameter::TGParameterVoiceBank, nTG); nVB = pUIMenu->m_pMiniDexed->GetSysExFileLoader ()->GetNextBankDown(nVB); - pUIMenu->m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterVoiceBank, nVB, nTG); + pUIMenu->m_pMiniDexed->SetTGParameter (CMiniDexed::TTGParameter::TGParameterVoiceBank, nVB, nTG); } - pUIMenu->m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterProgram, nValue, nTG); + pUIMenu->m_pMiniDexed->SetTGParameter (CMiniDexed::TTGParameter::TGParameterProgram, nValue, nTG); break; case MenuEventStepUp: @@ -588,11 +1155,11 @@ void CUIMenu::EditProgramNumber (CUIMenu *pUIMenu, TMenuEvent Event) { // Switch up a voice bank and reset to voice 0 nValue = 0; - int nVB = pUIMenu->m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterVoiceBank, nTG); + int nVB = pUIMenu->m_pMiniDexed->GetTGParameter(CMiniDexed::TTGParameter::TGParameterVoiceBank, nTG); nVB = pUIMenu->m_pMiniDexed->GetSysExFileLoader ()->GetNextBankUp(nVB); - pUIMenu->m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterVoiceBank, nVB, nTG); + pUIMenu->m_pMiniDexed->SetTGParameter (CMiniDexed::TTGParameter::TGParameterVoiceBank, nVB, nTG); } - pUIMenu->m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterProgram, nValue, nTG); + pUIMenu->m_pMiniDexed->SetTGParameter (CMiniDexed::TTGParameter::TGParameterProgram, nValue, nTG); break; case MenuEventPressAndStepDown: @@ -735,6 +1302,60 @@ void CUIMenu::EditTGParameter2 (CUIMenu *pUIMenu, TMenuEvent Event) // second me } +void CUIMenu::EditTGParameter3(CUIMenu *pUIMenu, TMenuEvent Event) +{ + unsigned nTG = pUIMenu->m_nCurrentMenuItem; + + CMiniDexed::TTGParameter Param = (CMiniDexed::TTGParameter) pUIMenu->m_nCurrentParameter; + + const TParameter &rParam = s_TGParameter[Param]; + + int nValue = pUIMenu->m_pMiniDexed->GetTGParameter (Param, nTG); + + switch (Event) + { + case MenuEventUpdate: + break; + + case MenuEventStepDown: + nValue -= rParam.Increment; + if (nValue < rParam.Minimum) + { + nValue = rParam.Minimum; + } + pUIMenu->m_pMiniDexed->SetTGParameter (Param, nValue, nTG); + break; + + case MenuEventStepUp: + nValue += rParam.Increment; + if (nValue > rParam.Maximum) + { + nValue = rParam.Maximum; + } + pUIMenu->m_pMiniDexed->SetTGParameter (Param, nValue, nTG); + break; + + case MenuEventPressAndStepDown: + case MenuEventPressAndStepUp: + pUIMenu->TGShortcutHandler (Event); + return; + + default: + return; + } + + string TG ("TG"); + TG += to_string (nTG+1); + + string Value = GetTGValueString (Param, pUIMenu->m_pMiniDexed->GetTGParameter (Param, nTG)); + + pUIMenu->m_pUI->DisplayWrite (TG.c_str (), + pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, + Value.c_str (), + nValue > rParam.Minimum, nValue < rParam.Maximum); + +} + void CUIMenu::EditVoiceParameter (CUIMenu *pUIMenu, TMenuEvent Event) { unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-2]; @@ -1553,5 +2174,3 @@ void CUIMenu::EditTGParameterModulation (CUIMenu *pUIMenu, TMenuEvent Event) nValue > rParam.Minimum, nValue < rParam.Maximum); } - - diff --git a/src/uimenu.h b/src/uimenu.h index 78384be9..9cd86932 100644 --- a/src/uimenu.h +++ b/src/uimenu.h @@ -23,6 +23,7 @@ #ifndef _uimenu_h #define _uimenu_h +#include "extra_features.h" #include #include @@ -84,6 +85,7 @@ class CUIMenu static void EditOPParameter (CUIMenu *pUIMenu, TMenuEvent Event); static void SavePerformance (CUIMenu *pUIMenu, TMenuEvent Event); static void EditTGParameter2 (CUIMenu *pUIMenu, TMenuEvent Event); + static void EditTGParameter3 (CUIMenu *pUIMenu, TMenuEvent Event); static void EditTGParameterModulation (CUIMenu *pUIMenu, TMenuEvent Event); static void PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event); static void SavePerformanceNewFile (CUIMenu *pUIMenu, TMenuEvent Event); @@ -138,7 +140,38 @@ class CUIMenu static const TMenuItem s_MainMenu[]; static const TMenuItem s_TGMenu[]; static const TMenuItem s_EffectsMenu[]; - static const TMenuItem s_ReverbMenu[]; +#if defined(MIXING_CONSOLE_ENABLE) + static const TMenuItem s_TGFXMenu[]; + static const TMenuItem s_FXTube[]; + static const TMenuItem s_FXTubeLevels[]; + static const TMenuItem s_FXTubeSend[]; + static const TMenuItem s_FXChorus[]; + static const TMenuItem s_FXChorusLevels[]; + static const TMenuItem s_FXChorusSend[]; + static const TMenuItem s_FXFlanger[]; + static const TMenuItem s_FXFlangerLevels[]; + static const TMenuItem s_FXFlangerSend[]; + static const TMenuItem s_FXOrbitone[]; + static const TMenuItem s_FXOrbitoneLevels[]; + static const TMenuItem s_FXOrbitoneSend[]; + static const TMenuItem s_FXPhaser[]; + static const TMenuItem s_FXPhaserLevels[]; + static const TMenuItem s_FXPhaserSend[]; + static const TMenuItem s_FXDelay[]; + static const TMenuItem s_FXDelayLevels[]; + static const TMenuItem s_FXDelaySend[]; +#endif +#if defined(PLATE_REVERB_ENABLE) || defined(MIXING_CONSOLE_ENABLE) + static const TMenuItem s_FXPlateReverb[]; +#endif +#if defined(MIXING_CONSOLE_ENABLE) + static const TMenuItem s_FXPlateReverbLevels[]; + static const TMenuItem s_FXPlateReverbSend[]; + static const TMenuItem s_FXReverberator[]; + static const TMenuItem s_FXReverberatorLevels[]; + static const TMenuItem s_FXReverberatorSend[]; + static const TMenuItem s_FXMainOutputLevels[]; +#endif static const TMenuItem s_EditVoiceMenu[]; static const TMenuItem s_OperatorMenu[]; static const TMenuItem s_SaveMenu[]; diff --git a/wiki-update/Chorus--Channel.png b/wiki-update/Chorus--Channel.png new file mode 100644 index 00000000..65d3e98c Binary files /dev/null and b/wiki-update/Chorus--Channel.png differ diff --git a/wiki-update/Delay--Channel.png b/wiki-update/Delay--Channel.png new file mode 100644 index 00000000..026b5539 Binary files /dev/null and b/wiki-update/Delay--Channel.png differ diff --git a/wiki-update/Flanger--Channel.png b/wiki-update/Flanger--Channel.png new file mode 100644 index 00000000..775bd0ab Binary files /dev/null and b/wiki-update/Flanger--Channel.png differ diff --git a/wiki-update/Orbitone--Channel.png b/wiki-update/Orbitone--Channel.png new file mode 100644 index 00000000..eb43aa9e Binary files /dev/null and b/wiki-update/Orbitone--Channel.png differ diff --git a/wiki-update/Phaser--Channel.png b/wiki-update/Phaser--Channel.png new file mode 100644 index 00000000..8d2a6a74 Binary files /dev/null and b/wiki-update/Phaser--Channel.png differ diff --git a/wiki-update/PlateReverb--Channel.png b/wiki-update/PlateReverb--Channel.png new file mode 100644 index 00000000..822ed16b Binary files /dev/null and b/wiki-update/PlateReverb--Channel.png differ diff --git a/wiki-update/Reverberator--Channel.png b/wiki-update/Reverberator--Channel.png new file mode 100644 index 00000000..2044fe13 Binary files /dev/null and b/wiki-update/Reverberator--Channel.png differ diff --git a/wiki-update/Tube--Channel.png b/wiki-update/Tube--Channel.png new file mode 100644 index 00000000..b680ce62 Binary files /dev/null and b/wiki-update/Tube--Channel.png differ diff --git a/wiki-update/Tube--overdrive-response.png b/wiki-update/Tube--overdrive-response.png new file mode 100644 index 00000000..db5bef17 Binary files /dev/null and b/wiki-update/Tube--overdrive-response.png differ diff --git a/wiki-update/fx-page.md b/wiki-update/fx-page.md new file mode 100644 index 00000000..bb651d41 --- /dev/null +++ b/wiki-update/fx-page.md @@ -0,0 +1,470 @@ +# Mixing Console & Audio Effect + +On multi-core devices, MiniDexed can now be equiped with a complex and versatile mixing console that is composed of a multi-FX processor. + +After the volume and pan controls, it is possible to set the send level of each Tone Generator to the individual Audio Effect and Main output. Below is the TG channel strip: + +Mixing Console - TG Channel Strip + +Each FX has the possibility to return to any other Audio FX unit as well as Main Output. + +The diagram below shows the synopsis of this Mixing Console. + +Mixing Console - TG Channel Strip + +## The Audio FX + +The following audio FX are available: + +* **Tube:** an amplifier that mimmic the response of valve based amplifier. + + FX Tube Channel Strip + + * ***Enable [Off - On]:*** Enable / disable the FX unit. + * ***Overdrive [0 - 99]:*** the overdrive amount the more the overdrive the less linear is the amplification. Below is the amplifier response based on different value of the overdrive: ![Tube - Overdrive response](Tube--overdrive-response.png) + +
+
+ +* **Chorus:** this effect is an audio signal processing technique that creates a richer, thicker sound by duplicating the original signal and slightly delaying and modulating the duplicated signal in pitch and time. The modulated and delayed signals are then mixed back together with the original signal, creating a sound that seems to be coming from multiple sources or voices. +The result of a chorus effect is a distinctive "shimmering" or "swirling" sound, similar to the sound of a choir or a group of instruments playing together. Chorus effects are commonly used in music production to add depth and complexity to a sound, particularly for guitars, keyboards, and vocals. They are also used in some audio equipment, such as guitar pedals and synthesizers, to produce a chorus effect in real-time. +This implementation is a standard Chorus FX that is based on 4 LFO: + | LFO | Min. freq. | Max. freq. | Phase | + |-----|------------|------------|-------| + | 1 | 0.0 Hz | 0.25 Hz | 0° | + | 2 | 0.0 Hz | 0.25 Hz | 90° | + | 3 | 0.0 Hz | 0.35 Hz | 0° | + | 4 | 0.0 Hz | 0.35 Hz | 90° | + + FX Tube Channel Strip + + * ***Enable [Off - On]:*** Enable / disable the FX unit. + * ***Rate [0 - 99]:*** modulate the LFO rate between thei minimum frequency (Rate = 0) and their maximum frequency (Rate = 99). + * ***Depth [0 - 99]:*** the level of modulation of the FX. The higher the value the more prononced the audio processing is. + +
+
+ +* **Flanger:** this effect is an audio signal processing technique that creates a sweeping, swirling sound by mixing a delayed copy of an audio signal with the original signal. The delay time of the copied signal is varied over time, creating a series of peaks and troughs in the frequency spectrum of the combined signal. +The resulting sound is similar to a chorus effect, but with a more pronounced "swooshing" or "jet engine" effect, as the peaks and troughs of the frequency spectrum sweep up and down in a cyclic pattern. Flangers are often used in music production to create an "otherworldly" or psychedelic effect, particularly on guitars and keyboards. +The name "flanger" comes from the original method of creating the effect, which involved using two tape recorders to play back the same signal simultaneously, while a finger was used to slightly slow down or speed up the flange (rim) of one of the reels, causing the delayed signal to vary in time. Today, flanger effects are typically created digitally, using specialized signal processing algorithms in software or hardware devices such as guitar pedals or studio processors. +This implementation is based on 2 LFO: + | LFO | Min. freq. | Max. freq. | Phase | + |-----|------------|------------|-------| + | 1 | 0.1 Hz | 5.0 Hz | 0° | + | 2 | 0.1 Hz | 5.0 Hz | 90° | + + FX Tube Channel Strip + + * ***Enable [Off - On]:*** Enable / disable the FX unit. + * ***Rate [0 - 99]:*** modulate the LFO rate between thei minimum frequency (Rate = 0) and their maximum frequency (Rate = 99). + * ***Depth [0 - 99]:*** the level of modulation of the FX. The higher the value the more prononced the audio processing is. + * ***Feedback [0 - 99]:*** the amount of of processed signal that is re-injected at the audio effect input. + +
+
+ +* ***Orbitone:*** this is an ensemble effect that is an audio signal processing technique that creates the impression of multiple instruments or voices playing together. It is similar to a chorus effect in that it involves duplicating and modulating an audio signal, but with some differences in the way it is applied. +In an ensemble effect, the duplicated signals are modulated with a low-frequency oscillator (LFO) that varies the pitch and amplitude of the signals over time. The modulated signals are then mixed with the original signal, creating a sound that is thicker and richer, as if several instruments or voices were playing in unison. +The resulting sound can be used to add depth and character to a musical performance, particularly for string, brass, or vocal sounds. Ensemble effects are often used in music production to create a sense of warmth, depth, and movement in a mix. +This implementation is based on 4 LFO: + | LFO | Min. freq. | Max. freq. | Phase | + |-----|------------|------------|-------| + | 1 | 0.0 Hz | 1.0 Hz | 0° | + | 2 | 0.0 Hz | 1.0 Hz | 120° | + | 3 | 0.0 Hz | 1.0 Hz | 240° | + | 4 | 0.0 Hz | 8.8 Hz | 0° | + | 5 | 0.0 Hz | 8.8 Hz | 120° | + | 6 | 0.0 Hz | 8.8 Hz | 240° | + + FX Tube Channel Strip + + * ***Enable [Off - On]:*** Enable / disable the FX unit. + * ***Rate [0 - 99]:*** modulate the LFO rate between thei minimum frequency (Rate = 0) and their maximum frequency (Rate = 99). + * ***Depth [0 - 99]:*** the level of modulation of the FX. The higher the value the more prononced the audio processing is. + * ***Feedback [0 - 99]:*** the amount of of processed signal that is re-injected at the audio effect input. + +
+
+ +* ***Phaser:*** a phaser or phase shifter effect is an audio signal processing technique that creates a sweeping, "whooshing" sound by altering the phase relationship between two identical signals. This is achieved by splitting the audio signal into two parts, delaying one of them slightly, and then combining them again. The delay time is varied over time, causing certain frequencies in the audio signal to be boosted and others to be attenuated. +The resulting sound is characterized by a series of peaks and troughs in the frequency spectrum, which move up and down in a sweeping pattern. The phaser effect is often used in music production to create a sense of movement and space in a mix, and is particularly effective on guitars, keyboards, and other melodic instruments. +This implementation is based on 2 LFO: + | LFO | Min. freq. | Max. freq. | Phase | + |-----|------------|------------|-------| + | 1 | 0.0 Hz | 2.5 Hz | 0° | + | 2 | 0.0 Hz | 2.5 Hz | 90° | + + FX Tube Channel Strip + + * ***Enable [Off - On]:*** Enable / disable the FX unit. + * ***Rate [0 - 99]:*** modulate the LFO rate between thei minimum frequency (Rate = 0) and their maximum frequency (Rate = 99). + * ***Depth [0 - 99]:*** the level of modulation of the FX. The higher the value the more prononced the audio processing is. + * ***Feedback [0 - 99]:*** the amount of of processed signal that is re-injected at the audio effect input. + * ***Nb Stages [2 - 24]:*** The number of state variable filter stages that the audio signal will traverse. + +
+
+ +* **Delay:** the is an audio signal processing technique that creates a repeated, delayed version of an original sound. It does this by temporarily storing a copy of the original audio signal in a buffer, and then playing it back at a later time. +The delay time can be set to vary from a few milliseconds to 1 second, and the repeated sound can be manipulated in various ways to create a range of different effects. +Delay effects are used in a wide variety of musical genres to add texture, depth, and interest to a mix. They can be used on almost any sound source, including vocals, guitars, keyboards, drums and other melodic instruments. +The implemention of this delay accept negative values for both left and right delay. Negative values will echo sound reversed. + + FX Tube Channel Strip + + * ***Enable [Off - On]:*** Enable / disable the FX unit. + * **Left Delay [-99 - 99]:** the left delay from 0 to 2 seconds. + * **Right Delay [-99 - 99]:** the left delay from 0 to 2 seconds. + * ***Feedback [0 - 99]:*** the amount of of processed signal that is re-injected at the audio effect input. + +
+
+ +* **Plate Reverb:** A plate reverb effect is an audio signal processing technique that simulates the sound of a large, metallic plate vibrating in response to an audio signal. It is one of the most iconic and widely used types of artificial reverberation in music production. +The plate reverb effect is created by passing an audio signal through a transducer that causes a large metal plate to vibrate in response. The vibrations of the plate are then picked up by one or more microphones and mixed back with the original signal. The sound of the plate reverb is characterized by a smooth decay and a diffuse, ambient quality that can add warmth and depth to a mix. +Plate reverb effects are often used in music production to add space, depth, and character to a mix. They are particularly effective on vocals, drums, and other percussive sounds, and can be used to create a sense of space and distance in a recording. The effect can be adjusted to achieve varying degrees of intensity, decay time, and other parameters. + + FX Tube Channel Strip + + * ***Enable [Off - On]:*** Enable / disable the FX unit. + * ***Size [0 - 99]:*** the size of the simulated metallic plate hence of the room. + * ***High Damping [0 - 99]:*** the amount of high-frequency attenuation or absorption applied to the plate's vibrations. It is a key parameter that can have a significant impact on the overall sound of the plate reverb effect. + * ***Low Damping [0 - 99]:*** the amount of low-frequency attenuation or absorption applied to the plate's vibrations. It is a key parameter that can have a significant impact on the overall sound of the plate reverb effect. + * ***Low Pass [0 - 99]:*** the low pass parameter of a plate reverb refers to a filter that is applied to the reverb signal to attenuate or cut off frequencies above a certain threshold. + A low pass filter can be used to create a darker, more mellow sound by reducing or removing high-frequency content from the reverb effect. This can be useful when trying to create a more vintage or retro sound, or when trying to soften harsh or bright sounds in a mix. + * ***Diffusion [0 - 99]:*** the diffusion parameter of a plate reverb refers to the degree to which the reflections in the reverb decay are dispersed or spread out in time and space. It determines how "dense" or "sparse" the reverb effect sounds and how the individual reflections blend together. + A higher diffusion parameter means that the reflections in the reverb decay will be more widely dispersed, resulting in a more "washed out" or diffuse sound. This can be useful when trying to create a sense of space or depth in a mix, or when trying to create a more ambient or atmospheric effect. + Conversely, a lower diffusion parameter means that the reflections in the reverb decay will be more closely spaced, resulting in a more defined and focused sound. This can be useful when trying to create a more natural-sounding reverb effect, or when trying to highlight the details of a particular instrument or sound in a mix. + * ***Level [0 - 99]:*** the level parameter of a plate reverb refers to the overall level or amplitude of the reverb effect. It determines how loud the reverb signal is in relation to the dry or unprocessed signal. + +
+
+ +* **Reverberator:** the reverberator effect, commonly known as reverb, is an audio processing effect that simulates the sound reflections and acoustic environment of a physical space. +When a sound is produced in a physical space, it travels through the air and reflects off the walls, floors, and other surfaces in the environment. These reflections create a sense of space and depth, and contribute to the character and quality of the sound. +A reverberator effect works by simulating these reflections, and can be used to create a wide range of sonic environments, from small and intimate spaces like a bedroom or bathroom, to large and expansive spaces like a concert hall or cathedral. +Reverberator can be applied to individual tracks or to a mix, and can be adjusted in various ways, such as changing the decay time or "size" of the simulated space, adjusting the frequency content of the reverb, and adjusting the level or balance of the reverb effect in relation to the dry or unprocessed signal. +Reverberator is a common effect used in many genres of music, as well as in film, television, and other forms of audio production. It can be used to create a sense of realism, depth, and immersion, or to add a sense of ambiance, texture, or mood to a recording or production. +This implementation pushes the reverberation to reach almost the shimmer effect. + + FX Tube Channel Strip + + * ***Enable [Off - On]:*** Enable / disable the FX unit. + * **Gain [0 - 99]:** the gain parameter of the reverberator refers to the overall level or amplitude of the reverberator effect. It determines how loud the reverberator signal is in relation to the dry or unprocessed signal. + * **Time [0 - 99]:** the time determines the size of the simulated reverberating space. + * **Diffusion [0 - 99]:** the diffusion parameter of a reverberator refers to the degree to which the reflections in the reverb decay are dispersed or spread out in time and space. It determines how "dense" or "sparse" the reverberator effect sounds and how the individual reflections blend together. + A higher diffusion parameter means that the reflections in the reverb decay will be more widely dispersed, resulting in a more "washed out" or diffuse sound. This can be useful when trying to create a sense of space or depth in a mix, or when trying to create a more ambient or atmospheric effect. + Conversely, a lower diffusion parameter means that the reflections in the reverberator decay will be more closely spaced, resulting in a more defined and focused sound. This can be useful when trying to create a more natural-sounding reverberator effect, or when trying to highlight the details of a particular instrument or sound in a mix. + * **Low Pass [0 - 99]:** the low pass parameter of a plate reverb refers to a filter that is applied to the reverb signal to attenuate or cut off frequencies above a certain threshold. + A low pass filter can be used to create a darker, more mellow sound by reducing or removing high-frequency content from the reverb effect. This can be useful when trying to create a more vintage or retro sound, or when trying to soften harsh or bright sounds in a mix. + +## The Menu structure + +* *MiniDexed* + * *TG1* + * *Voice* + * *Bank* + * *Volume* + * *Pan* + * **FX-Send** + * **> Tub** *[0 - 99]*: the amount of the Tone Generator signal that will be send to the Tube FX Unit. + * **> ChR** *[0 - 99]*: the amount of the Tone Generator signal that will be send to the Chorus FX Unit. + * **> Flg** *[0 - 99]*: the amount of the Tone Generator signal that will be send to the Flanger FX Unit. + * **> Orb** *[0 - 99]*: the amount of the Tone Generator signal that will be send to the Orbitone FX Unit. + * **> PhR** *[0 - 99]*: the amount of the Tone Generator signal that will be send to the Phaser FX Unit. + * **> Dly** *[0 - 99]*: the amount of the Tone Generator signal that will be send to the Delay FX Unit. + * **> Plt** *[0 - 99]*: the amount of the Tone Generator signal that will be send to the Plate Reverb FX Unit. + * **> Rev** *[0 - 99]*: the amount of the Tone Generator signal that will be send to the Reverberator FX Unit. + * **> Main** *[0 - 99]*: the amount of the Tone Generator signal that will be send to the Main output hence dry. + * *Detune* + * *Cutoff* + * *Resonance* + * *Pitch Bend* + * *Portamento* + * *Poly/Mono* + * *Modulation* + * *Channel* + * *Edit Voice:* + * ... + * *TG2* + * ... *similar to TG1 sub-menu* ... + * *TG3* + * ... *similar to TG1 sub-menu* ... + * *TG4* + * ... *similar to TG1 sub-menu* ... + * *TG5* + * ... *similar to TG1 sub-menu* ... + * *TG6* + * ... *similar to TG1 sub-menu* ... + * *TG7* + * ... *similar to TG1 sub-menu* ... + * *TG8* + * ... *similar to TG1 sub-menu* ... + * **Effects** + * Compress *[on - off]*: Enable / disable the FX unit for all Tone Generators. + * **Tube** + * **Enable** *[on - off]*: Enable / disable the FX unit. + * **Overdrv** *[0 - 99]*: The overdrive amount of the FX unit. + * **Levels** + * **TG1 >** *[0 - 99]* - shortcut to [TG1] >> [FX-Send] >> [> Tub] + * **TG2 >** *[0 - 99]* - shortcut to [TG2] >> [FX-Send] >> [> Tub] + * **TG3 >** *[0 - 99]* - shortcut to [TG3] >> [FX-Send] >> [> Tub] + * **TG4 >** *[0 - 99]* - shortcut to [TG4] >> [FX-Send] >> [> Tub] + * **TG5 >** *[0 - 99]* - shortcut to [TG5] >> [FX-Send] >> [> Tub] + * **TG6 >** *[0 - 99]* - shortcut to [TG6] >> [FX-Send] >> [> Tub] + * **TG7 >** *[0 - 99]* - shortcut to [TG7] >> [FX-Send] >> [> Tub] + * **TG8 >** *[0 - 99]* - shortcut to [TG8] >> [FX-Send] >> [> Tub] + * **ChR >** *[0 - 99]* - shortcut to [Effects] >> [Chorus] >> [> Tub] + * **Flg >** *[0 - 99]* - shortcut to [Effects] >> [FlangR] >> [> Tub] + * **Orb >** *[0 - 99]* - shortcut to [Effects] >> [Orb] >> [> Tub] + * **PhR >** *[0 - 99]* - shortcut to [Effects] >> [PhasR] >> [> Tub] + * **Dly >** *[0 - 99]* - shortcut to [Effects] >> [Delay] >> [> Tub] + * **Plt >** *[0 - 99]* - shortcut to [Effects] >> [Plt Rvb] >> [> Tub] + * **Rev >** *[0 - 99]* - shortcut to [Effects] >> [Rvbrtor] >> [> Tub] + * **FX-Send** + * **> ChR** *[0 - 99]*: The amount of signal processed by the Tube FX unit that will be sent to the Chorus FX unit. + * **> Flg** *[0 - 99]*: The amount of signal processed by the Tube FX unit that will be sent to the Flanger FX unit. + * **> Orb** *[0 - 99]*: The amount of signal processed by the Tube FX unit that will be sent to the Orbitone FX unit. + * **> PhR** *[0 - 99]*: The amount of signal processed by the Tube FX unit that will be sent to the Phaser FX unit. + * **> Dly** *[0 - 99]*: The amount of signal processed by the Tube FX unit that will be sent to the Delay FX unit. + * **> Plt** *[0 - 99]*: The amount of signal processed by the Tube FX unit that will be sent to the Plate Reverb FX unit. + * **> Rev** *[0 - 99]*: The amount of signal processed by the Tube FX unit that will be sent to the Reverberator FX unit. + * **> Main** *[0 - 99]*: The amount of signal processed by the Tube FX unit that will be sent to the Main output. + * **Chorus** + * **Enable** *[on - off]*: Enable / disable the FX unit. + * **Rate** *[0 - 99]*: The speed of the LFO of the FX unit. + * **Depth** *[0 - 99]*: The amount of effect that is applied to the input signal. + * **Levels** + * **TG1 >** *[0 - 99]* - shortcut to [TG1] >> [FX-Send] >> [> ChR] + * **TG2 >** *[0 - 99]* - shortcut to [TG2] >> [FX-Send] >> [> ChR] + * **TG3 >** *[0 - 99]* - shortcut to [TG3] >> [FX-Send] >> [> ChR] + * **TG4 >** *[0 - 99]* - shortcut to [TG4] >> [FX-Send] >> [> ChR] + * **TG5 >** *[0 - 99]* - shortcut to [TG5] >> [FX-Send] >> [> ChR] + * **TG6 >** *[0 - 99]* - shortcut to [TG6] >> [FX-Send] >> [> ChR] + * **TG7 >** *[0 - 99]* - shortcut to [TG7] >> [FX-Send] >> [> ChR] + * **TG8 >** *[0 - 99]* - shortcut to [TG8] >> [FX-Send] >> [> ChR] + * **Tub >** *[0 - 99]* - shortcut to [Effects] >> [Tube] >> [> ChR] + * **Flg >** *[0 - 99]* - shortcut to [Effects] >> [FlangR] >> [> ChR] + * **Orb >** *[0 - 99]* - shortcut to [Effects] >> [Orb] >> [> ChR] + * **PhR >** *[0 - 99]* - shortcut to [Effects] >> [PhasR] >> [> ChR] + * **Dly >** *[0 - 99]* - shortcut to [Effects] >> [Delay] >> [> ChR] + * **Plt >** *[0 - 99]* - shortcut to [Effects] >> [Plt Rvb] >> [> ChR] + * **Rev >** *[0 - 99]* - shortcut to [Effects] >> [Rvbrtor] >> [> ChR] + * **FX-Send** + * **> Tub** *[0 - 99]*: The amount of signal processed by the Chorus FX unit that will be sent to the Tube FX unit. + * **> Flg** *[0 - 99]*: The amount of signal processed by the Chorus FX unit that will be sent to the Flanger FX unit. + * **> Orb** *[0 - 99]*: The amount of signal processed by the Chorus FX unit that will be sent to the Orbitone FX unit. + * **> PhR** *[0 - 99]*: The amount of signal processed by the Chorus FX unit that will be sent to the Phaser FX unit. + * **> Dly** *[0 - 99]*: The amount of signal processed by the Chorus FX unit that will be sent to the Delay FX unit. + * **> Plt** *[0 - 99]*: The amount of signal processed by the Chorus FX unit that will be sent to the Plate Reverb FX unit. + * **> Rev** *[0 - 99]*: The amount of signal processed by the Chorus FX unit that will be sent to the Reverberator FX unit. + * **> Main** *[0 - 99]*: The amount of signal processed by the Chorus FX unit that will be sent to the Main output. + * **FlangR** + * **Enable** *[on - off]*: Enable / disable the FX unit. + * **Rate** *[0 - 99]*: The speed of the LFO of the FX unit. + * **Depth** *[0 - 99]*: The amount of effect that is applied to the input signal. + * **Feedbck** *[0 - 99]*: The amount of processed signal that will be reinjected at the input. + * **Levels** + * **TG1 >** *[0 - 99]* - shortcut to [TG1] >> [FX-Send] >> [> Flg] + * **TG2 >** *[0 - 99]* - shortcut to [TG2] >> [FX-Send] >> [> Flg] + * **TG3 >** *[0 - 99]* - shortcut to [TG3] >> [FX-Send] >> [> Flg] + * **TG4 >** *[0 - 99]* - shortcut to [TG4] >> [FX-Send] >> [> Flg] + * **TG5 >** *[0 - 99]* - shortcut to [TG5] >> [FX-Send] >> [> Flg] + * **TG6 >** *[0 - 99]* - shortcut to [TG6] >> [FX-Send] >> [> Flg] + * **TG7 >** *[0 - 99]* - shortcut to [TG7] >> [FX-Send] >> [> Flg] + * **TG8 >** *[0 - 99]* - shortcut to [TG8] >> [FX-Send] >> [> Flg] + * **Tub >** *[0 - 99]* - shortcut to [Effects] >> [Tube] >> [> Flg] + * **ChR >** *[0 - 99]* - shortcut to [Effects] >> [Chorus] >> [> Flg] + * **Orb >** *[0 - 99]* - shortcut to [Effects] >> [Orb] >> [> Flg] + * **PhR >** *[0 - 99]* - shortcut to [Effects] >> [PhasR] >> [> Flg] + * **Dly >** *[0 - 99]* - shortcut to [Effects] >> [Delay] >> [> Flg] + * **Plt >** *[0 - 99]* - shortcut to [Effects] >> [Plt Rvb] >> [> Flg] + * **Rev >** *[0 - 99]* - shortcut to [Effects] >> [Rvbrtor] >> [> Flg] + * **FX-Send** + * **> Tub** *[0 - 99]*: The amount of signal processed by the Flanger FX unit that will be sent to the Tube FX unit. + * **> ChR** *[0 - 99]*: The amount of signal processed by the Flanger FX unit that will be sent to the Chorus FX unit. + * **> Orb** *[0 - 99]*: The amount of signal processed by the Flanger FX unit that will be sent to the Orbitone FX unit. + * **> PhR** *[0 - 99]*: The amount of signal processed by the Flanger FX unit that will be sent to the Phaser FX unit. + * **> Dly** *[0 - 99]*: The amount of signal processed by the Flanger FX unit that will be sent to the Delay FX unit. + * **> Plt** *[0 - 99]*: The amount of signal processed by the Flanger FX unit that will be sent to the Plate Reverb FX unit. + * **> Rev** *[0 - 99]*: The amount of signal processed by the Flanger FX unit that will be sent to the Reverberator FX unit. + * **> Main** *[0 - 99]*: The amount of signal processed by the Flanger FX unit that will be sent to the Main output. + * **Orb** + * **Enable** *[on - off]*: Enable / disable the FX unit. + * **Rate** *[0 - 99]*: The speed of the LFO of the FX unit. + * **Depth** *[0 - 99]*: The amount of effect that is applied to the input signal. + * **Levels** + * **TG1 >** *[0 - 99]* - shortcut to [TG1] >> [FX-Send] >> [> Orb] + * **TG2 >** *[0 - 99]* - shortcut to [TG2] >> [FX-Send] >> [> Orb] + * **TG3 >** *[0 - 99]* - shortcut to [TG3] >> [FX-Send] >> [> Orb] + * **TG4 >** *[0 - 99]* - shortcut to [TG4] >> [FX-Send] >> [> Orb] + * **TG5 >** *[0 - 99]* - shortcut to [TG5] >> [FX-Send] >> [> Orb] + * **TG6 >** *[0 - 99]* - shortcut to [TG6] >> [FX-Send] >> [> Orb] + * **TG7 >** *[0 - 99]* - shortcut to [TG7] >> [FX-Send] >> [> Orb] + * **TG8 >** *[0 - 99]* - shortcut to [TG8] >> [FX-Send] >> [> Orb] + * **Tub >** *[0 - 99]* - shortcut to [Effects] >> [Tube] >> [> Orb] + * **ChR >** *[0 - 99]* - shortcut to [Effects] >> [Chorus] >> [> Orb] + * **Flg >** *[0 - 99]* - shortcut to [Effects] >> [FlangR] >> [> Orb] + * **PhR >** *[0 - 99]* - shortcut to [Effects] >> [PhasR] >> [> Orb] + * **Dly >** *[0 - 99]* - shortcut to [Effects] >> [Delay] >> [> Orb] + * **Plt >** *[0 - 99]* - shortcut to [Effects] >> [Plt Rvb] >> [> Orb] + * **Rev >** *[0 - 99]* - shortcut to [Effects] >> [Rvbrtor] >> [> Orb] + * **FX-Send** + * **> Tub** *[0 - 99]*: The amount of signal processed by the Orbitone FX unit that will be sent to the Tube FX unit. + * **> ChR** *[0 - 99]*: The amount of signal processed by the Orbitone FX unit that will be sent to the Chorus FX unit. + * **> Flg** *[0 - 99]*: The amount of signal processed by the Orbitone FX unit that will be sent to the Flanger FX unit. + * **> PhR** *[0 - 99]*: The amount of signal processed by the Orbitone FX unit that will be sent to the Phaser FX unit. + * **> Dly** *[0 - 99]*: The amount of signal processed by the Orbitone FX unit that will be sent to the Delay FX unit. + * **> Plt** *[0 - 99]*: The amount of signal processed by the Orbitone FX unit that will be sent to the Plate Reverb FX unit. + * **> Rev** *[0 - 99]*: The amount of signal processed by the Orbitone FX unit that will be sent to the Reverberator FX unit. + * **> Main** *[0 - 99]*: The amount of signal processed by the Orbitone FX unit that will be sent to the Main output. + * **PhasR** + * **Enable** *[on - off]*: Enable / disable the FX unit. + * **Rate** *[0 - 99]*: The speed of the LFO of the FX unit. + * **Depth** *[0 - 99]*: The amount of effect that is applied to the input signal. + * **Feedbck** *[0 - 99]*: The amount of processed signal that will be reinjected at the input. + * **Stages** *[2 - 24]*: The number of processing phasing stages. + * **Levels** + * **TG1 >** *[0 - 99]* - shortcut to [TG1] >> [FX-Send] >> [> PhR] + * **TG2 >** *[0 - 99]* - shortcut to [TG2] >> [FX-Send] >> [> PhR] + * **TG3 >** *[0 - 99]* - shortcut to [TG3] >> [FX-Send] >> [> PhR] + * **TG4 >** *[0 - 99]* - shortcut to [TG4] >> [FX-Send] >> [> PhR] + * **TG5 >** *[0 - 99]* - shortcut to [TG5] >> [FX-Send] >> [> PhR] + * **TG6 >** *[0 - 99]* - shortcut to [TG6] >> [FX-Send] >> [> PhR] + * **TG7 >** *[0 - 99]* - shortcut to [TG7] >> [FX-Send] >> [> PhR] + * **TG8 >** *[0 - 99]* - shortcut to [TG8] >> [FX-Send] >> [> PhR] + * **Tub >** *[0 - 99]* - shortcut to [Effects] >> [Tube] >> [> PhR] + * **ChR >** *[0 - 99]* - shortcut to [Effects] >> [Chorus] >> [> PhR] + * **Flg >** *[0 - 99]* - shortcut to [Effects] >> [FlangR] >> [> PhR] + * **Orb >** *[0 - 99]* - shortcut to [Effects] >> [Orb] >> [> PhR] + * **Dly >** *[0 - 99]* - shortcut to [Effects] >> [Delay] >> [> PhR] + * **Plt >** *[0 - 99]* - shortcut to [Effects] >> [Plt Rvb] >> [> PhR] + * **Rev >** *[0 - 99]* - shortcut to [Effects] >> [Rvbrtor] >> [> PhR] + * **FX-Send** + * **> Tub** *[0 - 99]*: The amount of signal processed by the Phaser FX unit that will be sent to the Tube FX unit. + * **> ChR** *[0 - 99]*: The amount of signal processed by the Phaser FX unit that will be sent to the Chorus FX unit. + * **> Flg** *[0 - 99]*: The amount of signal processed by the Phaser FX unit that will be sent to the Flanger FX unit. + * **> Orb** *[0 - 99]*: The amount of signal processed by the Phaser FX unit that will be sent to the Orbitone FX unit. + * **> Dly** *[0 - 99]*: The amount of signal processed by the Phaser FX unit that will be sent to the Delay FX unit. + * **> Plt** *[0 - 99]*: The amount of signal processed by the Phaser FX unit that will be sent to the Plate Reverb FX unit. + * **> Rev** *[0 - 99]*: The amount of signal processed by the Phaser FX unit that will be sent to the Reverberator FX unit. + * **> Main** *[0 - 99]*: The amount of signal processed by the Phaser FX unit that will be sent to the Main output. + * **Delay** + * **Enable** *[on - off]*: Enable / disable the FX unit. + * **L Delay** *[-99 - 99]*: The delay time for the Left channel. Negtive values play the echoed part of the processed sound reversed. + * **R Delay** *[-99 - 99]*: The delay time for the Right channel. Negtive values play the echoed part of the processed sound reversed. + * **Feedbck** *[0 - 99]*: The amount of processed signal that will be reinjected at the input. + * **Levels** + * **TG1 >** *[0 - 99]* - shortcut to [TG1] >> [FX-Send] >> [> Dly] + * **TG2 >** *[0 - 99]* - shortcut to [TG2] >> [FX-Send] >> [> Dly] + * **TG3 >** *[0 - 99]* - shortcut to [TG3] >> [FX-Send] >> [> Dly] + * **TG4 >** *[0 - 99]* - shortcut to [TG4] >> [FX-Send] >> [> Dly] + * **TG5 >** *[0 - 99]* - shortcut to [TG5] >> [FX-Send] >> [> Dly] + * **TG6 >** *[0 - 99]* - shortcut to [TG6] >> [FX-Send] >> [> Dly] + * **TG7 >** *[0 - 99]* - shortcut to [TG7] >> [FX-Send] >> [> Dly] + * **TG8 >** *[0 - 99]* - shortcut to [TG8] >> [FX-Send] >> [> Dly] + * **Tub >** *[0 - 99]* - shortcut to [Effects] >> [Tube] >> [> Dly] + * **ChR >** *[0 - 99]* - shortcut to [Effects] >> [Chorus] >> [> Dly] + * **Flg >** *[0 - 99]* - shortcut to [Effects] >> [FlangR] >> [> Dly] + * **Orb >** *[0 - 99]* - shortcut to [Effects] >> [Orb] >> [> Dly] + * **PhR >** *[0 - 99]* - shortcut to [Effects] >> [PhasR] >> [> Dly] + * **Plt >** *[0 - 99]* - shortcut to [Effects] >> [Plt Rvb] >> [> Dly] + * **Rev >** *[0 - 99]* - shortcut to [Effects] >> [Rvbrtor] >> [> Dly] + * **FX-Send** + * **> Tub** *[0 - 99]*: The amount of signal processed by the Delay FX unit that will be sent to the Tube FX unit. + * **> ChR** *[0 - 99]*: The amount of signal processed by the Delay FX unit that will be sent to the Chorus FX unit. + * **> Flg** *[0 - 99]*: The amount of signal processed by the Delay FX unit that will be sent to the Flanger FX unit. + * **> Orb** *[0 - 99]*: The amount of signal processed by the Delay FX unit that will be sent to the Orbitone FX unit. + * **> PhR** *[0 - 99]*: The amount of signal processed by the Delay FX unit that will be sent to the Phaser FX unit. + * **> Plt** *[0 - 99]*: The amount of signal processed by the Delay FX unit that will be sent to the Plate Reverb FX unit. + * **> Rev** *[0 - 99]*: The amount of signal processed by the Delay FX unit that will be sent to the Reverberator FX unit. + * **> Main** *[0 - 99]*: The amount of signal processed by the Delay FX unit that will be sent to the Main output. + * **Plt Rvb** + * **Enable** *[on - off]*: Enable / disable the FX unit. + * **Size** *[0 - 99]*: The size of the plate that simulate the size of the reverberating room. + * **High damp** *[0 - 99]*: The amount of high-frequency attenuation. + * **Low damp** *[0 - 99]*: The amount of low-frequency attenuation. + * **Low pass** *[0 - 99]*: The low pass cutoff frequency. + * **Diffusion** *[0 - 99]*: The degree to which the reflections in the reverb decay are dispersed or spread out in time and space. + * **Level** *[0 - 99]*: The overall level or amplitude of the reverb effect. + * **Levels** + * **TG1 >** *[0 - 99]* - shortcut to [TG1] >> [FX-Send] >> [> Plt] + * **TG2 >** *[0 - 99]* - shortcut to [TG2] >> [FX-Send] >> [> Plt] + * **TG3 >** *[0 - 99]* - shortcut to [TG3] >> [FX-Send] >> [> Plt] + * **TG4 >** *[0 - 99]* - shortcut to [TG4] >> [FX-Send] >> [> Plt] + * **TG5 >** *[0 - 99]* - shortcut to [TG5] >> [FX-Send] >> [> Plt] + * **TG6 >** *[0 - 99]* - shortcut to [TG6] >> [FX-Send] >> [> Plt] + * **TG7 >** *[0 - 99]* - shortcut to [TG7] >> [FX-Send] >> [> Plt] + * **TG8 >** *[0 - 99]* - shortcut to [TG8] >> [FX-Send] >> [> Plt] + * **Tub >** *[0 - 99]* - shortcut to [Effects] >> [Tube] >> [> Plt] + * **ChR >** *[0 - 99]* - shortcut to [Effects] >> [Chorus] >> [> Plt] + * **Flg >** *[0 - 99]* - shortcut to [Effects] >> [FlangR] >> [> Plt] + * **Orb >** *[0 - 99]* - shortcut to [Effects] >> [Orb] >> [> Plt] + * **PhR >** *[0 - 99]* - shortcut to [Effects] >> [PhasR] >> [> Plt] + * **Dly >** *[0 - 99]* - shortcut to [Effects] >> [Delay] >> [> Plt] + * **Rev >** *[0 - 99]* - shortcut to [Effects] >> [Rvbrtor] >> [> Plt] + * **FX-Send** + * **> Tub** *[0 - 99]*: The amount of signal processed by the Plate Reverb FX unit that will be sent to the Tube FX unit. + * **> ChR** *[0 - 99]*: The amount of signal processed by the Plate Reverb FX unit that will be sent to the Chorus FX unit. + * **> Flg** *[0 - 99]*: The amount of signal processed by the Plate Reverb FX unit that will be sent to the Flanger FX unit. + * **> Orb** *[0 - 99]*: The amount of signal processed by the Plate Reverb FX unit that will be sent to the Orbitone FX unit. + * **> PhR** *[0 - 99]*: The amount of signal processed by the Plate Reverb FX unit that will be sent to the Phaser FX unit. + * **> Dly** *[0 - 99]*: The amount of signal processed by the Plate Reverb FX unit that will be sent to the Delay FX unit. + * **> Rev** *[0 - 99]*: The amount of signal processed by the Plate Reverb FX unit that will be sent to the Reverberator FX unit. + * **> Main** *[0 - 99]*: The amount of signal processed by the Plate Reverb FX unit that will be sent to the Main output. + * **Rvbrtor** + * **Enable** *[on - off]*: Enable / disable the FX unit. + * **Gain** *[0 - 99]*: The overall level or amplitude of the FX. + * **Time** *[0 - 99]*: The time determines the size of the simulated reverberating space. + * **Diffusion** *[0 - 99]*: The degree to which the reflections in the reverb decay are dispersed or spread out in time and space. + * **Low pass** *[0 - 99]*: The low pass cutoff frequency. + * **Levels** + * **TG1 >** *[0 - 99]* - shortcut to [TG1] >> [FX-Send] >> [> Rev] + * **TG2 >** *[0 - 99]* - shortcut to [TG2] >> [FX-Send] >> [> Rev] + * **TG3 >** *[0 - 99]* - shortcut to [TG3] >> [FX-Send] >> [> Rev] + * **TG4 >** *[0 - 99]* - shortcut to [TG4] >> [FX-Send] >> [> Rev] + * **TG5 >** *[0 - 99]* - shortcut to [TG5] >> [FX-Send] >> [> Rev] + * **TG6 >** *[0 - 99]* - shortcut to [TG6] >> [FX-Send] >> [> Rev] + * **TG7 >** *[0 - 99]* - shortcut to [TG7] >> [FX-Send] >> [> Rev] + * **TG8 >** *[0 - 99]* - shortcut to [TG8] >> [FX-Send] >> [> Rev] + * **Tub >** *[0 - 99]* - shortcut to [Effects] >> [Tube] >> [> Rev] + * **ChR >** *[0 - 99]* - shortcut to [Effects] >> [Chorus] >> [> Rev] + * **Flg >** *[0 - 99]* - shortcut to [Effects] >> [FlangR] >> [> Rev] + * **Orb >** *[0 - 99]* - shortcut to [Effects] >> [Orb] >> [> Rev] + * **PhR >** *[0 - 99]* - shortcut to [Effects] >> [PhasR] >> [> Rev] + * **Dly >** *[0 - 99]* - shortcut to [Effects] >> [Delay] >> [> Rev] + * **Plt >** *[0 - 99]* - shortcut to [Effects] >> [Plt Rvb] >> [> Rev] + * **FX-Send** + * **> Tub** *[0 - 99]*: The amount of signal processed by the Reverberator FX unit that will be sent to the Tube FX unit. + * **> ChR** *[0 - 99]*: The amount of signal processed by the Reverberator FX unit that will be sent to the Chorus FX unit. + * **> Flg** *[0 - 99]*: The amount of signal processed by the Reverberator FX unit that will be sent to the Flanger FX unit. + * **> Orb** *[0 - 99]*: The amount of signal processed by the Reverberator FX unit that will be sent to the Orbitone FX unit. + * **> PhR** *[0 - 99]*: The amount of signal processed by the Reverberator FX unit that will be sent to the Phaser FX unit. + * **> Dly** *[0 - 99]*: The amount of signal processed by the Reverberator FX unit that will be sent to the Delay FX unit. + * **> Plt** *[0 - 99]*: The amount of signal processed by the Reverberator FX unit that will be sent to the Plate Reverb FX unit. + * **> Main** *[0 - 99]*: The amount of signal processed by the Reverberator FX unit that will be sent to the Main output. + * **MainOut** + * **TG1 >** *[0 - 99]* - shortcut to [TG1] >> [FX-Send] >> [> Main] + * **TG2 >** *[0 - 99]* - shortcut to [TG2] >> [FX-Send] >> [> Main] + * **TG3 >** *[0 - 99]* - shortcut to [TG3] >> [FX-Send] >> [> Main] + * **TG4 >** *[0 - 99]* - shortcut to [TG4] >> [FX-Send] >> [> Main] + * **TG5 >** *[0 - 99]* - shortcut to [TG5] >> [FX-Send] >> [> Main] + * **TG6 >** *[0 - 99]* - shortcut to [TG6] >> [FX-Send] >> [> Main] + * **TG7 >** *[0 - 99]* - shortcut to [TG7] >> [FX-Send] >> [> Main] + * **TG8 >** *[0 - 99]* - shortcut to [TG8] >> [FX-Send] >> [> Main] + * **Tub >** *[0 - 99]* - shortcut to [Effects] >> [Tube] >> [> Main] + * **ChR >** *[0 - 99]* - shortcut to [Effects] >> [Chorus] >> [> Main] + * **Flg >** *[0 - 99]* - shortcut to [Effects] >> [FlangR] >> [> Main] + * **Orb >** *[0 - 99]* - shortcut to [Effects] >> [Orb] >> [> Main] + * **PhR >** *[0 - 99]* - shortcut to [Effects] >> [PhasR] >> [> Main] + * **Dly >** *[0 - 99]* - shortcut to [Effects] >> [Delay] >> [> Main] + * **Plt >** *[0 - 99]* - shortcut to [Effects] >> [Plt Rvb] >> [> Main] + * **Rev >** *[0 - 99]* - shortcut to [Effects] >> [Rvbrtor] >> [> Main] + * *Performance* + * *Load* + * ... + * *Save* + * ... + * *Delete* + * ... diff --git a/wiki-update/mixing-console--TG-Channel.png b/wiki-update/mixing-console--TG-Channel.png new file mode 100644 index 00000000..e8efcc6a Binary files /dev/null and b/wiki-update/mixing-console--TG-Channel.png differ diff --git a/wiki-update/mixing-console-overview.png b/wiki-update/mixing-console-overview.png new file mode 100644 index 00000000..0821431f Binary files /dev/null and b/wiki-update/mixing-console-overview.png differ