Fixing many FX bugs and change of test framework

pull/409/head
abscisys 2 years ago
parent f14ac0a967
commit 926ba5339e
  1. 6
      .gitignore
  2. 117
      src/debug.hpp
  3. 38
      src/effect_mixer.hpp
  4. 549
      src/effect_platervbstereo.cpp
  5. 18
      src/effect_platervbstereo.h
  6. 42
      src/extra_features.h
  7. 14
      src/fx.h
  8. 10
      src/fx_base.h
  9. 37
      src/fx_chorus.cpp
  10. 75
      src/fx_chorus.h
  11. 431
      src/fx_components.cpp
  12. 516
      src/fx_components.h
  13. 98
      src/fx_delay.cpp
  14. 157
      src/fx_delay.h
  15. 226
      src/fx_engine.hpp
  16. 43
      src/fx_flanger.cpp
  17. 102
      src/fx_flanger.h
  18. 49
      src/fx_orbitone.cpp
  19. 67
      src/fx_orbitone.h
  20. 38
      src/fx_phaser.cpp
  21. 131
      src/fx_phaser.h
  22. 12
      src/fx_rack.cpp
  23. 60
      src/fx_rack.h
  24. 44
      src/fx_shimmer_reverb.cpp
  25. 9
      src/fx_shimmer_reverb.h
  26. 178
      src/fx_svf.cpp
  27. 91
      src/fx_svf.h
  28. 34
      src/fx_tube.cpp
  29. 45
      src/fx_tube.h
  30. 18
      src/fx_unit.hpp
  31. 2
      src/minidexed.cpp
  32. 123
      src/test/Makefile
  33. 7
      src/test/all_tests.cpp
  34. 46
      src/test/arm_functions.cpp
  35. 235
      src/test/fxrack_test.cpp
  36. 69
      src/test/test_cpp.cpp
  37. 89
      src/test/test_cpp_performance.cpp
  38. 22
      src/test/test_framework.cpp
  39. 71
      src/test/test_fx.cpp
  40. 156
      src/test/test_fx_components.cpp
  41. 132
      src/test/test_fx_helper.cpp
  42. 38
      src/test/test_fx_helper.h
  43. 117
      src/test/test_fx_rack.cpp
  44. 51
      src/test/wave.h
  45. 87
      src/test/wavein.cpp
  46. 14
      src/test/waveout.cpp

6
.gitignore vendored

