diff --git a/.gitignore b/.gitignore index 597ceeb..18ee53c 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,6 @@ sdcard .vscode/ # temporary tests -src/test/fxrack_test -src/test/result*.wav -src/test/*.csv +src/test/*.bin +src/test/results +src/test/objects diff --git a/src/debug.hpp b/src/debug.hpp new file mode 100644 index 0000000..b0f845b --- /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:\ + 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:\ + 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 44184ab..88dac62 100644 --- a/src/effect_mixer.hpp +++ b/src/effect_mixer.hpp @@ -33,15 +33,16 @@ public: 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 @@ public: 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 ce2af1e..6605caa 100644 --- a/src/effect_platervbstereo.cpp +++ b/src/effect_platervbstereo.cpp @@ -83,7 +83,8 @@ const int16_t AudioWaveformSine[257] = { -4808, -4011, -3212, -2410, -1608, -804, 0 }; -AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate) +AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate) : + FXElement(samplerate) { input_attn = 0.5f; in_allp_k = INP_ALLP_COEFF; @@ -156,9 +157,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 +194,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; + + // 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; +} + +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 +454,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 +462,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 23538c4..9d24926 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 @@ private: 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 index a86a657..8f6705f 100644 --- a/src/extra_features.h +++ b/src/extra_features.h @@ -18,8 +18,48 @@ // #pragma once -#if defined(ARM_ALLOW_MULTI_CORE) +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete +#if defined(ARM_ALLOW_MULTI_CORE) #define FXRACK_ENABLE //Add support for the FXRack +#endif + +#ifdef DEBUG + +#include +#include +#include +#include +#include + +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) +#define DEBUG_VALUE(lbl, idx, v) #endif diff --git a/src/fx.h b/src/fx.h index c8d9203..30758de 100644 --- a/src/fx.h +++ b/src/fx.h @@ -14,7 +14,7 @@ // // fx.h // -// Base class for Stereo audio effects proposed in the context of the MiniDexed project +// Base classes for Stereo audio effects proposed in the context of the MiniDexed project // #pragma once @@ -22,21 +22,23 @@ #include #include "common.h" -#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&) = delete; \ - void operator=(const TypeName&) = delete +#include "debug.hpp" +#include "fx_base.h" -class FXBase +class INSPECTABLE(FXBase) { DISALLOW_COPY_AND_ASSIGN(FXBase); protected: FXBase(float32_t sampling_rate); - virtual ~FXBase(); public: + virtual ~FXBase(); + float32_t getSamplingRate() const; + virtual void reset() = 0; + private: const float32_t SamplingRate; }; diff --git a/src/fx_base.h b/src/fx_base.h new file mode 100644 index 0000000..ec6f135 --- /dev/null +++ b/src/fx_base.h @@ -0,0 +1,10 @@ +#pragma once + +#include "extra_features.h" + +enum StereoChannels +{ + Left = 0, + Right, + kNumChannels +}; diff --git a/src/fx_chorus.cpp b/src/fx_chorus.cpp index 0330a5e..a84edeb 100644 --- a/src/fx_chorus.cpp +++ b/src/fx_chorus.cpp @@ -5,20 +5,18 @@ #define LFO1_MAX_FREQ 0.25f #define LFO2_MAX_FREQ 0.35f -#define FULLSCALE_DEPTH_RATIO 1536.0f - Chorus::Chorus(float32_t sampling_rate) : FXElement(sampling_rate), - engine_(sampling_rate, 0.0f, 0.0f), + engine_(sampling_rate, 0.0f), rate_(0.0f), depth_(0.0f), fullscale_depth_(0.0f), feedback_(0.0f) { - this->lfo_[LFO_Index::Sin1] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO1_MAX_FREQ); - this->lfo_[LFO_Index::Cos1] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO1_MAX_FREQ, Constants::MPI_2); - this->lfo_[LFO_Index::Sin2] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO2_MAX_FREQ); - this->lfo_[LFO_Index::Cos2] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO2_MAX_FREQ, Constants::MPI_2); + this->lfo_[LFOIndex::Sin1] = new LFO(sampling_rate, 0.0f, LFO1_MAX_FREQ); + this->lfo_[LFOIndex::Cos1] = new LFO(sampling_rate, 0.0f, LFO1_MAX_FREQ, Constants::MPI_2); + this->lfo_[LFOIndex::Sin2] = new LFO(sampling_rate, 0.0f, LFO2_MAX_FREQ); + this->lfo_[LFOIndex::Cos2] = new LFO(sampling_rate, 0.0f, LFO2_MAX_FREQ, Constants::MPI_2); this->setRate(0.1f); this->setDepth(0.15f); @@ -26,25 +24,34 @@ Chorus::Chorus(float32_t sampling_rate) : Chorus::~Chorus() { - for(unsigned i = 0; i < 4; ++i) + 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; - Engine::DelayLine line; + static Engine::DelayLine line; Engine::Context c; this->engine_.start(&c); // Update LFO. - float32_t sin_1 = this->lfo_[LFO_Index::Sin1]->process(); - float32_t cos_1 = this->lfo_[LFO_Index::Cos1]->process(); - float32_t sin_2 = this->lfo_[LFO_Index::Sin2]->process(); - float32_t cos_2 = this->lfo_[LFO_Index::Cos2]->process(); + 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; @@ -67,7 +74,7 @@ void Chorus::processSample(float32_t inL, float32_t inR, float32_t& outL, float3 void Chorus::setRate(float32_t rate) { this->rate_ = constrain(rate, 0.0f, 1.0f); - for(unsigned i = 0; i < 4; ++i) + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) { this->lfo_[i]->setNormalizedFrequency(this->rate_); } @@ -84,7 +91,7 @@ void Chorus::setDepth(float32_t depth) if(this->depth_ != depth) { this->depth_ = depth; - this->fullscale_depth_ = this->depth_ * FULLSCALE_DEPTH_RATIO; + this->fullscale_depth_ = this->depth_ * CHORUS_FULLSCALE_DEPTH_RATIO; } } diff --git a/src/fx_chorus.h b/src/fx_chorus.h index d9710e9..a7575c4 100644 --- a/src/fx_chorus.h +++ b/src/fx_chorus.h @@ -22,22 +22,26 @@ #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 LFO_Index + enum LFOIndex { Sin1 = 0, Sin2, Cos1, - Cos2 + 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); @@ -47,7 +51,7 @@ public: float32_t getRate() const; private: - typedef FxEngine<2048, FORMAT_16_BIT, false> Engine; + typedef FxEngine<2048, Format::FORMAT_FLOAT32, false> Engine; Engine engine_; float32_t rate_; // Normalized frequency for the 2 LFOs frequencies (0.0 - 10.0) @@ -55,5 +59,68 @@ private: 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_[4]; + 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 index d689aa9..852563d 100644 --- a/src/fx_components.cpp +++ b/src/fx_components.cpp @@ -7,16 +7,266 @@ /////////////////////////////// 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; -///////////////////////// -// LFO implemlentation // -///////////////////////// -LFO::LFO(float32_t sampling_rate, Waveform waveform, float32_t min_frequency, float32_t max_frequency, float32_t initial_phase) : + +///////////////////////////// +// FastLFO implemlentation // +///////////////////////////// +FastLFO::FastLFO(float32_t sampling_rate, float32_t min_frequency, float32_t max_frequency, float32_t initial_phase) : + FXBase(sampling_rate), + InitialPhase(initial_phase), + min_frequency_(min_frequency), + max_frequency_(max_frequency), + frequency_(0.0f), + 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_); +} + +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->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->updateCoefficient(); + } +} + +float32_t FastLFO::getFrequency() const +{ + return this->frequency_; +} + +void FastLFO::updateCoefficient() +{ + float32_t frequency = this->unitary_frequency_; + + 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(); +} + +void FastLFO::reset() +{ + // 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_; + float32_t p = 0.0f; + float32_t t_p = this->InitialPhase - Constants::MPI_2; + if(t_p < 0.0f) + { + t_p += Constants::M2PI; + } + while(p < t_p) + { + this->process(); + p += p_i; + } +} + +float32_t FastLFO::process() +{ + float32_t temp = this->y0_; + this->y0_ = this->iir_coefficient_ * this->y0_ - this->y1_; + this->y1_ = temp; + this->current_ = (temp + 0.5f) * 2.0f - 1.0f; + + return this->current_; +} + +float32_t FastLFO::current() const +{ + return this->current_; +} + + +//////////////////////////////////////////////// +// InterpolatedSineOscillator implemlentation // +//////////////////////////////////////////////// +bool InterpolatedSineOscillator::ClassInitializer() +{ + 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::DataPoints[i] = std::sin(phase); + phase += phase_increment; + } + + return true; +} + +float32_t InterpolatedSineOscillator::DataPoints[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) : FXBase(sampling_rate), + InitialPhase(initial_phase), + min_frequency_(min_frequency), + max_frequency_(max_frequency), + 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 out = 0.0f; + + float32_t findex = this->phase_index_; + size_t index1 = static_cast(findex); + size_t index2 = index1 + 1; + + float32_t f1 = InterpolatedSineOscillator::DataPoints[index1]; + float32_t f2 = InterpolatedSineOscillator::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) : + FXBase(sampling_rate), + InitialPhase(initial_phase), min_frequency_(min_frequency), max_frequency_(max_frequency), - waveform_(waveform), normalized_frequency_(-1.0f), frequency_(0.0f), phase_(initial_phase), @@ -26,25 +276,28 @@ LFO::LFO(float32_t sampling_rate, Waveform waveform, float32_t min_frequency, fl rnd_generator_(rnd_device_()), rnd_distribution_(-1.0f, 1.0f) { - this->setWaveform(waveform); + assert(this->min_frequency_ <= this->max_frequency_); + assert(this->max_frequency_ < sampling_rate / 2.0f); + + this->setWaveform(Waveform::Sine); this->setFrequency(this->min_frequency_); } -LFO::~LFO() +ComplexLFO::~ComplexLFO() { } -void LFO::setWaveform(Waveform waveform) +void ComplexLFO::setWaveform(Waveform waveform) { this->waveform_ = waveform; } -LFO::Waveform LFO::getWaveform() const +ComplexLFO::Waveform ComplexLFO::getWaveform() const { return this->waveform_; } -void LFO::setNormalizedFrequency(float32_t normalized_frequency) +void ComplexLFO::setNormalizedFrequency(float32_t normalized_frequency) { normalized_frequency = constrain(normalized_frequency, 0.0f, 1.0f); if(this->normalized_frequency_ != normalized_frequency) @@ -56,12 +309,12 @@ void LFO::setNormalizedFrequency(float32_t normalized_frequency) } } -float32_t LFO::getNormalizedFrequency() const +float32_t ComplexLFO::getNormalizedFrequency() const { return this->normalized_frequency_; } -void LFO::setFrequency(float32_t frequency) +void ComplexLFO::setFrequency(float32_t frequency) { frequency = constrain(frequency, this->min_frequency_, this->max_frequency_); if(this->frequency_ != frequency) @@ -73,18 +326,24 @@ void LFO::setFrequency(float32_t frequency) } } -float32_t LFO::getFrequency() const +float32_t ComplexLFO::getFrequency() const { return this->frequency_; } -float32_t LFO::process() +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 = std::sin(this->phase_); + out = arm_sin_f32(this->phase_); break; case Waveform::Saw: out = Constants::M1_PI * this->phase_ - 1.0f; @@ -123,7 +382,7 @@ float32_t LFO::process() return out; } -float32_t LFO::current() const +float32_t ComplexLFO::current() const { return this->current_sample_; } @@ -132,7 +391,7 @@ float32_t LFO::current() const //////////////////////////////////// // JitterGenerator implementation // //////////////////////////////////// -JitterGenerator::JitterGenerator(float32_t sampling_rate) : +JitterGenerator::JitterGenerator(float32_t sampling_rate) : FXBase(sampling_rate), rnd_generator_(rnd_device_()), rnd_distribution_(-1.0f, 1.0f), @@ -151,6 +410,9 @@ 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; @@ -165,7 +427,7 @@ float32_t JitterGenerator::getSpeed() const void JitterGenerator::setMagnitude(float32_t magnitude) { - this->magnitude_ = magnitude; + this->magnitude_ = constrain(magnitude, 0.0f, 1.0f); } float32_t JitterGenerator::getMagnitude() const @@ -173,9 +435,14 @@ float32_t JitterGenerator::getMagnitude() const return this->magnitude_; } +void JitterGenerator::reset() +{ + this->phase_ = 0.0f; +} + float32_t JitterGenerator::process() { - float32_t out = std::sin(this->phase_); + float32_t out = arm_sin_f32(this->phase_); this->phase_ += this->phase_increment_ * (1.0f + this->magnitude_ * this->rnd_distribution_(this->rnd_generator_)); if(this->phase_ > Constants::M2PI) @@ -186,6 +453,116 @@ float32_t JitterGenerator::process() 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::getCurrent() 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 // ////////////////////////////////// @@ -198,7 +575,7 @@ float32_t softSaturator1(float32_t in, float32_t 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) @@ -208,14 +585,14 @@ float32_t softSaturator1(float32_t in, float32_t threshold) 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) { - constexpr static float kTubeCurve = 4.0f; - constexpr static float kTubeBias = 0.5f; + const static float kTubeCurve = 4.0f; + const static float kTubeBias = 0.5f; float absInput = std::abs(input); float output = 0.0f; @@ -247,6 +624,20 @@ float32_t softSaturator2(float32_t input, float32_t saturation) 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; diff --git a/src/fx_components.h b/src/fx_components.h index 619a444..d48060a 100644 --- a/src/fx_components.h +++ b/src/fx_components.h @@ -28,12 +28,195 @@ struct Constants { 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 LFO : public FXBase + +class FastLFO : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(FastLFO); + +public: + FastLFO(float32_t sampling_rate, float32_t min_frequency = 0.01f, float32_t max_frequency = 10.0f, float32_t initial_phase = 0.0f); + 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_; + float32_t frequency_; + float32_t normalized_frequency_; + float32_t unitary_frequency_; + + 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 InterpolatedSineOscillator : public FXBase { - DISALLOW_COPY_AND_ASSIGN(LFO); + DISALLOW_COPY_AND_ASSIGN(InterpolatedSineOscillator); + +public: + InterpolatedSineOscillator(float32_t sampling_rate, float32_t min_frequency = 0.01f, float32_t max_frequency = 10.0f, float32_t initial_phase = 0.0f); + 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 = 352800; + static const float32_t DeltaTime; + static float32_t DataPoints[]; + + const float32_t InitialPhase; + const float32_t min_frequency_; + const float32_t max_frequency_; + 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 { @@ -44,8 +227,8 @@ public: Noise } Waveform; - LFO(float32_t sampling_rate, Waveform waveform = Waveform::Sine, float32_t min_frequency = 0.01f, float32_t max_frequency = 10.0f, float32_t initial_phase = 0.0f); - ~LFO(); + ComplexLFO(float32_t sampling_rate, float32_t min_frequency = 0.01f, float32_t max_frequency = 10.0f, float32_t initial_phase = 0.0f); + virtual ~ComplexLFO(); void setWaveform(Waveform waveform); Waveform getWaveform() const; @@ -56,10 +239,12 @@ public: 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_; Waveform waveform_; @@ -72,163 +257,61 @@ private: std::random_device rnd_device_; std::mt19937 rnd_generator_; std::uniform_real_distribution rnd_distribution_; -}; -template -class Buffer -{ - DISALLOW_COPY_AND_ASSIGN(Buffer); - -public: - Buffer() : - index_(0) - { - this->values_ = new T*[nb_channels]; - for(unsigned i = 0; i < nb_channels; ++i) - { - this->values_[i] = new T[size]; - } - this->reset(); - } - - virtual ~Buffer() - { - for(unsigned i = 0; i < nb_channels; ++i) - { - delete[] this->values_[i]; - } - delete[] this->values_; - } - - void reset(bool reset_index = true) - { - this->zero(); - - if(reset_index) - { - this->index_ = 0; - } - } - - T& operator[](unsigned channel) - { - assert(channel < nb_channels); - return *(this->values_[channel] + this->index_); - } - - bool operator++() - { - this->index_++; - if(this->index_ >= size) - { - if(circular_buffer) - { - this->index_ = 0; - return true; - } - else - { - this->index_ = size - 1; - return false; - } - } - return true; - } - - bool operator--() - { - if(this->index_ > 0) - { - this->index_--; - return true; - } - else - { - if(circular_buffer) - { - this->index_ = size - 1; - return true; - } - else - { - this->index_ = 0; - return false; - } - } - } - - void copy(T* buffer, unsigned channel, unsigned nb, bool from_start = true) - { - assert(channel < nb_channels); - unsigned start = from_start ? 0 : this->index_; - unsigned _nb = std::min(nb, size - start); - memcpy(this->values_[channel] + start, buffer, _nb); - } - - void zero() - { - for(unsigned c = 0; c < nb_channels; ++c) - { - memset(this->values_[c], 0, size * sizeof(T)); - } - } - - void scale(T scale) - { - for(unsigned c = 0; c < nb_channels; ++c) - { - for(unsigned i = 0; i < size; ++i) - { - this->values_[c][i] *= scale; - } - } - } - - unsigned index() const - { - return this->index_; - } - - unsigned nbChannels() const - { - return nb_channels; - } - - unsigned bufferSize() const - { - return size; - } - - bool isCircularBuffer() const - { - return circular_buffer; - } - -private: - unsigned index_; - T** values_; + 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; + ) }; -template -class Buffer -{ - void scale(float32_t scale) - { - for(unsigned c = 0; c < nb_channels; ++c) - { - arm_scale_f32(this->values_[c], scale, this->values_[c], size); - } - } - - void copy(float32_t* buffer, unsigned channel, unsigned nb, bool from_start = true) - { - assert(channel < nb_channels); - unsigned start = from_start ? 0 : this->index_; - unsigned _nb = std::min(nb, size - start); - arm_copy_f32(buffer, this->values_[channel] + start, _nb); - } -}; +typedef ComplexLFO LFO; class JitterGenerator : public FXBase @@ -245,6 +328,7 @@ public: void setMagnitude(float32_t magnitude); float32_t getMagnitude() const; + virtual void reset() override; float32_t process(); private: @@ -255,9 +339,127 @@ private: 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 getCurrent() 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); \ No newline at end of file diff --git a/src/fx_delay.cpp b/src/fx_delay.cpp index af4a396..39ccbe2 100644 --- a/src/fx_delay.cpp +++ b/src/fx_delay.cpp @@ -2,18 +2,21 @@ #include -#define MAX_DELAY_TIME 1.0f -#define MAX_FLUTTER_DELAY_TIME 0.01f +#define MAX_DELAY_TIME 2.0f +#define MAX_FLUTTER_DELAY_TIME 0.001f -#define LPF_CUTOFF_REF 14000.0f -#define HPF_CUTOFF_REF 60.0f +#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::Type::LPF, LPF_CUTOFF_REF), - hpf_(sampling_rate, StateVariableFilter::Type::HPF, HPF_CUTOFF_REF) + hpf_(sampling_rate, StateVariableFilter::Type::HPF, HPF_CUTOFF_REF), + ratio_(1.0f) { this->setCutoffChangeRatio(0.0f); + this->lpf_.setGainDB(0.82f); + this->hpf_.setGainDB(0.82f); } Delay::LowHighPassFilter::~LowHighPassFilter() @@ -22,10 +25,22 @@ Delay::LowHighPassFilter::~LowHighPassFilter() void Delay::LowHighPassFilter::setCutoffChangeRatio(float32_t ratio) { - ratio += 1.0f; + static const float32_t weight = 4.0f; - this->lpf_.setCutoff(LPF_CUTOFF_REF * ratio); - this->hpf_.setCutoff(HPF_CUTOFF_REF * ratio); + 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) @@ -39,17 +54,19 @@ Delay::Delay(const float32_t sampling_rate, float32_t default_delay_time, float3 MaxSampleDelayTime((MAX_DELAY_TIME + MAX_FLUTTER_DELAY_TIME) * sampling_rate * MAX_DELAY_TIME), read_pos_L_(0), read_pos_R_(0), - filter_(sampling_rate) + filter_(sampling_rate), + jitter_generator_(sampling_rate) { this->buffer_L_ = new float32_t[this->MaxSampleDelayTime]; this->buffer_R_ = new float32_t[this->MaxSampleDelayTime]; - memset(this->buffer_L_, 0, this->MaxSampleDelayTime * sizeof(float32_t)); - memset(this->buffer_R_, 0, this->MaxSampleDelayTime * sizeof(float32_t)); - this->setLeftDelayTime(default_delay_time); this->setRightDelayTime(default_delay_time); - this->setFeedbak(default_feedback_level); + this->setFeedback(default_feedback_level); + this->setFlutterRate(0.2f); + this->setFlutterAmount(0.05f); + + this->reset(); } Delay::~Delay() @@ -58,10 +75,33 @@ Delay::~Delay() 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->read_pos_L_ = 0; + this->read_pos_R_ = 0; + this->filter_.reset(); + this->jitter_generator_.reset(); +} + void Delay::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) { - float32_t delay_time_L = (MAX_DELAY_TIME * this->getLeftDelayTime() ) * this->getSamplingRate(); - float32_t delay_time_R = (MAX_DELAY_TIME * this->getRightDelayTime()) * this->getSamplingRate(); + static const float32_t max_delay_time = MAX_DELAY_TIME * this->getSamplingRate(); + float32_t jitter_delay_time = 0.0f; + if(this->jitter_amount_ != 0.0f) + { + float32_t jitter_ratio = this->jitter_generator_.process(); + if(jitter_ratio != 0.0f) + { + jitter_ratio *= this->jitter_amount_; + jitter_delay_time = MAX_FLUTTER_DELAY_TIME * jitter_ratio * this->getSamplingRate(); + } + } + + // this->filter_.setCutoffChangeRatio(jitter_ratio); + float32_t delay_time_L = jitter_delay_time + max_delay_time * this->getLeftDelayTime(); + float32_t delay_time_R = jitter_delay_time + max_delay_time * this->getRightDelayTime(); // Calculate write positions unsigned write_pos_L = static_cast(this->MaxSampleDelayTime + this->read_pos_L_ + delay_time_L) % this->MaxSampleDelayTime; @@ -79,8 +119,8 @@ void Delay::processSample(float32_t inL, float32_t inR, float32_t& outL, float32 outR ); - this->buffer_L_[write_pos_L] += outL * this->getFeedbackLevel(); - this->buffer_R_[write_pos_R] += outR * this->getFeedbackLevel(); + this->buffer_L_[write_pos_L] += outL * this->getFeedback(); + this->buffer_R_[write_pos_R] += outR * this->getFeedback(); // Increment read positions ++this->read_pos_L_; @@ -115,12 +155,32 @@ float32_t Delay::getRightDelayTime() const return this->delay_time_R_; } -void Delay::setFeedbak(float32_t feedback) +void Delay::setFeedback(float32_t feedback) { this->feedback_ = constrain(feedback, 0.0, 1.0); } -float32_t Delay::getFeedbackLevel() const +float32_t Delay::getFeedback() const { return this->feedback_; } + +void Delay::setFlutterRate(float32_t rate) +{ + this->jitter_generator_.setRate(rate); +} + +float32_t Delay::getFlutterRate() const +{ + return this->jitter_generator_.getRate(); +} + +void Delay::setFlutterAmount(float32_t amount) +{ + this->jitter_amount_ = constrain(amount, 0.0f, 1.0f); +} + +float32_t Delay::getFlutterAmount() const +{ + return this->jitter_amount_; +} diff --git a/src/fx_delay.h b/src/fx_delay.h index 1e7494e..08dc879 100644 --- a/src/fx_delay.h +++ b/src/fx_delay.h @@ -22,8 +22,6 @@ #include "fx_components.h" #include "fx_svf.h" -#include - class Delay : public FXElement { DISALLOW_COPY_AND_ASSIGN(Delay); @@ -36,6 +34,7 @@ class Delay : public FXElement 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); @@ -43,13 +42,57 @@ class Delay : public FXElement 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); @@ -58,18 +101,118 @@ public: void setRightDelayTime(float32_t delay_time); float32_t getRightDelayTime() const; - void setFeedbak(float32_t feedback); - float32_t getFeedbackLevel() const; + void setFeedback(float32_t feedback); + float32_t getFeedback() const; + + void setFlutterRate(float32_t rate); + float32_t getFlutterRate() const; + + void setFlutterAmount(float32_t amount); + float32_t getFlutterAmount() const; private: const size_t MaxSampleDelayTime; - size_t read_pos_L_; - size_t read_pos_R_; + unsigned read_pos_L_; + unsigned read_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) + float32_t jitter_amount_; LowHighPassFilter filter_; + PerlinNoiseGenerator jitter_generator_; + + 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, '|', "read_pos_L_"); + SS__TEXT(ss, ' ', space, std::left, '|', "read_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_"); + SS__TEXT(ss, ' ', space, std::left, '|', "jitter_amount_"); + SS__TEXT(ss, ' ', space, std::left, '|', "filter_"); + 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->read_pos_L_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->read_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_); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->jitter_amount_); + 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_"); + this->jitter_generator_.dump(out, deepInspection, tag + ".jitter_generator_"); + } + + out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl; + ) + + IMPLEMENT_INSPECT( + size_t nb_errors = 0; + nb_errors += inspector(tag + ".read_pos_L_", static_cast(this->read_pos_L_), 0.0f, static_cast(this->MaxSampleDelayTime), deepInspection); + nb_errors += inspector(tag + ".read_pos_R_", static_cast(this->read_pos_R_), 0.0f, static_cast(this->MaxSampleDelayTime), deepInspection); + nb_errors += inspector(tag + ".delay_time_L_", this->delay_time_L_, 0.0f, static_cast(this->MaxSampleDelayTime), deepInspection); + nb_errors += inspector(tag + ".delay_time_R_", this->delay_time_R_, 0.0f, static_cast(this->MaxSampleDelayTime), deepInspection); + nb_errors += inspector(tag + ".feedback_", this->feedback_, 0.0f, 1.0f, deepInspection); + nb_errors += inspector(tag + ".jitter_amount_", this->jitter_amount_, 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_"); + nb_errors += this->jitter_generator_.inspect(inspector, deepInspection, tag + ".jitter_generator_"); + } + + return nb_errors; + ) }; diff --git a/src/fx_engine.hpp b/src/fx_engine.hpp index 3975631..d8b5008 100644 --- a/src/fx_engine.hpp +++ b/src/fx_engine.hpp @@ -1,26 +1,22 @@ #pragma once -#include "fx_components.h" +#if defined(DEBUG) +#include +#include +#endif #include #include #include -#define MAKE_INTEGRAL_FRACTIONAL(x) \ - int32_t x ## _integral = static_cast(x); \ - float x ## _fractional = x - static_cast(x ## _integral); +#include "fx_components.h" enum Format { FORMAT_12_BIT, FORMAT_16_BIT, - FORMAT_32_BIT -}; - -enum LFOIndex -{ - LFO_1, - LFO_2 + FORMAT_32_BIT, + FORMAT_FLOAT32 }; template @@ -44,7 +40,7 @@ inline int16_t clip16(int32_t x) } template <> -struct DataType +struct DataType { typedef uint16_t T; @@ -59,9 +55,57 @@ struct DataType } }; +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 = FORMAT_16_BIT, + Format format, bool enable_lfo = true> class FxEngine : public FXBase { @@ -70,12 +114,19 @@ class FxEngine : public FXBase public: typedef typename DataType::T T; - FxEngine(float32_t sampling_rate, float32_t max_lfo1_frequency = 1.0f, float32_t max_lfo2_frequency = 1.0f) : - FXBase(sampling_rate) + enum LFOIndex { - this->buffer_ = new uint16_t[size]; - this->lfo_[LFO_1] = enable_lfo ? new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, max_lfo1_frequency) : nullptr; - this->lfo_[LFO_2] = enable_lfo ? new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, max_lfo2_frequency) : nullptr; + 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) : nullptr; this->clear(); } @@ -84,17 +135,25 @@ public: delete[] this->buffer_; if(enable_lfo) { - delete this->lfo_[LFO_1]; - delete this->lfo_[LFO_2]; + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) delete this->lfo_[i]; } } void clear() { - memset(this->buffer_, 0, size * sizeof(uint16_t)); + 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 { }; @@ -141,7 +200,7 @@ public: buffer_(nullptr), write_ptr_(0) { - memset(this->lfo_value_, 0, 2 * sizeof(float32_t)); + memset(this->lfo_value_, 0, LFOIndex::kLFOCount * sizeof(float32_t)); } ~Context() @@ -177,7 +236,7 @@ public: template inline void write(D& d, int32_t offset, float32_t scale) { - assert(D::base + D::length <= size); + assert((D::base + D::length) <= size); T w = DataType::compress(this->accumulator_); if(offset == -1) { @@ -212,7 +271,7 @@ public: template inline void read(D& d, int32_t offset, float32_t scale) { - assert(D::base + D::length <= size); + assert((D::base + D::length) <= size); T r; if(offset == -1) { @@ -248,11 +307,15 @@ public: template inline void interpolate(D& d, float32_t offset, float32_t scale) { - assert(D::base + D::length <= size); - MAKE_INTEGRAL_FRACTIONAL(offset); + assert((D::base + D::length) <= size); + + int32_t offset_integral = static_cast(offset); + float32_t offset_fractional = offset - static_cast(offset_integral); + float32_t a = DataType::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base) & MASK]); float32_t b = DataType::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base + 1) & MASK]); float32_t x = a + (b - a) * offset_fractional; + this->previous_read_ = x; this->accumulator_ += x * scale; } @@ -260,26 +323,22 @@ public: template inline void interpolate(D& d, float32_t offset, LFOIndex index, float32_t amplitude, float32_t scale) { - assert(D::base + D::length <= size); - offset += amplitude * this->lfo_value_[index]; - MAKE_INTEGRAL_FRACTIONAL(offset); - float32_t a = DataType::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base) & MASK]); - float32_t b = DataType::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base + 1) & MASK]); - float32_t x = a + (b - a) * offset_fractional; - this->previous_read_ = x; - this->accumulator_ += x * scale; + assert(index < LFOIndex::kLFOCount); + + this->interpolate(d, offset + amplitude * this->lfo_value_[index], scale); } private: float32_t accumulator_; float32_t previous_read_; - float32_t lfo_value_[2]; + 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); @@ -288,13 +347,14 @@ public: 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) + inline void start(Context* c) { --this->write_ptr_; if(this->write_ptr_ < 0) @@ -303,12 +363,14 @@ public: } c->accumulator_ = 0.0f; c->previous_read_ = 0.0f; - c->buffer_ = buffer_; - c->write_ptr_ = write_ptr_; + c->buffer_ = this->buffer_; + c->write_ptr_ = this->write_ptr_; if(enable_lfo) { - c->lfo_value_[LFO_1] = this->lfo_[LFO_1]->process(); - c->lfo_value_[LFO_2] = this->lfo_[LFO_2]->process(); + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) + { + c->lfo_value_[i] = this->lfo_[i]->process(); + } } } @@ -318,8 +380,86 @@ private: MASK = size - 1 }; - uint16_t* buffer_; - unsigned write_ptr_; + 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 < size; ++i) + { + this->lfo_[i]->inspect(inspector, deepInspection, tag + ".lfo_[ " + std::to_string(i) + " ]"); + } + } + } + + return nb_errors; - LFO* lfo_[2]; + ) }; diff --git a/src/fx_flanger.cpp b/src/fx_flanger.cpp index b8325e2..1d66e0b 100644 --- a/src/fx_flanger.cpp +++ b/src/fx_flanger.cpp @@ -10,16 +10,14 @@ Flanger::Flanger(float32_t sampling_rate, float32_t rate, float32_t depth, float this->delay_lineL_ = new float32_t[this->MaxDelayLineSize]; this->delay_lineR_ = new float32_t[this->MaxDelayLineSize]; - 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, 2 * sizeof(float32_t)); - - this->lfo_[LFO_Index::LFO_L] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.1f, 5.0f); - this->lfo_[LFO_Index::LFO_R] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.1f, 5.0f, Constants::MPI_2); + this->lfo_[LFOIndex::LFO_L] = new LFO(sampling_rate, 0.1f, 5.0f); + this->lfo_[LFOIndex::LFO_R] = new LFO(sampling_rate, 0.1f, 5.0f, Constants::MPI_2); this->setRate(rate); this->setDepth(depth); this->setFeedback(feedback); + + this->reset(); } Flanger::~Flanger() @@ -27,8 +25,10 @@ Flanger::~Flanger() delete[] this->delay_lineL_; delete[] this->delay_lineR_; - delete this->lfo_[LFO_Index::LFO_L]; - delete this->lfo_[LFO_Index::LFO_R]; + 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) @@ -36,6 +36,19 @@ inline float32_t linearIterpolationnterp(float32_t inX, float32_t inY, float32_t 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, 2 * 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 @@ -49,8 +62,8 @@ void Flanger::processSample(float32_t inL, float32_t inR, float32_t& outL, float } // Configure LFO for effect processing - float32_t lfo_l = this->lfo_[LFO_L]->process() * this->depth_; - float32_t lfo_r = this->lfo_[LFO_R]->process() * this->depth_; + 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); @@ -95,8 +108,8 @@ void Flanger::processSample(float32_t inL, float32_t inR, float32_t& outL, float float32_t delay_sample_r = linearIterpolationnterp(this->delay_lineR_[currentR], this->delay_lineR_[nextR], fractionR); // Store delayed samples as feedback - this->feedback_samples_[0] = delay_sample_l * this->feedback_; - this->feedback_samples_[1] = delay_sample_r * this->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; outR = delay_sample_r; @@ -104,13 +117,13 @@ void Flanger::processSample(float32_t inL, float32_t inR, float32_t& outL, float void Flanger::setRate(float32_t rate) { - this->lfo_[LFO_Index::LFO_L]->setNormalizedFrequency(rate); - this->lfo_[LFO_Index::LFO_R]->setNormalizedFrequency(rate); + this->lfo_[LFOIndex::LFO_L]->setNormalizedFrequency(rate); + this->lfo_[LFOIndex::LFO_R]->setNormalizedFrequency(rate); } float32_t Flanger::getRate() const { - return this->lfo_[LFO_Index::LFO_L]->getNormalizedFrequency(); + return this->lfo_[LFOIndex::LFO_L]->getNormalizedFrequency(); } void Flanger::setDepth(float32_t depth) diff --git a/src/fx_flanger.h b/src/fx_flanger.h index 6e6377c..8a52bf5 100644 --- a/src/fx_flanger.h +++ b/src/fx_flanger.h @@ -27,15 +27,17 @@ class Flanger : public FXElement DISALLOW_COPY_AND_ASSIGN(Flanger); public: - enum LFO_Index + enum LFOIndex { LFO_L = 0, - LFO_R + 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); @@ -52,9 +54,101 @@ private: float32_t* delay_lineL_; float32_t* delay_lineR_; unsigned write_index_; - float32_t feedback_samples_[2]; + float32_t feedback_samples_[StereoChannels::kNumChannels]; - LFO* lfo_[2]; + 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 -}; \ No newline at end of file + + 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 index 7434e8d..e19a52f 100644 --- a/src/fx_orbitone.cpp +++ b/src/fx_orbitone.cpp @@ -5,23 +5,21 @@ #define LFO_SLOW_MAX_FREQUENCY 1.0f #define LFO_FAST_MAX_FREQUENCY 8.8f -#define FULLSCALE_DEPTH_RATIO 256.0f - Orbitone::Orbitone(float32_t sampling_rate, float32_t rate, float32_t depth) : FXElement(sampling_rate), - engine_(sampling_rate, 0.0f, 0.0f), + engine_(sampling_rate, 0.0f), depth_(0.0f), fullscale_depth_(0.0f) { - this->lfo_[LFO_Index::Slow0 ] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO_SLOW_MAX_FREQUENCY, 0.0f); - this->lfo_[LFO_Index::Slow120] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO_SLOW_MAX_FREQUENCY, 2.0f * PI / 3.0); - this->lfo_[LFO_Index::Slow240] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO_SLOW_MAX_FREQUENCY, 4.0f * PI / 3.0); + this->lfo_[LFOIndex::Slow0 ] = new LFO(sampling_rate, 0.0f, LFO_SLOW_MAX_FREQUENCY, 0.0f); + this->lfo_[LFOIndex::Slow120] = new LFO(sampling_rate, 0.0f, LFO_SLOW_MAX_FREQUENCY, 2.0f * PI / 3.0); + this->lfo_[LFOIndex::Slow240] = new LFO(sampling_rate, 0.0f, LFO_SLOW_MAX_FREQUENCY, 4.0f * PI / 3.0); - this->lfo_[LFO_Index::Fast0 ] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO_FAST_MAX_FREQUENCY, 0.0f); - this->lfo_[LFO_Index::Fast120] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO_FAST_MAX_FREQUENCY, 2.0f * PI / 3.0); - this->lfo_[LFO_Index::Fast240] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO_FAST_MAX_FREQUENCY, 4.0f * PI / 3.0); + this->lfo_[LFOIndex::Fast0 ] = new LFO(sampling_rate, 0.0f, LFO_FAST_MAX_FREQUENCY, 0.0f); + this->lfo_[LFOIndex::Fast120] = new LFO(sampling_rate, 0.0f, LFO_FAST_MAX_FREQUENCY, 2.0f * PI / 3.0); + this->lfo_[LFOIndex::Fast240] = new LFO(sampling_rate, 0.0f, LFO_FAST_MAX_FREQUENCY, 4.0f * PI / 3.0); - for(unsigned i = 0; i < 6; ++i) + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) { this->lfo_[i]->setNormalizedFrequency(rate); } @@ -31,12 +29,21 @@ Orbitone::Orbitone(float32_t sampling_rate, float32_t rate, float32_t depth) : Orbitone::~Orbitone() { - for(unsigned i = 0; i < 6; ++i) + 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; @@ -46,13 +53,13 @@ void Orbitone::processSample(float32_t inL, float32_t inR, float32_t& outL, floa this->engine_.start(&c); - float32_t slow_0 = this->lfo_[LFO_Index::Slow0 ]->process(); - float32_t slow_120 = this->lfo_[LFO_Index::Slow120]->process(); - float32_t slow_240 = this->lfo_[LFO_Index::Slow240]->process(); + 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_[LFO_Index::Fast0 ]->process(); - float32_t fast_120 = this->lfo_[LFO_Index::Fast120]->process(); - float32_t fast_240 = this->lfo_[LFO_Index::Fast240]->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; @@ -85,9 +92,9 @@ void Orbitone::processSample(float32_t inL, float32_t inR, float32_t& outL, floa void Orbitone::setRate(float32_t rate) { rate = constrain(rate, 0.0f, 1.0f); - if(this->lfo_[LFO_Index::Slow0]->getNormalizedFrequency() != rate) + if(this->lfo_[LFOIndex::Slow0]->getNormalizedFrequency() != rate) { - for(unsigned i = 0; i < 6; ++i) + for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) { this->lfo_[i]->setNormalizedFrequency(rate); } @@ -96,7 +103,7 @@ void Orbitone::setRate(float32_t rate) float32_t Orbitone::getRate() const { - return this->lfo_[LFO_Index::Slow0]->getNormalizedFrequency(); + return this->lfo_[LFOIndex::Slow0]->getNormalizedFrequency(); } void Orbitone::setDepth(float32_t depth) @@ -105,7 +112,7 @@ void Orbitone::setDepth(float32_t depth) if(this->depth_ != depth) { this->depth_ = depth; - this->fullscale_depth_ = this->depth_ * FULLSCALE_DEPTH_RATIO; + this->fullscale_depth_ = this->depth_ * ORBITONE_FULLSCALE_DEPTH_RATIO; } } diff --git a/src/fx_orbitone.h b/src/fx_orbitone.h index 6c5dfd3..bd0b9ff 100644 --- a/src/fx_orbitone.h +++ b/src/fx_orbitone.h @@ -22,24 +22,28 @@ #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 LFO_Index + enum LFOIndex { Slow0 = 0, Slow120, Slow240, Fast0, Fast120, - Fast240 + 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); @@ -49,11 +53,66 @@ public: float32_t getDepth() const; private: - typedef FxEngine<4096, FORMAT_16_BIT, false> Engine; + typedef FxEngine<4096, Format::FORMAT_FLOAT32, false> Engine; Engine engine_; float32_t depth_; float32_t fullscale_depth_; - LFO* lfo_[6]; -}; \ No newline at end of file + 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 index 2afb5d8..7b81508 100644 --- a/src/fx_phaser.cpp +++ b/src/fx_phaser.cpp @@ -3,17 +3,23 @@ #include #include -AllpassDelay::AllpassDelay() : +Phaser::AllpassDelay::AllpassDelay() : + FXElement(0.0f), a1_(0.0f) { - memset(this->z_, 0, 2 * sizeof(float32_t)); + this->reset(); } -AllpassDelay::~AllpassDelay() +Phaser::AllpassDelay::~AllpassDelay() { } -void AllpassDelay::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +void Phaser::AllpassDelay::reset() +{ + memset(this->z_, 0, 2 * sizeof(float32_t)); +} + +void Phaser::AllpassDelay::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) { outL = inL * -this->a1_ + this->z_[0]; this->z_[0] = outL * this->a1_ + inL; @@ -22,15 +28,15 @@ void AllpassDelay::processSample(float32_t inL, float32_t inR, float32_t& outL, this->z_[1] = outR * this->a1_ + inR; } -void AllpassDelay::setDelay(float32_t delay) +void Phaser::AllpassDelay::setDelay(float32_t delay) { this->a1_ = (1.0f - delay) / (1.0f + delay); } -Phaser::Phaser(float32_t sampling_rate, float32_t rate, float32_t depth, float32_t feedback) : +Phaser::Phaser(float32_t sampling_rate, float32_t rate, float32_t depth, float32_t feedback, unsigned nb_stages) : FXElement(sampling_rate), - lfo_(sampling_rate, LFO::Waveform::Sine, 0.0f, 2.5f), + lfo_(sampling_rate, 0.0f, 2.5f), depth_(0.0f), feedback_(0.0f), dmin_(0.0f), @@ -39,15 +45,27 @@ Phaser::Phaser(float32_t sampling_rate, float32_t rate, float32_t depth, float32 this->setRate(rate); this->setDepth(depth); this->setFeedback(feedback); + this->setNbStages(nb_stages); this->setFrequencyRange(440.0f, 1600.0f); - memset(this->z_, 0, 2 * sizeof(float32_t)); + this->reset(); } Phaser::~Phaser() { } +void Phaser::reset() +{ + memset(this->z_, 0, 2 * sizeof(float32_t)); + + for(unsigned i = 0; i < MAX_NB_PHASES; ++i) + { + this->stages_[i].reset(); + } + this->lfo_.reset(); +} + void Phaser::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) { float32_t d = this->dmin_ + (this->dmax_ - this->dmin_) * ((1.0f + this->lfo_.process()) / 2.0f); @@ -63,8 +81,8 @@ void Phaser::processSample(float32_t inL, float32_t inR, float32_t& outL, float3 this->z_[0] = sampleL; this->z_[1] = sampleR; - outL = inL + this->z_[0] * this->depth_; - outR = inR + this->z_[1] * this->depth_; + outL = inL + this->z_[StereoChannels::Left ] * this->depth_; + outR = inR + this->z_[StereoChannels::Right] * this->depth_; } void Phaser::setFrequencyRange(float32_t min_frequency, float32_t max_frequency) diff --git a/src/fx_phaser.h b/src/fx_phaser.h index fa6390c..702816b 100644 --- a/src/fx_phaser.h +++ b/src/fx_phaser.h @@ -20,34 +20,68 @@ #include "fx_components.h" -class AllpassDelay +#define MAX_NB_PHASES 24 + +class Phaser : public FXElement { - DISALLOW_COPY_AND_ASSIGN(AllpassDelay); + DISALLOW_COPY_AND_ASSIGN(Phaser); public: - AllpassDelay(); - virtual ~AllpassDelay(); + class AllpassDelay : public FXElement + { + DISALLOW_COPY_AND_ASSIGN(AllpassDelay); - virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR); + public: + AllpassDelay(); + virtual ~AllpassDelay(); - void setDelay(float32_t delay); + virtual void reset(); + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR); -private: - float32_t a1_; - float32_t z_[2]; -}; + void setDelay(float32_t delay); + private: + float32_t a1_; + float32_t z_[StereoChannels::kNumChannels]; -#define MAX_NB_PHASES 24 + IMPLEMENT_DUMP( + const size_t space = 10; + const size_t precision = 6; -class Phaser : public FXElement -{ - DISALLOW_COPY_AND_ASSIGN(Phaser); + std::stringstream ss; -public: - Phaser(float32_t sampling_rate, float32_t rate = 0.5f, float32_t depth = 1.0f, float32_t feedback = 0.7f); + 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); @@ -72,5 +106,68 @@ private: float32_t dmax_; unsigned nb_stages_; AllpassDelay stages_[MAX_NB_PHASES]; - float32_t z_[2]; + 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_.dump(out, deepInspection, tag + ".lfo_"); + 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_.inspect(inspector, deepInspection, tag + ".lfo_"); + 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_rack.cpp b/src/fx_rack.cpp index 1021e14..89e0589 100644 --- a/src/fx_rack.cpp +++ b/src/fx_rack.cpp @@ -39,9 +39,19 @@ FXRack::~FXRack() delete this->fxShimmerReverb_; } +inline void FXRack::reset() +{ + auto end = this->fx_chain_.end(); + for(FXChain::iterator it = this->fx_chain_.begin(); it != end; it++) + { + (*it)->reset();; + } +} + inline void FXRack::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) { - for(FXChain::iterator it = this->fx_chain_.begin(); it != this->fx_chain_.end(); it++) + FXChain::iterator end = this->fx_chain_.end(); + for(FXChain::iterator it = this->fx_chain_.begin(); it != end; it++) { (*it)->processSample(inL, inR, outL, outR); diff --git a/src/fx_rack.h b/src/fx_rack.h index 9e013c7..e81778c 100644 --- a/src/fx_rack.h +++ b/src/fx_rack.h @@ -19,7 +19,6 @@ #pragma once #include "fx.h" -#include "fx_unit.hpp" #include "fx_tube.h" #include "fx_chorus.h" #include "fx_flanger.h" @@ -27,6 +26,7 @@ #include "fx_phaser.h" #include "fx_delay.h" #include "fx_shimmer_reverb.h" +#include "fx_unit.hpp" #include @@ -40,6 +40,7 @@ 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; @@ -71,4 +72,61 @@ private: FXUnit* fxPhaser_; FXUnit* fxDelay_; FXUnit* fxShimmerReverb_; + + 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->fxShimmerReverb_->dump(out, deepInspection, tag + ".fxShimmerReverb_"); + } + + 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->fxShimmerReverb_->inspect(inspector, deepInspection, tag + ".fxShimmerReverb_"); + } + + return nb_errors; + ) }; \ No newline at end of file diff --git a/src/fx_shimmer_reverb.cpp b/src/fx_shimmer_reverb.cpp index 18c12c7..b163421 100644 --- a/src/fx_shimmer_reverb.cpp +++ b/src/fx_shimmer_reverb.cpp @@ -5,7 +5,6 @@ #define TAIL , -1 - ShimmerReverb::ShimmerReverb(float32_t sampling_rate) : FXElement(sampling_rate), engine_(sampling_rate), @@ -13,8 +12,9 @@ ShimmerReverb::ShimmerReverb(float32_t sampling_rate) : diffusion_(-1.0f), lp_(-1.0f) { - this->engine_.setLFOFrequency(LFO_1, 0.5f); - this->engine_.setLFOFrequency(LFO_2, 0.3f); + this->engine_.setLFOFrequency(Engine::LFOIndex::LFO_1, 0.5f); + this->engine_.setLFOFrequency(Engine::LFOIndex::LFO_2, 0.3f); + this->setInputGain(1.0f); this->setLP(0.7f); this->setDiffusion(0.625f); @@ -24,22 +24,27 @@ ShimmerReverb::~ShimmerReverb() { } +void ShimmerReverb::reset() +{ + this->engine_.reset(); +} + void ShimmerReverb::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; + 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; @@ -60,14 +65,13 @@ void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t lp_1 = this->lp_decay_1_; float32_t lp_2 = this->lp_decay_2_; - float32_t wet; + float32_t wet = 0.0f; float32_t apout = 0.0f; engine_.start(&c); // Smear AP1 inside the loop. - c.interpolate(ap1, 10.0f, LFO_1, 60.0f, 1.0f); + c.interpolate(ap1, 10.0f, Engine::LFOIndex::LFO_1, 60.0f, 1.0f); c.write(ap1, 100, 0.0f); - c.read(inL + inR, gain); // Diffuse through 4 allpasses. @@ -83,7 +87,7 @@ void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL, // Main reverb loop. c.load(apout); - c.interpolate(del2, 4680.0f, LFO_2, 100.0f, krt); + 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); @@ -92,10 +96,10 @@ void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL, c.write(del1, 2.0f); c.write(wet, 0.0f); - outR += wet; + outR = wet; c.load(apout); - // c.Interpolate(del1, 4450.0f, LFO_1, 50.0f, krt); + c.interpolate(del1, 4450.0f, Engine::LFOIndex::LFO_1, 50.0f, krt); c.read(del1 TAIL, krt); c.lp(lp_2, klp); c.read(dap2a TAIL, kap); @@ -105,8 +109,8 @@ void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL, c.write(del2, 2.0f); c.write(wet, 0.0f); - outR += wet; - + outR = wet; + this->lp_decay_1_ = lp_1; this->lp_decay_2_ = lp_2; } diff --git a/src/fx_shimmer_reverb.h b/src/fx_shimmer_reverb.h index 9369336..612cf8e 100644 --- a/src/fx_shimmer_reverb.h +++ b/src/fx_shimmer_reverb.h @@ -23,15 +23,17 @@ #include "fx_components.h" #include "fx_engine.hpp" +#define SHIMMER_REVERB_BUFFER_SIZE 16384 + 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 gain); @@ -47,7 +49,7 @@ public: float32_t getLP() const; private: - typedef FxEngine<16384, FORMAT_16_BIT, true> Engine; + typedef FxEngine Engine; Engine engine_; float32_t input_gain_; @@ -57,4 +59,7 @@ private: float32_t lp_decay_1_; float32_t lp_decay_2_; + + IMPLEMENT_DUMP() + IMPLEMENT_INSPECT(return 0u;) }; diff --git a/src/fx_svf.cpp b/src/fx_svf.cpp index 22451ad..bf34825 100644 --- a/src/fx_svf.cpp +++ b/src/fx_svf.cpp @@ -5,16 +5,15 @@ StateVariableFilter::StateVariableFilter(float32_t sampling_rate, Type type, float32_t cutoff) : FXElement(sampling_rate), type_(type), - cutoff_(0.0f), - resonance_(0.0f), - peak_gain_(0.0f) + gain_(-1.0f), + cutoff_(cutoff), + resonance_(0.0f) { - memset(this->z1_, 0, 2 * sizeof(float32_t)); - memset(this->z2_, 0, 2 * sizeof(float32_t)); - - this->setPeakGainDB(1.0f); this->setCutoff(cutoff); this->setResonance(0.0f); + this->setGainDB(0.0f); + + this->reset(); } StateVariableFilter::~StateVariableFilter() @@ -32,7 +31,9 @@ void StateVariableFilter::setFilterType(Type type) void StateVariableFilter::setCutoff(float32_t cutoff) { - cutoff = constrain(cutoff, 1.0f, this->getSamplingRate() / 2.0f); + static const float32_t max_frequency = 0.45f * this->getSamplingRate(); + + cutoff = constrain(cutoff, 1.0f, max_frequency); if(this->cutoff_ != cutoff) { this->cutoff_ = cutoff; @@ -50,11 +51,13 @@ void StateVariableFilter::setResonance(float32_t resonance) } } -void StateVariableFilter::setPeakGainDB(float32_t gain) +void StateVariableFilter::setGainDB(float32_t gainDB) { - if(this->peak_gain_ != gain) + gainDB = constrain(gainDB, -1.0f, 1.0f); + if(this->gain_ != gainDB) { - this->peak_gain_ = gain; + this->gain_ = gainDB; + this->g_ = std::pow(10.0f, 1.2f * this->gain_); this->updateCoefficients(); } } @@ -62,124 +65,87 @@ void StateVariableFilter::setPeakGainDB(float32_t gain) void StateVariableFilter::updateCoefficients() { // Compute the filter coefficients based on the current parameter values - float32_t w0 = PI * this->cutoff_ / this->getSamplingRate(); - float32_t V = pow(10, fabs(this->peak_gain_) / 20.0f); - float32_t K = std::tan(w0); - float32_t K2 = K * K; - float32_t norm; + 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->type_) { case Type::LPF: - norm = 1.0f / (1.0f + K / this->resonance_ + K2); - this->a0_ = K2 * norm; - this->a1_ = 2.0f * this->a0_; - this->a2_ = this->a0_; - this->b1_ = 2.0f * (K2 - 1.0f) * norm; - this->b2_ = (1.0f - K / this->resonance_ + K2) * norm; + this->d1_ = 0.0f; + this->d0_ = 0.25f * this->c1_ * this->c2_; break; case Type::HPF: - norm = 1.0f / (1.0f + K / this->resonance_ + K2); - this->a0_ = norm; - this->a1_ = -2.0f * this->a0_; - this->a2_ = this->a0_; - this->b1_ = 2.0f * (K2 - 1.0f) * norm; - this->b2_ = (1.0f - K / this->resonance_ + K2) * norm; + this->d1_ = 0.0f; + this->d0_ = 1.0f - 0.5f * this->c1_ + 0.25f * this->c1_ * this->c2_; break; case Type::BPF: - norm = 1.0f / (1.0f + K / this->resonance_ + K2); - this->a0_ = K / this->resonance_ * norm; - this->a1_ = 0.0f; - this->a2_ = -this->a0_; - this->b1_ = 2.0f * (K2 - 1.0f) * norm; - this->b2_ = (1.0f - K / this->resonance_ + K2) * norm; - break; - case Type::NOTCH: - norm = 1.0f / (1.0f + K / this->resonance_ + K2); - this->a0_ = (1.0f + K2) * norm; - this->a1_ = 2.0f * (K2 - 1.0f) * norm; - this->a2_ = this->a0_; - this->b1_ = 2.0f * (K2 - 1.0f) * norm; - this->b2_ = (1.0f - K / this->resonance_ + K2) * norm; + this->d1_ = 1.0f - this->c2_; + this->d0_ = this->d1_ * this->c1_ * 0.5f; break; - case Type::PEQ: - if(this->peak_gain_ >= 0) + } + + 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->type_) + { + case Type::LPF: { - // boost - norm = 1.0f / (1.0f + 1.0f / this->resonance_ * K + K2); - this->a0_ = (1.0f + V / this->resonance_ * K + K2) * norm; - this->a1_ = 2.0f * (K2 - 1) * norm; - this->a2_ = (1.0f - V / this->resonance_ * K + K2) * norm; - this->b1_ = this->a1_; - this->b2_ = (1.0f - 1.0f / this->resonance_ * K + K2) * norm; + 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; } - else { - // cut - norm = 1.0f / (1 + V / this->resonance_ * K + K2); - this->a0_ = (1.0f + 1.0f / this->resonance_ * K + K2) * norm; - this->a1_ = 2.0f * (K2 - 1) * norm; - this->a2_ = (1.0f - 1.0f / this->resonance_ * K + K2) * norm; - this->b1_ = this->a1_; - this->b2_ = (1.0f - V / this->resonance_ * K + K2) * norm; + 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 Type::LSH: - if(this->peak_gain_ >= 0) - { - // boost - norm = 1 / (1 + std::sqrt(2) * K + K2); - this->a0_ = (1.0f + std::sqrt(2.0f * V) * K + V * K2) * norm; - this->a1_ = 2.0f * (V * K2 - 1.0f) * norm; - this->a2_ = (1.0f - std::sqrt(2.0f * V) * K + V * K2) * norm; - this->b1_ = 2.0f * (K2 - 1.0f) * norm; - this->b2_ = (1.0f - std::sqrt(2.0f) * K + K2) * norm; + + case Type::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; } - else - { - // cutK * K - norm = 1.0f / (1.0f + std::sqrt(2.0f * V) * K + V * K2); - this->a0_ = (1.0f + std::sqrt(2.0f) * K + K2) * norm; - this->a1_ = 2.0f * (K2 - 1.0f) * norm; - this->a2_ = (1.0f - std::sqrt(2.0f) * K + K2) * norm; - this->b1_ = 2.0f * (V * K2 - 1.0f) * norm; - this->b2_ = (1.0f - std::sqrt(2.0f * V) * K + V * K2) * norm; + { + 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 Type::HSH: - if(this->peak_gain_ >= 0) + + case Type::BPF: { - // boost - norm = 1.0f / (1.0f + std::sqrt(2.0f) * K + K2); - this->a0_ = (V + std::sqrt(2.0f * V) * K + K2) * norm; - this->a1_ = 2.0f * (K2 - V) * norm; - this->a2_ = (V - std::sqrt(2.0f * V) * K + K2) * norm; - this->b1_ = 2.0f * (K2 - 1.0f) * norm; - this->b2_ = (1.0f - std::sqrt(2.0f) * K + K2) * norm; + 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; } - else { - // cut - norm = 1.0f / (V + std::sqrt(2.0f * V) * K + K2); - this->a0_ = (1.0f + std::sqrt(2.0f) * K + K2) * norm; - this->a1_ = 2.0f * (K2 - 1.0f) * norm; - this->a2_ = (1.0f - std::sqrt(2.0f) * K + K2) * norm; - this->b1_ = 2.0f * (K2 - V) * norm; - this->b2_ = (V - std::sqrt(2.0f * V) * K + K2) * norm; + 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; + break; } } - -void StateVariableFilter::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) -{ - const float32_t gain = 10.0f; - - outL = (inL * this->a0_ + this->z1_[0]) * gain; - this->z1_[0] = inL * this->a1_ + this->z2_[0] - this->b1_ * outL; - this->z2_[0] = inL * this->a2_ - this->b2_ * outL; - - outR = (inR * this->a0_ + this->z1_[1]) * gain; - this->z1_[0] = inR * this->a1_ + this->z2_[1] - this->b1_ * outR; - this->z2_[0] = inR * this->a2_ - this->b2_ * outR; -} diff --git a/src/fx_svf.h b/src/fx_svf.h index cf81adf..ef94862 100644 --- a/src/fx_svf.h +++ b/src/fx_svf.h @@ -29,35 +29,98 @@ public: { LPF, // Low pass filter HPF, // High pass filter - BPF, // Band pass filter - NOTCH, // Notch Filter - PEQ, // Peaking band EQ filter - LSH, // Low shelf filter - HSH // High shelf filter + BPF // Band pass filter } Type; StateVariableFilter(float32_t sampling_rate, Type type, float32_t cutoff); virtual ~StateVariableFilter(); void setFilterType(Type type); + void setGainDB(float32_t gainDB); void setCutoff(float32_t cutoff); void setResonance(float32_t resonance); - void setPeakGainDB(float32_t gainDB); + virtual void reset() override; virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; private: void updateCoefficients(); Type type_; + float32_t gain_; float32_t cutoff_; float32_t resonance_; - float32_t peak_gain_; - float32_t a0_; - float32_t a1_; - float32_t a2_; - float32_t b1_; - float32_t b2_; - float32_t z1_[2]; - float32_t z2_[2]; + 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, '|', "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, '+'); + out << "\t" << ss.str() << std::endl; + + SS_RESET(ss, precision, std::left); + 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; + ) }; \ No newline at end of file diff --git a/src/fx_tube.cpp b/src/fx_tube.cpp index db55dc8..e6b1660 100644 --- a/src/fx_tube.cpp +++ b/src/fx_tube.cpp @@ -4,8 +4,9 @@ Tube::Tube(float32_t samplingRate) : FXElement(samplingRate), - overdrive_(0.0f), - saturation_(0.0f) + overdrive_(1.0f), + saturator_factor_(1.0f), + gain_factor_(1.0f) { this->setOverdrive(0.0f); } @@ -14,16 +15,37 @@ Tube::~Tube() { } +void Tube::reset() +{ + // nothing to be done +} + void Tube::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) { - outL = softSaturator2(inL, this->saturation_); - outR = softSaturator2(inR, this->saturation_); + float32_t x = inL * this->saturator_factor_; + float32_t abs_x = std::abs(x); + float32_t sat_x = std::log(1.0f + abs_x) * this->gain_factor_; + + outL = inL > 0 ? sat_x : -sat_x; + + x = inR * this->saturator_factor_; + abs_x = std::abs(x); + sat_x = std::log(1.0f + abs_x) * this->gain_factor_; + + outR = inR > 0 ? sat_x : -sat_x; } void Tube::setOverdrive(float32_t overdrive) { - this->overdrive_ = constrain(overdrive, 0.0f, 1.0f); - this->saturation_ = 2.0f * this->overdrive_; + static const float32_t N = 200.0f; + + overdrive = constrain(overdrive, 0.0f, 1.0f); + if(this->overdrive_ != overdrive) + { + this->overdrive_ = overdrive; + this->saturator_factor_ = 1.0f + N * overdrive; + this->gain_factor_ = 1.0f / std::log(1.0f + this->saturator_factor_); + } } float32_t Tube::getOverdrive() const diff --git a/src/fx_tube.h b/src/fx_tube.h index 11dfb80..fa775e3 100644 --- a/src/fx_tube.h +++ b/src/fx_tube.h @@ -18,7 +18,7 @@ // #pragma once -#include "fx_components.h" +#include "fx.h" class Tube : public FXElement { @@ -28,6 +28,7 @@ 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); @@ -35,5 +36,45 @@ public: private: float32_t overdrive_; - float32_t saturation_; + 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; + ) }; \ No newline at end of file diff --git a/src/fx_unit.hpp b/src/fx_unit.hpp index f899cd8..1ad591d 100644 --- a/src/fx_unit.hpp +++ b/src/fx_unit.hpp @@ -70,7 +70,8 @@ class FXUnit : public virtual FXUnitModule, public virtual _FXElement public: FXUnit(float32_t sampling_rate, bool enable = true, float32_t wet_level = 0.5f) : FXUnitModule(), - _FXElement(sampling_rate) + _FXElement(sampling_rate), + is_reset_(false) { this->setEnable(enable); this->setWetLevel(wet_level); @@ -80,15 +81,27 @@ public: { } + void reset() + { + if(!this->is_reset_) + { + _FXElement::reset(); + this->is_reset_ = true; + } + } + void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) { 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(); @@ -96,4 +109,7 @@ public: outR = this->getWetLevel() * outR + dry * inR; } } + +private: + bool is_reset_; }; \ No newline at end of file diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 3c8774c..7cc1f2c 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -928,7 +928,7 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue) case ParameterFXChainDelayFeedback: nValue = constrain((int)nValue, 0, 99); this->m_FXSpinLock.Acquire(); - this->fx_rack->getDelay()->setFeedbak(nValue / 99.0f); + this->fx_rack->getDelay()->setFeedback(nValue / 99.0f); this->m_FXSpinLock.Release(); break; diff --git a/src/test/Makefile b/src/test/Makefile index 5f61429..6c8e61f 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -1,92 +1,63 @@ -CXX := gcc -# CXXFLAGS := -O2 -CXXFLAGS := -g -DEFINES := -DCPU=x86 -INCLUDES := -I../../CMSIS_5/CMSIS/DSP/Include/ -I../../CMSIS_5/CMSIS/Core/Include/ -GCC := $(CXX) $(INCLUDES) $(CXXFLAGS) +OBJDIR := objects +OUTPUT_FOLDER = results +EXE := all_test.bin +BETA := beta.bin -LD := gcc -LIBS := -lm -lstdc++ +CXX := g++ +CXXFLAGS = -g -std=c++20 -MMD -MP +DEFINES = -DCPU=x86 -DDEBUG -DOUTPUT_FOLDER="\"$(OUTPUT_FOLDER)\"" +INCLUDES = -I../../CMSIS_5/CMSIS/DSP/Include/ \ + -I../../CMSIS_5/CMSIS/Core/Include/ \ + -I../../Synth_Dexed/src/ -OBJS := \ - wavein.o \ - waveout.o \ - fx.o \ - fx_components.o \ - fx_svf.o \ - fx_tube.o \ - fx_chorus.o \ - fx_phaser.o \ - fx_orbitone.o \ - fx_flanger.o \ - fx_delay.o \ - fx_shimmer_reverb.o \ - fx_rack.o \ - fxrack_test.o +-include $(TST_OBJS:.o=.d) +-include $(FX__OBJS:.o=.d) -test: fxrack_test - ./fxrack_test +LD := g++ +LIBS := -lm -lstdc++ -lgtest -lpthread -# %.o: ../%.cpp -# $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ +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_reverb.cpp +FX__SRCS += ../fx_rack.cpp -wavein.o: wavein.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ +TST_SRCS := $(filter-out waveplay.cpp, $(wildcard *.cpp)) -waveout.o: waveout.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ +FX__OBJS = $(patsubst ../%, $(OBJDIR)/%, $(FX__SRCS:.cpp=.o)) +TST_OBJS = $(TST_SRCS:%.cpp=$(OBJDIR)/%.o) -# waveplay.o: waveplay.cpp -# $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ +all: $(EXE) test -fx.o: ../fx.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ +test: $(EXE) $(OUTPUT_FOLDER) + rm -rf $(OUTPUT_FOLDER)/* + ./$(EXE) -fx_components.o: ../fx_components.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ +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) -fx_svf.o: ../fx_svf.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ +$(OBJDIR): + mkdir -p $@ -fx_tube.o: ../fx_tube.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ +$(OUTPUT_FOLDER): + mkdir -p $@ -../fx_chorus.cpp: ../fx_engine.hpp - touch ../fx_chorus.cpp +$(OBJDIR)/%.o: %.cpp $(OBJDIR) + $(CXX) $(CXXFLAGS) $(DEFINES) $(INCLUDES) -c $< -o $@ -fx_chorus.o: ../fx_chorus.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ +$(OBJDIR)/%.o: ../%.cpp $(OBJDIR) + $(CXX) $(CXXFLAGS) $(DEFINES) $(INCLUDES) -c $< -o $@ -fx_phaser.o: ../fx_phaser.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ - -../fx_orbitone.cpp: ../fx_engine.hpp - touch ../fx_orbitone.cpp - -fx_orbitone.o: ../fx_orbitone.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ - -fx_flanger.o: ../fx_flanger.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ - -fx_delay.o: ../fx_delay.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ - -../fx_shimmer_reverb.cpp: ../fx_engine.hpp - touch ../fx_shimmer_reverb.cpp - -fx_shimmer_reverb.o: ../fx_shimmer_reverb.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ - -fx_rack.o: ../fx_rack.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ - -fxrack_test.o: fxrack_test.cpp - $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ - - -fxrack_test: $(OBJS) - $(LD) $(OBJS) -o fxrack_test $(LIBS) +$(EXE): $(TST_OBJS) $(FX__OBJS) + $(LD) $(CXXFLAGS) $(call wildcard,$(TST_OBJS)) $(call wildcard,$(FX__OBJS)) -o $@ $(LIBS) clean: - rm -f *.o fxrack_test + rm -rf *.o $(OBJDIR) $(EXE) $(OUTPUT_FOLDER) diff --git a/src/test/all_tests.cpp b/src/test/all_tests.cpp new file mode 100644 index 0000000..4c994ef --- /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 0000000..2964f48 --- /dev/null +++ b/src/test/arm_functions.cpp @@ -0,0 +1,46 @@ +#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 m = 0.0f; + for(size_t i = 0; i < blockSize; ++i) m += in[i] * weights[i]; + return m; +} + +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/fxrack_test.cpp b/src/test/fxrack_test.cpp deleted file mode 100644 index 9e75e3c..0000000 --- a/src/test/fxrack_test.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include "../fx_rack.h" - -#include -#include -#include -#include -#include -#include -#include "wave.h" - -using namespace std; - -#define FS 44100.0f -#define MAX_SVF_SAMPLES 10000000 -#define MAX_NB_ERRORS 100 - -std::random_device rd; -std::mt19937 gen(rd()); -std::uniform_real_distribution dist(-1.0f, 1.0f); - -void testLFO(unsigned& step) -{ - cout << "Step #" << (++step) << ": Testing LFO" << endl; - - const float32_t freq = 10.0f; - - LFO lfo(FS, LFO::Waveform::Sine, 0.0f, freq); - unsigned size = static_cast(8.0f * FS / freq); - float32_t rate = 0.0f; - float32_t rate_increment = freq / 2.0f / FS; - - // float32_t* output = new float32_t[size]; - ofstream out("result.csv"); - - struct comma_separator : std::numpunct - { - virtual char do_decimal_point() const override { return ','; } - }; - - out.imbue(std::locale(out.getloc(), new comma_separator)); - out << fixed << showpoint; - - out << "index;LFO" << endl; - for(unsigned i = 0; i < size; ++i) - { - lfo.setNormalizedFrequency(rate); - out << i << ";" << lfo.process() << endl; - rate += rate_increment; - - if(rate >= 1.0f || rate <= 0.0f) - { - rate_increment *= -1.0f; - } - } -} - -void testFlutter(unsigned& step) -{ - cout << "Step #" << (++step) << ": Testing JitterGenerator" << endl; - - JitterGenerator jg(FS); - jg.setSpeed(1.0f); - jg.setMagnitude(0.1f); - - for (unsigned i = 0; i < 1000; ++i) - { - cout << jg.process() << endl; - } -} - -void testSVF(unsigned& step) -{ - float32_t inL, inR; - float32_t outL, outR; - StateVariableFilter svf(FS, StateVariableFilter::Type::LPF, 12000.0f); - - cout << "Step #" << (++step) << ": Testing SVF in LPF mode" << endl; - { - svf.setFilterType(StateVariableFilter::Type::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 = dist(gen); - inR = dist(gen); - svf.processSample(inL, inR, outL, outR); - - if (std::abs(outL) > 1.0f) - nbErrors++; - if (std::abs(outR) > 1.0f) - nbErrors++; - } - cout << "nbSamples: " << nbSamples << " -- nbErrors: " << nbErrors << endl; - } - - cout << "Step #" << (++step) << ": Testing SVF in HPF mode" << endl; - { - svf.setFilterType(StateVariableFilter::Type::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 = dist(gen); - inR = dist(gen); - svf.processSample(inL, inR, outL, outR); - - if (std::abs(outL) > 1.0f) - nbErrors++; - if (std::abs(outR) > 1.0f) - nbErrors++; - } - cout << "nbSamples: " << nbSamples << " -- nbErrors: " << nbErrors << endl; - } -} - -enum FXSitch -{ - Tube = 1 << 0, - Chorus = 1 << 1, - Phaser = 1 << 2, - Orbitone = 1 << 3, - Flanger = 1 << 4, - Delay = 1 << 5, - Shimmer = 1 << 6 -}; - -#define Active(fxSwitch, FxID) (fxSwitch & FxID) == FxID - -void testFXRack(unsigned& step, unsigned fxSwitch) -{ - cout << "Step #" << (++step) << ": Intanciation FXRack" << endl; - FXRack *rack = new FXRack(44100.0f); - - cout << "Step #" << (++step) << ": Test preparation" << endl; - rack->setEnable(true); - rack->setWetLevel(1.0f); - - rack->getTube()->setEnable(Active(fxSwitch, FXSitch::Tube)); - rack->getTube()->setWetLevel(0.25f); - rack->getTube()->setOverdrive(0.25f); - - rack->getChorus()->setEnable(Active(fxSwitch, FXSitch::Chorus)); - rack->getChorus()->setWetLevel(0.5f); - rack->getChorus()->setRate(0.4f); - rack->getChorus()->setDepth(0.5f); - - rack->getPhaser()->setEnable(Active(fxSwitch, FXSitch::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->getOrbitone()->setEnable(Active(fxSwitch, FXSitch::Orbitone)); - rack->getOrbitone()->setWetLevel(0.8f); - rack->getOrbitone()->setRate(0.4f); - rack->getOrbitone()->setDepth(0.5f); - - rack->getFlanger()->setEnable(Active(fxSwitch, FXSitch::Flanger)); - rack->getFlanger()->setWetLevel(0.5f); - rack->getFlanger()->setRate(0.03f); - rack->getFlanger()->setDepth(0.75f); - rack->getFlanger()->setFeedback(0.5f); - - rack->getDelay()->setEnable(Active(fxSwitch, FXSitch::Delay)); - rack->getDelay()->setWetLevel(0.6f); - rack->getDelay()->setLeftDelayTime(0.075f); - rack->getDelay()->setLeftDelayTime(0.05f); - rack->getDelay()->setFeedbak(0.5f); - - rack->getShimmerReverb()->setEnable(Active(fxSwitch, FXSitch::Shimmer)); - rack->getShimmerReverb()->setWetLevel(0.5f); - rack->getShimmerReverb()->setInputGain(0.35f); - rack->getShimmerReverb()->setTime(0.89f); - rack->getShimmerReverb()->setDiffusion(0.75f); - rack->getShimmerReverb()->setLP(0.8f); - - unsigned nbRepeats = 4; - - unsigned size; - float32_t** samples = readWaveFile("test.wav", size); - float32_t* sampleOutL = new float32_t[size * nbRepeats]; - float32_t* sampleOutR = new float32_t[size * nbRepeats]; - memset(sampleOutL, 0, size * nbRepeats * sizeof(float32_t)); - memset(sampleOutR, 0, size * nbRepeats * sizeof(float32_t)); - - for (unsigned i = 0; i < nbRepeats; ++i) - { - rack->process(samples[0], samples[1], sampleOutL + i * size, sampleOutR + i * size, size); - } - - saveWaveFile("result.wav", sampleOutL, sampleOutR, nbRepeats * size, static_cast(FS), 16); - - delete[] sampleOutL; - delete[] sampleOutR; - delete[] samples[0]; - delete[] samples[1]; - delete[] samples; - - cout << "Step #" << (++step) << ": Test cleanup" << endl; - delete rack; -} - -int main() -{ - unsigned step = 0; - - // testLFO(step); - // testFlutter(step); - // testSVF(step); - // testFXRack(step, FXSitch::Tube); - // testFXRack(step, FXSitch::Flanger); - // testFXRack(step, FXSitch::Phaser); - // testFXRack(step, FXSitch::Chorus); - // testFXRack(step, FXSitch::Orbitone); - // testFXRack(step, FXSitch::Delay); - // testFXRack(step, FXSitch::Shimmer); - testFXRack( - step, - FXSitch::Tube | - FXSitch::Chorus | - FXSitch::Flanger | - FXSitch::Orbitone | - FXSitch::Phaser | - FXSitch::Delay | - FXSitch::Shimmer); - - return 0; -} diff --git a/src/test/test_cpp.cpp b/src/test/test_cpp.cpp new file mode 100644 index 0000000..99fc501 --- /dev/null +++ b/src/test/test_cpp.cpp @@ -0,0 +1,69 @@ +#include + +#include "../fx_components.h" +#include +#include + +int nb = 0; + +int NbIteration() { + nb++; + return 3; +} + +TEST(Cpp, NbCallsInUpperBoudariesInForLoop) +{ + for(int i = 0; i < NbIteration(); ++i) + { + // Does something + } + EXPECT_EQ(nb, 4); +} + +#define CLASS_INIT(clazz) clazz::StaticInit() +class StaticCtorTest +{ +private: + static int n_; + +public: + int i_; + + static int StaticInit() + { + static int i = 0; + i++; + + StaticCtorTest::n_ = 2; + + return i; + } + + StaticCtorTest() : i_(0) + { + static int init = CLASS_INIT(StaticCtorTest); + static int NB = 0; + EXPECT_EQ(init, 1); + + this->i_ = ++NB; + + EXPECT_EQ(StaticCtorTest::n_, 2); + } + + ~StaticCtorTest() + { + } +}; + +int StaticCtorTest::n_ = 0; + +TEST(Cpp, StaticCtorTest) +{ + StaticCtorTest obj1; + StaticCtorTest obj2; + StaticCtorTest obj3; + + EXPECT_EQ(obj1.i_, 1); + EXPECT_EQ(obj2.i_, 2); + EXPECT_EQ(obj3.i_, 3); +} diff --git a/src/test/test_cpp_performance.cpp b/src/test/test_cpp_performance.cpp new file mode 100644 index 0000000..e99f599 --- /dev/null +++ b/src/test/test_cpp_performance.cpp @@ -0,0 +1,89 @@ +#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_LE(d1, d2 + 100); +} + +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, 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 = 5.0f; + + FastLFO lfo1(SAMPLING_FREQUENCY, freq, 440.0f); + lfo1.setFrequency(freq); + + ComplexLFO lfo2(SAMPLING_FREQUENCY, freq, 440.0f); + lfo2.setFrequency(freq); + + std::ofstream out(getResultFile(full_test_name + ".FastLFOTuning-data.csv", true)); + setupOuputStreamForCSV(out); + out << "index;FastLFO;ComplexLFO" << std::endl; + for(size_t i = 0; i < NB; ++i) + { + out + << i << ";" + << lfo1.process() << ";" + << lfo2.process() << std::endl; + } + out.close(); +} diff --git a/src/test/test_framework.cpp b/src/test/test_framework.cpp new file mode 100644 index 0000000..b3402c2 --- /dev/null +++ b/src/test/test_framework.cpp @@ -0,0 +1,22 @@ +#include + +#include "test_fx_helper.h" +#include "wave.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); + } + + EXPECT_EQ(nb_errors, 0); +} \ No newline at end of file diff --git a/src/test/test_fx.cpp b/src/test/test_fx.cpp new file mode 100644 index 0000000..d6aaf77 --- /dev/null +++ b/src/test/test_fx.cpp @@ -0,0 +1,71 @@ +#include + +#include "test_fx_helper.h" +#include "wave.h" + +#include "../effect_platervbstereo.h" + +TEST(FXElement, PlateReverbMigration) +{ + 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 unsigned nbRepeats = 4; + + AudioEffectPlateReverb* reverb = new AudioEffectPlateReverb(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); + + size_t size; + float32_t** samples = readWaveFile(AUDIO_SOURCE_FILE, size); + float32_t* sampleOutL = new float32_t[size * nbRepeats]; + float32_t* sampleOutR = new float32_t[size * nbRepeats]; + memset(sampleOutL, 0, size * nbRepeats * sizeof(float32_t)); + memset(sampleOutR, 0, size * nbRepeats * sizeof(float32_t)); + + unsigned index = 0; + for(unsigned i = 0; i < nbRepeats; ++i) + { + for(unsigned j = 0; j < size; ++j) + { + reverb->processSample(samples[0][j], samples[1][j], sampleOutL[index], sampleOutR[index]); + ++index; + } + } + saveWaveFile(getResultFile(full_test_name + ".PlateReverb-new.wav", true), sampleOutL, sampleOutR, nbRepeats * size, static_cast(SAMPLING_FREQUENCY), 16); + + unsigned indexOut = 0; + for (unsigned i = 0; i < nbRepeats; ++i) + { + unsigned len = size; + unsigned indexIn = 0; + + while(len > 0) + { + unsigned grainSize = (len < 1024 ? len : 1024); + + reverb->doReverb(samples[0] + indexIn, samples[1] + indexIn, sampleOutL + indexOut, sampleOutR + indexOut, grainSize); + + indexIn += grainSize; + indexOut += grainSize; + len -= grainSize; + } + + } + saveWaveFile(getResultFile(full_test_name + ".PlateReverb-legacy.wav", true), sampleOutL, sampleOutR, nbRepeats * size, static_cast(SAMPLING_FREQUENCY), 16); + + delete[] sampleOutL; + delete[] sampleOutR; + delete[] samples[0]; + delete[] samples[1]; + delete[] samples; + + delete reverb; +} diff --git a/src/test/test_fx_components.cpp b/src/test/test_fx_components.cpp new file mode 100644 index 0000000..06624a1 --- /dev/null +++ b/src/test/test_fx_components.cpp @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wave.h" + +#include "test_fx_helper.h" + +#include "../fx_rack.h" +#include "../effect_platervbstereo.h" + +#define MAX_SVF_SAMPLES 10000000 +#define MAX_NB_ERRORS 100 + +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::Type::LPF, 12000.0f); + + { + svf.setFilterType(StateVariableFilter::Type::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.setFilterType(StateVariableFilter::Type::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); +} + +TEST(CppOptimization, FastLFOPrecisionTest) +{ + 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); + FastLFO 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); +} diff --git a/src/test/test_fx_helper.cpp b/src/test/test_fx_helper.cpp new file mode 100644 index 0000000..8b5a025 --- /dev/null +++ b/src/test/test_fx_helper.cpp @@ -0,0 +1,132 @@ +#include "test_fx_helper.h" + +#include +#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 fxShimmer = Active(scenario, FXSwitch::FX__ShimmerReverb); + 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(fxShimmer) + { + 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); +} diff --git a/src/test/test_fx_helper.h b/src/test/test_fx_helper.h new file mode 100644 index 0000000..7975390 --- /dev/null +++ b/src/test/test_fx_helper.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +#include "../fx.h" + +#define AUDIO_SOURCE_FILE "test.wav" + +#define SAMPLING_FREQUENCY 44100.0f + +#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__ShimmerReverb, + 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 {}; diff --git a/src/test/test_fx_rack.cpp b/src/test/test_fx_rack.cpp new file mode 100644 index 0000000..78ec6d9 --- /dev/null +++ b/src/test/test_fx_rack.cpp @@ -0,0 +1,117 @@ +#include +#include + +#include "test_fx_helper.h" +#include "wave.h" + +#include "../fx_rack.h" +#include "../effect_platervbstereo.h" + +using namespace std; + +#define MAX_SVF_SAMPLES 10000000 +#define MAX_NB_ERRORS 100 + +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.25f); + + 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.5f); + + 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.15f); + rack->getDelay()->setLeftDelayTime(0.2f); + rack->getDelay()->setFeedback(0.35f); + rack->getDelay()->setFlutterRate(0.0f); + rack->getDelay()->setFlutterAmount(0.0f); + + rack->getShimmerReverb()->setEnable(Active(scenario, FXSwitch::FX__ShimmerReverb)); + rack->getShimmerReverb()->setWetLevel(0.5f); + rack->getShimmerReverb()->setInputGain(0.35f); + rack->getShimmerReverb()->setTime(0.89f); + rack->getShimmerReverb()->setDiffusion(0.75f); + rack->getShimmerReverb()->setLP(0.8f); +} + +TEST_P(FXScenarioTest, FXRackResetAllScenarios) +{ + FXRack *rack = new FXRack(SAMPLING_FREQUENCY); + + int fxSwitch = this->GetParam(); + rack->setEnable(true); + setupRack(rack, fxSwitch); + rack->reset(); + + delete rack; +} + +TEST_P(FXScenarioTest, ScenarioProcessing) +{ + 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 unsigned nbRepeats = 1; + size_t size; + float32_t** samples = readWaveFile(AUDIO_SOURCE_FILE, size); + float32_t* sampleOutL = new float32_t[size * nbRepeats]; + float32_t* sampleOutR = new float32_t[size * nbRepeats]; + memset(sampleOutL, 0, size * nbRepeats * sizeof(float32_t)); + memset(sampleOutR, 0, size * nbRepeats * sizeof(float32_t)); + + FXRack *rack = new FXRack(SAMPLING_FREQUENCY); + + int fxSwitch = this->GetParam(); + rack->setEnable(true); + setupRack(rack, fxSwitch); + rack->reset(); + + string name = getScenarioName(fxSwitch); + + for(unsigned i = 0; i < nbRepeats; ++i) + { + rack->process(samples[0], samples[1], sampleOutL + i * size, sampleOutR + i * size, size); + } + + stringstream ss; + ss << full_test_name << "-fx-rack" << name << ".wav"; + saveWaveFile(getResultFile(ss.str(), true), sampleOutL, sampleOutR, nbRepeats * size, static_cast(SAMPLING_FREQUENCY), 16); + + delete[] samples[0]; + delete[] samples[1]; + delete[] samples; + + delete[] sampleOutL; + delete[] sampleOutR; + + delete rack; +} + +INSTANTIATE_TEST_SUITE_P(FXRack, FXScenarioTest, testing::Range(0, 1 << (FXSwitch::FX__ShimmerReverb + 1))); diff --git a/src/test/wave.h b/src/test/wave.h index 21b6fcb..068cf0c 100644 --- a/src/test/wave.h +++ b/src/test/wave.h @@ -4,11 +4,30 @@ #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 { - char chunkId[4]; + ChunkID chunkId; uint32_t chunkSize; - char format[4]; - char subchunk1Id[4]; + ChunkID format; + ChunkID subchunk1Id; uint32_t subchunk1Size; uint16_t audioFormat; uint16_t numChannels; @@ -16,16 +35,38 @@ struct WaveHeader { 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 { char subchunk2Id[4]; uint32_t subchunk2Size; }; -float32_t** readWaveFile(const std::string& fileName, unsigned& size); +float32_t** readWaveFile(const std::string& fileName, size_t& size); void saveWaveFile(const std::string& fileName, float32_t* LChannel, float32_t* RChannel, - unsigned size, + size_t size, int sampleRate, int bitsPerSample); diff --git a/src/test/wavein.cpp b/src/test/wavein.cpp index c69d8b8..f66ab6c 100644 --- a/src/test/wavein.cpp +++ b/src/test/wavein.cpp @@ -4,7 +4,36 @@ #include #include -float32_t** readWaveFile(const std::string& fileName, unsigned& size) +#if defined(DEBUG) +#define ASSERT_NORMALIZED(x) assert(x <= 1.0f && x >= -1.0f) +#else +#define ASSERT_NORMALIZED(x) +#endif + +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) { std::ifstream file(fileName, std::ios::binary); if(!file) @@ -13,38 +42,53 @@ float32_t** readWaveFile(const std::string& fileName, unsigned& size) return nullptr; } - WaveHeader header; - file.read((char*)&header, sizeof(header)); + WaveHeaderRIFF riff; + if(!readChunk(file, id2int("RIFF"), riff)) + { + std::cerr << "The file " << fileName << " does not contain any 'RIFF' chunk" << std::endl; + return nullptr; + } - std::cout << "Sampling rate: " << header.sampleRate << std::endl; - std::cout << "# channels: " << header.numChannels << std::endl; - std::cout << "Resolution: " << header.bitsPerSample << " bits" << std::endl; + 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; + } - if(strncmp(header.chunkId, "RIFF", 4) != 0 || strncmp(header.format, "WAVE", 4) != 0) + WaveHeaderFMT fmt; + if(!readChunk(file, id2int("fmt "), fmt)) { - std::cerr << "Error: not a WAVE file" << std::endl; + std::cerr << "The file " << fileName << " does not contain any 'fmt ' chunk" << std::endl; return nullptr; } - if(header.audioFormat != 1) + 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; } - size = header.subchunk2Size / (header.bitsPerSample / 8); + size = data.subchunk2Size / (fmt.bitsPerSample / 8); float32_t* LChannel = new float32_t[size]; float32_t* RChannel = new float32_t[size]; + unsigned increment = fmt.numChannels; unsigned i = 0; while(!file.eof() && i < size) { - if(header.bitsPerSample == 8) + if(fmt.bitsPerSample == 8) { uint8_t LSample; file.read((char*)&LSample, 1); LChannel[i] = LSample / 128.0f - 1.0f; - if(header.numChannels == 2) + if(fmt.numChannels == 2) { uint8_t RSample; file.read((char*)&RSample, 1); @@ -55,12 +99,12 @@ float32_t** readWaveFile(const std::string& fileName, unsigned& size) RChannel[i] = LChannel[i]; } } - else if (header.bitsPerSample == 16) + else if(fmt.bitsPerSample == 16) { int16_t LSample; file.read((char*)&LSample, 2); LChannel[i] = LSample / 32768.0f; - if(header.numChannels == 2) + if(fmt.numChannels == 2) { int16_t RSample; file.read((char*)&RSample, 2); @@ -71,12 +115,12 @@ float32_t** readWaveFile(const std::string& fileName, unsigned& size) RChannel[i] = LChannel[i]; } } - else if (header.bitsPerSample == 24) + else if(fmt.bitsPerSample == 24) { int32_t LSample; file.read((char*)&LSample, 3); LChannel[i] = LSample / 8388608.0f; - if(header.numChannels == 2) + if(fmt.numChannels == 2) { int32_t RSample; file.read((char*)&RSample, 3); @@ -87,12 +131,12 @@ float32_t** readWaveFile(const std::string& fileName, unsigned& size) RChannel[i] = LChannel[i]; } } - else if (header.bitsPerSample == 32) + else if(fmt.bitsPerSample == 32) { int32_t LSample; file.read((char*)&LSample, 4); LChannel[i] = LSample / 2147483648.0f; - if(header.numChannels == 2) + if(fmt.numChannels == 2) { int32_t RSample; file.read((char*)&RSample, 4); @@ -105,11 +149,14 @@ float32_t** readWaveFile(const std::string& fileName, unsigned& size) } else { - std::cerr << "Error: unsupported bit depth: " << header.bitsPerSample << std::endl; + std::cerr << "Error: unsupported bit depth: " << fmt.bitsPerSample << std::endl; return nullptr; } - ++i; + // ASSERT_NORMALIZED(LChannel[i]); + // ASSERT_NORMALIZED(RChannel[i]); + + i += increment; } assert(i == size); diff --git a/src/test/waveout.cpp b/src/test/waveout.cpp index 8794a56..18c6a43 100644 --- a/src/test/waveout.cpp +++ b/src/test/waveout.cpp @@ -7,7 +7,7 @@ void saveWaveFile(const std::string& fileName, float32_t* LChannel, float32_t* RChannel, - unsigned size, + size_t size, int sampleRate, int bitsPerSample) { @@ -30,10 +30,14 @@ void saveWaveFile(const std::string& fileName, header.subchunk1Size = 16; header.audioFormat = 1; - std::strncpy(header.chunkId, "RIFF", 4); - std::strncpy(header.format, "WAVE", 4); - std::strncpy(header.subchunk1Id, "fmt ", 4); - std::strncpy(header.subchunk2Id, "data", 4); + 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));