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. 437
      src/fx_components.cpp
  12. 516
      src/fx_components.h
  13. 98
      src/fx_delay.cpp
  14. 159
      src/fx_delay.h
  15. 226
      src/fx_engine.hpp
  16. 43
      src/fx_flanger.cpp
  17. 104
      src/fx_flanger.h
  18. 49
      src/fx_orbitone.cpp
  19. 69
      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. 46
      src/fx_shimmer_reverb.cpp
  25. 9
      src/fx_shimmer_reverb.h
  26. 184
      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/
# temporary tests
src/test/fxrack_test
src/test/result*.wav
src/test/*.csv
src/test/*.bin
src/test/results
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;
}
void doAddMix(uint8_t channel, float32_t* in)
void doAddMix(uint8_t channel, float32_t* in)
{
float32_t tmp[buffer_length];
assert(in);
if(multiplier[channel]!=UNITY_GAIN)
float32_t tmp[buffer_length];
bool isScaled = (multiplier[channel]!=UNITY_GAIN);
if(isScaled)
arm_scale_f32(in,multiplier[channel],tmp,buffer_length);
arm_add_f32(sumbufL, tmp, sumbufL, buffer_length);
arm_add_f32(sumbufL, (isScaled ? tmp : in), sumbufL, buffer_length);
}
void gain(uint8_t channel, float32_t gain)
@ -119,37 +120,36 @@ public:
void doAddMix(uint8_t channel, float32_t* in)
{
float32_t tmp[buffer_length];
assert(in);
float32_t tmp[buffer_length];
// left
arm_scale_f32(in, panorama[channel][0], tmp, buffer_length);
if(multiplier[channel]!=UNITY_GAIN)
arm_scale_f32(tmp,multiplier[channel],tmp,buffer_length);
arm_scale_f32(in, panorama[channel][0] * multiplier[channel], tmp, buffer_length);
arm_add_f32(sumbufL, tmp, sumbufL, buffer_length);
// right
arm_scale_f32(in, panorama[channel][1], tmp, buffer_length);
if(multiplier[channel]!=UNITY_GAIN)
arm_scale_f32(tmp,multiplier[channel],tmp,buffer_length);
arm_scale_f32(in, panorama[channel][1] * multiplier[channel], tmp, buffer_length);
arm_add_f32(sumbufR, tmp, sumbufR, buffer_length);
}
void doAddMix(uint8_t channel, float32_t* inL, float32_t* inR)
{
float32_t tmp[buffer_length];
assert(inL);
assert(inR);
float32_t tmp[buffer_length];
bool isScaled = (multiplier[channel]!=UNITY_GAIN);
// left
if(multiplier[channel]!=UNITY_GAIN)
if(isScaled)
arm_scale_f32(inL,multiplier[channel],tmp,buffer_length);
arm_add_f32(sumbufL, tmp, sumbufL, buffer_length);
arm_add_f32(sumbufL, (isScaled ? tmp : inL), sumbufL, buffer_length);
// right
if(multiplier[channel]!=UNITY_GAIN)
if(isScaled)
arm_scale_f32(inR,multiplier[channel],tmp,buffer_length);
arm_add_f32(sumbufR, tmp, sumbufR, buffer_length);
arm_add_f32(sumbufR, (isScaled ? tmp : inR), sumbufR, buffer_length);
}
void getMix(float32_t* bufferL, float32_t* bufferR)

@ -83,7 +83,8 @@ const int16_t AudioWaveformSine[257] = {
-4808, -4011, -3212, -2410, -1608, -804, 0
};
AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate)
AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate) :
FXElement(samplerate)
{
input_attn = 0.5f;
in_allp_k = INP_ALLP_COEFF;
@ -156,9 +157,33 @@ AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate)
reverb_level = 0.0f;
}
AudioEffectPlateReverb::~AudioEffectPlateReverb()
{
}
// #define sat16(n, rshift) signed_saturate_rshift((n), 16, (rshift))
void AudioEffectPlateReverb::doReverb(const float32_t* inblockL, const float32_t* inblockR, float32_t* rvbblockL, float32_t* rvbblockR, uint16_t len)
void AudioEffectPlateReverb::reset()
{
memset(in_allp1_bufL, 0, sizeof(in_allp1_bufL));
memset(in_allp2_bufL, 0, sizeof(in_allp2_bufL));
memset(in_allp3_bufL, 0, sizeof(in_allp3_bufL));
memset(in_allp4_bufL, 0, sizeof(in_allp4_bufL));
memset(in_allp1_bufR, 0, sizeof(in_allp1_bufR));
memset(in_allp2_bufR, 0, sizeof(in_allp2_bufR));
memset(in_allp3_bufR, 0, sizeof(in_allp3_bufR));
memset(in_allp4_bufR, 0, sizeof(in_allp4_bufR));
memset(lp_allp1_buf, 0, sizeof(lp_allp1_buf));
memset(lp_allp2_buf, 0, sizeof(lp_allp2_buf));
memset(lp_allp3_buf, 0, sizeof(lp_allp3_buf));
memset(lp_allp4_buf, 0, sizeof(lp_allp4_buf));
memset(lp_dly1_buf, 0, sizeof(lp_dly1_buf));
memset(lp_dly2_buf, 0, sizeof(lp_dly2_buf));
memset(lp_dly3_buf, 0, sizeof(lp_dly3_buf));
memset(lp_dly4_buf, 0, sizeof(lp_dly4_buf));
}
void AudioEffectPlateReverb::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{
float32_t input, acc, temp1, temp2;
uint16_t temp16;
@ -169,6 +194,259 @@ void AudioEffectPlateReverb::doReverb(const float32_t* inblockL, const float32_t
int32_t y0, y1;
int64_t y;
uint32_t idx;
rv_time = rv_time_k;
// do the LFOs
lfo1_phase_acc += lfo1_adder;
idx = lfo1_phase_acc >> 24; // 8bit lookup table address
y0 = AudioWaveformSine[idx];
y1 = AudioWaveformSine[idx+1];
idx = lfo1_phase_acc & 0x00FFFFFF; // lower 24 bit = fractional part
y = (int64_t)y0 * (0x00FFFFFF - idx);
y += (int64_t)y1 * idx;
lfo1_out_sin = (int32_t) (y >> (32-8)); // 16bit output
idx = ((lfo1_phase_acc >> 24)+64) & 0xFF;
y0 = AudioWaveformSine[idx];
y1 = AudioWaveformSine[idx + 1];
y = (int64_t)y0 * (0x00FFFFFF - idx);
y += (int64_t)y1 * idx;
lfo1_out_cos = (int32_t) (y >> (32-8)); // 16bit output
lfo2_phase_acc += lfo2_adder;
idx = lfo2_phase_acc >> 24; // 8bit lookup table address
y0 = AudioWaveformSine[idx];
y1 = AudioWaveformSine[idx+1];
idx = lfo2_phase_acc & 0x00FFFFFF; // lower 24 bit = fractional part
y = (int64_t)y0 * (0x00FFFFFF - idx);
y += (int64_t)y1 * idx;
lfo2_out_sin = (int32_t) (y >> (32-8)); //32-8->output 16bit,
idx = ((lfo2_phase_acc >> 24)+64) & 0xFF;
y0 = AudioWaveformSine[idx];
y1 = AudioWaveformSine[idx + 1];
y = (int64_t)y0 * (0x00FFFFFF - idx);
y += (int64_t)y1 * idx;
lfo2_out_cos = (int32_t) (y >> (32-8)); // 16bit output
input = inL * input_attn;
// chained input allpasses, channel L
acc = in_allp1_bufL[in_allp1_idxL] + input * in_allp_k;
in_allp1_bufL[in_allp1_idxL] = input - in_allp_k * acc;
input = acc;
if (++in_allp1_idxL >= sizeof(in_allp1_bufL)/sizeof(float32_t)) in_allp1_idxL = 0;
acc = in_allp2_bufL[in_allp2_idxL] + input * in_allp_k;
in_allp2_bufL[in_allp2_idxL] = input - in_allp_k * acc;
input = acc;
if (++in_allp2_idxL >= sizeof(in_allp2_bufL)/sizeof(float32_t)) in_allp2_idxL = 0;
acc = in_allp3_bufL[in_allp3_idxL] + input * in_allp_k;
in_allp3_bufL[in_allp3_idxL] = input - in_allp_k * acc;
input = acc;
if (++in_allp3_idxL >= sizeof(in_allp3_bufL)/sizeof(float32_t)) in_allp3_idxL = 0;
acc = in_allp4_bufL[in_allp4_idxL] + input * in_allp_k;
in_allp4_bufL[in_allp4_idxL] = input - in_allp_k * acc;
in_allp_out_L = acc;
if (++in_allp4_idxL >= sizeof(in_allp4_bufL)/sizeof(float32_t)) in_allp4_idxL = 0;
input = inR * input_attn;
// chained input allpasses, channel R
acc = in_allp1_bufR[in_allp1_idxR] + input * in_allp_k;
in_allp1_bufR[in_allp1_idxR] = input - in_allp_k * acc;
input = acc;
if (++in_allp1_idxR >= sizeof(in_allp1_bufR)/sizeof(float32_t)) in_allp1_idxR = 0;
acc = in_allp2_bufR[in_allp2_idxR] + input * in_allp_k;
in_allp2_bufR[in_allp2_idxR] = input - in_allp_k * acc;
input = acc;
if (++in_allp2_idxR >= sizeof(in_allp2_bufR)/sizeof(float32_t)) in_allp2_idxR = 0;
acc = in_allp3_bufR[in_allp3_idxR] + input * in_allp_k;
in_allp3_bufR[in_allp3_idxR] = input - in_allp_k * acc;
input = acc;
if (++in_allp3_idxR >= sizeof(in_allp3_bufR)/sizeof(float32_t)) in_allp3_idxR = 0;
acc = in_allp4_bufR[in_allp4_idxR] + input * in_allp_k;
in_allp4_bufR[in_allp4_idxR] = input - in_allp_k * acc;
in_allp_out_R = acc;
if (++in_allp4_idxR >= sizeof(in_allp4_bufR)/sizeof(float32_t)) in_allp4_idxR = 0;
// input allpases done, start loop allpases
input = lp_allp_out + in_allp_out_R;
acc = lp_allp1_buf[lp_allp1_idx] + input * loop_allp_k; // input is the lp allpass chain output
lp_allp1_buf[lp_allp1_idx] = input - loop_allp_k * acc;
input = acc;
if (++lp_allp1_idx >= sizeof(lp_allp1_buf)/sizeof(float32_t))lp_allp1_idx = 0;
acc = lp_dly1_buf[lp_dly1_idx]; // read the end of the delay
lp_dly1_buf[lp_dly1_idx] = input; // write new sample
input = acc;
if (++lp_dly1_idx >= sizeof(lp_dly1_buf)/sizeof(float32_t)) lp_dly1_idx = 0; // update index
// hi/lo shelving filter
temp1 = input - lpf1;
lpf1 += temp1 * lp_lowpass_f;
temp2 = input - lpf1;
temp1 = lpf1 - hpf1;
hpf1 += temp1 * lp_hipass_f;
acc = lpf1 + temp2*lp_hidamp_k + hpf1*lp_lodamp_k;
acc = acc * rv_time * rv_time_scaler; // scale by the reveb time
input = acc + in_allp_out_L;
acc = lp_allp2_buf[lp_allp2_idx] + input * loop_allp_k;
lp_allp2_buf[lp_allp2_idx] = input - loop_allp_k * acc;
input = acc;
if (++lp_allp2_idx >= sizeof(lp_allp2_buf)/sizeof(float32_t)) lp_allp2_idx = 0;
acc = lp_dly2_buf[lp_dly2_idx]; // read the end of the delay
lp_dly2_buf[lp_dly2_idx] = input; // write new sample
input = acc;
if (++lp_dly2_idx >= sizeof(lp_dly2_buf)/sizeof(float32_t)) lp_dly2_idx = 0; // update index
// hi/lo shelving filter
temp1 = input - lpf2;
lpf2 += temp1 * lp_lowpass_f;
temp2 = input - lpf2;
temp1 = lpf2 - hpf2;
hpf2 += temp1 * lp_hipass_f;
acc = lpf2 + temp2*lp_hidamp_k + hpf2*lp_lodamp_k;
acc = acc * rv_time * rv_time_scaler;
input = acc + in_allp_out_R;
acc = lp_allp3_buf[lp_allp3_idx] + input * loop_allp_k;
lp_allp3_buf[lp_allp3_idx] = input - loop_allp_k * acc;
input = acc;
if (++lp_allp3_idx >= sizeof(lp_allp3_buf)/sizeof(float32_t)) lp_allp3_idx = 0;
acc = lp_dly3_buf[lp_dly3_idx]; // read the end of the delay
lp_dly3_buf[lp_dly3_idx] = input; // write new sample
input = acc;
if (++lp_dly3_idx >= sizeof(lp_dly3_buf)/sizeof(float32_t)) lp_dly3_idx = 0; // update index
// hi/lo shelving filter
temp1 = input - lpf3;
lpf3 += temp1 * lp_lowpass_f;
temp2 = input - lpf3;
temp1 = lpf3 - hpf3;
hpf3 += temp1 * lp_hipass_f;
acc = lpf3 + temp2*lp_hidamp_k + hpf3*lp_lodamp_k;
acc = acc * rv_time * rv_time_scaler;
input = acc + in_allp_out_L;
acc = lp_allp4_buf[lp_allp4_idx] + input * loop_allp_k;
lp_allp4_buf[lp_allp4_idx] = input - loop_allp_k * acc;
input = acc;
if (++lp_allp4_idx >= sizeof(lp_allp4_buf)/sizeof(float32_t)) lp_allp4_idx = 0;
acc = lp_dly4_buf[lp_dly4_idx]; // read the end of the delay
lp_dly4_buf[lp_dly4_idx] = input; // write new sample
input = acc;
if (++lp_dly4_idx >= sizeof(lp_dly4_buf)/sizeof(float32_t)) lp_dly4_idx= 0; // update index
// hi/lo shelving filter
temp1 = input - lpf4;
lpf4 += temp1 * lp_lowpass_f;
temp2 = input - lpf4;
temp1 = lpf4 - hpf4;
hpf4 += temp1 * lp_hipass_f;
acc = lpf4 + temp2*lp_hidamp_k + hpf4*lp_lodamp_k;
acc = acc * rv_time * rv_time_scaler;
lp_allp_out = acc;
// channel L:
#ifdef TAP1_MODULATED
temp16 = (lp_dly1_idx + lp_dly1_offset_L + (lfo1_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly1_buf)/sizeof(float32_t));
temp1 = lp_dly1_buf[temp16++]; // sample now
if (temp16 >= sizeof(lp_dly1_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly1_buf[temp16]; // sample next
input = (float32_t)(lfo1_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc = (temp1*(1.0f-input) + temp2*input)* 0.8f;
#else
temp16 = (lp_dly1_idx + lp_dly1_offset_L) % (sizeof(lp_dly1_buf)/sizeof(float32_t));
acc = lp_dly1_buf[temp16]* 0.8f;
#endif
#ifdef TAP2_MODULATED
temp16 = (lp_dly2_idx + lp_dly2_offset_L + (lfo1_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly2_buf)/sizeof(float32_t));
temp1 = lp_dly2_buf[temp16++];
if (temp16 >= sizeof(lp_dly2_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly2_buf[temp16];
input = (float32_t)(lfo1_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc += (temp1*(1.0f-input) + temp2*input)* 0.7f;
#else
temp16 = (lp_dly2_idx + lp_dly2_offset_L) % (sizeof(lp_dly2_buf)/sizeof(float32_t));
acc += (temp1*(1.0f-input) + temp2*input)* 0.6f;
#endif
temp16 = (lp_dly3_idx + lp_dly3_offset_L + (lfo2_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly3_buf)/sizeof(float32_t));
temp1 = lp_dly3_buf[temp16++];
if (temp16 >= sizeof(lp_dly3_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly3_buf[temp16];
input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc += (temp1*(1.0f-input) + temp2*input)* 0.6f;
temp16 = (lp_dly4_idx + lp_dly4_offset_L + (lfo2_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly4_buf)/sizeof(float32_t));
temp1 = lp_dly4_buf[temp16++];
if (temp16 >= sizeof(lp_dly4_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly4_buf[temp16];
input = (float32_t)(lfo2_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc += (temp1*(1.0f-input) + temp2*input)* 0.5f;
// Master lowpass filter
temp1 = acc - master_lowpass_l;
master_lowpass_l += temp1 * master_lowpass_f;
outL = master_lowpass_l;
// Channel R
#ifdef TAP1_MODULATED
temp16 = (lp_dly1_idx + lp_dly1_offset_R + (lfo2_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly1_buf)/sizeof(float32_t));
temp1 = lp_dly1_buf[temp16++]; // sample now
if (temp16 >= sizeof(lp_dly1_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly1_buf[temp16]; // sample next
input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc = (temp1*(1.0f-input) + temp2*input)* 0.8f;
#else
temp16 = (lp_dly1_idx + lp_dly1_offset_R) % (sizeof(lp_dly1_buf)/sizeof(float32_t));
acc = lp_dly1_buf[temp16] * 0.8f;
#endif
#ifdef TAP2_MODULATED
temp16 = (lp_dly2_idx + lp_dly2_offset_R + (lfo1_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly2_buf)/sizeof(float32_t));
temp1 = lp_dly2_buf[temp16++];
if (temp16 >= sizeof(lp_dly2_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly2_buf[temp16];
input = (float32_t)(lfo1_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc += (temp1*(1.0f-input) + temp2*input)* 0.7f;
#else
temp16 = (lp_dly2_idx + lp_dly2_offset_R) % (sizeof(lp_dly2_buf)/sizeof(float32_t));
acc += (temp1*(1.0f-input) + temp2*input)* 0.7f;
#endif
temp16 = (lp_dly3_idx + lp_dly3_offset_R + (lfo2_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly3_buf)/sizeof(float32_t));
temp1 = lp_dly3_buf[temp16++];
if (temp16 >= sizeof(lp_dly3_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly3_buf[temp16];
input = (float32_t)(lfo2_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc += (temp1*(1.0f-input) + temp2*input)* 0.6f;
temp16 = (lp_dly4_idx + lp_dly4_offset_R + (lfo1_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly4_buf)/sizeof(float32_t));
temp1 = lp_dly4_buf[temp16++];
if (temp16 >= sizeof(lp_dly4_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly4_buf[temp16];
input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc += (temp1*(1.0f-input) + temp2*input)* 0.5f;
// Master lowpass filter
temp1 = acc - master_lowpass_r;
master_lowpass_r += temp1 * master_lowpass_f;
outR = master_lowpass_r;
}
void AudioEffectPlateReverb::doReverb(const float32_t* inblockL, const float32_t* inblockR, float32_t* rvbblockL, float32_t* rvbblockR, uint16_t len)
{
static bool cleanup_done = false;
// handle bypass, 1st call will clean the buffers to avoid continuing the previous reverb tail
@ -176,23 +454,7 @@ void AudioEffectPlateReverb::doReverb(const float32_t* inblockL, const float32_t
{
if (!cleanup_done)
{
memset(in_allp1_bufL, 0, sizeof(in_allp1_bufL));
memset(in_allp2_bufL, 0, sizeof(in_allp2_bufL));
memset(in_allp3_bufL, 0, sizeof(in_allp3_bufL));
memset(in_allp4_bufL, 0, sizeof(in_allp4_bufL));
memset(in_allp1_bufR, 0, sizeof(in_allp1_bufR));
memset(in_allp2_bufR, 0, sizeof(in_allp2_bufR));
memset(in_allp3_bufR, 0, sizeof(in_allp3_bufR));
memset(in_allp4_bufR, 0, sizeof(in_allp4_bufR));
memset(lp_allp1_buf, 0, sizeof(lp_allp1_buf));
memset(lp_allp2_buf, 0, sizeof(lp_allp2_buf));
memset(lp_allp3_buf, 0, sizeof(lp_allp3_buf));
memset(lp_allp4_buf, 0, sizeof(lp_allp4_buf));
memset(lp_dly1_buf, 0, sizeof(lp_dly1_buf));
memset(lp_dly2_buf, 0, sizeof(lp_dly2_buf));
memset(lp_dly3_buf, 0, sizeof(lp_dly3_buf));
memset(lp_dly4_buf, 0, sizeof(lp_dly4_buf));
this->reset();
cleanup_done = true;
}
@ -200,255 +462,8 @@ void AudioEffectPlateReverb::doReverb(const float32_t* inblockL, const float32_t
}
cleanup_done = false;
rv_time = rv_time_k;
for (uint16_t i=0; i < len; i++)
{
// do the LFOs
lfo1_phase_acc += lfo1_adder;
idx = lfo1_phase_acc >> 24; // 8bit lookup table address
y0 = AudioWaveformSine[idx];
y1 = AudioWaveformSine[idx+1];
idx = lfo1_phase_acc & 0x00FFFFFF; // lower 24 bit = fractional part
y = (int64_t)y0 * (0x00FFFFFF - idx);
y += (int64_t)y1 * idx;
lfo1_out_sin = (int32_t) (y >> (32-8)); // 16bit output
idx = ((lfo1_phase_acc >> 24)+64) & 0xFF;
y0 = AudioWaveformSine[idx];
y1 = AudioWaveformSine[idx + 1];
y = (int64_t)y0 * (0x00FFFFFF - idx);
y += (int64_t)y1 * idx;
lfo1_out_cos = (int32_t) (y >> (32-8)); // 16bit output
lfo2_phase_acc += lfo2_adder;
idx = lfo2_phase_acc >> 24; // 8bit lookup table address
y0 = AudioWaveformSine[idx];
y1 = AudioWaveformSine[idx+1];
idx = lfo2_phase_acc & 0x00FFFFFF; // lower 24 bit = fractional part
y = (int64_t)y0 * (0x00FFFFFF - idx);
y += (int64_t)y1 * idx;
lfo2_out_sin = (int32_t) (y >> (32-8)); //32-8->output 16bit,
idx = ((lfo2_phase_acc >> 24)+64) & 0xFF;
y0 = AudioWaveformSine[idx];
y1 = AudioWaveformSine[idx + 1];
y = (int64_t)y0 * (0x00FFFFFF - idx);
y += (int64_t)y1 * idx;
lfo2_out_cos = (int32_t) (y >> (32-8)); // 16bit output
input = inblockL[i] * input_attn;
// chained input allpasses, channel L
acc = in_allp1_bufL[in_allp1_idxL] + input * in_allp_k;
in_allp1_bufL[in_allp1_idxL] = input - in_allp_k * acc;
input = acc;
if (++in_allp1_idxL >= sizeof(in_allp1_bufL)/sizeof(float32_t)) in_allp1_idxL = 0;
acc = in_allp2_bufL[in_allp2_idxL] + input * in_allp_k;
in_allp2_bufL[in_allp2_idxL] = input - in_allp_k * acc;
input = acc;
if (++in_allp2_idxL >= sizeof(in_allp2_bufL)/sizeof(float32_t)) in_allp2_idxL = 0;
acc = in_allp3_bufL[in_allp3_idxL] + input * in_allp_k;
in_allp3_bufL[in_allp3_idxL] = input - in_allp_k * acc;
input = acc;
if (++in_allp3_idxL >= sizeof(in_allp3_bufL)/sizeof(float32_t)) in_allp3_idxL = 0;
acc = in_allp4_bufL[in_allp4_idxL] + input * in_allp_k;
in_allp4_bufL[in_allp4_idxL] = input - in_allp_k * acc;
in_allp_out_L = acc;
if (++in_allp4_idxL >= sizeof(in_allp4_bufL)/sizeof(float32_t)) in_allp4_idxL = 0;
input = inblockR[i] * input_attn;
// chained input allpasses, channel R
acc = in_allp1_bufR[in_allp1_idxR] + input * in_allp_k;
in_allp1_bufR[in_allp1_idxR] = input - in_allp_k * acc;
input = acc;
if (++in_allp1_idxR >= sizeof(in_allp1_bufR)/sizeof(float32_t)) in_allp1_idxR = 0;
acc = in_allp2_bufR[in_allp2_idxR] + input * in_allp_k;
in_allp2_bufR[in_allp2_idxR] = input - in_allp_k * acc;
input = acc;
if (++in_allp2_idxR >= sizeof(in_allp2_bufR)/sizeof(float32_t)) in_allp2_idxR = 0;
acc = in_allp3_bufR[in_allp3_idxR] + input * in_allp_k;
in_allp3_bufR[in_allp3_idxR] = input - in_allp_k * acc;
input = acc;
if (++in_allp3_idxR >= sizeof(in_allp3_bufR)/sizeof(float32_t)) in_allp3_idxR = 0;
acc = in_allp4_bufR[in_allp4_idxR] + input * in_allp_k;
in_allp4_bufR[in_allp4_idxR] = input - in_allp_k * acc;
in_allp_out_R = acc;
if (++in_allp4_idxR >= sizeof(in_allp4_bufR)/sizeof(float32_t)) in_allp4_idxR = 0;
// input allpases done, start loop allpases
input = lp_allp_out + in_allp_out_R;
acc = lp_allp1_buf[lp_allp1_idx] + input * loop_allp_k; // input is the lp allpass chain output
lp_allp1_buf[lp_allp1_idx] = input - loop_allp_k * acc;
input = acc;
if (++lp_allp1_idx >= sizeof(lp_allp1_buf)/sizeof(float32_t)) lp_allp1_idx = 0;
acc = lp_dly1_buf[lp_dly1_idx]; // read the end of the delay
lp_dly1_buf[lp_dly1_idx] = input; // write new sample
input = acc;
if (++lp_dly1_idx >= sizeof(lp_dly1_buf)/sizeof(float32_t)) lp_dly1_idx = 0; // update index
// hi/lo shelving filter
temp1 = input - lpf1;
lpf1 += temp1 * lp_lowpass_f;
temp2 = input - lpf1;
temp1 = lpf1 - hpf1;
hpf1 += temp1 * lp_hipass_f;
acc = lpf1 + temp2*lp_hidamp_k + hpf1*lp_lodamp_k;
acc = acc * rv_time * rv_time_scaler; // scale by the reveb time
input = acc + in_allp_out_L;
acc = lp_allp2_buf[lp_allp2_idx] + input * loop_allp_k;
lp_allp2_buf[lp_allp2_idx] = input - loop_allp_k * acc;
input = acc;
if (++lp_allp2_idx >= sizeof(lp_allp2_buf)/sizeof(float32_t)) lp_allp2_idx = 0;
acc = lp_dly2_buf[lp_dly2_idx]; // read the end of the delay
lp_dly2_buf[lp_dly2_idx] = input; // write new sample
input = acc;
if (++lp_dly2_idx >= sizeof(lp_dly2_buf)/sizeof(float32_t)) lp_dly2_idx = 0; // update index
// hi/lo shelving filter
temp1 = input - lpf2;
lpf2 += temp1 * lp_lowpass_f;
temp2 = input - lpf2;
temp1 = lpf2 - hpf2;
hpf2 += temp1 * lp_hipass_f;
acc = lpf2 + temp2*lp_hidamp_k + hpf2*lp_lodamp_k;
acc = acc * rv_time * rv_time_scaler;
input = acc + in_allp_out_R;
acc = lp_allp3_buf[lp_allp3_idx] + input * loop_allp_k;
lp_allp3_buf[lp_allp3_idx] = input - loop_allp_k * acc;
input = acc;
if (++lp_allp3_idx >= sizeof(lp_allp3_buf)/sizeof(float32_t)) lp_allp3_idx = 0;
acc = lp_dly3_buf[lp_dly3_idx]; // read the end of the delay
lp_dly3_buf[lp_dly3_idx] = input; // write new sample
input = acc;
if (++lp_dly3_idx >= sizeof(lp_dly3_buf)/sizeof(float32_t)) lp_dly3_idx = 0; // update index
// hi/lo shelving filter
temp1 = input - lpf3;
lpf3 += temp1 * lp_lowpass_f;
temp2 = input - lpf3;
temp1 = lpf3 - hpf3;
hpf3 += temp1 * lp_hipass_f;
acc = lpf3 + temp2*lp_hidamp_k + hpf3*lp_lodamp_k;
acc = acc * rv_time * rv_time_scaler;
input = acc + in_allp_out_L;
acc = lp_allp4_buf[lp_allp4_idx] + input * loop_allp_k;
lp_allp4_buf[lp_allp4_idx] = input - loop_allp_k * acc;
input = acc;
if (++lp_allp4_idx >= sizeof(lp_allp4_buf)/sizeof(float32_t)) lp_allp4_idx = 0;
acc = lp_dly4_buf[lp_dly4_idx]; // read the end of the delay
lp_dly4_buf[lp_dly4_idx] = input; // write new sample
input = acc;
if (++lp_dly4_idx >= sizeof(lp_dly4_buf)/sizeof(float32_t)) lp_dly4_idx= 0; // update index
// hi/lo shelving filter
temp1 = input - lpf4;
lpf4 += temp1 * lp_lowpass_f;
temp2 = input - lpf4;
temp1 = lpf4 - hpf4;
hpf4 += temp1 * lp_hipass_f;
acc = lpf4 + temp2*lp_hidamp_k + hpf4*lp_lodamp_k;
acc = acc * rv_time * rv_time_scaler;
lp_allp_out = acc;
// channel L:
#ifdef TAP1_MODULATED
temp16 = (lp_dly1_idx + lp_dly1_offset_L + (lfo1_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly1_buf)/sizeof(float32_t));
temp1 = lp_dly1_buf[temp16++]; // sample now
if (temp16 >= sizeof(lp_dly1_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly1_buf[temp16]; // sample next
input = (float32_t)(lfo1_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc = (temp1*(1.0f-input) + temp2*input)* 0.8f;
#else
temp16 = (lp_dly1_idx + lp_dly1_offset_L) % (sizeof(lp_dly1_buf)/sizeof(float32_t));
acc = lp_dly1_buf[temp16]* 0.8f;
#endif
#ifdef TAP2_MODULATED
temp16 = (lp_dly2_idx + lp_dly2_offset_L + (lfo1_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly2_buf)/sizeof(float32_t));
temp1 = lp_dly2_buf[temp16++];
if (temp16 >= sizeof(lp_dly2_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly2_buf[temp16];
input = (float32_t)(lfo1_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc += (temp1*(1.0f-input) + temp2*input)* 0.7f;
#else
temp16 = (lp_dly2_idx + lp_dly2_offset_L) % (sizeof(lp_dly2_buf)/sizeof(float32_t));
acc += (temp1*(1.0f-input) + temp2*input)* 0.6f;
#endif
temp16 = (lp_dly3_idx + lp_dly3_offset_L + (lfo2_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly3_buf)/sizeof(float32_t));
temp1 = lp_dly3_buf[temp16++];
if (temp16 >= sizeof(lp_dly3_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly3_buf[temp16];
input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc += (temp1*(1.0f-input) + temp2*input)* 0.6f;
temp16 = (lp_dly4_idx + lp_dly4_offset_L + (lfo2_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly4_buf)/sizeof(float32_t));
temp1 = lp_dly4_buf[temp16++];
if (temp16 >= sizeof(lp_dly4_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly4_buf[temp16];
input = (float32_t)(lfo2_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc += (temp1*(1.0f-input) + temp2*input)* 0.5f;
// Master lowpass filter
temp1 = acc - master_lowpass_l;
master_lowpass_l += temp1 * master_lowpass_f;
rvbblockL[i] = master_lowpass_l;
// Channel R
#ifdef TAP1_MODULATED
temp16 = (lp_dly1_idx + lp_dly1_offset_R + (lfo2_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly1_buf)/sizeof(float32_t));
temp1 = lp_dly1_buf[temp16++]; // sample now
if (temp16 >= sizeof(lp_dly1_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly1_buf[temp16]; // sample next
input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc = (temp1*(1.0f-input) + temp2*input)* 0.8f;
#else
temp16 = (lp_dly1_idx + lp_dly1_offset_R) % (sizeof(lp_dly1_buf)/sizeof(float32_t));
acc = lp_dly1_buf[temp16] * 0.8f;
#endif
#ifdef TAP2_MODULATED
temp16 = (lp_dly2_idx + lp_dly2_offset_R + (lfo1_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly2_buf)/sizeof(float32_t));
temp1 = lp_dly2_buf[temp16++];
if (temp16 >= sizeof(lp_dly2_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly2_buf[temp16];
input = (float32_t)(lfo1_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc += (temp1*(1.0f-input) + temp2*input)* 0.7f;
#else
temp16 = (lp_dly2_idx + lp_dly2_offset_R) % (sizeof(lp_dly2_buf)/sizeof(float32_t));
acc += (temp1*(1.0f-input) + temp2*input)* 0.7f;
#endif
temp16 = (lp_dly3_idx + lp_dly3_offset_R + (lfo2_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly3_buf)/sizeof(float32_t));
temp1 = lp_dly3_buf[temp16++];
if (temp16 >= sizeof(lp_dly3_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly3_buf[temp16];
input = (float32_t)(lfo2_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc += (temp1*(1.0f-input) + temp2*input)* 0.6f;
temp16 = (lp_dly4_idx + lp_dly4_offset_R + (lfo1_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly4_buf)/sizeof(float32_t));
temp1 = lp_dly4_buf[temp16++];
if (temp16 >= sizeof(lp_dly4_buf)/sizeof(float32_t)) temp16 = 0;
temp2 = lp_dly4_buf[temp16];
input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k
acc += (temp1*(1.0f-input) + temp2*input)* 0.5f;
// Master lowpass filter
temp1 = acc - master_lowpass_r;
master_lowpass_r += temp1 * master_lowpass_f;
rvbblockR[i] = master_lowpass_r;
this->processSample(inblockL[i], inblockR[i], rvbblockL[i], rvbblockR[i]);
}
}

@ -44,9 +44,7 @@
#ifndef _EFFECT_PLATERVBSTEREO_H
#define _EFFECT_PLATERVBSTEREO_H
#include <stdint.h>
#include <arm_math.h>
#include "common.h"
#include "fx_components.h"
/***
* Loop delay modulation: comment/uncomment to switch sin/cos
@ -56,11 +54,18 @@
//#define TAP1_MODULATED
#define TAP2_MODULATED
class AudioEffectPlateReverb
class AudioEffectPlateReverb : public FXElement
{
DISALLOW_COPY_AND_ASSIGN(AudioEffectPlateReverb);
public:
AudioEffectPlateReverb(float32_t samplerate);
void doReverb(const float32_t* inblockL, const float32_t* inblockR, float32_t* rvbblockL, float32_t* rvbblockR,uint16_t len);
virtual ~AudioEffectPlateReverb();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void doReverb(const float32_t* inblockL, const float32_t* inblockR, float32_t* rvbblockL, float32_t* rvbblockR, uint16_t len);
void size(float n)
{
@ -191,6 +196,9 @@ private:
uint32_t lfo2_phase_acc; // LFO 2
uint32_t lfo2_adder;
IMPLEMENT_DUMP()
IMPLEMENT_INSPECT(return 0u;)
};
#endif // _EFFECT_PLATEREV_H

@ -18,8 +18,48 @@
//
#pragma once
#if defined(ARM_ALLOW_MULTI_CORE)
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&) = delete; \
void operator=(const TypeName&) = delete
#if defined(ARM_ALLOW_MULTI_CORE)
#define FXRACK_ENABLE //Add support for the FXRack
#endif
#ifdef DEBUG
#include <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

@ -14,7 +14,7 @@
//
// fx.h
//
// Base class for Stereo audio effects proposed in the context of the MiniDexed project
// Base classes for Stereo audio effects proposed in the context of the MiniDexed project
//
#pragma once
@ -22,21 +22,23 @@
#include <arm_math.h>
#include "common.h"
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&) = delete; \
void operator=(const TypeName&) = delete
#include "debug.hpp"
#include "fx_base.h"
class FXBase
class INSPECTABLE(FXBase)
{
DISALLOW_COPY_AND_ASSIGN(FXBase);
protected:
FXBase(float32_t sampling_rate);
virtual ~FXBase();
public:
virtual ~FXBase();
float32_t getSamplingRate() const;
virtual void reset() = 0;
private:
const float32_t SamplingRate;
};

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

@ -22,22 +22,26 @@
#include "fx_components.h"
#include "fx_engine.hpp"
#define CHORUS_FULLSCALE_DEPTH_RATIO 1536.0f
class Chorus : public FXElement
{
DISALLOW_COPY_AND_ASSIGN(Chorus);
public:
enum LFO_Index
enum LFOIndex
{
Sin1 = 0,
Sin2,
Cos1,
Cos2
Cos2,
kLFOCount
};
Chorus(float32_t sampling_rate);
virtual ~Chorus();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setDepth(float32_t depth);
@ -47,7 +51,7 @@ public:
float32_t getRate() const;
private:
typedef FxEngine<2048, FORMAT_16_BIT, false> Engine;
typedef FxEngine<2048, Format::FORMAT_FLOAT32, false> Engine;
Engine engine_;
float32_t rate_; // Normalized frequency for the 2 LFOs frequencies (0.0 - 10.0)
@ -55,5 +59,68 @@ private:
float32_t fullscale_depth_; // Equivalent to depth_ but in the range of (0.0 - 384.0)
float32_t feedback_; // Feedback level of the chorus (0.0 - 1.0)
LFO* lfo_[4];
LFO* lfo_[LFOIndex::kLFOCount];
IMPLEMENT_DUMP(
const size_t space = 16;
const size_t precision = 5;
std::stringstream ss;
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
if(deepInspection)
{
this->engine_.dump(out, deepInspection, tag + ".engine_");
}
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "rate_");
SS__TEXT(ss, ' ', space, std::left, '|', "depth_");
SS__TEXT(ss, ' ', space, std::left, '|', "fullscale_depth_");
SS__TEXT(ss, ' ', space, std::left, '|', "feedback_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->rate_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->depth_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->fullscale_depth_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->feedback_);
out << "\t" << ss.str() << std::endl;
if(deepInspection)
{
for(size_t i = 0; i < LFOIndex::kLFOCount; ++i)
{
this->lfo_[i]->dump(out, deepInspection, tag + ".lfo_[ " + std::to_string(i) + " ]");
}
}
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
size_t nb_errors = 0u;
if(deepInspection)
{
nb_errors += this->engine_.inspect(inspector, deepInspection, tag + ".engine_");
for(size_t i = 0; i < LFOIndex::kLFOCount; ++i)
{
nb_errors += this->lfo_[i]->inspect(inspector, deepInspection, tag + ".lfo_[ " + std::to_string(i) + " ]");
}
}
nb_errors += inspector(tag + ".rate_", this->rate_, 0.0f, 1.0f, deepInspection);
nb_errors += inspector(tag + ".depth_", this->depth_, 0.0f, 1.0f, deepInspection);
nb_errors += inspector(tag + ".fullscale_depth_", this->fullscale_depth_, 0.0f, CHORUS_FULLSCALE_DEPTH_RATIO, deepInspection);
return nb_errors;
)
};

@ -7,16 +7,266 @@
///////////////////////////////
const float32_t Constants::M2PI = 2.0f * PI;
const float32_t Constants::MPI_2 = PI / 2.0f;
const float32_t Constants::MPI_3 = PI / 3.0f;
const float32_t Constants::MPI_4 = PI / 4.0f;
const float32_t Constants::M1_PI = 1.0f / PI;
/////////////////////////
// LFO implemlentation //
/////////////////////////
LFO::LFO(float32_t sampling_rate, Waveform waveform, float32_t min_frequency, float32_t max_frequency, float32_t initial_phase) :
/////////////////////////////
// FastLFO implemlentation //
/////////////////////////////
FastLFO::FastLFO(float32_t sampling_rate, float32_t min_frequency, float32_t max_frequency, float32_t initial_phase) :
FXBase(sampling_rate),
InitialPhase(initial_phase),
min_frequency_(min_frequency),
max_frequency_(max_frequency),
frequency_(0.0f),
y0_(0.0f),
y1_(0.0f),
iir_coefficient_(0.0f),
initial_amplitude_(0.0f)
{
assert(this->min_frequency_ <= this->max_frequency_);
assert(this->max_frequency_ < sampling_rate / 2.0f);
this->setFrequency(this->min_frequency_);
}
FastLFO::~FastLFO()
{
}
void FastLFO::setNormalizedFrequency(float32_t normalized_frequency)
{
normalized_frequency = constrain(normalized_frequency, 0.0f, 1.0f);
if(this->normalized_frequency_ != normalized_frequency)
{
float32_t frequency = mapfloat(normalized_frequency, 0.0f, 1.0f, this->min_frequency_, this->max_frequency_);
this->normalized_frequency_ = normalized_frequency;
this->frequency_ = frequency;
this->unitary_frequency_ = this->frequency_ / this->getSamplingRate();
this->updateCoefficient();
}
}
float32_t FastLFO::getNormalizedFrequency() const
{
return this->normalized_frequency_;
}
void FastLFO::setFrequency(float32_t frequency)
{
frequency = constrain(frequency, this->min_frequency_, this->max_frequency_);
if(this->frequency_ != frequency)
{
float32_t normalized_frequency = mapfloat(frequency, this->min_frequency_, this->max_frequency_, 0.0f, 1.0f);
this->normalized_frequency_ = normalized_frequency;
this->frequency_ = frequency;
this->unitary_frequency_ = this->frequency_ / this->getSamplingRate();
this->updateCoefficient();
}
}
float32_t FastLFO::getFrequency() const
{
return this->frequency_;
}
void FastLFO::updateCoefficient()
{
float32_t frequency = this->unitary_frequency_;
float32_t sign = 16.0f;
frequency -= 0.25f;
if(frequency < 0.0f)
{
frequency = -frequency;
}
else
{
if(frequency > 0.5f)
{
frequency -= 0.5f;
}
else
{
sign = -16.0f;
}
}
this->iir_coefficient_ = sign * frequency * (1.0f - 2.0f * frequency);
this->initial_amplitude_ = this->iir_coefficient_ * 0.25f;
this->reset();
}
void FastLFO::reset()
{
// computing cos(0) = sin(-PI/2)
this->y1_ = this->initial_amplitude_;
this->y0_ = 0.5f;
if(this->unitary_frequency_ == 0.0f)
{
return;
}
float32_t p_i = Constants::M2PI * this->unitary_frequency_;
float32_t p = 0.0f;
float32_t t_p = this->InitialPhase - Constants::MPI_2;
if(t_p < 0.0f)
{
t_p += Constants::M2PI;
}
while(p < t_p)
{
this->process();
p += p_i;
}
}
float32_t FastLFO::process()
{
float32_t temp = this->y0_;
this->y0_ = this->iir_coefficient_ * this->y0_ - this->y1_;
this->y1_ = temp;
this->current_ = (temp + 0.5f) * 2.0f - 1.0f;
return this->current_;
}
float32_t FastLFO::current() const
{
return this->current_;
}
////////////////////////////////////////////////
// InterpolatedSineOscillator implemlentation //
////////////////////////////////////////////////
bool InterpolatedSineOscillator::ClassInitializer()
{
float32_t phase_increment = Constants::M2PI / static_cast<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),
max_frequency_(max_frequency),
waveform_(waveform),
normalized_frequency_(-1.0f),
frequency_(0.0f),
phase_(initial_phase),
@ -26,25 +276,28 @@ LFO::LFO(float32_t sampling_rate, Waveform waveform, float32_t min_frequency, fl
rnd_generator_(rnd_device_()),
rnd_distribution_(-1.0f, 1.0f)
{
this->setWaveform(waveform);
assert(this->min_frequency_ <= this->max_frequency_);
assert(this->max_frequency_ < sampling_rate / 2.0f);
this->setWaveform(Waveform::Sine);
this->setFrequency(this->min_frequency_);
}
LFO::~LFO()
ComplexLFO::~ComplexLFO()
{
}
void LFO::setWaveform(Waveform waveform)
void ComplexLFO::setWaveform(Waveform waveform)
{
this->waveform_ = waveform;
}
LFO::Waveform LFO::getWaveform() const
ComplexLFO::Waveform ComplexLFO::getWaveform() const
{
return this->waveform_;
}
void LFO::setNormalizedFrequency(float32_t normalized_frequency)
void ComplexLFO::setNormalizedFrequency(float32_t normalized_frequency)
{
normalized_frequency = constrain(normalized_frequency, 0.0f, 1.0f);
if(this->normalized_frequency_ != normalized_frequency)
@ -56,12 +309,12 @@ void LFO::setNormalizedFrequency(float32_t normalized_frequency)
}
}
float32_t LFO::getNormalizedFrequency() const
float32_t ComplexLFO::getNormalizedFrequency() const
{
return this->normalized_frequency_;
}
void LFO::setFrequency(float32_t frequency)
void ComplexLFO::setFrequency(float32_t frequency)
{
frequency = constrain(frequency, this->min_frequency_, this->max_frequency_);
if(this->frequency_ != frequency)
@ -73,18 +326,24 @@ void LFO::setFrequency(float32_t frequency)
}
}
float32_t LFO::getFrequency() const
float32_t ComplexLFO::getFrequency() const
{
return this->frequency_;
}
float32_t LFO::process()
void ComplexLFO::reset()
{
this->phase_ = this->InitialPhase;
this->current_sample_ = 0.0f;
}
float32_t ComplexLFO::process()
{
float32_t out = 0.0f;
switch(this->waveform_)
{
case Waveform::Sine:
out = std::sin(this->phase_);
out = arm_sin_f32(this->phase_);
break;
case Waveform::Saw:
out = Constants::M1_PI * this->phase_ - 1.0f;
@ -123,7 +382,7 @@ float32_t LFO::process()
return out;
}
float32_t LFO::current() const
float32_t ComplexLFO::current() const
{
return this->current_sample_;
}
@ -132,7 +391,7 @@ float32_t LFO::current() const
////////////////////////////////////
// JitterGenerator implementation //
////////////////////////////////////
JitterGenerator::JitterGenerator(float32_t sampling_rate) :
JitterGenerator::JitterGenerator(float32_t sampling_rate) :
FXBase(sampling_rate),
rnd_generator_(rnd_device_()),
rnd_distribution_(-1.0f, 1.0f),
@ -151,6 +410,9 @@ JitterGenerator::~JitterGenerator()
void JitterGenerator::setSpeed(float32_t speed)
{
static const float32_t max_frequency = 0.45f * this->getSamplingRate();
speed = constrain(speed, 0.0f, max_frequency);
if(this->speed_ != speed)
{
this->speed_ = speed;
@ -165,7 +427,7 @@ float32_t JitterGenerator::getSpeed() const
void JitterGenerator::setMagnitude(float32_t magnitude)
{
this->magnitude_ = magnitude;
this->magnitude_ = constrain(magnitude, 0.0f, 1.0f);
}
float32_t JitterGenerator::getMagnitude() const
@ -173,9 +435,14 @@ float32_t JitterGenerator::getMagnitude() const
return this->magnitude_;
}
void JitterGenerator::reset()
{
this->phase_ = 0.0f;
}
float32_t JitterGenerator::process()
{
float32_t out = std::sin(this->phase_);
float32_t out = arm_sin_f32(this->phase_);
this->phase_ += this->phase_increment_ * (1.0f + this->magnitude_ * this->rnd_distribution_(this->rnd_generator_));
if(this->phase_ > Constants::M2PI)
@ -186,6 +453,116 @@ float32_t JitterGenerator::process()
return out;
}
//////////////////////////////////////////
// PerlinNoiseGenerator implemlentation //
//////////////////////////////////////////
#define MAX_FREQUENCY_PERLIN_NOISE_GENERATOR 0.5f
const float32_t PerlinNoiseGenerator::Gradients[] =
{
-1.0f, +1.0f,
-1.0f, -1.0f,
+1.0f, -1.0f,
+1.0f, +1.0f
};
PerlinNoiseGenerator::PerlinNoiseGenerator(float32_t sampling_rate, float32_t rate) :
FXBase(sampling_rate),
rate_(0.0f),
phase_(0.0f),
phase_increment_(0.0f),
current_(0.0f)
{
this->setRate(rate);
this->reset();
}
PerlinNoiseGenerator::~PerlinNoiseGenerator()
{
}
void PerlinNoiseGenerator::setRate(float32_t rate)
{
rate = constrain(rate, 0.0f, 1.0f);
if(rate != this->rate_)
{
this->rate_ = rate;
this->phase_increment_ = Constants::M2PI * rate / this->getSamplingRate();
}
}
float32_t PerlinNoiseGenerator::getRate() const
{
return this->rate_;
}
float32_t PerlinNoiseGenerator::getCurrent() const
{
return this->current_;
}
void PerlinNoiseGenerator::reset()
{
this->phase_ = 0.0f;
this->current_ = 0.0f;
}
float32_t PerlinNoiseGenerator::process()
{
if(this->rate_ == 0.0f)
{
return this->current_ = 0.0f;
}
this->current_ = PerlinNoiseGenerator::perlin(this->phase_);
this->phase_ += this->phase_increment_;
if(this->phase_ >= Constants::M2PI)
{
this->phase_ -= Constants::M2PI;
}
return this->current_;
}
int PerlinNoiseGenerator::hash(int x)
{
x = ((x << 13) ^ x);
return (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff;
}
float32_t PerlinNoiseGenerator::interpolate(float32_t a, float32_t b, float32_t x)
{
float32_t ft = x * PI;
float32_t f = (1.0f - arm_cos_f32(ft)) * 0.5;
return a * (1.0f - f) + b * f;
}
float32_t PerlinNoiseGenerator::perlin(float32_t x)
{
// Find the unit square that contains x
int squareX = (int)x;
// Find the relative x of x within that square
double relX = x - squareX;
// Calculate the hashes for the square's four corners
int h1 = PerlinNoiseGenerator::hash(squareX);
int h2 = PerlinNoiseGenerator::hash(squareX + 1);
// Calculate the gradients for each corner
double grad1 = PerlinNoiseGenerator::Gradients[h1 & 3];
double grad2 = PerlinNoiseGenerator::Gradients[h2 & 3];
// Calculate the dot products between the gradient vectors and the distance vectors
double dot1 = grad1 * relX;
double dot2 = grad2 * (relX - 1);
// Interpolate the dot products and return the final noise value
return PerlinNoiseGenerator::interpolate(dot1, dot2, relX);
}
//////////////////////////////////
// softSaturate implemlentation //
//////////////////////////////////
@ -198,7 +575,7 @@ float32_t softSaturator1(float32_t in, float32_t threshold)
y = x;
}
else if(x > threshold)
{
{
y = threshold + (x - threshold) / (1.0f + std::pow((x - threshold) / (1.0f - threshold), 2.0f));
}
else if(x > 1.0f)
@ -208,14 +585,14 @@ float32_t softSaturator1(float32_t in, float32_t threshold)
float32_t g = 2.0f / (1.0f + threshold);
y *= g;
return (in < 0.0f) ? -y : y;
}
float32_t softSaturator2(float32_t input, float32_t saturation)
{
constexpr static float kTubeCurve = 4.0f;
constexpr static float kTubeBias = 0.5f;
const static float kTubeCurve = 4.0f;
const static float kTubeBias = 0.5f;
float absInput = std::abs(input);
float output = 0.0f;
@ -247,6 +624,20 @@ float32_t softSaturator2(float32_t input, float32_t saturation)
return output;
}
float32_t softSaturator3(float32_t input, float32_t overdrive)
{
const float32_t w = (1.0f + overdrive) * Constants::MPI_4;
return constrain(std::tan(w * input), -1.0f, 1.0f);
}
float32_t softSaturator4(float32_t input, float32_t saturator_factor)
{
float32_t x = input * saturator_factor;
float32_t abs_x = std::abs(x);
float32_t sat_x = std::log(1.0f + abs_x) / std::log(1.0f + saturator_factor);
return x > 0 ? sat_x : -sat_x;
}
float32_t waveFolder(float32_t input, float32_t bias)
{
bias = 0.5 + (2.0f - bias) / 4.0f;

@ -28,12 +28,195 @@ struct Constants
{
const static float32_t M2PI; // 2 * PI
const static float32_t MPI_2; // PI / 2
const static float32_t MPI_3; // PI / 3
const static float32_t MPI_4; // PI / 4
const static float32_t M1_PI; // 1 / PI
};
class LFO : public FXBase
class FastLFO : public FXBase
{
DISALLOW_COPY_AND_ASSIGN(FastLFO);
public:
FastLFO(float32_t sampling_rate, float32_t min_frequency = 0.01f, float32_t max_frequency = 10.0f, float32_t initial_phase = 0.0f);
virtual ~FastLFO();
void setNormalizedFrequency(float32_t normalized_frequency);
float32_t getNormalizedFrequency() const;
void setFrequency(float32_t frequency);
float32_t getFrequency() const;
virtual void reset() override;
float32_t process();
float32_t current() const;
private:
void updateCoefficient();
const float32_t InitialPhase;
const float32_t min_frequency_;
const float32_t max_frequency_;
float32_t frequency_;
float32_t normalized_frequency_;
float32_t unitary_frequency_;
float32_t y0_;
float32_t y1_;
float32_t iir_coefficient_;
float32_t initial_amplitude_;
float32_t current_;
IMPLEMENT_DUMP(
const size_t space = 21;
const size_t precision = 5;
std::stringstream ss;
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "InitialPhase");
SS__TEXT(ss, ' ', space, std::left, '|', "frequency_");
SS__TEXT(ss, ' ', space, std::left, '|', "normalized_frequency_");
SS__TEXT(ss, ' ', space, std::left, '|', "unitary_frequency_");
SS__TEXT(ss, ' ', space, std::left, '|', "y0_");
SS__TEXT(ss, ' ', space, std::left, '|', "y1_");
SS__TEXT(ss, ' ', space, std::left, '|', "iir_coefficient_");
SS__TEXT(ss, ' ', space, std::left, '|', "initial_amplitude_");
SS__TEXT(ss, ' ', space, std::left, '|', "current_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->InitialPhase);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->frequency_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->normalized_frequency_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->unitary_frequency_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->y0_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->y1_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->iir_coefficient_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->initial_amplitude_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->current_);
out << "\t" << ss.str() << std::endl;
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
size_t nb_errors = 0u;
nb_errors += inspector(tag + ".InitialPhase", this->InitialPhase, 0.0f, Constants::M2PI, deepInspection);
nb_errors += inspector(tag + ".frequency_", this->frequency_, this->min_frequency_, this->max_frequency_, deepInspection);
nb_errors += inspector(tag + ".normalized_frequency_", this->normalized_frequency_, 0.0f, 1.0f, deepInspection);
nb_errors += inspector(tag + ".unitary_frequency_", this->unitary_frequency_, this->min_frequency_ / this->getSamplingRate(), this->max_frequency_ / this->getSamplingRate(), deepInspection);
nb_errors += inspector(tag + ".current_", this->current_, -1.0f, 1.0f, deepInspection);
return nb_errors;
)
};
class InterpolatedSineOscillator : public FXBase
{
DISALLOW_COPY_AND_ASSIGN(LFO);
DISALLOW_COPY_AND_ASSIGN(InterpolatedSineOscillator);
public:
InterpolatedSineOscillator(float32_t sampling_rate, float32_t min_frequency = 0.01f, float32_t max_frequency = 10.0f, float32_t initial_phase = 0.0f);
virtual ~InterpolatedSineOscillator();
void setNormalizedFrequency(float32_t normalized_frequency);
float32_t getNormalizedFrequency() const;
void setFrequency(float32_t frequency);
float32_t getFrequency() const;
virtual void reset() override;
float32_t process();
float32_t current() const;
private:
static bool ClassInitializer();
static const size_t DataPointSize = 352800;
static const float32_t DeltaTime;
static float32_t DataPoints[];
const float32_t InitialPhase;
const float32_t min_frequency_;
const float32_t max_frequency_;
float32_t frequency_;
float32_t normalized_frequency_;
float32_t phase_index_;
float32_t phase_index_increment_;
float32_t current_sample_;
IMPLEMENT_DUMP(
const size_t space = 22;
const size_t precision = 5;
std::stringstream ss;
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "InitialPhase");
SS__TEXT(ss, ' ', space, std::left, '|', "normalized_frequency_");
SS__TEXT(ss, ' ', space, std::left, '|', "frequency_");
SS__TEXT(ss, ' ', space, std::left, '|', "phase_index_");
SS__TEXT(ss, ' ', space, std::left, '|', "phase_index_increment_");
SS__TEXT(ss, ' ', space, std::left, '|', "current_sample_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->InitialPhase);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->normalized_frequency_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->frequency_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_index_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_index_increment_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->current_sample_);
out << "\t" << ss.str() << std::endl;
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
size_t nb_errors = 0u;
nb_errors += inspector(tag + ".InitialPhase", this->InitialPhase, 0.0f, Constants::M2PI, deepInspection);
nb_errors += inspector(tag + ".normalized_frequency_", this->normalized_frequency_, 0.0f, 1.0f, deepInspection);
nb_errors += inspector(tag + ".frequency_", this->frequency_, this->min_frequency_, this->max_frequency_, deepInspection);
nb_errors += inspector(tag + ".phase_index_", this->phase_index_, 0.0f, static_cast<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:
typedef enum {
@ -44,8 +227,8 @@ public:
Noise
} Waveform;
LFO(float32_t sampling_rate, Waveform waveform = Waveform::Sine, float32_t min_frequency = 0.01f, float32_t max_frequency = 10.0f, float32_t initial_phase = 0.0f);
~LFO();
ComplexLFO(float32_t sampling_rate, float32_t min_frequency = 0.01f, float32_t max_frequency = 10.0f, float32_t initial_phase = 0.0f);
virtual ~ComplexLFO();
void setWaveform(Waveform waveform);
Waveform getWaveform() const;
@ -56,10 +239,12 @@ public:
void setFrequency(float32_t frequency);
float32_t getFrequency() const;
virtual void reset() override;
float32_t process();
float32_t current() const;
private:
const float32_t InitialPhase;
const float32_t min_frequency_;
const float32_t max_frequency_;
Waveform waveform_;
@ -72,163 +257,61 @@ private:
std::random_device rnd_device_;
std::mt19937 rnd_generator_;
std::uniform_real_distribution<float32_t> rnd_distribution_;
};
template<typename T, unsigned size, unsigned nb_channels = 2, bool circular_buffer = true>
class Buffer
{
DISALLOW_COPY_AND_ASSIGN(Buffer);
public:
Buffer() :
index_(0)
{
this->values_ = new T*[nb_channels];
for(unsigned i = 0; i < nb_channels; ++i)
{
this->values_[i] = new T[size];
}
this->reset();
}
virtual ~Buffer()
{
for(unsigned i = 0; i < nb_channels; ++i)
{
delete[] this->values_[i];
}
delete[] this->values_;
}
void reset(bool reset_index = true)
{
this->zero();
if(reset_index)
{
this->index_ = 0;
}
}
T& operator[](unsigned channel)
{
assert(channel < nb_channels);
return *(this->values_[channel] + this->index_);
}
bool operator++()
{
this->index_++;
if(this->index_ >= size)
{
if(circular_buffer)
{
this->index_ = 0;
return true;
}
else
{
this->index_ = size - 1;
return false;
}
}
return true;
}
bool operator--()
{
if(this->index_ > 0)
{
this->index_--;
return true;
}
else
{
if(circular_buffer)
{
this->index_ = size - 1;
return true;
}
else
{
this->index_ = 0;
return false;
}
}
}
void copy(T* buffer, unsigned channel, unsigned nb, bool from_start = true)
{
assert(channel < nb_channels);
unsigned start = from_start ? 0 : this->index_;
unsigned _nb = std::min(nb, size - start);
memcpy(this->values_[channel] + start, buffer, _nb);
}
void zero()
{
for(unsigned c = 0; c < nb_channels; ++c)
{
memset(this->values_[c], 0, size * sizeof(T));
}
}
void scale(T scale)
{
for(unsigned c = 0; c < nb_channels; ++c)
{
for(unsigned i = 0; i < size; ++i)
{
this->values_[c][i] *= scale;
}
}
}
unsigned index() const
{
return this->index_;
}
unsigned nbChannels() const
{
return nb_channels;
}
unsigned bufferSize() const
{
return size;
}
bool isCircularBuffer() const
{
return circular_buffer;
}
private:
unsigned index_;
T** values_;
IMPLEMENT_DUMP(
const size_t space = 21;
const size_t precision = 5;
std::stringstream ss;
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "InitialPhase");
SS__TEXT(ss, ' ', space, std::left, '|', "normalized_frequency_");
SS__TEXT(ss, ' ', space, std::left, '|', "frequency_");
SS__TEXT(ss, ' ', space, std::left, '|', "phase_");
SS__TEXT(ss, ' ', space, std::left, '|', "phase_increment_");
SS__TEXT(ss, ' ', space, std::left, '|', "current_sample_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->InitialPhase);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->normalized_frequency_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->frequency_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_increment_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->current_sample_);
out << "\t" << ss.str() << std::endl;
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
size_t nb_errors = 0u;
nb_errors += inspector(tag + ".InitialPhase", this->InitialPhase, 0.0f, Constants::M2PI, deepInspection);
nb_errors += inspector(tag + ".normalized_frequency_", this->normalized_frequency_, 0.0f, 1.0f, deepInspection);
nb_errors += inspector(tag + ".frequency_", this->frequency_, this->min_frequency_, this->max_frequency_, deepInspection);
nb_errors += inspector(tag + ".phase_", this->phase_, 0.0f, Constants::M2PI, deepInspection);
nb_errors += inspector(tag + ".phase_increment_", this->phase_increment_, 0.0f, Constants::M2PI, deepInspection);
nb_errors += inspector(tag + ".current_sample_", this->current_sample_, -1.0f, 1.0f, deepInspection);
return nb_errors;
)
};
template<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
@ -245,6 +328,7 @@ public:
void setMagnitude(float32_t magnitude);
float32_t getMagnitude() const;
virtual void reset() override;
float32_t process();
private:
@ -255,9 +339,127 @@ private:
float32_t magnitude_;
float32_t phase_;
float32_t phase_increment_;
IMPLEMENT_DUMP(
const size_t space = 16;
const size_t precision = 5;
std::stringstream ss;
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "speed_");
SS__TEXT(ss, ' ', space, std::left, '|', "magnitude_");
SS__TEXT(ss, ' ', space, std::left, '|', "phase_");
SS__TEXT(ss, ' ', space, std::left, '|', "phase_increment_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->speed_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->magnitude_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_increment_);
out << "\t" << ss.str() << std::endl;
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
size_t nb_errors = 0u;
nb_errors += inspector(tag + ".speed_", this->speed_, 0.0f, 0.45f * this->getSamplingRate(), deepInspection);
nb_errors += inspector(tag + ".magnitude_", this->magnitude_, 0.0f, 1.0f, deepInspection);
nb_errors += inspector(tag + ".phase_", this->phase_, 0.0f, Constants::M2PI, deepInspection);
nb_errors += inspector(tag + ".phase_increment_", this->phase_increment_, 0.0f, 0.45f * Constants::M2PI, deepInspection);
return nb_errors;
)
};
class PerlinNoiseGenerator : public FXBase
{
DISALLOW_COPY_AND_ASSIGN(PerlinNoiseGenerator);
public:
PerlinNoiseGenerator(float32_t sampling_rate, float32_t rate = 0.2f);
virtual ~PerlinNoiseGenerator();
void setRate(float32_t rate);
float32_t getRate() const;
float32_t getCurrent() const;
virtual void reset() override;
float32_t process();
private:
static int hash(int x);
static float32_t interpolate(float32_t a, float32_t b, float32_t x);
static float32_t perlin(float32_t x);
float32_t rate_;
float32_t phase_;
float32_t phase_increment_;
float32_t current_;
static const float32_t Gradients[];
IMPLEMENT_DUMP(
const size_t space = 16;
const size_t precision = 5;
std::stringstream ss;
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "rate_");
SS__TEXT(ss, ' ', space, std::left, '|', "phase_");
SS__TEXT(ss, ' ', space, std::left, '|', "phase_increment_");
SS__TEXT(ss, ' ', space, std::left, '|', "current_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->rate_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->phase_increment_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->current_);
out << "\t" << ss.str() << std::endl;
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
size_t nb_errors = 0u;
nb_errors += inspector(tag + ".rate_", this->rate_, 0.0f, 1.0f, deepInspection);
nb_errors += inspector(tag + ".phase_", this->phase_, 0.0f, Constants::M2PI, deepInspection);
nb_errors += inspector(tag + ".phase_increment_", this->phase_increment_, 0.0f, Constants::M2PI / this->getSamplingRate(), deepInspection);
nb_errors += inspector(tag + ".current_", this->current_, -1.0f, 1.0f, deepInspection);
return nb_errors;
)
};
float32_t softSaturator1(float32_t in, float32_t threshold);
float32_t softSaturator2(float32_t in, float32_t saturation);
float32_t softSaturator3(float32_t in, float32_t saturation);
float32_t softSaturator4(float32_t in, float32_t saturation);
float32_t waveFolder(float32_t input, float32_t bias);

@ -2,18 +2,21 @@
#include <cmath>
#define MAX_DELAY_TIME 1.0f
#define MAX_FLUTTER_DELAY_TIME 0.01f
#define MAX_DELAY_TIME 2.0f
#define MAX_FLUTTER_DELAY_TIME 0.001f
#define LPF_CUTOFF_REF 14000.0f
#define HPF_CUTOFF_REF 60.0f
#define LPF_CUTOFF_REF 12000.0f
#define HPF_CUTOFF_REF 80.0f
Delay::LowHighPassFilter::LowHighPassFilter(float32_t sampling_rate) :
FXElement(sampling_rate),
lpf_(sampling_rate, StateVariableFilter::Type::LPF, LPF_CUTOFF_REF),
hpf_(sampling_rate, StateVariableFilter::Type::HPF, HPF_CUTOFF_REF)
hpf_(sampling_rate, StateVariableFilter::Type::HPF, HPF_CUTOFF_REF),
ratio_(1.0f)
{
this->setCutoffChangeRatio(0.0f);
this->lpf_.setGainDB(0.82f);
this->hpf_.setGainDB(0.82f);
}
Delay::LowHighPassFilter::~LowHighPassFilter()
@ -22,10 +25,22 @@ Delay::LowHighPassFilter::~LowHighPassFilter()
void Delay::LowHighPassFilter::setCutoffChangeRatio(float32_t ratio)
{
ratio += 1.0f;
static const float32_t weight = 4.0f;
this->lpf_.setCutoff(LPF_CUTOFF_REF * ratio);
this->hpf_.setCutoff(HPF_CUTOFF_REF * ratio);
ratio = constrain(ratio, -1.0f, 1.0f);
if(ratio != this->ratio_)
{
this->ratio_ = ratio;
ratio /= 10.0f;
this->lpf_.setCutoff(LPF_CUTOFF_REF * (1.0f - ratio / weight));
this->hpf_.setCutoff(HPF_CUTOFF_REF * (1.0f + ratio * weight));
}
}
void Delay::LowHighPassFilter::reset()
{
this->lpf_.reset();
this->hpf_.reset();
}
void Delay::LowHighPassFilter::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
@ -39,17 +54,19 @@ Delay::Delay(const float32_t sampling_rate, float32_t default_delay_time, float3
MaxSampleDelayTime((MAX_DELAY_TIME + MAX_FLUTTER_DELAY_TIME) * sampling_rate * MAX_DELAY_TIME),
read_pos_L_(0),
read_pos_R_(0),
filter_(sampling_rate)
filter_(sampling_rate),
jitter_generator_(sampling_rate)
{
this->buffer_L_ = new float32_t[this->MaxSampleDelayTime];
this->buffer_R_ = new float32_t[this->MaxSampleDelayTime];
memset(this->buffer_L_, 0, this->MaxSampleDelayTime * sizeof(float32_t));
memset(this->buffer_R_, 0, this->MaxSampleDelayTime * sizeof(float32_t));
this->setLeftDelayTime(default_delay_time);
this->setRightDelayTime(default_delay_time);
this->setFeedbak(default_feedback_level);
this->setFeedback(default_feedback_level);
this->setFlutterRate(0.2f);
this->setFlutterAmount(0.05f);
this->reset();
}
Delay::~Delay()
@ -58,10 +75,33 @@ Delay::~Delay()
delete[] this->buffer_R_;
}
void Delay::reset()
{
memset(this->buffer_L_, 0, this->MaxSampleDelayTime * sizeof(float32_t));
memset(this->buffer_R_, 0, this->MaxSampleDelayTime * sizeof(float32_t));
this->read_pos_L_ = 0;
this->read_pos_R_ = 0;
this->filter_.reset();
this->jitter_generator_.reset();
}
void Delay::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{
float32_t delay_time_L = (MAX_DELAY_TIME * this->getLeftDelayTime() ) * this->getSamplingRate();
float32_t delay_time_R = (MAX_DELAY_TIME * this->getRightDelayTime()) * this->getSamplingRate();
static const float32_t max_delay_time = MAX_DELAY_TIME * this->getSamplingRate();
float32_t jitter_delay_time = 0.0f;
if(this->jitter_amount_ != 0.0f)
{
float32_t jitter_ratio = this->jitter_generator_.process();
if(jitter_ratio != 0.0f)
{
jitter_ratio *= this->jitter_amount_;
jitter_delay_time = MAX_FLUTTER_DELAY_TIME * jitter_ratio * this->getSamplingRate();
}
}
// this->filter_.setCutoffChangeRatio(jitter_ratio);
float32_t delay_time_L = jitter_delay_time + max_delay_time * this->getLeftDelayTime();
float32_t delay_time_R = jitter_delay_time + max_delay_time * this->getRightDelayTime();
// Calculate write positions
unsigned write_pos_L = static_cast<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
);
this->buffer_L_[write_pos_L] += outL * this->getFeedbackLevel();
this->buffer_R_[write_pos_R] += outR * this->getFeedbackLevel();
this->buffer_L_[write_pos_L] += outL * this->getFeedback();
this->buffer_R_[write_pos_R] += outR * this->getFeedback();
// Increment read positions
++this->read_pos_L_;
@ -115,12 +155,32 @@ float32_t Delay::getRightDelayTime() const
return this->delay_time_R_;
}
void Delay::setFeedbak(float32_t feedback)
void Delay::setFeedback(float32_t feedback)
{
this->feedback_ = constrain(feedback, 0.0, 1.0);
}
float32_t Delay::getFeedbackLevel() const
float32_t Delay::getFeedback() const
{
return this->feedback_;
}
void Delay::setFlutterRate(float32_t rate)
{
this->jitter_generator_.setRate(rate);
}
float32_t Delay::getFlutterRate() const
{
return this->jitter_generator_.getRate();
}
void Delay::setFlutterAmount(float32_t amount)
{
this->jitter_amount_ = constrain(amount, 0.0f, 1.0f);
}
float32_t Delay::getFlutterAmount() const
{
return this->jitter_amount_;
}

@ -22,8 +22,6 @@
#include "fx_components.h"
#include "fx_svf.h"
#include <random>
class Delay : public FXElement
{
DISALLOW_COPY_AND_ASSIGN(Delay);
@ -36,6 +34,7 @@ class Delay : public FXElement
LowHighPassFilter(float32_t sampling_rate);
virtual ~LowHighPassFilter();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setCutoffChangeRatio(float32_t ratio);
@ -43,13 +42,57 @@ class Delay : public FXElement
private:
StateVariableFilter lpf_;
StateVariableFilter hpf_;
};
float32_t ratio_;
IMPLEMENT_DUMP(
const size_t space = 10;
const size_t precision = 5;
std::stringstream ss;
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "ratio_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->ratio_);
out << "\t" << ss.str() << std::endl;
if(deepInspection)
{
out << "\t" << std::endl;
this->lpf_.dump(out, deepInspection, tag + ".lpf_");
this->hpf_.dump(out, deepInspection, tag + ".hpf_");
}
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
size_t nb_errors = 0u;
if(deepInspection)
{
nb_errors += this->lpf_.inspect(inspector, deepInspection, tag + ".lpf_");
nb_errors += this->hpf_.inspect(inspector, deepInspection, tag + ".hpf_");
}
nb_errors += inspector(tag + ".ratio_", this->ratio_, -1.0f, 1.0f, deepInspection);
return nb_errors;
)
};
public:
Delay(const float32_t sampling_rate, float32_t default_delay_time = 0.25f, float32_t default_flutter_level = 1.0f, float32_t default_wet_level = 0.5f);
virtual ~Delay();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setLeftDelayTime(float32_t delay_time);
@ -58,18 +101,118 @@ public:
void setRightDelayTime(float32_t delay_time);
float32_t getRightDelayTime() const;
void setFeedbak(float32_t feedback);
float32_t getFeedbackLevel() const;
void setFeedback(float32_t feedback);
float32_t getFeedback() const;
void setFlutterRate(float32_t rate);
float32_t getFlutterRate() const;
void setFlutterAmount(float32_t amount);
float32_t getFlutterAmount() const;
private:
const size_t MaxSampleDelayTime;
size_t read_pos_L_;
size_t read_pos_R_;
unsigned read_pos_L_;
unsigned read_pos_R_;
float32_t* buffer_L_;
float32_t* buffer_R_;
float32_t delay_time_L_; // Left delay time in seconds (0.0 - 2.0)
float32_t delay_time_R_; // Right delay time in seconds (0.0 - 2.0)
float32_t feedback_; // Feedback (0.0 - 1.0)
float32_t jitter_amount_;
LowHighPassFilter filter_;
PerlinNoiseGenerator jitter_generator_;
IMPLEMENT_DUMP(
const size_t space = 18;
const size_t precision = 5;
std::stringstream ss;
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "read_pos_L_");
SS__TEXT(ss, ' ', space, std::left, '|', "read_pos_R_");
SS__TEXT(ss, ' ', space, std::left, '|', "delay_time_L_");
SS__TEXT(ss, ' ', space, std::left, '|', "delay_time_R_");
SS__TEXT(ss, ' ', space, std::left, '|', "feedback_");
SS__TEXT(ss, ' ', space, std::left, '|', "jitter_amount_");
SS__TEXT(ss, ' ', space, std::left, '|', "filter_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->read_pos_L_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->read_pos_R_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->delay_time_L_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->delay_time_R_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->feedback_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->jitter_amount_);
out << "\t" << ss.str() << std::endl;
if(deepInspection)
{
out << "Flanger internal delay lines:" << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "index");
SS__TEXT(ss, ' ', space, std::left, '|', "buffer_L_");
SS__TEXT(ss, ' ', space, std::left, '|', "buffer_R_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
for(size_t i = 0; i < this->MaxSampleDelayTime; ++i)
{
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", i);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->buffer_L_[i]);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->buffer_R_[i]);
out << "\t" << ss.str() << std::endl;
}
this->filter_.dump(out, deepInspection, tag + ".filter_");
this->jitter_generator_.dump(out, deepInspection, tag + ".jitter_generator_");
}
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
size_t nb_errors = 0;
nb_errors += inspector(tag + ".read_pos_L_", static_cast<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
#include "fx_components.h"
#if defined(DEBUG)
#include <iostream>
#include <cmath>
#endif
#include <algorithm>
#include <climits>
#include <cassert>
#define MAKE_INTEGRAL_FRACTIONAL(x) \
int32_t x ## _integral = static_cast<int32_t>(x); \
float x ## _fractional = x - static_cast<float>(x ## _integral);
#include "fx_components.h"
enum Format
{
FORMAT_12_BIT,
FORMAT_16_BIT,
FORMAT_32_BIT
};
enum LFOIndex
{
LFO_1,
LFO_2
FORMAT_32_BIT,
FORMAT_FLOAT32
};
template <Format format>
@ -44,7 +40,7 @@ inline int16_t clip16(int32_t x)
}
template <>
struct DataType<FORMAT_16_BIT>
struct DataType<Format::FORMAT_12_BIT>
{
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 <
size_t size,
Format format = FORMAT_16_BIT,
Format format,
bool enable_lfo = true>
class FxEngine : public FXBase
{
@ -70,12 +114,19 @@ class FxEngine : public FXBase
public:
typedef typename DataType<format>::T T;
FxEngine(float32_t sampling_rate, float32_t max_lfo1_frequency = 1.0f, float32_t max_lfo2_frequency = 1.0f) :
FXBase(sampling_rate)
enum LFOIndex
{
this->buffer_ = new uint16_t[size];
this->lfo_[LFO_1] = enable_lfo ? new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, max_lfo1_frequency) : nullptr;
this->lfo_[LFO_2] = enable_lfo ? new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, max_lfo2_frequency) : nullptr;
LFO_1 = 0,
LFO_2,
kLFOCount
};
FxEngine(float32_t sampling_rate, float32_t max_lfo_frequency = 20.0f) :
FXBase(sampling_rate),
write_ptr_(0)
{
this->buffer_ = new T[size];
for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) this->lfo_[i] = enable_lfo ? new LFO(sampling_rate, 0.0f, max_lfo_frequency) : nullptr;
this->clear();
}
@ -84,17 +135,25 @@ public:
delete[] this->buffer_;
if(enable_lfo)
{
delete this->lfo_[LFO_1];
delete this->lfo_[LFO_2];
for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) delete this->lfo_[i];
}
}
void clear()
{
memset(this->buffer_, 0, size * sizeof(uint16_t));
memset(this->buffer_, 0, size * sizeof(T));
this->write_ptr_ = 0;
}
virtual void reset() override
{
this->clear();
if(enable_lfo)
{
for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i) this->lfo_[i]->reset();
}
}
struct Empty
{
};
@ -141,7 +200,7 @@ public:
buffer_(nullptr),
write_ptr_(0)
{
memset(this->lfo_value_, 0, 2 * sizeof(float32_t));
memset(this->lfo_value_, 0, LFOIndex::kLFOCount * sizeof(float32_t));
}
~Context()
@ -177,7 +236,7 @@ public:
template <typename D>
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_);
if(offset == -1)
{
@ -212,7 +271,7 @@ public:
template <typename D>
inline void read(D& d, int32_t offset, float32_t scale)
{
assert(D::base + D::length <= size);
assert((D::base + D::length) <= size);
T r;
if(offset == -1)
{
@ -248,11 +307,15 @@ public:
template <typename D>
inline void interpolate(D& d, float32_t offset, float32_t scale)
{
assert(D::base + D::length <= size);
MAKE_INTEGRAL_FRACTIONAL(offset);
assert((D::base + D::length) <= size);
int32_t offset_integral = static_cast<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 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;
}
@ -260,26 +323,22 @@ public:
template <typename D>
inline void interpolate(D& d, float32_t offset, LFOIndex index, float32_t amplitude, float32_t scale)
{
assert(D::base + D::length <= size);
offset += amplitude * this->lfo_value_[index];
MAKE_INTEGRAL_FRACTIONAL(offset);
float32_t a = DataType<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;
assert(index < LFOIndex::kLFOCount);
this->interpolate(d, offset + amplitude * this->lfo_value_[index], scale);
}
private:
float32_t accumulator_;
float32_t previous_read_;
float32_t lfo_value_[2];
float32_t lfo_value_[LFOIndex::kLFOCount];
T* buffer_;
int32_t write_ptr_;
};
inline void setLFOFrequency(LFOIndex index, float32_t frequency)
{
assert(index < LFOIndex::kLFOCount);
if(enable_lfo)
{
this->lfo_[index]->setFrequency(frequency);
@ -288,13 +347,14 @@ public:
inline void setLFONormalizedFrequency(LFOIndex index, float32_t normalized_frequency)
{
assert(index < LFOIndex::kLFOCount);
if(enable_lfo)
{
this->lfo_[index]->setNormalizedFrequency(normalized_frequency);
}
}
inline void start(Context *c)
inline void start(Context* c)
{
--this->write_ptr_;
if(this->write_ptr_ < 0)
@ -303,12 +363,14 @@ public:
}
c->accumulator_ = 0.0f;
c->previous_read_ = 0.0f;
c->buffer_ = buffer_;
c->write_ptr_ = write_ptr_;
c->buffer_ = this->buffer_;
c->write_ptr_ = this->write_ptr_;
if(enable_lfo)
{
c->lfo_value_[LFO_1] = this->lfo_[LFO_1]->process();
c->lfo_value_[LFO_2] = this->lfo_[LFO_2]->process();
for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i)
{
c->lfo_value_[i] = this->lfo_[i]->process();
}
}
}
@ -318,8 +380,86 @@ private:
MASK = size - 1
};
uint16_t* buffer_;
unsigned write_ptr_;
T* buffer_;
int32_t write_ptr_;
LFO* lfo_[LFOIndex::kLFOCount];
IMPLEMENT_DUMP(
const size_t space = 10;
const size_t precision = 5;
std::stringstream ss;
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "write_ptr_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->write_ptr_);
out << "\t" << ss.str() << std::endl;
if(deepInspection)
{
out << "FXEngine internal buffer:" << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "index");
SS__TEXT(ss, ' ', space, std::left, '|', "buffer_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
for(size_t i = 0; i < size; ++i)
{
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", i);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->buffer_[i]);
out << "\t" << ss.str() << std::endl;
}
if(enable_lfo)
{
for(size_t i = 0; i < LFOIndex::kLFOCount; ++i)
{
this->lfo_[i]->dump(out, deepInspection, tag + ".lfo_[ " + std::to_string(i) + " ]");
}
}
}
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
size_t nb_errors = 0u;
nb_errors += inspector(tag + ".write_ptr_", static_cast<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_lineR_ = new float32_t[this->MaxDelayLineSize];
memset(this->delay_lineL_, 0, this->MaxDelayLineSize * sizeof(float32_t));
memset(this->delay_lineR_, 0, this->MaxDelayLineSize * sizeof(float32_t));
memset(this->feedback_samples_, 0, 2 * sizeof(float32_t));
this->lfo_[LFO_Index::LFO_L] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.1f, 5.0f);
this->lfo_[LFO_Index::LFO_R] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.1f, 5.0f, Constants::MPI_2);
this->lfo_[LFOIndex::LFO_L] = new LFO(sampling_rate, 0.1f, 5.0f);
this->lfo_[LFOIndex::LFO_R] = new LFO(sampling_rate, 0.1f, 5.0f, Constants::MPI_2);
this->setRate(rate);
this->setDepth(depth);
this->setFeedback(feedback);
this->reset();
}
Flanger::~Flanger()
@ -27,8 +25,10 @@ Flanger::~Flanger()
delete[] this->delay_lineL_;
delete[] this->delay_lineR_;
delete this->lfo_[LFO_Index::LFO_L];
delete this->lfo_[LFO_Index::LFO_R];
for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i)
{
delete this->lfo_[i];
}
}
inline float32_t linearIterpolationnterp(float32_t inX, float32_t inY, float32_t inPhase)
@ -36,6 +36,19 @@ inline float32_t linearIterpolationnterp(float32_t inX, float32_t inY, float32_t
return (1.0f - inPhase) * inX + inPhase * inY;
}
void Flanger::reset()
{
memset(this->delay_lineL_, 0, this->MaxDelayLineSize * sizeof(float32_t));
memset(this->delay_lineR_, 0, this->MaxDelayLineSize * sizeof(float32_t));
memset(this->feedback_samples_, 0, 2 * sizeof(float32_t));
this->write_index_ = 0;
for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i)
{
this->lfo_[i]->reset();
}
}
void Flanger::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{
// Write sample and any feedback into delay buffers
@ -49,8 +62,8 @@ void Flanger::processSample(float32_t inL, float32_t inR, float32_t& outL, float
}
// Configure LFO for effect processing
float32_t lfo_l = this->lfo_[LFO_L]->process() * this->depth_;
float32_t lfo_r = this->lfo_[LFO_R]->process() * this->depth_;
float32_t lfo_l = this->lfo_[LFOIndex::LFO_L]->process() * this->depth_;
float32_t lfo_r = this->lfo_[LFOIndex::LFO_R]->process() * this->depth_;
// Map LFO range to millisecond range according to Chorus or Flanger effect
float32_t lfoMappedL = mapfloat(lfo_l, -1.0f, 1.0f, 0.001f, 0.005f);
@ -95,8 +108,8 @@ void Flanger::processSample(float32_t inL, float32_t inR, float32_t& outL, float
float32_t delay_sample_r = linearIterpolationnterp(this->delay_lineR_[currentR], this->delay_lineR_[nextR], fractionR);
// Store delayed samples as feedback
this->feedback_samples_[0] = delay_sample_l * this->feedback_;
this->feedback_samples_[1] = delay_sample_r * this->feedback_;
this->feedback_samples_[StereoChannels::Left ] = delay_sample_l * this->feedback_;
this->feedback_samples_[StereoChannels::Right] = delay_sample_r * this->feedback_;
outL = delay_sample_l;
outR = delay_sample_r;
@ -104,13 +117,13 @@ void Flanger::processSample(float32_t inL, float32_t inR, float32_t& outL, float
void Flanger::setRate(float32_t rate)
{
this->lfo_[LFO_Index::LFO_L]->setNormalizedFrequency(rate);
this->lfo_[LFO_Index::LFO_R]->setNormalizedFrequency(rate);
this->lfo_[LFOIndex::LFO_L]->setNormalizedFrequency(rate);
this->lfo_[LFOIndex::LFO_R]->setNormalizedFrequency(rate);
}
float32_t Flanger::getRate() const
{
return this->lfo_[LFO_Index::LFO_L]->getNormalizedFrequency();
return this->lfo_[LFOIndex::LFO_L]->getNormalizedFrequency();
}
void Flanger::setDepth(float32_t depth)

@ -27,15 +27,17 @@ class Flanger : public FXElement
DISALLOW_COPY_AND_ASSIGN(Flanger);
public:
enum LFO_Index
enum LFOIndex
{
LFO_L = 0,
LFO_R
LFO_R,
kLFOCount
};
Flanger(float32_t sampling_rate, float32_t rate = 0.5f, float32_t depth = 0.5f, float32_t feedback = 0.0f);
virtual ~Flanger();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setRate(float32_t rate);
@ -52,9 +54,101 @@ private:
float32_t* delay_lineL_;
float32_t* delay_lineR_;
unsigned write_index_;
float32_t feedback_samples_[2];
float32_t feedback_samples_[StereoChannels::kNumChannels];
LFO* lfo_[2];
LFO* lfo_[LFOIndex::kLFOCount];
float32_t depth_; // Depth of the flanger effect in milliseconds (0.0 - 10.0)
float32_t feedback_; // Amount of feedback to apply to the delay line
};
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_FAST_MAX_FREQUENCY 8.8f
#define FULLSCALE_DEPTH_RATIO 256.0f
Orbitone::Orbitone(float32_t sampling_rate, float32_t rate, float32_t depth) :
FXElement(sampling_rate),
engine_(sampling_rate, 0.0f, 0.0f),
engine_(sampling_rate, 0.0f),
depth_(0.0f),
fullscale_depth_(0.0f)
{
this->lfo_[LFO_Index::Slow0 ] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO_SLOW_MAX_FREQUENCY, 0.0f);
this->lfo_[LFO_Index::Slow120] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO_SLOW_MAX_FREQUENCY, 2.0f * PI / 3.0);
this->lfo_[LFO_Index::Slow240] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO_SLOW_MAX_FREQUENCY, 4.0f * PI / 3.0);
this->lfo_[LFOIndex::Slow0 ] = new LFO(sampling_rate, 0.0f, LFO_SLOW_MAX_FREQUENCY, 0.0f);
this->lfo_[LFOIndex::Slow120] = new LFO(sampling_rate, 0.0f, LFO_SLOW_MAX_FREQUENCY, 2.0f * PI / 3.0);
this->lfo_[LFOIndex::Slow240] = new LFO(sampling_rate, 0.0f, LFO_SLOW_MAX_FREQUENCY, 4.0f * PI / 3.0);
this->lfo_[LFO_Index::Fast0 ] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO_FAST_MAX_FREQUENCY, 0.0f);
this->lfo_[LFO_Index::Fast120] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO_FAST_MAX_FREQUENCY, 2.0f * PI / 3.0);
this->lfo_[LFO_Index::Fast240] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO_FAST_MAX_FREQUENCY, 4.0f * PI / 3.0);
this->lfo_[LFOIndex::Fast0 ] = new LFO(sampling_rate, 0.0f, LFO_FAST_MAX_FREQUENCY, 0.0f);
this->lfo_[LFOIndex::Fast120] = new LFO(sampling_rate, 0.0f, LFO_FAST_MAX_FREQUENCY, 2.0f * PI / 3.0);
this->lfo_[LFOIndex::Fast240] = new LFO(sampling_rate, 0.0f, LFO_FAST_MAX_FREQUENCY, 4.0f * PI / 3.0);
for(unsigned i = 0; i < 6; ++i)
for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i)
{
this->lfo_[i]->setNormalizedFrequency(rate);
}
@ -31,12 +29,21 @@ Orbitone::Orbitone(float32_t sampling_rate, float32_t rate, float32_t depth) :
Orbitone::~Orbitone()
{
for(unsigned i = 0; i < 6; ++i)
for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i)
{
delete this->lfo_[i];
}
}
void Orbitone::reset()
{
this->engine_.reset();
for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i)
{
this->lfo_[i]->reset();
}
}
void Orbitone::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{
typedef Engine::Reserve<2047, Engine::Reserve<2047> > Memory;
@ -46,13 +53,13 @@ void Orbitone::processSample(float32_t inL, float32_t inR, float32_t& outL, floa
this->engine_.start(&c);
float32_t slow_0 = this->lfo_[LFO_Index::Slow0 ]->process();
float32_t slow_120 = this->lfo_[LFO_Index::Slow120]->process();
float32_t slow_240 = this->lfo_[LFO_Index::Slow240]->process();
float32_t slow_0 = this->lfo_[LFOIndex::Slow0 ]->process();
float32_t slow_120 = this->lfo_[LFOIndex::Slow120]->process();
float32_t slow_240 = this->lfo_[LFOIndex::Slow240]->process();
float32_t fast_0 = this->lfo_[LFO_Index::Fast0 ]->process();
float32_t fast_120 = this->lfo_[LFO_Index::Fast120]->process();
float32_t fast_240 = this->lfo_[LFO_Index::Fast240]->process();
float32_t fast_0 = this->lfo_[LFOIndex::Fast0 ]->process();
float32_t fast_120 = this->lfo_[LFOIndex::Fast120]->process();
float32_t fast_240 = this->lfo_[LFOIndex::Fast240]->process();
float32_t a = this->fullscale_depth_ * 1.0f;
float32_t b = this->fullscale_depth_ * 0.1f;
@ -85,9 +92,9 @@ void Orbitone::processSample(float32_t inL, float32_t inR, float32_t& outL, floa
void Orbitone::setRate(float32_t rate)
{
rate = constrain(rate, 0.0f, 1.0f);
if(this->lfo_[LFO_Index::Slow0]->getNormalizedFrequency() != rate)
if(this->lfo_[LFOIndex::Slow0]->getNormalizedFrequency() != rate)
{
for(unsigned i = 0; i < 6; ++i)
for(unsigned i = 0; i < LFOIndex::kLFOCount; ++i)
{
this->lfo_[i]->setNormalizedFrequency(rate);
}
@ -96,7 +103,7 @@ void Orbitone::setRate(float32_t rate)
float32_t Orbitone::getRate() const
{
return this->lfo_[LFO_Index::Slow0]->getNormalizedFrequency();
return this->lfo_[LFOIndex::Slow0]->getNormalizedFrequency();
}
void Orbitone::setDepth(float32_t depth)
@ -105,7 +112,7 @@ void Orbitone::setDepth(float32_t depth)
if(this->depth_ != depth)
{
this->depth_ = depth;
this->fullscale_depth_ = this->depth_ * FULLSCALE_DEPTH_RATIO;
this->fullscale_depth_ = this->depth_ * ORBITONE_FULLSCALE_DEPTH_RATIO;
}
}

@ -22,24 +22,28 @@
#include "fx_components.h"
#include "fx_engine.hpp"
#define ORBITONE_FULLSCALE_DEPTH_RATIO 256.0f
class Orbitone : public FXElement
{
DISALLOW_COPY_AND_ASSIGN(Orbitone);
public:
enum LFO_Index
enum LFOIndex
{
Slow0 = 0,
Slow120,
Slow240,
Fast0,
Fast120,
Fast240
Fast240,
kLFOCount
};
Orbitone(float32_t sampling_rate, float32_t rate = 0.5f, float32_t depth = 0.5f);
virtual ~Orbitone();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setRate(float32_t rate);
@ -49,11 +53,66 @@ public:
float32_t getDepth() const;
private:
typedef FxEngine<4096, FORMAT_16_BIT, false> Engine;
typedef FxEngine<4096, Format::FORMAT_FLOAT32, false> Engine;
Engine engine_;
float32_t depth_;
float32_t fullscale_depth_;
LFO* lfo_[6];
};
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 <cmath>
AllpassDelay::AllpassDelay() :
Phaser::AllpassDelay::AllpassDelay() :
FXElement(0.0f),
a1_(0.0f)
{
memset(this->z_, 0, 2 * sizeof(float32_t));
this->reset();
}
AllpassDelay::~AllpassDelay()
Phaser::AllpassDelay::~AllpassDelay()
{
}
void AllpassDelay::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
void Phaser::AllpassDelay::reset()
{
memset(this->z_, 0, 2 * sizeof(float32_t));
}
void Phaser::AllpassDelay::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{
outL = inL * -this->a1_ + this->z_[0];
this->z_[0] = outL * this->a1_ + inL;
@ -22,15 +28,15 @@ void AllpassDelay::processSample(float32_t inL, float32_t inR, float32_t& outL,
this->z_[1] = outR * this->a1_ + inR;
}
void AllpassDelay::setDelay(float32_t delay)
void Phaser::AllpassDelay::setDelay(float32_t delay)
{
this->a1_ = (1.0f - delay) / (1.0f + delay);
}
Phaser::Phaser(float32_t sampling_rate, float32_t rate, float32_t depth, float32_t feedback) :
Phaser::Phaser(float32_t sampling_rate, float32_t rate, float32_t depth, float32_t feedback, unsigned nb_stages) :
FXElement(sampling_rate),
lfo_(sampling_rate, LFO::Waveform::Sine, 0.0f, 2.5f),
lfo_(sampling_rate, 0.0f, 2.5f),
depth_(0.0f),
feedback_(0.0f),
dmin_(0.0f),
@ -39,15 +45,27 @@ Phaser::Phaser(float32_t sampling_rate, float32_t rate, float32_t depth, float32
this->setRate(rate);
this->setDepth(depth);
this->setFeedback(feedback);
this->setNbStages(nb_stages);
this->setFrequencyRange(440.0f, 1600.0f);
memset(this->z_, 0, 2 * sizeof(float32_t));
this->reset();
}
Phaser::~Phaser()
{
}
void Phaser::reset()
{
memset(this->z_, 0, 2 * sizeof(float32_t));
for(unsigned i = 0; i < MAX_NB_PHASES; ++i)
{
this->stages_[i].reset();
}
this->lfo_.reset();
}
void Phaser::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{
float32_t d = this->dmin_ + (this->dmax_ - this->dmin_) * ((1.0f + this->lfo_.process()) / 2.0f);
@ -63,8 +81,8 @@ void Phaser::processSample(float32_t inL, float32_t inR, float32_t& outL, float3
this->z_[0] = sampleL;
this->z_[1] = sampleR;
outL = inL + this->z_[0] * this->depth_;
outR = inR + this->z_[1] * this->depth_;
outL = inL + this->z_[StereoChannels::Left ] * this->depth_;
outR = inR + this->z_[StereoChannels::Right] * this->depth_;
}
void Phaser::setFrequencyRange(float32_t min_frequency, float32_t max_frequency)

@ -20,34 +20,68 @@
#include "fx_components.h"
class AllpassDelay
#define MAX_NB_PHASES 24
class Phaser : public FXElement
{
DISALLOW_COPY_AND_ASSIGN(AllpassDelay);
DISALLOW_COPY_AND_ASSIGN(Phaser);
public:
AllpassDelay();
virtual ~AllpassDelay();
class AllpassDelay : public FXElement
{
DISALLOW_COPY_AND_ASSIGN(AllpassDelay);
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR);
public:
AllpassDelay();
virtual ~AllpassDelay();
void setDelay(float32_t delay);
virtual void reset();
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR);
private:
float32_t a1_;
float32_t z_[2];
};
void setDelay(float32_t delay);
private:
float32_t a1_;
float32_t z_[StereoChannels::kNumChannels];
#define MAX_NB_PHASES 24
IMPLEMENT_DUMP(
const size_t space = 10;
const size_t precision = 6;
class Phaser : public FXElement
{
DISALLOW_COPY_AND_ASSIGN(Phaser);
std::stringstream ss;
public:
Phaser(float32_t sampling_rate, float32_t rate = 0.5f, float32_t depth = 1.0f, float32_t feedback = 0.7f);
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "a1_");
SS__TEXT(ss, ' ', space, std::left, '|', "z_[ L ]");
SS__TEXT(ss, ' ', space, std::left, '|', "z_[ R ]");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->a1_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->z_[StereoChannels::Left ]);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->z_[StereoChannels::Right]);
out << "\t" << ss.str() << std::endl;
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
return 0u;
)
};
Phaser(float32_t sampling_rate, float32_t rate = 0.5f, float32_t depth = 1.0f, float32_t feedback = 0.7f, unsigned nb_stages = 12);
virtual ~Phaser();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setFrequencyRange(float32_t min_frequency, float32_t max_frequency);
@ -72,5 +106,68 @@ private:
float32_t dmax_;
unsigned nb_stages_;
AllpassDelay stages_[MAX_NB_PHASES];
float32_t z_[2];
float32_t z_[StereoChannels::kNumChannels];
IMPLEMENT_DUMP(
const size_t space = 12;
const size_t precision = 6;
std::stringstream ss;
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "depth_");
SS__TEXT(ss, ' ', space, std::left, '|', "feedback_");
SS__TEXT(ss, ' ', space, std::left, '|', "dmin_");
SS__TEXT(ss, ' ', space, std::left, '|', "dmax_");
SS__TEXT(ss, ' ', space, std::left, '|', "nb_stages_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->depth_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->feedback_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->dmin_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->dmax_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->nb_stages_);
out << "\t" << ss.str() << std::endl;
if(deepInspection)
{
this->lfo_.dump(out, deepInspection, tag + ".lfo_");
for(unsigned i = 0; i < MAX_NB_PHASES; ++i)
{
this->stages_[i].dump(out, deepInspection, tag + ".stages_[ " + std::to_string(i) + " ]");
}
}
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
size_t nb_errors = 0u;
nb_errors += inspector(tag + ".depth_", this->depth_, 0.0f, 1.0f, deepInspection);
nb_errors += inspector(tag + ".feedback_", this->feedback_, 0.0f, 0.97f, deepInspection);
nb_errors += inspector(tag + ".nb_stages_", static_cast<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_;
}
inline void FXRack::reset()
{
auto end = this->fx_chain_.end();
for(FXChain::iterator it = this->fx_chain_.begin(); it != end; it++)
{
(*it)->reset();;
}
}
inline void FXRack::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{
for(FXChain::iterator it = this->fx_chain_.begin(); it != this->fx_chain_.end(); it++)
FXChain::iterator end = this->fx_chain_.end();
for(FXChain::iterator it = this->fx_chain_.begin(); it != end; it++)
{
(*it)->processSample(inL, inR, outL, outR);

@ -19,7 +19,6 @@
#pragma once
#include "fx.h"
#include "fx_unit.hpp"
#include "fx_tube.h"
#include "fx_chorus.h"
#include "fx_flanger.h"
@ -27,6 +26,7 @@
#include "fx_phaser.h"
#include "fx_delay.h"
#include "fx_shimmer_reverb.h"
#include "fx_unit.hpp"
#include <vector>
@ -40,6 +40,7 @@ public:
FXRack(float32_t sampling_rate, bool enable = true, float32_t wet = 1.0f);
virtual ~FXRack();
virtual void reset() override;
virtual inline void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
virtual void process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) override;
@ -71,4 +72,61 @@ private:
FXUnit<Phaser>* fxPhaser_;
FXUnit<Delay>* fxDelay_;
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
ShimmerReverb::ShimmerReverb(float32_t sampling_rate) :
FXElement(sampling_rate),
engine_(sampling_rate),
@ -13,8 +12,9 @@ ShimmerReverb::ShimmerReverb(float32_t sampling_rate) :
diffusion_(-1.0f),
lp_(-1.0f)
{
this->engine_.setLFOFrequency(LFO_1, 0.5f);
this->engine_.setLFOFrequency(LFO_2, 0.3f);
this->engine_.setLFOFrequency(Engine::LFOIndex::LFO_1, 0.5f);
this->engine_.setLFOFrequency(Engine::LFOIndex::LFO_2, 0.3f);
this->setInputGain(1.0f);
this->setLP(0.7f);
this->setDiffusion(0.625f);
@ -24,22 +24,27 @@ ShimmerReverb::~ShimmerReverb()
{
}
void ShimmerReverb::reset()
{
this->engine_.reset();
}
void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{
// This is the Griesinger topology described in the Dattorro paper
// (4 AP diffusers on the input, then a loop of 2x 2AP+1Delay).
// Modulation is applied in the loop of the first diffuser AP for additional
// smearing; and to the two long delays for a slow shimmer/chorus effect.
typedef Engine::Reserve<113,
Engine::Reserve<162,
Engine::Reserve<241,
Engine::Reserve<399,
Engine::Reserve<1653,
Engine::Reserve<2038,
Engine::Reserve<3411,
Engine::Reserve<1913,
Engine::Reserve<1663,
Engine::Reserve<4782> > > > > > > > > > Memory;
typedef Engine::Reserve< 113,
Engine::Reserve< 162,
Engine::Reserve< 241,
Engine::Reserve< 399,
Engine::Reserve<1653,
Engine::Reserve<2038,
Engine::Reserve<3411,
Engine::Reserve<1913,
Engine::Reserve<1663,
Engine::Reserve<4782> > > > > > > > > > Memory;
Engine::DelayLine<Memory, 0> ap1;
Engine::DelayLine<Memory, 1> ap2;
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_2 = this->lp_decay_2_;
float32_t wet;
float32_t wet = 0.0f;
float32_t apout = 0.0f;
engine_.start(&c);
// Smear AP1 inside the loop.
c.interpolate(ap1, 10.0f, LFO_1, 60.0f, 1.0f);
c.interpolate(ap1, 10.0f, Engine::LFOIndex::LFO_1, 60.0f, 1.0f);
c.write(ap1, 100, 0.0f);
c.read(inL + inR, gain);
// Diffuse through 4 allpasses.
@ -83,7 +87,7 @@ void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL,
// Main reverb loop.
c.load(apout);
c.interpolate(del2, 4680.0f, LFO_2, 100.0f, krt);
c.interpolate(del2, 4680.0f, Engine::LFOIndex::LFO_2, 100.0f, krt);
c.lp(lp_1, klp);
c.read(dap1a TAIL, -kap);
c.writeAllPass(dap1a, kap);
@ -92,10 +96,10 @@ void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL,
c.write(del1, 2.0f);
c.write(wet, 0.0f);
outR += wet;
outR = wet;
c.load(apout);
// c.Interpolate(del1, 4450.0f, LFO_1, 50.0f, krt);
c.interpolate(del1, 4450.0f, Engine::LFOIndex::LFO_1, 50.0f, krt);
c.read(del1 TAIL, krt);
c.lp(lp_2, klp);
c.read(dap2a TAIL, kap);
@ -105,8 +109,8 @@ void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL,
c.write(del2, 2.0f);
c.write(wet, 0.0f);
outR += wet;
outR = wet;
this->lp_decay_1_ = lp_1;
this->lp_decay_2_ = lp_2;
}

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

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

@ -29,35 +29,98 @@ public:
{
LPF, // Low pass filter
HPF, // High pass filter
BPF, // Band pass filter
NOTCH, // Notch Filter
PEQ, // Peaking band EQ filter
LSH, // Low shelf filter
HSH // High shelf filter
BPF // Band pass filter
} Type;
StateVariableFilter(float32_t sampling_rate, Type type, float32_t cutoff);
virtual ~StateVariableFilter();
void setFilterType(Type type);
void setGainDB(float32_t gainDB);
void setCutoff(float32_t cutoff);
void setResonance(float32_t resonance);
void setPeakGainDB(float32_t gainDB);
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
private:
void updateCoefficients();
Type type_;
float32_t gain_;
float32_t cutoff_;
float32_t resonance_;
float32_t peak_gain_;
float32_t a0_;
float32_t a1_;
float32_t a2_;
float32_t b1_;
float32_t b2_;
float32_t z1_[2];
float32_t z2_[2];
float32_t g_;
float32_t w_;
float32_t a_;
float32_t b_;
float32_t c1_;
float32_t c2_;
float32_t d0_;
float32_t d1_;
float32_t z1_[StereoChannels::kNumChannels];
float32_t z2_[StereoChannels::kNumChannels];
IMPLEMENT_DUMP(
const size_t space = 12;
const size_t precision = 6;
std::stringstream ss;
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "gain_");
SS__TEXT(ss, ' ', space, std::left, '|', "cutoff_");
SS__TEXT(ss, ' ', space, std::left, '|', "resonance_");
SS__TEXT(ss, ' ', space, std::left, '|', "g_");
SS__TEXT(ss, ' ', space, std::left, '|', "w_");
SS__TEXT(ss, ' ', space, std::left, '|', "a_");
SS__TEXT(ss, ' ', space, std::left, '|', "b_");
SS__TEXT(ss, ' ', space, std::left, '|', "c1_");
SS__TEXT(ss, ' ', space, std::left, '|', "c2_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->gain_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->cutoff_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->resonance_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->g_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->w_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->a_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->b_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->c1_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->c2_);
out << "\t" << ss.str() << std::endl;
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
size_t nb_errors = 0u;
nb_errors += inspector(tag + "gain_", this->gain_, -1.0f, 1.0f, deepInspection);
nb_errors += inspector(tag + "cutoff_", this->cutoff_, 1.0f, this->getSamplingRate() / 2.0f, deepInspection);
nb_errors += inspector(tag + "resonance_", this->resonance_, 0.005f, 1.0f, deepInspection);
nb_errors += inspector(tag + "g_", this->g_, 0.0f, 16.0f, deepInspection);
nb_errors += inspector(tag + "w_", this->w_, 0.0f, 13.0f, deepInspection);
nb_errors += inspector(tag + "a_", this->a_, 0.0f, 2526.0f, deepInspection);
nb_errors += inspector(tag + "b_", this->b_, 0.0f, 160.0f, deepInspection);
nb_errors += inspector(tag + "c1_", this->c1_, 0.0f, 2.06f, deepInspection);
nb_errors += inspector(tag + "c2_", this->c2_, 0.0f, 0.06f, deepInspection);
return nb_errors;
)
};

@ -4,8 +4,9 @@
Tube::Tube(float32_t samplingRate) :
FXElement(samplingRate),
overdrive_(0.0f),
saturation_(0.0f)
overdrive_(1.0f),
saturator_factor_(1.0f),
gain_factor_(1.0f)
{
this->setOverdrive(0.0f);
}
@ -14,16 +15,37 @@ Tube::~Tube()
{
}
void Tube::reset()
{
// nothing to be done
}
void Tube::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{
outL = softSaturator2(inL, this->saturation_);
outR = softSaturator2(inR, this->saturation_);
float32_t x = inL * this->saturator_factor_;
float32_t abs_x = std::abs(x);
float32_t sat_x = std::log(1.0f + abs_x) * this->gain_factor_;
outL = inL > 0 ? sat_x : -sat_x;
x = inR * this->saturator_factor_;
abs_x = std::abs(x);
sat_x = std::log(1.0f + abs_x) * this->gain_factor_;
outR = inR > 0 ? sat_x : -sat_x;
}
void Tube::setOverdrive(float32_t overdrive)
{
this->overdrive_ = constrain(overdrive, 0.0f, 1.0f);
this->saturation_ = 2.0f * this->overdrive_;
static const float32_t N = 200.0f;
overdrive = constrain(overdrive, 0.0f, 1.0f);
if(this->overdrive_ != overdrive)
{
this->overdrive_ = overdrive;
this->saturator_factor_ = 1.0f + N * overdrive;
this->gain_factor_ = 1.0f / std::log(1.0f + this->saturator_factor_);
}
}
float32_t Tube::getOverdrive() const

@ -18,7 +18,7 @@
//
#pragma once
#include "fx_components.h"
#include "fx.h"
class Tube : public FXElement
{
@ -28,6 +28,7 @@ public:
Tube(float32_t sampling_rate);
virtual ~Tube();
virtual void reset() override;
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
void setOverdrive(float32_t overdrive);
@ -35,5 +36,45 @@ public:
private:
float32_t overdrive_;
float32_t saturation_;
float32_t saturator_factor_;
float32_t gain_factor_;
IMPLEMENT_DUMP(
const size_t space = 17;
const size_t precision = 5;
std::stringstream ss;
out << "START " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space, std::left, '|', "overdrive_");
SS__TEXT(ss, ' ', space, std::left, '|', "saturator_factor_");
SS__TEXT(ss, ' ', space, std::left, '|', "gain_factor_");
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
SS_SPACE(ss, '-', space, std::left, '+');
out << "\t" << ss.str() << std::endl;
SS_RESET(ss, precision, std::left);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->overdrive_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->saturator_factor_);
SS__TEXT(ss, ' ', space - 1, std::right, " |", this->gain_factor_);
out << "\t" << ss.str() << std::endl;
out << "END " << tag << "(" << typeid(*this).name() << ") dump" << std::endl << std::endl;
)
IMPLEMENT_INSPECT(
size_t nb_errors = 0;
nb_errors += inspector(tag + ".overdrive_", this->overdrive_, 0.0f, 1.0f, deepInspection);
nb_errors += inspector(tag + ".saturator_factor_", this->saturator_factor_, 1.0f, 201.0f, deepInspection);
nb_errors += inspector(tag + ".gain_factor_", this->gain_factor_, 0.0f, 4.0f, deepInspection);
return nb_errors;
)
};

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

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

@ -1,92 +1,63 @@
CXX := gcc
# CXXFLAGS := -O2
CXXFLAGS := -g
DEFINES := -DCPU=x86
INCLUDES := -I../../CMSIS_5/CMSIS/DSP/Include/ -I../../CMSIS_5/CMSIS/Core/Include/
GCC := $(CXX) $(INCLUDES) $(CXXFLAGS)
OBJDIR := objects
OUTPUT_FOLDER = results
EXE := all_test.bin
BETA := beta.bin
LD := gcc
LIBS := -lm -lstdc++
CXX := g++
CXXFLAGS = -g -std=c++20 -MMD -MP
DEFINES = -DCPU=x86 -DDEBUG -DOUTPUT_FOLDER="\"$(OUTPUT_FOLDER)\""
INCLUDES = -I../../CMSIS_5/CMSIS/DSP/Include/ \
-I../../CMSIS_5/CMSIS/Core/Include/ \
-I../../Synth_Dexed/src/
OBJS := \
wavein.o \
waveout.o \
fx.o \
fx_components.o \
fx_svf.o \
fx_tube.o \
fx_chorus.o \
fx_phaser.o \
fx_orbitone.o \
fx_flanger.o \
fx_delay.o \
fx_shimmer_reverb.o \
fx_rack.o \
fxrack_test.o
-include $(TST_OBJS:.o=.d)
-include $(FX__OBJS:.o=.d)
test: fxrack_test
./fxrack_test
LD := g++
LIBS := -lm -lstdc++ -lgtest -lpthread
# %.o: ../%.cpp
# $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
FX__SRCS := ../fx.cpp
FX__SRCS += ../fx_components.cpp
FX__SRCS += ../fx_svf.cpp
FX__SRCS += ../fx_tube.cpp
FX__SRCS += ../fx_chorus.cpp
FX__SRCS += ../fx_phaser.cpp
FX__SRCS += ../fx_orbitone.cpp
FX__SRCS += ../fx_flanger.cpp
FX__SRCS += ../fx_delay.cpp
FX__SRCS += ../effect_platervbstereo.cpp
FX__SRCS += ../fx_shimmer_reverb.cpp
FX__SRCS += ../fx_rack.cpp
wavein.o: wavein.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
TST_SRCS := $(filter-out waveplay.cpp, $(wildcard *.cpp))
waveout.o: waveout.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
FX__OBJS = $(patsubst ../%, $(OBJDIR)/%, $(FX__SRCS:.cpp=.o))
TST_OBJS = $(TST_SRCS:%.cpp=$(OBJDIR)/%.o)
# waveplay.o: waveplay.cpp
# $(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
all: $(EXE) test
fx.o: ../fx.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
test: $(EXE) $(OUTPUT_FOLDER)
rm -rf $(OUTPUT_FOLDER)/*
./$(EXE)
fx_components.o: ../fx_components.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
test-debug: $(EXE) $(OUTPUT_FOLDER)
rm -rf $(OUTPUT_FOLDER)/*
valgrind --leak-check=full --leak-resolution=high --show-leak-kinds=all --xtree-leak=yes --show-mismatched-frees=yes --error-limit=no --log-file=$(OUTPUT_FOLDER)/valgrind-analysis-results.txt ./$(EXE)
fx_svf.o: ../fx_svf.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
$(OBJDIR):
mkdir -p $@
fx_tube.o: ../fx_tube.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
$(OUTPUT_FOLDER):
mkdir -p $@
../fx_chorus.cpp: ../fx_engine.hpp
touch ../fx_chorus.cpp
$(OBJDIR)/%.o: %.cpp $(OBJDIR)
$(CXX) $(CXXFLAGS) $(DEFINES) $(INCLUDES) -c $< -o $@
fx_chorus.o: ../fx_chorus.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
$(OBJDIR)/%.o: ../%.cpp $(OBJDIR)
$(CXX) $(CXXFLAGS) $(DEFINES) $(INCLUDES) -c $< -o $@
fx_phaser.o: ../fx_phaser.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
../fx_orbitone.cpp: ../fx_engine.hpp
touch ../fx_orbitone.cpp
fx_orbitone.o: ../fx_orbitone.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
fx_flanger.o: ../fx_flanger.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
fx_delay.o: ../fx_delay.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
../fx_shimmer_reverb.cpp: ../fx_engine.hpp
touch ../fx_shimmer_reverb.cpp
fx_shimmer_reverb.o: ../fx_shimmer_reverb.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
fx_rack.o: ../fx_rack.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
fxrack_test.o: fxrack_test.cpp
$(CXX) $(DEFINES) $(INCLUDES) $(CXXFLAGS) -c $^ -o $@
fxrack_test: $(OBJS)
$(LD) $(OBJS) -o fxrack_test $(LIBS)
$(EXE): $(TST_OBJS) $(FX__OBJS)
$(LD) $(CXXFLAGS) $(call wildcard,$(TST_OBJS)) $(call wildcard,$(FX__OBJS)) -o $@ $(LIBS)
clean:
rm -f *.o fxrack_test
rm -rf *.o $(OBJDIR) $(EXE) $(OUTPUT_FOLDER)

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

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

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

Loading…
Cancel
Save