@ -48,6 +48,6 @@ sdcard
.vscode/ .vscode/
# temporary tests # temporary tests
src/test/fxrack_test src/test/*.bin
src/test/result*.wav src/test/results
src/test/*.csv src/test/objects

@ -0,0 +1,117 @@
#pragma once
#if defined(DEBUG)
#include <cmath>
#include <string>
#include <iostream>
#include <sstream>
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

@ -33,15 +33,16 @@ public:
delete [] sumbufL; 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); 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_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) void gain(uint8_t channel, float32_t gain)
@ -119,37 +120,36 @@ public:
void doAddMix(uint8_t channel, float32_t* in) void doAddMix(uint8_t channel, float32_t* in)
{ {
float32_t tmp[buffer_length];
assert(in); assert(in);
float32_t tmp[buffer_length];
// left // left
arm_scale_f32(in, panorama[channel][0], tmp, buffer_length); arm_scale_f32(in, panorama[channel][0] * multiplier[channel], tmp, buffer_length);
if(multiplier[channel]!=UNITY_GAIN)
arm_scale_f32(tmp,multiplier[channel],tmp,buffer_length);
arm_add_f32(sumbufL, tmp, sumbufL, buffer_length); arm_add_f32(sumbufL, tmp, sumbufL, buffer_length);
// right // right
arm_scale_f32(in, panorama[channel][1], tmp, buffer_length); arm_scale_f32(in, panorama[channel][1] * multiplier[channel], tmp, buffer_length);
if(multiplier[channel]!=UNITY_GAIN)
arm_scale_f32(tmp,multiplier[channel],tmp,buffer_length);
arm_add_f32(sumbufR, tmp, sumbufR, buffer_length); arm_add_f32(sumbufR, tmp, sumbufR, buffer_length);
} }
void doAddMix(uint8_t channel, float32_t* inL, float32_t* inR) void doAddMix(uint8_t channel, float32_t* inL, float32_t* inR)
{ {
float32_t tmp[buffer_length];
assert(inL); assert(inL);
assert(inR); assert(inR);
float32_t tmp[buffer_length];
bool isScaled = (multiplier[channel]!=UNITY_GAIN);
// left // left
if(multiplier[channel]!=UNITY_GAIN) if(isScaled)
arm_scale_f32(inL,multiplier[channel],tmp,buffer_length); 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 // right
if(multiplier[channel]!=UNITY_GAIN) if(isScaled)
arm_scale_f32(inR,multiplier[channel],tmp,buffer_length); 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) void getMix(float32_t* bufferL, float32_t* bufferR)

@ -83,7 +83,8 @@ const int16_t AudioWaveformSine[257] = {
-4808, -4011, -3212, -2410, -1608, -804, 0 -4808, -4011, -3212, -2410, -1608, -804, 0
}; };
AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate) AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate) :
FXElement(samplerate)
{ {
input_attn = 0.5f; input_attn = 0.5f;
in_allp_k = INP_ALLP_COEFF; in_allp_k = INP_ALLP_COEFF;
@ -156,9 +157,33 @@ AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate)
reverb_level = 0.0f; reverb_level = 0.0f;
} }
AudioEffectPlateReverb::~AudioEffectPlateReverb()
{
}
// #define sat16(n, rshift) signed_saturate_rshift((n), 16, (rshift)) // #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; float32_t input, acc, temp1, temp2;
uint16_t temp16; uint16_t temp16;
@ -169,6 +194,259 @@ void AudioEffectPlateReverb::doReverb(const float32_t* inblockL, const float32_t
int32_t y0, y1; int32_t y0, y1;
int64_t y; int64_t y;
uint32_t idx; 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; static bool cleanup_done = false;
// handle bypass, 1st call will clean the buffers to avoid continuing the previous reverb tail // 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) if (!cleanup_done)
{ {
memset(in_allp1_bufL, 0, sizeof(in_allp1_bufL)); this->reset();
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));
cleanup_done = true; cleanup_done = true;
} }
@ -200,255 +462,8 @@ void AudioEffectPlateReverb::doReverb(const float32_t* inblockL, const float32_t
} }
cleanup_done = false; cleanup_done = false;
rv_time = rv_time_k;
for (uint16_t i=0; i < len; i++) for (uint16_t i=0; i < len; i++)
{ {
// do the LFOs this->processSample(inblockL[i], inblockR[i], rvbblockL[i], rvbblockR[i]);
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;
} }
} }

@ -44,9 +44,7 @@
#ifndef _EFFECT_PLATERVBSTEREO_H #ifndef _EFFECT_PLATERVBSTEREO_H
#define _EFFECT_PLATERVBSTEREO_H #define _EFFECT_PLATERVBSTEREO_H
#include <stdint.h> #include "fx_components.h"
#include <arm_math.h>
#include "common.h"
/*** /***
* Loop delay modulation: comment/uncomment to switch sin/cos * Loop delay modulation: comment/uncomment to switch sin/cos
@ -56,11 +54,18 @@
//#define TAP1_MODULATED //#define TAP1_MODULATED
#define TAP2_MODULATED #define TAP2_MODULATED
class AudioEffectPlateReverb class AudioEffectPlateReverb : public FXElement
{ {
DISALLOW_COPY_AND_ASSIGN(AudioEffectPlateReverb);
public: public:
AudioEffectPlateReverb(float32_t samplerate); 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) void size(float n)
{ {
@ -191,6 +196,9 @@ private:
uint32_t lfo2_phase_acc; // LFO 2 uint32_t lfo2_phase_acc; // LFO 2
uint32_t lfo2_adder; uint32_t lfo2_adder;
IMPLEMENT_DUMP()
IMPLEMENT_INSPECT(return 0u;)
}; };
#endif // _EFFECT_PLATEREV_H #endif // _EFFECT_PLATEREV_H

@ -18,8 +18,48 @@
// //
#pragma once #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 #define FXRACK_ENABLE //Add support for the FXRack
#endif
#ifdef DEBUG
#include <iostream>
#include <iomanip>
#include <chrono>
#include <unordered_map>
#include <string>
inline long long int getElapseTime(std::string marker = "")
{
static std::unordered_map<std::string, std::chrono::high_resolution_clock::time_point> 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<std::chrono::milliseconds>(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 #endif

@ -14,7 +14,7 @@
// //
// fx.h // 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 #pragma once
@ -22,21 +22,23 @@
#include <arm_math.h> #include <arm_math.h>
#include "common.h" #include "common.h"
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ #include "debug.hpp"
TypeName(const TypeName&) = delete; \ #include "fx_base.h"
void operator=(const TypeName&) = delete
class FXBase class INSPECTABLE(FXBase)
{ {
DISALLOW_COPY_AND_ASSIGN(FXBase); DISALLOW_COPY_AND_ASSIGN(FXBase);
protected: protected:
FXBase(float32_t sampling_rate); FXBase(float32_t sampling_rate);
virtual ~FXBase();
public: public:
virtual ~FXBase();
float32_t getSamplingRate() const; float32_t getSamplingRate() const;
virtual void reset() = 0;
private: private:
const float32_t SamplingRate; const float32_t SamplingRate;
}; };

@ -0,0 +1,10 @@
#pragma once
#include "extra_features.h"
enum StereoChannels
{
Left = 0,
Right,
kNumChannels
};

@ -5,20 +5,18 @@
#define LFO1_MAX_FREQ 0.25f #define LFO1_MAX_FREQ 0.25f
#define LFO2_MAX_FREQ 0.35f #define LFO2_MAX_FREQ 0.35f
#define FULLSCALE_DEPTH_RATIO 1536.0f
Chorus::Chorus(float32_t sampling_rate) : Chorus::Chorus(float32_t sampling_rate) :
FXElement(sampling_rate), FXElement(sampling_rate),
engine_(sampling_rate, 0.0f, 0.0f), engine_(sampling_rate, 0.0f),
rate_(0.0f), rate_(0.0f),
depth_(0.0f), depth_(0.0f),
fullscale_depth_(0.0f), fullscale_depth_(0.0f),
feedback_(0.0f) feedback_(0.0f)
{ {
this->lfo_[LFO_Index::Sin1] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO1_MAX_FREQ); this->lfo_[LFOIndex::Sin1] = new LFO(sampling_rate, 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_[LFOIndex::Cos1] = new LFO(sampling_rate, 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_[LFOIndex::Sin2] = new LFO(sampling_rate, 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::Cos2] = new LFO(sampling_rate, 0.0f, LFO2_MAX_FREQ, Constants::MPI_2);
this->setRate(0.1f); this->setRate(0.1f);
this->setDepth(0.15f); this->setDepth(0.15f);
@ -26,25 +24,34 @@ Chorus::Chorus(float32_t sampling_rate) :
Chorus::~Chorus() Chorus::~Chorus()
{ {
for(unsigned i = 0; i < 4; ++i) for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i)
{ {
delete this->lfo_[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) void Chorus::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{ {
typedef Engine::Reserve<2047> Memory; typedef Engine::Reserve<2047> Memory;
Engine::DelayLine<Memory, 0> line; static Engine::DelayLine<Memory, 0> line;
Engine::Context c; Engine::Context c;
this->engine_.start(&c); this->engine_.start(&c);
// Update LFO. // Update LFO.
float32_t sin_1 = this->lfo_[LFO_Index::Sin1]->process(); float32_t sin_1 = this->lfo_[LFOIndex::Sin1]->process();
float32_t cos_1 = this->lfo_[LFO_Index::Cos1]->process(); float32_t cos_1 = this->lfo_[LFOIndex::Cos1]->process();
float32_t sin_2 = this->lfo_[LFO_Index::Sin2]->process(); float32_t sin_2 = this->lfo_[LFOIndex::Sin2]->process();
float32_t cos_2 = this->lfo_[LFO_Index::Cos2]->process(); float32_t cos_2 = this->lfo_[LFOIndex::Cos2]->process();
float32_t wet; 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) void Chorus::setRate(float32_t rate)
{ {
this->rate_ = constrain(rate, 0.0f, 1.0f); 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_); this->lfo_[i]->setNormalizedFrequency(this->rate_);
} }
@ -84,7 +91,7 @@ void Chorus::setDepth(float32_t depth)
if(this->depth_ != depth) if(this->depth_ != depth)
{ {
this->depth_ = depth; this->depth_ = depth;
this->fullscale_depth_ = this->depth_ * FULLSCALE_DEPTH_RATIO; this->fullscale_depth_ = this->depth_ * CHORUS_FULLSCALE_DEPTH_RATIO;
} }
} }

@ -22,22 +22,26 @@
#include "fx_components.h" #include "fx_components.h"
#include "fx_engine.hpp" #include "fx_engine.hpp"
#define CHORUS_FULLSCALE_DEPTH_RATIO 1536.0f
class Chorus : public FXElement class Chorus : public FXElement
{ {
DISALLOW_COPY_AND_ASSIGN(Chorus); DISALLOW_COPY_AND_ASSIGN(Chorus);
public: public:
enum LFO_Index enum LFOIndex
{ {
Sin1 = 0, Sin1 = 0,
Sin2, Sin2,
Cos1, Cos1,
Cos2 Cos2,
kLFOCount
}; };
Chorus(float32_t sampling_rate); Chorus(float32_t sampling_rate);
virtual ~Chorus(); virtual ~Chorus();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setDepth(float32_t depth); void setDepth(float32_t depth);
@ -47,7 +51,7 @@ public:
float32_t getRate() const; float32_t getRate() const;
private: private:
typedef FxEngine<2048, FORMAT_16_BIT, false> Engine; typedef FxEngine<2048, Format::FORMAT_FLOAT32, false> Engine;
Engine engine_; Engine engine_;
float32_t rate_; // Normalized frequency for the 2 LFOs frequencies (0.0 - 10.0) 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 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) 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;
)
}; };

@ -7,16 +7,266 @@
/////////////////////////////// ///////////////////////////////
const float32_t Constants::M2PI = 2.0f * PI; const float32_t Constants::M2PI = 2.0f * PI;
const float32_t Constants::MPI_2 = PI / 2.0f; 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; const float32_t Constants::M1_PI = 1.0f / PI;
/////////////////////////
// LFO implemlentation // /////////////////////////////
///////////////////////// // FastLFO implemlentation //
LFO::LFO(float32_t sampling_rate, Waveform waveform, float32_t min_frequency, float32_t max_frequency, float32_t initial_phase) : /////////////////////////////
FastLFO::FastLFO(float32_t sampling_rate, float32_t min_frequency, float32_t max_frequency, float32_t initial_phase) :
FXBase(sampling_rate), 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<float32_t>(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<float32_t>(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<float32_t>(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<float32_t>(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<size_t>(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), min_frequency_(min_frequency),
max_frequency_(max_frequency), max_frequency_(max_frequency),
waveform_(waveform),
normalized_frequency_(-1.0f), normalized_frequency_(-1.0f),
frequency_(0.0f), frequency_(0.0f),
phase_(initial_phase), 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_generator_(rnd_device_()),
rnd_distribution_(-1.0f, 1.0f) 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_); this->setFrequency(this->min_frequency_);
} }
LFO::~LFO() ComplexLFO::~ComplexLFO()
{ {
} }
void LFO::setWaveform(Waveform waveform) void ComplexLFO::setWaveform(Waveform waveform)
{ {
this->waveform_ = waveform; this->waveform_ = waveform;
} }
LFO::Waveform LFO::getWaveform() const ComplexLFO::Waveform ComplexLFO::getWaveform() const
{ {
return this->waveform_; 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); normalized_frequency = constrain(normalized_frequency, 0.0f, 1.0f);
if(this->normalized_frequency_ != normalized_frequency) 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_; 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_); frequency = constrain(frequency, this->min_frequency_, this->max_frequency_);
if(this->frequency_ != 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_; 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; float32_t out = 0.0f;
switch(this->waveform_) switch(this->waveform_)
{ {
case Waveform::Sine: case Waveform::Sine:
out = std::sin(this->phase_); out = arm_sin_f32(this->phase_);
break; break;
case Waveform::Saw: case Waveform::Saw:
out = Constants::M1_PI * this->phase_ - 1.0f; out = Constants::M1_PI * this->phase_ - 1.0f;
@ -123,7 +382,7 @@ float32_t LFO::process()
return out; return out;
} }
float32_t LFO::current() const float32_t ComplexLFO::current() const
{ {
return this->current_sample_; return this->current_sample_;
} }
@ -151,6 +410,9 @@ JitterGenerator::~JitterGenerator()
void JitterGenerator::setSpeed(float32_t speed) 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) if(this->speed_ != speed)
{ {
this->speed_ = speed; this->speed_ = speed;
@ -165,7 +427,7 @@ float32_t JitterGenerator::getSpeed() const
void JitterGenerator::setMagnitude(float32_t magnitude) void JitterGenerator::setMagnitude(float32_t magnitude)
{ {
this->magnitude_ = magnitude; this->magnitude_ = constrain(magnitude, 0.0f, 1.0f);
} }
float32_t JitterGenerator::getMagnitude() const float32_t JitterGenerator::getMagnitude() const
@ -173,9 +435,14 @@ float32_t JitterGenerator::getMagnitude() const
return this->magnitude_; return this->magnitude_;
} }
void JitterGenerator::reset()
{
this->phase_ = 0.0f;
}
float32_t JitterGenerator::process() 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_)); this->phase_ += this->phase_increment_ * (1.0f + this->magnitude_ * this->rnd_distribution_(this->rnd_generator_));
if(this->phase_ > Constants::M2PI) if(this->phase_ > Constants::M2PI)
@ -186,6 +453,116 @@ float32_t JitterGenerator::process()
return out; 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 // // softSaturate implemlentation //
////////////////////////////////// //////////////////////////////////
@ -214,8 +591,8 @@ float32_t softSaturator1(float32_t in, float32_t threshold)
float32_t softSaturator2(float32_t input, float32_t saturation) float32_t softSaturator2(float32_t input, float32_t saturation)
{ {
constexpr static float kTubeCurve = 4.0f; const static float kTubeCurve = 4.0f;
constexpr static float kTubeBias = 0.5f; const static float kTubeBias = 0.5f;
float absInput = std::abs(input); float absInput = std::abs(input);
float output = 0.0f; float output = 0.0f;
@ -247,6 +624,20 @@ float32_t softSaturator2(float32_t input, float32_t saturation)
return output; return output;
} }
float32_t softSaturator3(float32_t input, float32_t overdrive)
{
const float32_t w = (1.0f + overdrive) * Constants::MPI_4;
return constrain(std::tan(w * input), -1.0f, 1.0f);
}
float32_t softSaturator4(float32_t input, float32_t saturator_factor)
{
float32_t x = input * saturator_factor;
float32_t abs_x = std::abs(x);
float32_t sat_x = std::log(1.0f + abs_x) / std::log(1.0f + saturator_factor);
return x > 0 ? sat_x : -sat_x;
}
float32_t waveFolder(float32_t input, float32_t bias) float32_t waveFolder(float32_t input, float32_t bias)
{ {
bias = 0.5 + (2.0f - bias) / 4.0f; bias = 0.5 + (2.0f - bias) / 4.0f;

@ -28,12 +28,195 @@ struct Constants
{ {
const static float32_t M2PI; // 2 * PI const static float32_t M2PI; // 2 * PI
const static float32_t MPI_2; // PI / 2 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 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<float32_t>(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: public:
typedef enum { typedef enum {
@ -44,8 +227,8 @@ public:
Noise Noise
} Waveform; } 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); ComplexLFO(float32_t sampling_rate, float32_t min_frequency = 0.01f, float32_t max_frequency = 10.0f, float32_t initial_phase = 0.0f);
~LFO(); virtual ~ComplexLFO();
void setWaveform(Waveform waveform); void setWaveform(Waveform waveform);
Waveform getWaveform() const; Waveform getWaveform() const;
@ -56,10 +239,12 @@ public:
void setFrequency(float32_t frequency); void setFrequency(float32_t frequency);
float32_t getFrequency() const; float32_t getFrequency() const;
virtual void reset() override;
float32_t process(); float32_t process();
float32_t current() const; float32_t current() const;
private: private:
const float32_t InitialPhase;
const float32_t min_frequency_; const float32_t min_frequency_;
const float32_t max_frequency_; const float32_t max_frequency_;
Waveform waveform_; Waveform waveform_;
@ -72,163 +257,61 @@ private:
std::random_device rnd_device_; std::random_device rnd_device_;
std::mt19937 rnd_generator_; std::mt19937 rnd_generator_;
std::uniform_real_distribution<float32_t> rnd_distribution_; std::uniform_real_distribution<float32_t> rnd_distribution_;
};
template<typename T, unsigned size, unsigned nb_channels = 2, bool circular_buffer = true> IMPLEMENT_DUMP(
class Buffer const size_t space = 21;
{ const size_t precision = 5;
DISALLOW_COPY_AND_ASSIGN(Buffer);
std::stringstream ss;
public:
Buffer() : out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
index_(0)
{ SS_RESET(ss, precision, std::left);
this->values_ = new T*[nb_channels]; SS__TEXT(ss, ' ', space, std::left, '|', "InitialPhase");
for(unsigned i = 0; i < nb_channels; ++i) SS__TEXT(ss, ' ', space, std::left, '|', "normalized_frequency_");
{ SS__TEXT(ss, ' ', space, std::left, '|', "frequency_");
this->values_[i] = new T[size]; SS__TEXT(ss, ' ', space, std::left, '|', "phase_");
} SS__TEXT(ss, ' ', space, std::left, '|', "phase_increment_");
this->reset(); SS__TEXT(ss, ' ', space, std::left, '|', "current_sample_");
} out << "\t" << ss.str() << std::endl;
virtual ~Buffer() SS_RESET(ss, precision, std::left);
{ SS_SPACE(ss, '-', space, std::left, '+');
for(unsigned i = 0; i < nb_channels; ++i) SS_SPACE(ss, '-', space, std::left, '+');
{ SS_SPACE(ss, '-', space, std::left, '+');
delete[] this->values_[i]; SS_SPACE(ss, '-', space, std::left, '+');
} SS_SPACE(ss, '-', space, std::left, '+');
delete[] this->values_; SS_SPACE(ss, '-', space, std::left, '+');
} out << "\t" << ss.str() << std::endl;
void reset(bool reset_index = true) SS_RESET(ss, precision, std::left);
{ SS__TEXT(ss, ' ', space - 1, std::right, " |", this->InitialPhase);
this->zero(); SS__TEXT(ss, ' ', space - 1, std::right, " |", this->normalized_frequency_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->frequency_);
if(reset_index) SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_);
{ SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_increment_);
this->index_ = 0; 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;
T& operator[](unsigned channel) )
{
assert(channel < nb_channels); IMPLEMENT_INSPECT(
return *(this->values_[channel] + this->index_); size_t nb_errors = 0u;
}
nb_errors += inspector(tag + ".InitialPhase", this->InitialPhase, 0.0f, Constants::M2PI, deepInspection);
bool operator++() 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);
this->index_++; nb_errors += inspector(tag + ".phase_", this->phase_, 0.0f, Constants::M2PI, deepInspection);
if(this->index_ >= size) 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);
if(circular_buffer)
{ return nb_errors;
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_;
}; };
template<unsigned size, unsigned nb_channels, bool circular_buffer>
class Buffer<float32_t, size, nb_channels, circular_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 class JitterGenerator : public FXBase
@ -245,6 +328,7 @@ public:
void setMagnitude(float32_t magnitude); void setMagnitude(float32_t magnitude);
float32_t getMagnitude() const; float32_t getMagnitude() const;
virtual void reset() override;
float32_t process(); float32_t process();
private: private:
@ -255,9 +339,127 @@ private:
float32_t magnitude_; float32_t magnitude_;
float32_t phase_; float32_t phase_;
float32_t phase_increment_; 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 softSaturator1(float32_t in, float32_t threshold);
float32_t softSaturator2(float32_t in, float32_t saturation); 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); float32_t waveFolder(float32_t input, float32_t bias);

@ -2,18 +2,21 @@
#include <cmath> #include <cmath>
#define MAX_DELAY_TIME 1.0f #define MAX_DELAY_TIME 2.0f
#define MAX_FLUTTER_DELAY_TIME 0.01f #define MAX_FLUTTER_DELAY_TIME 0.001f
#define LPF_CUTOFF_REF 14000.0f #define LPF_CUTOFF_REF 12000.0f
#define HPF_CUTOFF_REF 60.0f #define HPF_CUTOFF_REF 80.0f
Delay::LowHighPassFilter::LowHighPassFilter(float32_t sampling_rate) : Delay::LowHighPassFilter::LowHighPassFilter(float32_t sampling_rate) :
FXElement(sampling_rate), FXElement(sampling_rate),
lpf_(sampling_rate, StateVariableFilter::Type::LPF, LPF_CUTOFF_REF), 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->setCutoffChangeRatio(0.0f);
this->lpf_.setGainDB(0.82f);
this->hpf_.setGainDB(0.82f);
} }
Delay::LowHighPassFilter::~LowHighPassFilter() Delay::LowHighPassFilter::~LowHighPassFilter()
@ -22,10 +25,22 @@ Delay::LowHighPassFilter::~LowHighPassFilter()
void Delay::LowHighPassFilter::setCutoffChangeRatio(float32_t ratio) void Delay::LowHighPassFilter::setCutoffChangeRatio(float32_t ratio)
{ {
ratio += 1.0f; static const float32_t weight = 4.0f;
this->lpf_.setCutoff(LPF_CUTOFF_REF * ratio); ratio = constrain(ratio, -1.0f, 1.0f);
this->hpf_.setCutoff(HPF_CUTOFF_REF * ratio); 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) 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), MaxSampleDelayTime((MAX_DELAY_TIME + MAX_FLUTTER_DELAY_TIME) * sampling_rate * MAX_DELAY_TIME),
read_pos_L_(0), read_pos_L_(0),
read_pos_R_(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_L_ = new float32_t[this->MaxSampleDelayTime];
this->buffer_R_ = 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->setLeftDelayTime(default_delay_time);
this->setRightDelayTime(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() Delay::~Delay()
@ -58,10 +75,33 @@ Delay::~Delay()
delete[] this->buffer_R_; 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) 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(); static const float32_t max_delay_time = MAX_DELAY_TIME * this->getSamplingRate();
float32_t delay_time_R = (MAX_DELAY_TIME * this->getRightDelayTime()) * 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 // Calculate write positions
unsigned write_pos_L = static_cast<unsigned>(this->MaxSampleDelayTime + this->read_pos_L_ + delay_time_L) % this->MaxSampleDelayTime; unsigned write_pos_L = static_cast<unsigned>(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 outR
); );
this->buffer_L_[write_pos_L] += outL * this->getFeedbackLevel(); this->buffer_L_[write_pos_L] += outL * this->getFeedback();
this->buffer_R_[write_pos_R] += outR * this->getFeedbackLevel(); this->buffer_R_[write_pos_R] += outR * this->getFeedback();
// Increment read positions // Increment read positions
++this->read_pos_L_; ++this->read_pos_L_;
@ -115,12 +155,32 @@ float32_t Delay::getRightDelayTime() const
return this->delay_time_R_; 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); this->feedback_ = constrain(feedback, 0.0, 1.0);
} }
float32_t Delay::getFeedbackLevel() const float32_t Delay::getFeedback() const
{ {
return this->feedback_; 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_;
}

@ -22,8 +22,6 @@
#include "fx_components.h" #include "fx_components.h"
#include "fx_svf.h" #include "fx_svf.h"
#include <random>
class Delay : public FXElement class Delay : public FXElement
{ {
DISALLOW_COPY_AND_ASSIGN(Delay); DISALLOW_COPY_AND_ASSIGN(Delay);
@ -36,6 +34,7 @@ class Delay : public FXElement
LowHighPassFilter(float32_t sampling_rate); LowHighPassFilter(float32_t sampling_rate);
virtual ~LowHighPassFilter(); virtual ~LowHighPassFilter();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setCutoffChangeRatio(float32_t ratio); void setCutoffChangeRatio(float32_t ratio);
@ -43,13 +42,57 @@ class Delay : public FXElement
private: private:
StateVariableFilter lpf_; StateVariableFilter lpf_;
StateVariableFilter hpf_; 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: 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); 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 ~Delay();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setLeftDelayTime(float32_t delay_time); void setLeftDelayTime(float32_t delay_time);
@ -58,18 +101,118 @@ public:
void setRightDelayTime(float32_t delay_time); void setRightDelayTime(float32_t delay_time);
float32_t getRightDelayTime() const; float32_t getRightDelayTime() const;
void setFeedbak(float32_t feedback); void setFeedback(float32_t feedback);
float32_t getFeedbackLevel() const; float32_t getFeedback() const;
void setFlutterRate(float32_t rate);
float32_t getFlutterRate() const;
void setFlutterAmount(float32_t amount);
float32_t getFlutterAmount() const;
private: private:
const size_t MaxSampleDelayTime; const size_t MaxSampleDelayTime;
size_t read_pos_L_; unsigned read_pos_L_;
size_t read_pos_R_; unsigned read_pos_R_;
float32_t* buffer_L_; float32_t* buffer_L_;
float32_t* buffer_R_; float32_t* buffer_R_;
float32_t delay_time_L_; // Left delay time in seconds (0.0 - 2.0) 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 delay_time_R_; // Right delay time in seconds (0.0 - 2.0)
float32_t feedback_; // Feedback (0.0 - 1.0) float32_t feedback_; // Feedback (0.0 - 1.0)
float32_t jitter_amount_;
LowHighPassFilter filter_; 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<float32_t>(this->read_pos_L_), 0.0f, static_cast<float32_t>(this->MaxSampleDelayTime), deepInspection);
nb_errors += inspector(tag + ".read_pos_R_", static_cast<float32_t>(this->read_pos_R_), 0.0f, static_cast<float32_t>(this->MaxSampleDelayTime), deepInspection);
nb_errors += inspector(tag + ".delay_time_L_", this->delay_time_L_, 0.0f, static_cast<float32_t>(this->MaxSampleDelayTime), deepInspection);
nb_errors += inspector(tag + ".delay_time_R_", this->delay_time_R_, 0.0f, static_cast<float32_t>(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;
)
}; };

@ -1,26 +1,22 @@
#pragma once #pragma once
#include "fx_components.h" #if defined(DEBUG)
#include <iostream>
#include <cmath>
#endif
#include <algorithm> #include <algorithm>
#include <climits> #include <climits>
#include <cassert> #include <cassert>
#define MAKE_INTEGRAL_FRACTIONAL(x) \ #include "fx_components.h"
int32_t x ## _integral = static_cast<int32_t>(x); \
float x ## _fractional = x - static_cast<float>(x ## _integral);
enum Format enum Format
{ {
FORMAT_12_BIT, FORMAT_12_BIT,
FORMAT_16_BIT, FORMAT_16_BIT,
FORMAT_32_BIT FORMAT_32_BIT,
}; FORMAT_FLOAT32
enum LFOIndex
{
LFO_1,
LFO_2
}; };
template <Format format> template <Format format>
@ -44,7 +40,7 @@ inline int16_t clip16(int32_t x)
} }
template <> template <>
struct DataType<FORMAT_16_BIT> struct DataType<Format::FORMAT_12_BIT>
{ {
typedef uint16_t T; typedef uint16_t T;
@ -59,9 +55,57 @@ struct DataType<FORMAT_16_BIT>
} }
}; };
template <>
struct DataType<Format::FORMAT_16_BIT>
{
typedef uint32_t T;
static inline float32_t decompress(T value)
{
return static_cast<float32_t>(static_cast<int16_t>(value)) / 65536.0f;
}
static inline T compress(float32_t value)
{
return clip16(static_cast<int32_t>(value * 65536.0f));
}
};
template <>
struct DataType<Format::FORMAT_32_BIT>
{
typedef uint32_t T;
static inline float32_t decompress(T value)
{
return static_cast<float32_t>(static_cast<int64_t>(value)) / static_cast<float32_t>(UINT32_MAX);
}
static inline T compress(float32_t value)
{
return value * static_cast<float32_t>(INT32_MAX);
}
};
template <>
struct DataType<Format::FORMAT_FLOAT32>
{
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 < template <
size_t size, size_t size,
Format format = FORMAT_16_BIT, Format format,
bool enable_lfo = true> bool enable_lfo = true>
class FxEngine : public FXBase class FxEngine : public FXBase
{ {
@ -70,12 +114,19 @@ class FxEngine : public FXBase
public: public:
typedef typename DataType<format>::T T; typedef typename DataType<format>::T T;
FxEngine(float32_t sampling_rate, float32_t max_lfo1_frequency = 1.0f, float32_t max_lfo2_frequency = 1.0f) : enum LFOIndex
FXBase(sampling_rate)
{ {
this->buffer_ = new uint16_t[size]; LFO_1 = 0,
this->lfo_[LFO_1] = enable_lfo ? new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, max_lfo1_frequency) : nullptr; LFO_2,
this->lfo_[LFO_2] = enable_lfo ? new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, max_lfo2_frequency) : nullptr; 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(); this->clear();
} }
@ -84,17 +135,25 @@ public:
delete[] this->buffer_; delete[] this->buffer_;
if(enable_lfo) if(enable_lfo)
{ {
delete this->lfo_[LFO_1]; for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) delete this->lfo_[i];
delete this->lfo_[LFO_2];
} }
} }
void clear() void clear()
{ {
memset(this->buffer_, 0, size * sizeof(uint16_t)); memset(this->buffer_, 0, size * sizeof(T));
this->write_ptr_ = 0; 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 struct Empty
{ {
}; };
@ -141,7 +200,7 @@ public:
buffer_(nullptr), buffer_(nullptr),
write_ptr_(0) write_ptr_(0)
{ {
memset(this->lfo_value_, 0, 2 * sizeof(float32_t)); memset(this->lfo_value_, 0, LFOIndex::kLFOCount * sizeof(float32_t));
} }
~Context() ~Context()
@ -177,7 +236,7 @@ public:
template <typename D> template <typename D>
inline void write(D& d, int32_t offset, float32_t scale) 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<format>::compress(this->accumulator_); T w = DataType<format>::compress(this->accumulator_);
if(offset == -1) if(offset == -1)
{ {
@ -212,7 +271,7 @@ public:
template <typename D> template <typename D>
inline void read(D& d, int32_t offset, float32_t scale) 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; T r;
if(offset == -1) if(offset == -1)
{ {
@ -248,11 +307,15 @@ public:
template <typename D> template <typename D>
inline void interpolate(D& d, float32_t offset, float32_t scale) inline void interpolate(D& d, float32_t offset, float32_t scale)
{ {
assert(D::base + D::length <= size); assert((D::base + D::length) <= size);
MAKE_INTEGRAL_FRACTIONAL(offset);
int32_t offset_integral = static_cast<int32_t>(offset);
float32_t offset_fractional = offset - static_cast<float32_t>(offset_integral);
float32_t a = DataType<format>::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base) & MASK]); float32_t a = DataType<format>::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base) & MASK]);
float32_t b = DataType<format>::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base + 1) & MASK]); float32_t b = DataType<format>::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base + 1) & MASK]);
float32_t x = a + (b - a) * offset_fractional; float32_t x = a + (b - a) * offset_fractional;
this->previous_read_ = x; this->previous_read_ = x;
this->accumulator_ += x * scale; this->accumulator_ += x * scale;
} }
@ -260,26 +323,22 @@ public:
template <typename D> template <typename D>
inline void interpolate(D& d, float32_t offset, LFOIndex index, float32_t amplitude, float32_t scale) inline void interpolate(D& d, float32_t offset, LFOIndex index, float32_t amplitude, float32_t scale)
{ {
assert(D::base + D::length <= size); assert(index < LFOIndex::kLFOCount);
offset += amplitude * this->lfo_value_[index];
MAKE_INTEGRAL_FRACTIONAL(offset); this->interpolate(d, offset + amplitude * this->lfo_value_[index], scale);
float32_t a = DataType<format>::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base) & MASK]);
float32_t b = DataType<format>::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;
} }
private: private:
float32_t accumulator_; float32_t accumulator_;
float32_t previous_read_; float32_t previous_read_;
float32_t lfo_value_[2]; float32_t lfo_value_[LFOIndex::kLFOCount];
T* buffer_; T* buffer_;
int32_t write_ptr_; int32_t write_ptr_;
}; };
inline void setLFOFrequency(LFOIndex index, float32_t frequency) inline void setLFOFrequency(LFOIndex index, float32_t frequency)
{ {
assert(index < LFOIndex::kLFOCount);
if(enable_lfo) if(enable_lfo)
{ {
this->lfo_[index]->setFrequency(frequency); this->lfo_[index]->setFrequency(frequency);
@ -288,13 +347,14 @@ public:
inline void setLFONormalizedFrequency(LFOIndex index, float32_t normalized_frequency) inline void setLFONormalizedFrequency(LFOIndex index, float32_t normalized_frequency)
{ {
assert(index < LFOIndex::kLFOCount);
if(enable_lfo) if(enable_lfo)
{ {
this->lfo_[index]->setNormalizedFrequency(normalized_frequency); this->lfo_[index]->setNormalizedFrequency(normalized_frequency);
} }
} }
inline void start(Context *c) inline void start(Context* c)
{ {
--this->write_ptr_; --this->write_ptr_;
if(this->write_ptr_ < 0) if(this->write_ptr_ < 0)
@ -303,12 +363,14 @@ public:
} }
c->accumulator_ = 0.0f; c->accumulator_ = 0.0f;
c->previous_read_ = 0.0f; c->previous_read_ = 0.0f;
c->buffer_ = buffer_; c->buffer_ = this->buffer_;
c->write_ptr_ = write_ptr_; c->write_ptr_ = this->write_ptr_;
if(enable_lfo) if(enable_lfo)
{ {
c->lfo_value_[LFO_1] = this->lfo_[LFO_1]->process(); for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i)
c->lfo_value_[LFO_2] = this->lfo_[LFO_2]->process(); {
c->lfo_value_[i] = this->lfo_[i]->process();
}
} }
} }
@ -318,8 +380,86 @@ private:
MASK = size - 1 MASK = size - 1
}; };
uint16_t* buffer_; T* buffer_;
unsigned write_ptr_; 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<float32_t>(this->write_ptr_), 0.0f, static_cast<float32_t>(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]; )
}; };

@ -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_lineL_ = new float32_t[this->MaxDelayLineSize];
this->delay_lineR_ = new float32_t[this->MaxDelayLineSize]; this->delay_lineR_ = new float32_t[this->MaxDelayLineSize];
memset(this->delay_lineL_, 0, this->MaxDelayLineSize * sizeof(float32_t)); this->lfo_[LFOIndex::LFO_L] = new LFO(sampling_rate, 0.1f, 5.0f);
memset(this->delay_lineR_, 0, this->MaxDelayLineSize * sizeof(float32_t)); this->lfo_[LFOIndex::LFO_R] = new LFO(sampling_rate, 0.1f, 5.0f, Constants::MPI_2);
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->setRate(rate); this->setRate(rate);
this->setDepth(depth); this->setDepth(depth);
this->setFeedback(feedback); this->setFeedback(feedback);
this->reset();
} }
Flanger::~Flanger() Flanger::~Flanger()
@ -27,8 +25,10 @@ Flanger::~Flanger()
delete[] this->delay_lineL_; delete[] this->delay_lineL_;
delete[] this->delay_lineR_; delete[] this->delay_lineR_;
delete this->lfo_[LFO_Index::LFO_L]; for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i)
delete this->lfo_[LFO_Index::LFO_R]; {
delete this->lfo_[i];
}
} }
inline float32_t linearIterpolationnterp(float32_t inX, float32_t inY, float32_t inPhase) 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; 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) void Flanger::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{ {
// Write sample and any feedback into delay buffers // 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 // Configure LFO for effect processing
float32_t lfo_l = this->lfo_[LFO_L]->process() * this->depth_; float32_t lfo_l = this->lfo_[LFOIndex::LFO_L]->process() * this->depth_;
float32_t lfo_r = this->lfo_[LFO_R]->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 // Map LFO range to millisecond range according to Chorus or Flanger effect
float32_t lfoMappedL = mapfloat(lfo_l, -1.0f, 1.0f, 0.001f, 0.005f); float32_t 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); float32_t delay_sample_r = linearIterpolationnterp(this->delay_lineR_[currentR], this->delay_lineR_[nextR], fractionR);
// Store delayed samples as feedback // Store delayed samples as feedback
this->feedback_samples_[0] = delay_sample_l * this->feedback_; this->feedback_samples_[StereoChannels::Left ] = delay_sample_l * this->feedback_;
this->feedback_samples_[1] = delay_sample_r * this->feedback_; this->feedback_samples_[StereoChannels::Right] = delay_sample_r * this->feedback_;
outL = delay_sample_l; outL = delay_sample_l;
outR = delay_sample_r; 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) void Flanger::setRate(float32_t rate)
{ {
this->lfo_[LFO_Index::LFO_L]->setNormalizedFrequency(rate); this->lfo_[LFOIndex::LFO_L]->setNormalizedFrequency(rate);
this->lfo_[LFO_Index::LFO_R]->setNormalizedFrequency(rate); this->lfo_[LFOIndex::LFO_R]->setNormalizedFrequency(rate);
} }
float32_t Flanger::getRate() const 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) void Flanger::setDepth(float32_t depth)

@ -27,15 +27,17 @@ class Flanger : public FXElement
DISALLOW_COPY_AND_ASSIGN(Flanger); DISALLOW_COPY_AND_ASSIGN(Flanger);
public: public:
enum LFO_Index enum LFOIndex
{ {
LFO_L = 0, 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); Flanger(float32_t sampling_rate, float32_t rate = 0.5f, float32_t depth = 0.5f, float32_t feedback = 0.0f);
virtual ~Flanger(); virtual ~Flanger();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setRate(float32_t rate); void setRate(float32_t rate);
@ -52,9 +54,101 @@ private:
float32_t* delay_lineL_; float32_t* delay_lineL_;
float32_t* delay_lineR_; float32_t* delay_lineR_;
unsigned write_index_; 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 depth_; // Depth of the flanger effect in milliseconds (0.0 - 10.0)
float32_t feedback_; // Amount of feedback to apply to the delay line float32_t feedback_; // Amount of feedback to apply to the delay line
IMPLEMENT_DUMP(
const size_t space = 22;
const size_t precision = 5;
std::stringstream ss;
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "write_index_");
SS__TEXT(ss, ' ', space, std::left, '|', "feedback_samples_[ L ]");
SS__TEXT(ss, ' ', space, std::left, '|', "feedback_samples_[ R ]");
SS__TEXT(ss, ' ', space, std::left, '|', "depth_");
SS__TEXT(ss, ' ', space, std::left, '|', "feedback_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->write_index_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->feedback_samples_[StereoChannels::Left ]);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->feedback_samples_[StereoChannels::Right]);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->depth_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->feedback_);
out << "\t" << ss.str() << std::endl;
if(deepInspection)
{
out << "Flanger internal delay lines:" << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "index");
SS__TEXT(ss, ' ', space, std::left, '|', "delay_lineL_");
SS__TEXT(ss, ' ', space, std::left, '|', "delay_lineR_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
for(size_t i = 0; i < this->MaxDelayLineSize; ++i)
{
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", i);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->delay_lineL_[i]);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->delay_lineR_[i]);
out << "\t" << ss.str() << std::endl;
}
for(size_t i = 0; i < LFOIndex::kLFOCount; ++i)
{
this->lfo_[i]->dump(out, deepInspection, tag + ".lfo_[ " + std::to_string(i) + " ]");
}
}
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
size_t nb_errors = 0u;
nb_errors += inspector(tag + ".write_index_", static_cast<float32_t>(this->write_index_), 0.0, static_cast<float32_t>(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;
)
}; };

@ -5,23 +5,21 @@
#define LFO_SLOW_MAX_FREQUENCY 1.0f #define LFO_SLOW_MAX_FREQUENCY 1.0f
#define LFO_FAST_MAX_FREQUENCY 8.8f #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) : Orbitone::Orbitone(float32_t sampling_rate, float32_t rate, float32_t depth) :
FXElement(sampling_rate), FXElement(sampling_rate),
engine_(sampling_rate, 0.0f, 0.0f), engine_(sampling_rate, 0.0f),
depth_(0.0f), depth_(0.0f),
fullscale_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_[LFOIndex::Slow0 ] = new LFO(sampling_rate, 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_[LFOIndex::Slow120] = new LFO(sampling_rate, 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::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_[LFOIndex::Fast0 ] = new LFO(sampling_rate, 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_[LFOIndex::Fast120] = new LFO(sampling_rate, 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::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); this->lfo_[i]->setNormalizedFrequency(rate);
} }
@ -31,12 +29,21 @@ Orbitone::Orbitone(float32_t sampling_rate, float32_t rate, float32_t depth) :
Orbitone::~Orbitone() Orbitone::~Orbitone()
{ {
for(unsigned i = 0; i < 6; ++i) for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i)
{ {
delete this->lfo_[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) void Orbitone::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{ {
typedef Engine::Reserve<2047, Engine::Reserve<2047> > Memory; 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); this->engine_.start(&c);
float32_t slow_0 = this->lfo_[LFO_Index::Slow0 ]->process(); float32_t slow_0 = this->lfo_[LFOIndex::Slow0 ]->process();
float32_t slow_120 = this->lfo_[LFO_Index::Slow120]->process(); float32_t slow_120 = this->lfo_[LFOIndex::Slow120]->process();
float32_t slow_240 = this->lfo_[LFO_Index::Slow240]->process(); float32_t slow_240 = this->lfo_[LFOIndex::Slow240]->process();
float32_t fast_0 = this->lfo_[LFO_Index::Fast0 ]->process(); float32_t fast_0 = this->lfo_[LFOIndex::Fast0 ]->process();
float32_t fast_120 = this->lfo_[LFO_Index::Fast120]->process(); float32_t fast_120 = this->lfo_[LFOIndex::Fast120]->process();
float32_t fast_240 = this->lfo_[LFO_Index::Fast240]->process(); float32_t fast_240 = this->lfo_[LFOIndex::Fast240]->process();
float32_t a = this->fullscale_depth_ * 1.0f; float32_t a = this->fullscale_depth_ * 1.0f;
float32_t b = this->fullscale_depth_ * 0.1f; 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) void Orbitone::setRate(float32_t rate)
{ {
rate = constrain(rate, 0.0f, 1.0f); 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); this->lfo_[i]->setNormalizedFrequency(rate);
} }
@ -96,7 +103,7 @@ void Orbitone::setRate(float32_t rate)
float32_t Orbitone::getRate() const float32_t Orbitone::getRate() const
{ {
return this->lfo_[LFO_Index::Slow0]->getNormalizedFrequency(); return this->lfo_[LFOIndex::Slow0]->getNormalizedFrequency();
} }
void Orbitone::setDepth(float32_t depth) void Orbitone::setDepth(float32_t depth)
@ -105,7 +112,7 @@ void Orbitone::setDepth(float32_t depth)
if(this->depth_ != depth) if(this->depth_ != depth)
{ {
this->depth_ = depth; this->depth_ = depth;
this->fullscale_depth_ = this->depth_ * FULLSCALE_DEPTH_RATIO; this->fullscale_depth_ = this->depth_ * ORBITONE_FULLSCALE_DEPTH_RATIO;
} }
} }

@ -22,24 +22,28 @@
#include "fx_components.h" #include "fx_components.h"
#include "fx_engine.hpp" #include "fx_engine.hpp"
#define ORBITONE_FULLSCALE_DEPTH_RATIO 256.0f
class Orbitone : public FXElement class Orbitone : public FXElement
{ {
DISALLOW_COPY_AND_ASSIGN(Orbitone); DISALLOW_COPY_AND_ASSIGN(Orbitone);
public: public:
enum LFO_Index enum LFOIndex
{ {
Slow0 = 0, Slow0 = 0,
Slow120, Slow120,
Slow240, Slow240,
Fast0, Fast0,
Fast120, Fast120,
Fast240 Fast240,
kLFOCount
}; };
Orbitone(float32_t sampling_rate, float32_t rate = 0.5f, float32_t depth = 0.5f); Orbitone(float32_t sampling_rate, float32_t rate = 0.5f, float32_t depth = 0.5f);
virtual ~Orbitone(); virtual ~Orbitone();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setRate(float32_t rate); void setRate(float32_t rate);
@ -49,11 +53,66 @@ public:
float32_t getDepth() const; float32_t getDepth() const;
private: private:
typedef FxEngine<4096, FORMAT_16_BIT, false> Engine; typedef FxEngine<4096, Format::FORMAT_FLOAT32, false> Engine;
Engine engine_; Engine engine_;
float32_t depth_; float32_t depth_;
float32_t fullscale_depth_; float32_t fullscale_depth_;
LFO* lfo_[6]; 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;
)
}; };

@ -3,17 +3,23 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
AllpassDelay::AllpassDelay() : Phaser::AllpassDelay::AllpassDelay() :
FXElement(0.0f),
a1_(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]; outL = inL * -this->a1_ + this->z_[0];
this->z_[0] = outL * this->a1_ + inL; 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; 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); 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), FXElement(sampling_rate),
lfo_(sampling_rate, LFO::Waveform::Sine, 0.0f, 2.5f), lfo_(sampling_rate, 0.0f, 2.5f),
depth_(0.0f), depth_(0.0f),
feedback_(0.0f), feedback_(0.0f),
dmin_(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->setRate(rate);
this->setDepth(depth); this->setDepth(depth);
this->setFeedback(feedback); this->setFeedback(feedback);
this->setNbStages(nb_stages);
this->setFrequencyRange(440.0f, 1600.0f); this->setFrequencyRange(440.0f, 1600.0f);
memset(this->z_, 0, 2 * sizeof(float32_t)); this->reset();
} }
Phaser::~Phaser() 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) 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); 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_[0] = sampleL;
this->z_[1] = sampleR; this->z_[1] = sampleR;
outL = inL + this->z_[0] * this->depth_; outL = inL + this->z_[StereoChannels::Left ] * this->depth_;
outR = inR + this->z_[1] * this->depth_; outR = inR + this->z_[StereoChannels::Right] * this->depth_;
} }
void Phaser::setFrequencyRange(float32_t min_frequency, float32_t max_frequency) void Phaser::setFrequencyRange(float32_t min_frequency, float32_t max_frequency)

@ -20,34 +20,68 @@
#include "fx_components.h" #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: public:
AllpassDelay(); class AllpassDelay : public FXElement
virtual ~AllpassDelay(); {
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: void setDelay(float32_t delay);
float32_t a1_;
float32_t z_[2];
};
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 std::stringstream ss;
{
DISALLOW_COPY_AND_ASSIGN(Phaser);
public: out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
Phaser(float32_t sampling_rate, float32_t rate = 0.5f, float32_t depth = 1.0f, float32_t feedback = 0.7f);
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 ~Phaser();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setFrequencyRange(float32_t min_frequency, float32_t max_frequency); void setFrequencyRange(float32_t min_frequency, float32_t max_frequency);
@ -72,5 +106,68 @@ private:
float32_t dmax_; float32_t dmax_;
unsigned nb_stages_; unsigned nb_stages_;
AllpassDelay stages_[MAX_NB_PHASES]; 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<float32_t>(this->nb_stages_), 0.0f, static_cast<float32_t>(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;
)
}; };

@ -39,9 +39,19 @@ FXRack::~FXRack()
delete this->fxShimmerReverb_; 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) 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); (*it)->processSample(inL, inR, outL, outR);

@ -19,7 +19,6 @@
#pragma once #pragma once
#include "fx.h" #include "fx.h"
#include "fx_unit.hpp"
#include "fx_tube.h" #include "fx_tube.h"
#include "fx_chorus.h" #include "fx_chorus.h"
#include "fx_flanger.h" #include "fx_flanger.h"
@ -27,6 +26,7 @@
#include "fx_phaser.h" #include "fx_phaser.h"
#include "fx_delay.h" #include "fx_delay.h"
#include "fx_shimmer_reverb.h" #include "fx_shimmer_reverb.h"
#include "fx_unit.hpp"
#include <vector> #include <vector>
@ -40,6 +40,7 @@ public:
FXRack(float32_t sampling_rate, bool enable = true, float32_t wet = 1.0f); FXRack(float32_t sampling_rate, bool enable = true, float32_t wet = 1.0f);
virtual ~FXRack(); virtual ~FXRack();
virtual void reset() override;
virtual inline void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) 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; 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<Phaser>* fxPhaser_; FXUnit<Phaser>* fxPhaser_;
FXUnit<Delay>* fxDelay_; FXUnit<Delay>* fxDelay_;
FXUnit<ShimmerReverb>* fxShimmerReverb_; FXUnit<ShimmerReverb>* 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;
)
}; };

@ -5,7 +5,6 @@
#define TAIL , -1 #define TAIL , -1
ShimmerReverb::ShimmerReverb(float32_t sampling_rate) : ShimmerReverb::ShimmerReverb(float32_t sampling_rate) :
FXElement(sampling_rate), FXElement(sampling_rate),
engine_(sampling_rate), engine_(sampling_rate),
@ -13,8 +12,9 @@ ShimmerReverb::ShimmerReverb(float32_t sampling_rate) :
diffusion_(-1.0f), diffusion_(-1.0f),
lp_(-1.0f) lp_(-1.0f)
{ {
this->engine_.setLFOFrequency(LFO_1, 0.5f); this->engine_.setLFOFrequency(Engine::LFOIndex::LFO_1, 0.5f);
this->engine_.setLFOFrequency(LFO_2, 0.3f); this->engine_.setLFOFrequency(Engine::LFOIndex::LFO_2, 0.3f);
this->setInputGain(1.0f); this->setInputGain(1.0f);
this->setLP(0.7f); this->setLP(0.7f);
this->setDiffusion(0.625f); 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) 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 // This is the Griesinger topology described in the Dattorro paper
// (4 AP diffusers on the input, then a loop of 2x 2AP+1Delay). // (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 // 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. // smearing; and to the two long delays for a slow shimmer/chorus effect.
typedef Engine::Reserve<113, typedef Engine::Reserve< 113,
Engine::Reserve<162, Engine::Reserve< 162,
Engine::Reserve<241, Engine::Reserve< 241,
Engine::Reserve<399, Engine::Reserve< 399,
Engine::Reserve<1653, Engine::Reserve<1653,
Engine::Reserve<2038, Engine::Reserve<2038,
Engine::Reserve<3411, Engine::Reserve<3411,
Engine::Reserve<1913, Engine::Reserve<1913,
Engine::Reserve<1663, Engine::Reserve<1663,
Engine::Reserve<4782> > > > > > > > > > Memory; Engine::Reserve<4782> > > > > > > > > > Memory;
Engine::DelayLine<Memory, 0> ap1; Engine::DelayLine<Memory, 0> ap1;
Engine::DelayLine<Memory, 1> ap2; Engine::DelayLine<Memory, 1> ap2;
Engine::DelayLine<Memory, 2> ap3; Engine::DelayLine<Memory, 2> 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_1 = this->lp_decay_1_;
float32_t lp_2 = this->lp_decay_2_; float32_t lp_2 = this->lp_decay_2_;
float32_t wet; float32_t wet = 0.0f;
float32_t apout = 0.0f; float32_t apout = 0.0f;
engine_.start(&c); engine_.start(&c);
// Smear AP1 inside the loop. // 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.write(ap1, 100, 0.0f);
c.read(inL + inR, gain); c.read(inL + inR, gain);
// Diffuse through 4 allpasses. // Diffuse through 4 allpasses.
@ -83,7 +87,7 @@ void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL,
// Main reverb loop. // Main reverb loop.
c.load(apout); 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.lp(lp_1, klp);
c.read(dap1a TAIL, -kap); c.read(dap1a TAIL, -kap);
c.writeAllPass(dap1a, 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(del1, 2.0f);
c.write(wet, 0.0f); c.write(wet, 0.0f);
outR += wet; outR = wet;
c.load(apout); 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.read(del1 TAIL, krt);
c.lp(lp_2, klp); c.lp(lp_2, klp);
c.read(dap2a TAIL, kap); c.read(dap2a TAIL, kap);
@ -105,7 +109,7 @@ void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL,
c.write(del2, 2.0f); c.write(del2, 2.0f);
c.write(wet, 0.0f); c.write(wet, 0.0f);
outR += wet; outR = wet;
this->lp_decay_1_ = lp_1; this->lp_decay_1_ = lp_1;
this->lp_decay_2_ = lp_2; this->lp_decay_2_ = lp_2;

@ -23,15 +23,17 @@
#include "fx_components.h" #include "fx_components.h"
#include "fx_engine.hpp" #include "fx_engine.hpp"
#define SHIMMER_REVERB_BUFFER_SIZE 16384
class ShimmerReverb : public FXElement class ShimmerReverb : public FXElement
{ {
DISALLOW_COPY_AND_ASSIGN(ShimmerReverb); DISALLOW_COPY_AND_ASSIGN(ShimmerReverb);
public: public:
ShimmerReverb(float32_t sampling_rate); ShimmerReverb(float32_t sampling_rate);
virtual ~ShimmerReverb(); virtual ~ShimmerReverb();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setInputGain(float32_t gain); void setInputGain(float32_t gain);
@ -47,7 +49,7 @@ public:
float32_t getLP() const; float32_t getLP() const;
private: private:
typedef FxEngine<16384, FORMAT_16_BIT, true> Engine; typedef FxEngine<SHIMMER_REVERB_BUFFER_SIZE, Format::FORMAT_FLOAT32, true> Engine;
Engine engine_; Engine engine_;
float32_t input_gain_; float32_t input_gain_;
@ -57,4 +59,7 @@ private:
float32_t lp_decay_1_; float32_t lp_decay_1_;
float32_t lp_decay_2_; float32_t lp_decay_2_;
IMPLEMENT_DUMP()
IMPLEMENT_INSPECT(return 0u;)
}; };

@ -5,16 +5,15 @@
StateVariableFilter::StateVariableFilter(float32_t sampling_rate, Type type, float32_t cutoff) : StateVariableFilter::StateVariableFilter(float32_t sampling_rate, Type type, float32_t cutoff) :
FXElement(sampling_rate), FXElement(sampling_rate),
type_(type), type_(type),
cutoff_(0.0f), gain_(-1.0f),
resonance_(0.0f), cutoff_(cutoff),
peak_gain_(0.0f) 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->setCutoff(cutoff);
this->setResonance(0.0f); this->setResonance(0.0f);
this->setGainDB(0.0f);
this->reset();
} }
StateVariableFilter::~StateVariableFilter() StateVariableFilter::~StateVariableFilter()
@ -32,7 +31,9 @@ void StateVariableFilter::setFilterType(Type type)
void StateVariableFilter::setCutoff(float32_t cutoff) 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) if(this->cutoff_ != cutoff)
{ {
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(); this->updateCoefficients();
} }
} }
@ -62,124 +65,87 @@ void StateVariableFilter::setPeakGainDB(float32_t gain)
void StateVariableFilter::updateCoefficients() void StateVariableFilter::updateCoefficients()
{ {
// Compute the filter coefficients based on the current parameter values // Compute the filter coefficients based on the current parameter values
float32_t w0 = PI * this->cutoff_ / this->getSamplingRate(); this->w_ = 2.0f * std::tan(PI * this->cutoff_ / this->getSamplingRate());
float32_t V = pow(10, fabs(this->peak_gain_) / 20.0f); this->a_ = this->w_ / this->resonance_;
float32_t K = std::tan(w0); this->b_ = this->w_ * this->w_;
float32_t K2 = K * K; float32_t a_b = this->a_ + this->b_;
float32_t norm; this->c1_ = a_b / (1.0f + 0.5f * this->a_ + 0.25f * this->b_);
this->c2_ = this->b_ / a_b;
switch(this->type_) switch(this->type_)
{ {
case Type::LPF: case Type::LPF:
norm = 1.0f / (1.0f + K / this->resonance_ + K2); this->d1_ = 0.0f;
this->a0_ = K2 * norm; this->d0_ = 0.25f * this->c1_ * this->c2_;
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;
break; break;
case Type::HPF: case Type::HPF:
norm = 1.0f / (1.0f + K / this->resonance_ + K2); this->d1_ = 0.0f;
this->a0_ = norm; this->d0_ = 1.0f - 0.5f * this->c1_ + 0.25f * this->c1_ * this->c2_;
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;
break; break;
case Type::BPF: case Type::BPF:
norm = 1.0f / (1.0f + K / this->resonance_ + K2); this->d1_ = 1.0f - this->c2_;
this->a0_ = K / this->resonance_ * norm; this->d0_ = this->d1_ * this->c1_ * 0.5f;
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;
break; 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 const float32_t x = inL - this->z1_[StereoChannels::Left] - this->z2_[StereoChannels::Left] + 1e-20f;
norm = 1.0f / (1.0f + 1.0f / this->resonance_ * K + K2); this->z2_[StereoChannels::Left] += this->c2_ * this->z1_[StereoChannels::Left];
this->a0_ = (1.0f + V / this->resonance_ * K + K2) * norm; outL = gain * (this->d0_ * x + this->z2_[StereoChannels::Left]);
this->a1_ = 2.0f * (K2 - 1) * norm; this->z1_[StereoChannels::Left] += this->c1_ * x;
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;
} }
else
{ {
// cut const float32_t x = inR - this->z1_[StereoChannels::Right] - this->z2_[StereoChannels::Right] + 1e-20f;
norm = 1.0f / (1 + V / this->resonance_ * K + K2); this->z2_[StereoChannels::Right] += this->c2_ * this->z1_[StereoChannels::Right];
this->a0_ = (1.0f + 1.0f / this->resonance_ * K + K2) * norm; outR = gain * (this->d0_ * x + this->z2_[StereoChannels::Right]);
this->a1_ = 2.0f * (K2 - 1) * norm; this->z1_[StereoChannels::Right] += this->c1_ * x;
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;
} }
break; break;
case Type::LSH:
if(this->peak_gain_ >= 0) case Type::HPF:
{ {
// boost const float32_t x = inL - this->z1_[StereoChannels::Left] - this->z2_[StereoChannels::Left] + 1e-20f;
norm = 1 / (1 + std::sqrt(2) * K + K2); outL = gain * this->d0_ * x;
this->a0_ = (1.0f + std::sqrt(2.0f * V) * K + V * K2) * norm; this->z2_[StereoChannels::Left] += this->c2_ * this->z1_[StereoChannels::Left];
this->a1_ = 2.0f * (V * K2 - 1.0f) * norm; this->z1_[StereoChannels::Left] += this->c1_ * x;
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;
} }
else
{ {
// cutK * K const float32_t x = inR - this->z1_[StereoChannels::Right] - this->z2_[StereoChannels::Right] + 1e-20f;
norm = 1.0f / (1.0f + std::sqrt(2.0f * V) * K + V * K2); outR = gain * this->d0_ * x;
this->a0_ = (1.0f + std::sqrt(2.0f) * K + K2) * norm; this->z2_[StereoChannels::Right] += this->c2_ * this->z1_[StereoChannels::Right];
this->a1_ = 2.0f * (K2 - 1.0f) * norm; this->z1_[StereoChannels::Right] += this->c1_ * x;
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;
} }
break; break;
case Type::HSH:
if(this->peak_gain_ >= 0) case Type::BPF:
{ {
// boost const float32_t x = inL - this->z1_[StereoChannels::Left] - this->z2_[StereoChannels::Left] + 1e-20f;
norm = 1.0f / (1.0f + std::sqrt(2.0f) * K + K2); outL = gain * (this->d0_ * x) + this->d1_ * this->z1_[StereoChannels::Left];
this->a0_ = (V + std::sqrt(2.0f * V) * K + K2) * norm; this->z2_[StereoChannels::Left] += this->c2_ * this->z1_[StereoChannels::Left];
this->a1_ = 2.0f * (K2 - V) * norm; this->z1_[StereoChannels::Left] += this->c1_ * x;
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;
} }
else
{ {
// cut const float32_t x = inR - this->z1_[StereoChannels::Right] - this->z2_[StereoChannels::Right] + 1e-20f;
norm = 1.0f / (V + std::sqrt(2.0f * V) * K + K2); outL = gain * (this->d0_ * x) + this->d1_ * this->z1_[StereoChannels::Right];
this->a0_ = (1.0f + std::sqrt(2.0f) * K + K2) * norm; this->z2_[StereoChannels::Right] += this->c2_ * this->z1_[StereoChannels::Right];
this->a1_ = 2.0f * (K2 - 1.0f) * norm; this->z1_[StereoChannels::Right] += this->c1_ * x;
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;
} }
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;
}

@ -29,35 +29,98 @@ public:
{ {
LPF, // Low pass filter LPF, // Low pass filter
HPF, // High pass filter HPF, // High pass filter
BPF, // Band pass filter BPF // Band pass filter
NOTCH, // Notch Filter
PEQ, // Peaking band EQ filter
LSH, // Low shelf filter
HSH // High shelf filter
} Type; } Type;
StateVariableFilter(float32_t sampling_rate, Type type, float32_t cutoff); StateVariableFilter(float32_t sampling_rate, Type type, float32_t cutoff);
virtual ~StateVariableFilter(); virtual ~StateVariableFilter();
void setFilterType(Type type); void setFilterType(Type type);
void setGainDB(float32_t gainDB);
void setCutoff(float32_t cutoff); void setCutoff(float32_t cutoff);
void setResonance(float32_t resonance); 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; virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
private: private:
void updateCoefficients(); void updateCoefficients();
Type type_; Type type_;
float32_t gain_;
float32_t cutoff_; float32_t cutoff_;
float32_t resonance_; float32_t resonance_;
float32_t peak_gain_; float32_t g_;
float32_t a0_; float32_t w_;
float32_t a1_; float32_t a_;
float32_t a2_; float32_t b_;
float32_t b1_; float32_t c1_;
float32_t b2_; float32_t c2_;
float32_t z1_[2]; float32_t d0_;
float32_t z2_[2]; 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;
)
}; };

@ -4,8 +4,9 @@
Tube::Tube(float32_t samplingRate) : Tube::Tube(float32_t samplingRate) :
FXElement(samplingRate), FXElement(samplingRate),
overdrive_(0.0f), overdrive_(1.0f),
saturation_(0.0f) saturator_factor_(1.0f),
gain_factor_(1.0f)
{ {
this->setOverdrive(0.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) void Tube::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{ {
outL = softSaturator2(inL, this->saturation_); float32_t x = inL * this->saturator_factor_;
outR = softSaturator2(inR, this->saturation_); 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) void Tube::setOverdrive(float32_t overdrive)
{ {
this->overdrive_ = constrain(overdrive, 0.0f, 1.0f); static const float32_t N = 200.0f;
this->saturation_ = 2.0f * this->overdrive_;
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 float32_t Tube::getOverdrive() const

@ -18,7 +18,7 @@
// //
#pragma once #pragma once
#include "fx_components.h" #include "fx.h"
class Tube : public FXElement class Tube : public FXElement
{ {
@ -28,6 +28,7 @@ public:
Tube(float32_t sampling_rate); Tube(float32_t sampling_rate);
virtual ~Tube(); virtual ~Tube();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setOverdrive(float32_t overdrive); void setOverdrive(float32_t overdrive);
@ -35,5 +36,45 @@ public:
private: private:
float32_t overdrive_; 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;
)
}; };

@ -70,7 +70,8 @@ class FXUnit : public virtual FXUnitModule, public virtual _FXElement
public: public:
FXUnit(float32_t sampling_rate, bool enable = true, float32_t wet_level = 0.5f) : FXUnit(float32_t sampling_rate, bool enable = true, float32_t wet_level = 0.5f) :
FXUnitModule(), FXUnitModule(),
_FXElement(sampling_rate) _FXElement(sampling_rate),
is_reset_(false)
{ {
this->setEnable(enable); this->setEnable(enable);
this->setWetLevel(wet_level); 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) void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{ {
if(!this->isEnable() || this->getWetLevel() == 0.0f) if(!this->isEnable() || this->getWetLevel() == 0.0f)
{ {
this->reset();
outL = inL; outL = inL;
outR = inR; outR = inR;
} }
else else
{ {
this->is_reset_ = false;
_FXElement::processSample(inL, inR, outL, outR); _FXElement::processSample(inL, inR, outL, outR);
float32_t dry = 1.0f - this->getWetLevel(); float32_t dry = 1.0f - this->getWetLevel();
@ -96,4 +109,7 @@ public:
outR = this->getWetLevel() * outR + dry * inR; outR = this->getWetLevel() * outR + dry * inR;
} }
} }
private:
bool is_reset_;
}; };

@ -928,7 +928,7 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue)
case ParameterFXChainDelayFeedback: case ParameterFXChainDelayFeedback:
nValue = constrain((int)nValue, 0, 99); nValue = constrain((int)nValue, 0, 99);
this->m_FXSpinLock.Acquire(); this->m_FXSpinLock.Acquire();
this->fx_rack->getDelay()->setFeedbak(nValue / 99.0f); this->fx_rack->getDelay()->setFeedback(nValue / 99.0f);
this->m_FXSpinLock.Release(); this->m_FXSpinLock.Release();
break; break;

@ -1,92 +1,63 @@
CXX := gcc OBJDIR := objects
# CXXFLAGS := -O2 OUTPUT_FOLDER = results
CXXFLAGS := -g EXE := all_test.bin
DEFINES := -DCPU=x86 BETA := beta.bin
INCLUDES := -I../../CMSIS_5/CMSIS/DSP/Include/ -I../../CMSIS_5/CMSIS/Core/Include/
GCC := $(CXX) $(INCLUDES) $(CXXFLAGS)
LD := gcc CXX := g++
LIBS := -lm -lstdc++ 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 := \ -include $(TST_OBJS:.o=.d)
wavein.o \ -include $(FX__OBJS:.o=.d)
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
test: fxrack_test LD := g++
./fxrack_test LIBS := -lm -lstdc++ -lgtest -lpthread
# %.o: ../%.cpp FX__SRCS := ../fx.cpp
# $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ 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 TST_SRCS := $(filter-out waveplay.cpp, $(wildcard *.cpp))
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
waveout.o: waveout.cpp FX__OBJS = $(patsubst ../%, $(OBJDIR)/%, $(FX__SRCS:.cpp=.o))
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ TST_OBJS = $(TST_SRCS:%.cpp=$(OBJDIR)/%.o)
# waveplay.o: waveplay.cpp all: $(EXE) test
# $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
fx.o: ../fx.cpp test: $(EXE) $(OUTPUT_FOLDER)
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ rm -rf $(OUTPUT_FOLDER)/*
./$(EXE)
fx_components.o: ../fx_components.cpp test-debug: $(EXE) $(OUTPUT_FOLDER)
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ 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 $(OBJDIR):
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ mkdir -p $@
fx_tube.o: ../fx_tube.cpp $(OUTPUT_FOLDER):
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ mkdir -p $@
../fx_chorus.cpp: ../fx_engine.hpp $(OBJDIR)/%.o: %.cpp $(OBJDIR)
touch ../fx_chorus.cpp $(CXX) $(CXXFLAGS) $(DEFINES) $(INCLUDES) -c $< -o $@
fx_chorus.o: ../fx_chorus.cpp $(OBJDIR)/%.o: ../%.cpp $(OBJDIR)
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ $(CXX) $(CXXFLAGS) $(DEFINES) $(INCLUDES) -c $< -o $@
fx_phaser.o: ../fx_phaser.cpp $(EXE): $(TST_OBJS) $(FX__OBJS)
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@ $(LD) $(CXXFLAGS) $(call wildcard,$(TST_OBJS)) $(call wildcard,$(FX__OBJS)) -o $@ $(LIBS)
../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)
clean: clean:
rm -f *.o fxrack_test rm -rf *.o $(OBJDIR) $(EXE) $(OUTPUT_FOLDER)

@ -0,0 +1,7 @@
#include <gtest/gtest.h>
int main(int argc, char **argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

@ -0,0 +1,46 @@
#include <arm_math.h>
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];
}

@ -1,235 +0,0 @@
#include "../fx_rack.h"
#include <iomanip>
#include <iostream>
#include <fstream>
#include <locale>
#include <ctime>
#include <random>
#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<float32_t> 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<unsigned>(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<char>
{
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<unsigned>(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;
}

@ -0,0 +1,69 @@
#include <gtest/gtest.h>
#include "../fx_components.h"
#include <chrono>
#include <fstream>
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);
}

@ -0,0 +1,89 @@
#include <gtest/gtest.h>
#include <fstream>
#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<size_t>(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();
}

@ -0,0 +1,22 @@
#include <gtest/gtest.h>
#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);
}

@ -0,0 +1,71 @@
#include <gtest/gtest.h>
#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<unsigned>(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<unsigned>(SAMPLING_FREQUENCY), 16);
delete[] sampleOutL;
delete[] sampleOutR;
delete[] samples[0];
delete[] samples[1];
delete[] samples;
delete reverb;
}

@ -0,0 +1,156 @@
#include <gtest/gtest.h>
#include <iomanip>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <locale>
#include <ctime>
#include <cmath>
#include <random>
#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<unsigned>(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<size_t>(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<size_t>(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);
}

@ -0,0 +1,132 @@
#include "test_fx_helper.h"
#include <iostream>
#include <filesystem>
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<char>
{
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<float32_t> dist(-1.0f, 1.0f);
return dist(gen);
}

@ -0,0 +1,38 @@
#pragma once
#include <random>
#include <string>
#include <gtest/gtest.h>
#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<int> {};

@ -0,0 +1,117 @@
#include <gtest/gtest.h>
#include <cmath>
#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<unsigned>(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)));

@ -4,11 +4,30 @@
#include <arm_math.h> #include <arm_math.h>
#include <string> #include <string>
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 { struct WaveHeader {
char chunkId[4]; ChunkID chunkId;
uint32_t chunkSize; uint32_t chunkSize;
char format[4]; ChunkID format;
char subchunk1Id[4]; ChunkID subchunk1Id;
uint32_t subchunk1Size; uint32_t subchunk1Size;
uint16_t audioFormat; uint16_t audioFormat;
uint16_t numChannels; uint16_t numChannels;
@ -16,16 +35,38 @@ struct WaveHeader {
uint32_t byteRate; uint32_t byteRate;
uint16_t blockAlign; uint16_t blockAlign;
uint16_t bitsPerSample; 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]; char subchunk2Id[4];
uint32_t subchunk2Size; 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, void saveWaveFile(const std::string& fileName,
float32_t* LChannel, float32_t* LChannel,
float32_t* RChannel, float32_t* RChannel,
unsigned size, size_t size,
int sampleRate, int sampleRate,
int bitsPerSample); int bitsPerSample);

@ -4,7 +4,36 @@
#include <iostream> #include <iostream>
#include <cassert> #include <cassert>
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<typename T>
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); std::ifstream file(fileName, std::ios::binary);
if(!file) if(!file)
@ -13,38 +42,53 @@ float32_t** readWaveFile(const std::string& fileName, unsigned& size)
return nullptr; return nullptr;
} }
WaveHeader header; WaveHeaderRIFF riff;
file.read((char*)&header, sizeof(header)); 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; if(riff.format.Value != id2int("WAVE"))
std::cout << "# channels: " << header.numChannels << std::endl; {
std::cout << "Resolution: " << header.bitsPerSample << " bits" << std::endl; 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; 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; std::cerr << "Error: only support PCM format" << std::endl;
return nullptr; return nullptr;
} }
size = header.subchunk2Size / (header.bitsPerSample / 8); size = data.subchunk2Size / (fmt.bitsPerSample / 8);
float32_t* LChannel = new float32_t[size]; float32_t* LChannel = new float32_t[size];
float32_t* RChannel = new float32_t[size]; float32_t* RChannel = new float32_t[size];
unsigned increment = fmt.numChannels;
unsigned i = 0; unsigned i = 0;
while(!file.eof() && i < size) while(!file.eof() && i < size)
{ {
if(header.bitsPerSample == 8) if(fmt.bitsPerSample == 8)
{ {
uint8_t LSample; uint8_t LSample;
file.read((char*)&LSample, 1); file.read((char*)&LSample, 1);
LChannel[i] = LSample / 128.0f - 1.0f; LChannel[i] = LSample / 128.0f - 1.0f;
if(header.numChannels == 2) if(fmt.numChannels == 2)
{ {
uint8_t RSample; uint8_t RSample;
file.read((char*)&RSample, 1); file.read((char*)&RSample, 1);
@ -55,12 +99,12 @@ float32_t** readWaveFile(const std::string& fileName, unsigned& size)
RChannel[i] = LChannel[i]; RChannel[i] = LChannel[i];
} }
} }
else if (header.bitsPerSample == 16) else if(fmt.bitsPerSample == 16)
{ {
int16_t LSample; int16_t LSample;
file.read((char*)&LSample, 2); file.read((char*)&LSample, 2);
LChannel[i] = LSample / 32768.0f; LChannel[i] = LSample / 32768.0f;
if(header.numChannels == 2) if(fmt.numChannels == 2)
{ {
int16_t RSample; int16_t RSample;
file.read((char*)&RSample, 2); file.read((char*)&RSample, 2);
@ -71,12 +115,12 @@ float32_t** readWaveFile(const std::string& fileName, unsigned& size)
RChannel[i] = LChannel[i]; RChannel[i] = LChannel[i];
} }
} }
else if (header.bitsPerSample == 24) else if(fmt.bitsPerSample == 24)
{ {
int32_t LSample; int32_t LSample;
file.read((char*)&LSample, 3); file.read((char*)&LSample, 3);
LChannel[i] = LSample / 8388608.0f; LChannel[i] = LSample / 8388608.0f;
if(header.numChannels == 2) if(fmt.numChannels == 2)
{ {
int32_t RSample; int32_t RSample;
file.read((char*)&RSample, 3); file.read((char*)&RSample, 3);
@ -87,12 +131,12 @@ float32_t** readWaveFile(const std::string& fileName, unsigned& size)
RChannel[i] = LChannel[i]; RChannel[i] = LChannel[i];
} }
} }
else if (header.bitsPerSample == 32) else if(fmt.bitsPerSample == 32)
{ {
int32_t LSample; int32_t LSample;
file.read((char*)&LSample, 4); file.read((char*)&LSample, 4);
LChannel[i] = LSample / 2147483648.0f; LChannel[i] = LSample / 2147483648.0f;
if(header.numChannels == 2) if(fmt.numChannels == 2)
{ {
int32_t RSample; int32_t RSample;
file.read((char*)&RSample, 4); file.read((char*)&RSample, 4);
@ -105,11 +149,14 @@ float32_t** readWaveFile(const std::string& fileName, unsigned& size)
} }
else else
{ {
std::cerr << "Error: unsupported bit depth: " << header.bitsPerSample << std::endl; std::cerr << "Error: unsupported bit depth: " << fmt.bitsPerSample << std::endl;
return nullptr; return nullptr;
} }
++i; // ASSERT_NORMALIZED(LChannel[i]);
// ASSERT_NORMALIZED(RChannel[i]);
i += increment;
} }
assert(i == size); assert(i == size);

@ -7,7 +7,7 @@
void saveWaveFile(const std::string& fileName, void saveWaveFile(const std::string& fileName,
float32_t* LChannel, float32_t* LChannel,
float32_t* RChannel, float32_t* RChannel,
unsigned size, size_t size,
int sampleRate, int sampleRate,
int bitsPerSample) int bitsPerSample)
{ {
@ -30,10 +30,14 @@ void saveWaveFile(const std::string& fileName,
header.subchunk1Size = 16; header.subchunk1Size = 16;
header.audioFormat = 1; header.audioFormat = 1;
std::strncpy(header.chunkId, "RIFF", 4); header.chunkId.Value = id2int("RIFF");
std::strncpy(header.format, "WAVE", 4); header.format.Value = id2int("WAVE");
std::strncpy(header.subchunk1Id, "fmt ", 4); header.subchunk1Id.Value = id2int("fmt ");
std::strncpy(header.subchunk2Id, "data", 4); 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)); file.write((char*)&header, sizeof(header));

Loading…
Cancel
Save