Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9756c6c62 | |||
| 6a95053a53 | |||
| 12ae07d115 | |||
| a362ab6c91 | |||
| 3b4d069ece | |||
| f7fc4cfe75 | |||
| 82e752f7b8 | |||
| 1f818f4271 | |||
| 974f35541d | |||
| fe2c7bfe23 | |||
| c2a1cd157d | |||
| 6e08ec70c9 | |||
| 8af07582ae | |||
| b7ef24d324 | |||
| 8e04e9bf67 | |||
| 63a00e1744 | |||
| 6fb32fbcf6 | |||
| f4502eb699 | |||
| 64f252acfc | |||
| 41b2dce01a | |||
| 5693617912 | |||
| 53a9725e0c | |||
| 3ad3e1d265 | |||
| bd48a3a1a1 | |||
| 0d965fb2a0 | |||
| e4f12dede3 | |||
| 989ea9ba8f | |||
| abbba13aad | |||
| 391e50ad7f | |||
| d55490d7d1 | |||
| e6d6e4f482 | |||
| 51de37bfb6 | |||
| 3d79d24eff | |||
| 9095a6952a | |||
| 1c579f1aa2 | |||
| 2354be5896 | |||
| 1ab0d89b00 | |||
| 5b7385df07 | |||
| 989eafebd9 | |||
| 9c7d9abc18 | |||
| 90ce27a374 | |||
| d55ce17705 | |||
| d7945451f2 | |||
| c6def58726 | |||
| 9d121865d6 | |||
| 8b0e937935 | |||
| 24ef40bc07 | |||
| 83b5f9c877 | |||
| c586ba50a5 | |||
| d74a89b82d | |||
| 8f69b4fcf8 | |||
| 2b077a13a2 |
@@ -1,7 +1,6 @@
|
||||
---
|
||||
BasedOnStyle: LLVM
|
||||
ColumnLimit: "120"
|
||||
ConstructorInitializerIndentWidth: "0"
|
||||
ColumnLimit: "90"
|
||||
IndentWidth: "4"
|
||||
TabWidth: "4"
|
||||
UseTab: Always
|
||||
|
||||
@@ -11,4 +11,5 @@ target_include_directories(trnr-lib INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/oversampling
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/synth
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/util
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/gfx
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright 2024, Christopher Jan Herb. All rights reserved.
|
||||
Copyright 2025, Christopher Herb. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# trnr-lib
|
||||
|
||||
A collection of header only classes used in the development of software by Ternär Music Technology.
|
||||
header-only dsp functions and utilities
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
#pragma once
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace trnr {
|
||||
// Clipper based on ClipOnly2 by Chris Johnson
|
||||
class aw_cliponly2 {
|
||||
public:
|
||||
aw_cliponly2()
|
||||
{
|
||||
samplerate = 44100;
|
||||
|
||||
lastSampleL = 0.0;
|
||||
wasPosClipL = false;
|
||||
wasNegClipL = false;
|
||||
lastSampleR = 0.0;
|
||||
wasPosClipR = false;
|
||||
wasNegClipR = false;
|
||||
for (int x = 0; x < 16; x++) {
|
||||
intermediateL[x] = 0.0;
|
||||
intermediateR[x] = 0.0;
|
||||
}
|
||||
// this is reset: values being initialized only once. Startup values, whatever they are.
|
||||
}
|
||||
|
||||
void set_samplerate(double _samplerate) { samplerate = _samplerate; }
|
||||
|
||||
template <typename t_sample>
|
||||
void process_block(t_sample** inputs, t_sample** outputs, long sample_frames)
|
||||
{
|
||||
t_sample* in1 = inputs[0];
|
||||
t_sample* in2 = inputs[1];
|
||||
t_sample* out1 = outputs[0];
|
||||
t_sample* out2 = outputs[1];
|
||||
|
||||
double overallscale = 1.0;
|
||||
overallscale /= 44100.0;
|
||||
overallscale *= samplerate;
|
||||
|
||||
int spacing = floor(overallscale); // should give us working basic scaling, usually 2 or 4
|
||||
if (spacing < 1) spacing = 1;
|
||||
if (spacing > 16) spacing = 16;
|
||||
|
||||
while (--sample_frames >= 0) {
|
||||
double inputSampleL = *in1;
|
||||
double inputSampleR = *in2;
|
||||
|
||||
// begin ClipOnly2 stereo as a little, compressed chunk that can be dropped into code
|
||||
if (inputSampleL > 4.0) inputSampleL = 4.0;
|
||||
if (inputSampleL < -4.0) inputSampleL = -4.0;
|
||||
if (wasPosClipL == true) { // current will be over
|
||||
if (inputSampleL < lastSampleL) lastSampleL = 0.7058208 + (inputSampleL * 0.2609148);
|
||||
else lastSampleL = 0.2491717 + (lastSampleL * 0.7390851);
|
||||
}
|
||||
wasPosClipL = false;
|
||||
if (inputSampleL > 0.9549925859) {
|
||||
wasPosClipL = true;
|
||||
inputSampleL = 0.7058208 + (lastSampleL * 0.2609148);
|
||||
}
|
||||
if (wasNegClipL == true) { // current will be -over
|
||||
if (inputSampleL > lastSampleL) lastSampleL = -0.7058208 + (inputSampleL * 0.2609148);
|
||||
else lastSampleL = -0.2491717 + (lastSampleL * 0.7390851);
|
||||
}
|
||||
wasNegClipL = false;
|
||||
if (inputSampleL < -0.9549925859) {
|
||||
wasNegClipL = true;
|
||||
inputSampleL = -0.7058208 + (lastSampleL * 0.2609148);
|
||||
}
|
||||
intermediateL[spacing] = inputSampleL;
|
||||
inputSampleL = lastSampleL; // Latency is however many samples equals one 44.1k sample
|
||||
for (int x = spacing; x > 0; x--) intermediateL[x - 1] = intermediateL[x];
|
||||
lastSampleL = intermediateL[0]; // run a little buffer to handle this
|
||||
|
||||
if (inputSampleR > 4.0) inputSampleR = 4.0;
|
||||
if (inputSampleR < -4.0) inputSampleR = -4.0;
|
||||
if (wasPosClipR == true) { // current will be over
|
||||
if (inputSampleR < lastSampleR) lastSampleR = 0.7058208 + (inputSampleR * 0.2609148);
|
||||
else lastSampleR = 0.2491717 + (lastSampleR * 0.7390851);
|
||||
}
|
||||
wasPosClipR = false;
|
||||
if (inputSampleR > 0.9549925859) {
|
||||
wasPosClipR = true;
|
||||
inputSampleR = 0.7058208 + (lastSampleR * 0.2609148);
|
||||
}
|
||||
if (wasNegClipR == true) { // current will be -over
|
||||
if (inputSampleR > lastSampleR) lastSampleR = -0.7058208 + (inputSampleR * 0.2609148);
|
||||
else lastSampleR = -0.2491717 + (lastSampleR * 0.7390851);
|
||||
}
|
||||
wasNegClipR = false;
|
||||
if (inputSampleR < -0.9549925859) {
|
||||
wasNegClipR = true;
|
||||
inputSampleR = -0.7058208 + (lastSampleR * 0.2609148);
|
||||
}
|
||||
intermediateR[spacing] = inputSampleR;
|
||||
inputSampleR = lastSampleR; // Latency is however many samples equals one 44.1k sample
|
||||
for (int x = spacing; x > 0; x--) intermediateR[x - 1] = intermediateR[x];
|
||||
lastSampleR = intermediateR[0]; // run a little buffer to handle this
|
||||
// end ClipOnly2 stereo as a little, compressed chunk that can be dropped into code
|
||||
|
||||
*out1 = inputSampleL;
|
||||
*out2 = inputSampleR;
|
||||
|
||||
in1++;
|
||||
in2++;
|
||||
out1++;
|
||||
out2++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate;
|
||||
|
||||
double lastSampleL;
|
||||
double intermediateL[16];
|
||||
bool wasPosClipL;
|
||||
bool wasNegClipL;
|
||||
double lastSampleR;
|
||||
double intermediateR[16];
|
||||
bool wasPosClipR;
|
||||
bool wasNegClipR; // Stereo ClipOnly2
|
||||
// default stuff
|
||||
};
|
||||
} // namespace trnr
|
||||
@@ -1,109 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstdlib>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace trnr {
|
||||
// soft clipper based on ClipSoftly by Chris Johnson
|
||||
class aw_clipsoftly {
|
||||
public:
|
||||
aw_clipsoftly()
|
||||
{
|
||||
samplerate = 44100;
|
||||
|
||||
lastSampleL = 0.0;
|
||||
lastSampleR = 0.0;
|
||||
for (int x = 0; x < 16; x++) {
|
||||
intermediateL[x] = 0.0;
|
||||
intermediateR[x] = 0.0;
|
||||
}
|
||||
fpdL = 1.0;
|
||||
while (fpdL < 16386) fpdL = rand() * UINT32_MAX;
|
||||
fpdR = 1.0;
|
||||
while (fpdR < 16386) fpdR = rand() * UINT32_MAX;
|
||||
// this is reset: values being initialized only once. Startup values, whatever they are.
|
||||
}
|
||||
|
||||
void set_samplerate(double _samplerate) { samplerate = _samplerate; }
|
||||
|
||||
template <typename t_sample>
|
||||
void process_block(t_sample** inputs, t_sample** outputs, long sample_frames)
|
||||
{
|
||||
t_sample* in1 = inputs[0];
|
||||
t_sample* in2 = inputs[1];
|
||||
t_sample* out1 = outputs[0];
|
||||
t_sample* out2 = outputs[1];
|
||||
|
||||
double overallscale = 1.0;
|
||||
overallscale /= 44100.0;
|
||||
overallscale *= samplerate;
|
||||
int spacing = floor(overallscale); // should give us working basic scaling, usually 2 or 4
|
||||
if (spacing < 1) spacing = 1;
|
||||
if (spacing > 16) spacing = 16;
|
||||
|
||||
while (--sample_frames >= 0) {
|
||||
double inputSampleL = *in1;
|
||||
double inputSampleR = *in2;
|
||||
if (fabs(inputSampleL) < 1.18e-23) inputSampleL = fpdL * 1.18e-17;
|
||||
if (fabs(inputSampleR) < 1.18e-23) inputSampleR = fpdR * 1.18e-17;
|
||||
|
||||
double softSpeed = fabs(inputSampleL);
|
||||
if (softSpeed < 1.0) softSpeed = 1.0;
|
||||
else softSpeed = 1.0 / softSpeed;
|
||||
if (inputSampleL > 1.57079633) inputSampleL = 1.57079633;
|
||||
if (inputSampleL < -1.57079633) inputSampleL = -1.57079633;
|
||||
inputSampleL = sin(inputSampleL) * 0.9549925859; // scale to what cliponly uses
|
||||
inputSampleL = (inputSampleL * softSpeed) + (lastSampleL * (1.0 - softSpeed));
|
||||
|
||||
softSpeed = fabs(inputSampleR);
|
||||
if (softSpeed < 1.0) softSpeed = 1.0;
|
||||
else softSpeed = 1.0 / softSpeed;
|
||||
if (inputSampleR > 1.57079633) inputSampleR = 1.57079633;
|
||||
if (inputSampleR < -1.57079633) inputSampleR = -1.57079633;
|
||||
inputSampleR = sin(inputSampleR) * 0.9549925859; // scale to what cliponly uses
|
||||
inputSampleR = (inputSampleR * softSpeed) + (lastSampleR * (1.0 - softSpeed));
|
||||
|
||||
intermediateL[spacing] = inputSampleL;
|
||||
inputSampleL = lastSampleL; // Latency is however many samples equals one 44.1k sample
|
||||
for (int x = spacing; x > 0; x--) intermediateL[x - 1] = intermediateL[x];
|
||||
lastSampleL = intermediateL[0]; // run a little buffer to handle this
|
||||
|
||||
intermediateR[spacing] = inputSampleR;
|
||||
inputSampleR = lastSampleR; // Latency is however many samples equals one 44.1k sample
|
||||
for (int x = spacing; x > 0; x--) intermediateR[x - 1] = intermediateR[x];
|
||||
lastSampleR = intermediateR[0]; // run a little buffer to handle this
|
||||
|
||||
// begin 64 bit stereo floating point dither
|
||||
// int expon; frexp((double)inputSampleL, &expon);
|
||||
fpdL ^= fpdL << 13;
|
||||
fpdL ^= fpdL >> 17;
|
||||
fpdL ^= fpdL << 5;
|
||||
// inputSampleL += ((double(fpdL)-uint32_t(0x7fffffff)) * 1.1e-44l * pow(2,expon+62));
|
||||
// frexp((double)inputSampleR, &expon);
|
||||
fpdR ^= fpdR << 13;
|
||||
fpdR ^= fpdR >> 17;
|
||||
fpdR ^= fpdR << 5;
|
||||
// inputSampleR += ((double(fpdR)-uint32_t(0x7fffffff)) * 1.1e-44l * pow(2,expon+62));
|
||||
// end 64 bit stereo floating point dither
|
||||
|
||||
*out1 = inputSampleL;
|
||||
*out2 = inputSampleR;
|
||||
|
||||
in1++;
|
||||
in2++;
|
||||
out1++;
|
||||
out2++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate;
|
||||
|
||||
double lastSampleL;
|
||||
double intermediateL[16];
|
||||
double lastSampleR;
|
||||
double intermediateR[16];
|
||||
uint32_t fpdL;
|
||||
uint32_t fpdR;
|
||||
// default stuff
|
||||
};
|
||||
} // namespace trnr
|
||||
208
clip/aw_tube2.h
208
clip/aw_tube2.h
@@ -1,208 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstdlib>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace trnr {
|
||||
// modeled tube preamp based on tube2 by Chris Johnson
|
||||
class aw_tube2 {
|
||||
public:
|
||||
aw_tube2()
|
||||
{
|
||||
samplerate = 44100;
|
||||
|
||||
A = 0.5;
|
||||
B = 0.5;
|
||||
previousSampleA = 0.0;
|
||||
previousSampleB = 0.0;
|
||||
previousSampleC = 0.0;
|
||||
previousSampleD = 0.0;
|
||||
previousSampleE = 0.0;
|
||||
previousSampleF = 0.0;
|
||||
fpdL = 1.0;
|
||||
while (fpdL < 16386) fpdL = rand() * UINT32_MAX;
|
||||
fpdR = 1.0;
|
||||
while (fpdR < 16386) fpdR = rand() * UINT32_MAX;
|
||||
// this is reset: values being initialized only once. Startup values, whatever they are.
|
||||
}
|
||||
|
||||
void set_input(double value) { A = clamp(value); }
|
||||
|
||||
void set_tube(double value) { B = clamp(value); }
|
||||
|
||||
void set_samplerate(double _samplerate) { samplerate = _samplerate; }
|
||||
|
||||
template <typename t_sample>
|
||||
void process_block(t_sample** inputs, t_sample** outputs, long sampleframes)
|
||||
{
|
||||
t_sample* in1 = inputs[0];
|
||||
t_sample* in2 = inputs[1];
|
||||
t_sample* out1 = outputs[0];
|
||||
t_sample* out2 = outputs[1];
|
||||
|
||||
double overallscale = 1.0;
|
||||
overallscale /= 44100.0;
|
||||
overallscale *= samplerate;
|
||||
|
||||
double inputPad = A;
|
||||
double iterations = 1.0 - B;
|
||||
int powerfactor = (9.0 * iterations) + 1;
|
||||
double asymPad = (double)powerfactor;
|
||||
double gainscaling = 1.0 / (double)(powerfactor + 1);
|
||||
double outputscaling = 1.0 + (1.0 / (double)(powerfactor));
|
||||
|
||||
while (--sampleframes >= 0) {
|
||||
double inputSampleL = *in1;
|
||||
double inputSampleR = *in2;
|
||||
if (fabs(inputSampleL) < 1.18e-23) inputSampleL = fpdL * 1.18e-17;
|
||||
if (fabs(inputSampleR) < 1.18e-23) inputSampleR = fpdR * 1.18e-17;
|
||||
|
||||
if (inputPad < 1.0) {
|
||||
inputSampleL *= inputPad;
|
||||
inputSampleR *= inputPad;
|
||||
}
|
||||
|
||||
if (overallscale > 1.9) {
|
||||
double stored = inputSampleL;
|
||||
inputSampleL += previousSampleA;
|
||||
previousSampleA = stored;
|
||||
inputSampleL *= 0.5;
|
||||
stored = inputSampleR;
|
||||
inputSampleR += previousSampleB;
|
||||
previousSampleB = stored;
|
||||
inputSampleR *= 0.5;
|
||||
} // for high sample rates on this plugin we are going to do a simple average
|
||||
|
||||
if (inputSampleL > 1.0) inputSampleL = 1.0;
|
||||
if (inputSampleL < -1.0) inputSampleL = -1.0;
|
||||
if (inputSampleR > 1.0) inputSampleR = 1.0;
|
||||
if (inputSampleR < -1.0) inputSampleR = -1.0;
|
||||
|
||||
// flatten bottom, point top of sine waveshaper L
|
||||
inputSampleL /= asymPad;
|
||||
double sharpen = -inputSampleL;
|
||||
if (sharpen > 0.0) sharpen = 1.0 + sqrt(sharpen);
|
||||
else sharpen = 1.0 - sqrt(-sharpen);
|
||||
inputSampleL -= inputSampleL * fabs(inputSampleL) * sharpen * 0.25;
|
||||
// this will take input from exactly -1.0 to 1.0 max
|
||||
inputSampleL *= asymPad;
|
||||
// flatten bottom, point top of sine waveshaper R
|
||||
inputSampleR /= asymPad;
|
||||
sharpen = -inputSampleR;
|
||||
if (sharpen > 0.0) sharpen = 1.0 + sqrt(sharpen);
|
||||
else sharpen = 1.0 - sqrt(-sharpen);
|
||||
inputSampleR -= inputSampleR * fabs(inputSampleR) * sharpen * 0.25;
|
||||
// this will take input from exactly -1.0 to 1.0 max
|
||||
inputSampleR *= asymPad;
|
||||
// end first asym section: later boosting can mitigate the extreme
|
||||
// softclipping of one side of the wave
|
||||
// and we are asym clipping more when Tube is cranked, to compensate
|
||||
|
||||
// original Tube algorithm: powerfactor widens the more linear region of the wave
|
||||
double factor = inputSampleL; // Left channel
|
||||
for (int x = 0; x < powerfactor; x++) factor *= inputSampleL;
|
||||
if ((powerfactor % 2 == 1) && (inputSampleL != 0.0)) factor = (factor / inputSampleL) * fabs(inputSampleL);
|
||||
factor *= gainscaling;
|
||||
inputSampleL -= factor;
|
||||
inputSampleL *= outputscaling;
|
||||
factor = inputSampleR; // Right channel
|
||||
for (int x = 0; x < powerfactor; x++) factor *= inputSampleR;
|
||||
if ((powerfactor % 2 == 1) && (inputSampleR != 0.0)) factor = (factor / inputSampleR) * fabs(inputSampleR);
|
||||
factor *= gainscaling;
|
||||
inputSampleR -= factor;
|
||||
inputSampleR *= outputscaling;
|
||||
|
||||
if (overallscale > 1.9) {
|
||||
double stored = inputSampleL;
|
||||
inputSampleL += previousSampleC;
|
||||
previousSampleC = stored;
|
||||
inputSampleL *= 0.5;
|
||||
stored = inputSampleR;
|
||||
inputSampleR += previousSampleD;
|
||||
previousSampleD = stored;
|
||||
inputSampleR *= 0.5;
|
||||
} // for high sample rates on this plugin we are going to do a simple average
|
||||
// end original Tube. Now we have a boosted fat sound peaking at 0dB exactly
|
||||
|
||||
// hysteresis and spiky fuzz L
|
||||
double slew = previousSampleE - inputSampleL;
|
||||
if (overallscale > 1.9) {
|
||||
double stored = inputSampleL;
|
||||
inputSampleL += previousSampleE;
|
||||
previousSampleE = stored;
|
||||
inputSampleL *= 0.5;
|
||||
} else previousSampleE = inputSampleL; // for this, need previousSampleC always
|
||||
if (slew > 0.0) slew = 1.0 + (sqrt(slew) * 0.5);
|
||||
else slew = 1.0 - (sqrt(-slew) * 0.5);
|
||||
inputSampleL -= inputSampleL * fabs(inputSampleL) * slew * gainscaling;
|
||||
// reusing gainscaling that's part of another algorithm
|
||||
if (inputSampleL > 0.52) inputSampleL = 0.52;
|
||||
if (inputSampleL < -0.52) inputSampleL = -0.52;
|
||||
inputSampleL *= 1.923076923076923;
|
||||
// hysteresis and spiky fuzz R
|
||||
slew = previousSampleF - inputSampleR;
|
||||
if (overallscale > 1.9) {
|
||||
double stored = inputSampleR;
|
||||
inputSampleR += previousSampleF;
|
||||
previousSampleF = stored;
|
||||
inputSampleR *= 0.5;
|
||||
} else previousSampleF = inputSampleR; // for this, need previousSampleC always
|
||||
if (slew > 0.0) slew = 1.0 + (sqrt(slew) * 0.5);
|
||||
else slew = 1.0 - (sqrt(-slew) * 0.5);
|
||||
inputSampleR -= inputSampleR * fabs(inputSampleR) * slew * gainscaling;
|
||||
// reusing gainscaling that's part of another algorithm
|
||||
if (inputSampleR > 0.52) inputSampleR = 0.52;
|
||||
if (inputSampleR < -0.52) inputSampleR = -0.52;
|
||||
inputSampleR *= 1.923076923076923;
|
||||
// end hysteresis and spiky fuzz section
|
||||
|
||||
// begin 64 bit stereo floating point dither
|
||||
// int expon; frexp((double)inputSampleL, &expon);
|
||||
fpdL ^= fpdL << 13;
|
||||
fpdL ^= fpdL >> 17;
|
||||
fpdL ^= fpdL << 5;
|
||||
// inputSampleL += ((double(fpdL)-uint32_t(0x7fffffff)) * 1.1e-44l * pow(2,expon+62));
|
||||
// frexp((double)inputSampleR, &expon);
|
||||
fpdR ^= fpdR << 13;
|
||||
fpdR ^= fpdR >> 17;
|
||||
fpdR ^= fpdR << 5;
|
||||
// inputSampleR += ((double(fpdR)-uint32_t(0x7fffffff)) * 1.1e-44l * pow(2,expon+62));
|
||||
// end 64 bit stereo floating point dither
|
||||
|
||||
*out1 = inputSampleL;
|
||||
*out2 = inputSampleR;
|
||||
|
||||
in1++;
|
||||
in2++;
|
||||
out1++;
|
||||
out2++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate;
|
||||
|
||||
double previousSampleA;
|
||||
double previousSampleB;
|
||||
double previousSampleC;
|
||||
double previousSampleD;
|
||||
double previousSampleE;
|
||||
double previousSampleF;
|
||||
|
||||
uint32_t fpdL;
|
||||
uint32_t fpdR;
|
||||
// default stuff
|
||||
|
||||
float A;
|
||||
float B;
|
||||
|
||||
double clamp(double& value)
|
||||
{
|
||||
if (value > 1) {
|
||||
value = 1;
|
||||
} else if (value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
||||
} // namespace trnr
|
||||
160
clip/clip.h
Normal file
160
clip/clip.h
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* clip.h
|
||||
* Copyright (c) 2016 Chris Johnson
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
* Based on ClipOnly2 by Chris Johnson, 2016
|
||||
* This file is a derivative of the above module.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* Changes:
|
||||
* - 2025-11-06 Christopher Herb:
|
||||
* - Templated audio buffer i/o
|
||||
* - Converted to procedural programming style
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace trnr {
|
||||
struct clip {
|
||||
double samplerate;
|
||||
|
||||
double last_sample_l;
|
||||
double intermediate_l[16];
|
||||
bool was_pos_clip_l;
|
||||
bool was_neg_clip_l;
|
||||
|
||||
double last_sample_r;
|
||||
double intermediate_r[16];
|
||||
bool was_pos_clip_r;
|
||||
bool was_neg_clip_r;
|
||||
};
|
||||
|
||||
inline void clip_init(clip& c, double _samplerate)
|
||||
{
|
||||
c.samplerate = 44100;
|
||||
|
||||
c.last_sample_l = 0.0;
|
||||
c.was_pos_clip_l = false;
|
||||
c.was_neg_clip_l = false;
|
||||
c.last_sample_r = 0.0;
|
||||
c.was_pos_clip_r = false;
|
||||
c.was_neg_clip_r = false;
|
||||
|
||||
for (int x = 0; x < 16; x++) {
|
||||
c.intermediate_l[x] = 0.0;
|
||||
c.intermediate_r[x] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename t_sample>
|
||||
inline void clip_process_block(clip& c, t_sample** inputs, t_sample** outputs,
|
||||
long sample_frames)
|
||||
{
|
||||
t_sample* in1 = inputs[0];
|
||||
t_sample* in2 = inputs[1];
|
||||
t_sample* out1 = outputs[0];
|
||||
t_sample* out2 = outputs[1];
|
||||
|
||||
double overallscale = 1.0;
|
||||
overallscale /= 44100.0;
|
||||
overallscale *= c.samplerate;
|
||||
|
||||
int spacing =
|
||||
floor(overallscale); // should give us working basic scaling, usually 2 or 4
|
||||
if (spacing < 1) spacing = 1;
|
||||
if (spacing > 16) spacing = 16;
|
||||
|
||||
while (--sample_frames >= 0) {
|
||||
double input_l = *in1;
|
||||
double input_r = *in2;
|
||||
|
||||
// begin ClipOnly2 stereo as a little, compressed chunk that can be dropped into
|
||||
// code
|
||||
if (input_l > 4.0) input_l = 4.0;
|
||||
if (input_l < -4.0) input_l = -4.0;
|
||||
if (c.was_pos_clip_l == true) { // current will be over
|
||||
if (input_l < c.last_sample_l)
|
||||
c.last_sample_l = 0.7058208 + (input_l * 0.2609148);
|
||||
else c.last_sample_l = 0.2491717 + (c.last_sample_l * 0.7390851);
|
||||
}
|
||||
c.was_pos_clip_l = false;
|
||||
if (input_l > 0.9549925859) {
|
||||
c.was_pos_clip_l = true;
|
||||
input_l = 0.7058208 + (c.last_sample_l * 0.2609148);
|
||||
}
|
||||
if (c.was_neg_clip_l == true) { // current will be -over
|
||||
if (input_l > c.last_sample_l)
|
||||
c.last_sample_l = -0.7058208 + (input_l * 0.2609148);
|
||||
else c.last_sample_l = -0.2491717 + (c.last_sample_l * 0.7390851);
|
||||
}
|
||||
c.was_neg_clip_l = false;
|
||||
if (input_l < -0.9549925859) {
|
||||
c.was_neg_clip_l = true;
|
||||
input_l = -0.7058208 + (c.last_sample_l * 0.2609148);
|
||||
}
|
||||
c.intermediate_l[spacing] = input_l;
|
||||
input_l =
|
||||
c.last_sample_l; // Latency is however many samples equals one 44.1k sample
|
||||
for (int x = spacing; x > 0; x--) c.intermediate_l[x - 1] = c.intermediate_l[x];
|
||||
c.last_sample_l = c.intermediate_l[0]; // run a little buffer to handle this
|
||||
|
||||
if (input_r > 4.0) input_r = 4.0;
|
||||
if (input_r < -4.0) input_r = -4.0;
|
||||
if (c.was_pos_clip_r == true) { // current will be over
|
||||
if (input_r < c.last_sample_r)
|
||||
c.last_sample_r = 0.7058208 + (input_r * 0.2609148);
|
||||
else c.last_sample_r = 0.2491717 + (c.last_sample_r * 0.7390851);
|
||||
}
|
||||
c.was_pos_clip_r = false;
|
||||
if (input_r > 0.9549925859) {
|
||||
c.was_pos_clip_r = true;
|
||||
input_r = 0.7058208 + (c.last_sample_r * 0.2609148);
|
||||
}
|
||||
if (c.was_neg_clip_r == true) { // current will be -over
|
||||
if (input_r > c.last_sample_r)
|
||||
c.last_sample_r = -0.7058208 + (input_r * 0.2609148);
|
||||
else c.last_sample_r = -0.2491717 + (c.last_sample_r * 0.7390851);
|
||||
}
|
||||
c.was_neg_clip_r = false;
|
||||
if (input_r < -0.9549925859) {
|
||||
c.was_neg_clip_r = true;
|
||||
input_r = -0.7058208 + (c.last_sample_r * 0.2609148);
|
||||
}
|
||||
c.intermediate_r[spacing] = input_r;
|
||||
input_r =
|
||||
c.last_sample_r; // Latency is however many samples equals one 44.1k sample
|
||||
for (int x = spacing; x > 0; x--) c.intermediate_r[x - 1] = c.intermediate_r[x];
|
||||
c.last_sample_r = c.intermediate_r[0]; // run a little buffer to handle this
|
||||
// end ClipOnly2 stereo as a little, compressed chunk that can be dropped into
|
||||
// code
|
||||
|
||||
*out1 = input_l;
|
||||
*out2 = input_r;
|
||||
|
||||
in1++;
|
||||
in2++;
|
||||
out1++;
|
||||
out2++;
|
||||
}
|
||||
}
|
||||
} // namespace trnr
|
||||
63
clip/fold.h
Normal file
63
clip/fold.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* fold.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace trnr {
|
||||
// folds the wave from -1 to 1
|
||||
inline float fold(float& sample)
|
||||
{
|
||||
while (sample > 1.0 || sample < -1.0) {
|
||||
|
||||
if (sample > 1.0) {
|
||||
sample = 2.0 - sample;
|
||||
} else if (sample < -1.0) {
|
||||
sample = -2.0 - sample;
|
||||
}
|
||||
}
|
||||
return sample;
|
||||
}
|
||||
|
||||
// folds the positive part of the wave independently from the negative part.
|
||||
inline float fold_bipolar(float& sample)
|
||||
{
|
||||
// fold positive values
|
||||
if (sample > 1.0) {
|
||||
sample = 2.0 - sample;
|
||||
|
||||
if (sample < 0.0) { sample = -sample; }
|
||||
|
||||
return fold(sample);
|
||||
}
|
||||
// fold negative values
|
||||
else if (sample < -1.0) {
|
||||
sample = -2.0 - sample;
|
||||
|
||||
if (sample > 0.0) { sample = -sample; }
|
||||
|
||||
return fold(sample);
|
||||
} else {
|
||||
return sample;
|
||||
}
|
||||
}
|
||||
}; // namespace trnr
|
||||
231
clip/tube.h
Normal file
231
clip/tube.h
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* tube.h
|
||||
* Copyright (c) 2016 Chris Johnson
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
* Based on Tube2 by Chris Johnson, 2016
|
||||
* This file is a derivative/major refactor of the above module.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* Changes:
|
||||
* - 2025-11-06 Christopher Herb:
|
||||
* - Templated audio buffer i/o
|
||||
* - Converted to procedural programming style
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <stdint.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace trnr {
|
||||
|
||||
struct tube {
|
||||
double samplerate;
|
||||
|
||||
double prev_sample_a;
|
||||
double prev_sample_b;
|
||||
double prev_sample_c;
|
||||
double prev_sample_d;
|
||||
double prev_sample_e;
|
||||
double prev_sample_f;
|
||||
|
||||
uint32_t fdp_l;
|
||||
uint32_t fdp_r;
|
||||
|
||||
float input_vol;
|
||||
float tube_amt;
|
||||
|
||||
void set_input(double value) { input_vol = clamp(value, 0.0, 1.0); }
|
||||
|
||||
void set_tube(double value) { tube_amt = clamp(value, 0.0, 1.0); }
|
||||
};
|
||||
|
||||
inline void tube_init(tube& t, double samplerate)
|
||||
{
|
||||
t.samplerate = 44100;
|
||||
|
||||
t.input_vol = 0.5;
|
||||
t.tube_amt = 0.5;
|
||||
t.prev_sample_a = 0.0;
|
||||
t.prev_sample_b = 0.0;
|
||||
t.prev_sample_c = 0.0;
|
||||
t.prev_sample_d = 0.0;
|
||||
t.prev_sample_e = 0.0;
|
||||
t.prev_sample_f = 0.0;
|
||||
t.fdp_l = 1.0;
|
||||
while (t.fdp_l < 16386) t.fdp_l = rand() * UINT32_MAX;
|
||||
t.fdp_r = 1.0;
|
||||
while (t.fdp_r < 16386) t.fdp_r = rand() * UINT32_MAX;
|
||||
}
|
||||
|
||||
template <typename t_sample>
|
||||
inline void tube_process_block(tube& t, t_sample** inputs, t_sample** outputs,
|
||||
long sampleframes)
|
||||
{
|
||||
t_sample* in1 = inputs[0];
|
||||
t_sample* in2 = inputs[1];
|
||||
t_sample* out1 = outputs[0];
|
||||
t_sample* out2 = outputs[1];
|
||||
|
||||
double overallscale = 1.0;
|
||||
overallscale /= 44100.0;
|
||||
overallscale *= t.samplerate;
|
||||
|
||||
double input_pad = t.input_vol;
|
||||
double iterations = 1.0 - t.tube_amt;
|
||||
int powerfactor = (9.0 * iterations) + 1;
|
||||
double asym_pad = (double)powerfactor;
|
||||
double gainscaling = 1.0 / (double)(powerfactor + 1);
|
||||
double outputscaling = 1.0 + (1.0 / (double)(powerfactor));
|
||||
|
||||
while (--sampleframes >= 0) {
|
||||
double input_l = *in1;
|
||||
double input_r = *in2;
|
||||
if (fabs(input_l) < 1.18e-23) input_l = t.fdp_l * 1.18e-17;
|
||||
if (fabs(input_r) < 1.18e-23) input_r = t.fdp_r * 1.18e-17;
|
||||
|
||||
if (input_pad < 1.0) {
|
||||
input_l *= input_pad;
|
||||
input_r *= input_pad;
|
||||
}
|
||||
|
||||
if (overallscale > 1.9) {
|
||||
double stored = input_l;
|
||||
input_l += t.prev_sample_a;
|
||||
t.prev_sample_a = stored;
|
||||
input_l *= 0.5;
|
||||
stored = input_r;
|
||||
input_r += t.prev_sample_b;
|
||||
t.prev_sample_b = stored;
|
||||
input_r *= 0.5;
|
||||
} // for high sample rates on this plugin we are going to do a simple average
|
||||
|
||||
if (input_l > 1.0) input_l = 1.0;
|
||||
if (input_l < -1.0) input_l = -1.0;
|
||||
if (input_r > 1.0) input_r = 1.0;
|
||||
if (input_r < -1.0) input_r = -1.0;
|
||||
|
||||
// flatten bottom, point top of sine waveshaper L
|
||||
input_l /= asym_pad;
|
||||
double sharpen = -input_l;
|
||||
if (sharpen > 0.0) sharpen = 1.0 + sqrt(sharpen);
|
||||
else sharpen = 1.0 - sqrt(-sharpen);
|
||||
input_l -= input_l * fabs(input_l) * sharpen * 0.25;
|
||||
// this will take input from exactly -1.0 to 1.0 max
|
||||
input_l *= asym_pad;
|
||||
// flatten bottom, point top of sine waveshaper R
|
||||
input_r /= asym_pad;
|
||||
sharpen = -input_r;
|
||||
if (sharpen > 0.0) sharpen = 1.0 + sqrt(sharpen);
|
||||
else sharpen = 1.0 - sqrt(-sharpen);
|
||||
input_r -= input_r * fabs(input_r) * sharpen * 0.25;
|
||||
// this will take input from exactly -1.0 to 1.0 max
|
||||
input_r *= asym_pad;
|
||||
// end first asym section: later boosting can mitigate the extreme
|
||||
// softclipping of one side of the wave
|
||||
// and we are asym clipping more when Tube is cranked, to compensate
|
||||
|
||||
// original Tube algorithm: powerfactor widens the more linear region of the wave
|
||||
double factor = input_l; // Left channel
|
||||
for (int x = 0; x < powerfactor; x++) factor *= input_l;
|
||||
if ((powerfactor % 2 == 1) && (input_l != 0.0))
|
||||
factor = (factor / input_l) * fabs(input_l);
|
||||
factor *= gainscaling;
|
||||
input_l -= factor;
|
||||
input_l *= outputscaling;
|
||||
factor = input_r; // Right channel
|
||||
for (int x = 0; x < powerfactor; x++) factor *= input_r;
|
||||
if ((powerfactor % 2 == 1) && (input_r != 0.0))
|
||||
factor = (factor / input_r) * fabs(input_r);
|
||||
factor *= gainscaling;
|
||||
input_r -= factor;
|
||||
input_r *= outputscaling;
|
||||
|
||||
if (overallscale > 1.9) {
|
||||
double stored = input_l;
|
||||
input_l += t.prev_sample_c;
|
||||
t.prev_sample_c = stored;
|
||||
input_l *= 0.5;
|
||||
stored = input_r;
|
||||
input_r += t.prev_sample_d;
|
||||
t.prev_sample_d = stored;
|
||||
input_r *= 0.5;
|
||||
} // for high sample rates on this plugin we are going to do a simple average
|
||||
// end original Tube. Now we have a boosted fat sound peaking at 0dB exactly
|
||||
|
||||
// hysteresis and spiky fuzz L
|
||||
double slew = t.prev_sample_e - input_l;
|
||||
if (overallscale > 1.9) {
|
||||
double stored = input_l;
|
||||
input_l += t.prev_sample_e;
|
||||
t.prev_sample_e = stored;
|
||||
input_l *= 0.5;
|
||||
} else t.prev_sample_e = input_l; // for this, need previousSampleC always
|
||||
if (slew > 0.0) slew = 1.0 + (sqrt(slew) * 0.5);
|
||||
else slew = 1.0 - (sqrt(-slew) * 0.5);
|
||||
input_l -= input_l * fabs(input_l) * slew * gainscaling;
|
||||
// reusing gainscaling that's part of another algorithm
|
||||
if (input_l > 0.52) input_l = 0.52;
|
||||
if (input_l < -0.52) input_l = -0.52;
|
||||
input_l *= 1.923076923076923;
|
||||
// hysteresis and spiky fuzz R
|
||||
slew = t.prev_sample_f - input_r;
|
||||
if (overallscale > 1.9) {
|
||||
double stored = input_r;
|
||||
input_r += t.prev_sample_f;
|
||||
t.prev_sample_f = stored;
|
||||
input_r *= 0.5;
|
||||
} else t.prev_sample_f = input_r; // for this, need previousSampleC always
|
||||
if (slew > 0.0) slew = 1.0 + (sqrt(slew) * 0.5);
|
||||
else slew = 1.0 - (sqrt(-slew) * 0.5);
|
||||
input_r -= input_r * fabs(input_r) * slew * gainscaling;
|
||||
// reusing gainscaling that's part of another algorithm
|
||||
if (input_r > 0.52) input_r = 0.52;
|
||||
if (input_r < -0.52) input_r = -0.52;
|
||||
input_r *= 1.923076923076923;
|
||||
// end hysteresis and spiky fuzz section
|
||||
|
||||
// begin 64 bit stereo floating point dither
|
||||
// int expon; frexp((double)inputSampleL, &expon);
|
||||
t.fdp_l ^= t.fdp_l << 13;
|
||||
t.fdp_l ^= t.fdp_l >> 17;
|
||||
t.fdp_l ^= t.fdp_l << 5;
|
||||
// inputSampleL += ((double(fpdL)-uint32_t(0x7fffffff)) * 1.1e-44l *
|
||||
// pow(2,expon+62)); frexp((double)inputSampleR, &expon);
|
||||
t.fdp_r ^= t.fdp_r << 13;
|
||||
t.fdp_r ^= t.fdp_r >> 17;
|
||||
t.fdp_r ^= t.fdp_r << 5;
|
||||
// inputSampleR += ((double(fpdR)-uint32_t(0x7fffffff)) * 1.1e-44l *
|
||||
// pow(2,expon+62)); end 64 bit stereo floating point dither
|
||||
|
||||
*out1 = input_l;
|
||||
*out2 = input_r;
|
||||
|
||||
in1++;
|
||||
in2++;
|
||||
out1++;
|
||||
out2++;
|
||||
}
|
||||
}
|
||||
} // namespace trnr
|
||||
@@ -1,52 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace trnr {
|
||||
class wavefolder {
|
||||
public:
|
||||
float amount = 1.f;
|
||||
|
||||
void process_sample(float& _sample)
|
||||
{
|
||||
if (amount > 1.f) { _sample = fold(_sample * amount); }
|
||||
}
|
||||
|
||||
private:
|
||||
// folds the wave from -1 to 1
|
||||
float fold(float _sample)
|
||||
{
|
||||
while (_sample > 1.0 || _sample < -1.0) {
|
||||
|
||||
if (_sample > 1.0) {
|
||||
_sample = 2.0 - _sample;
|
||||
} else if (_sample < -1.0) {
|
||||
_sample = -2.0 - _sample;
|
||||
}
|
||||
}
|
||||
|
||||
return _sample;
|
||||
}
|
||||
|
||||
// folds the positive part of the wave independently from the negative part.
|
||||
float fold_bipolar(float _sample)
|
||||
{
|
||||
// fold positive values
|
||||
if (_sample > 1.0) {
|
||||
_sample = 2.0 - _sample;
|
||||
|
||||
if (_sample < 0.0) { _sample = -_sample; }
|
||||
|
||||
return fold(_sample);
|
||||
}
|
||||
// fold negative values
|
||||
else if (_sample < -1.0) {
|
||||
_sample = -2.0 - _sample;
|
||||
|
||||
if (_sample > 0.0) { _sample = -_sample; }
|
||||
|
||||
return fold(_sample);
|
||||
} else {
|
||||
return _sample;
|
||||
}
|
||||
}
|
||||
};
|
||||
}; // namespace trnr
|
||||
62
companding/alaw.h
Normal file
62
companding/alaw.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* fold.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace trnr {
|
||||
|
||||
constexpr float A_LAW_A = 87.6f;
|
||||
|
||||
inline float alaw_encode(float input)
|
||||
{
|
||||
float sign = (input >= 0.0f) ? 1.0f : -1.0f;
|
||||
float abs_sample = std::fabs(input);
|
||||
|
||||
float output;
|
||||
if (abs_sample < (1.0f / A_LAW_A)) {
|
||||
output = sign * (A_LAW_A * abs_sample) / (1.0f + std::log(A_LAW_A));
|
||||
} else {
|
||||
output =
|
||||
sign * (1.0f + std::log(A_LAW_A * abs_sample)) / (1.0f + std::log(A_LAW_A));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
inline float alaw_decode(float input)
|
||||
{
|
||||
float sign = (input >= 0.0f) ? 1.0f : -1.0f;
|
||||
float abs_comp = std::fabs(input);
|
||||
|
||||
float sample;
|
||||
if (abs_comp < (1.0f / (1.0f + std::log(A_LAW_A)))) {
|
||||
sample = sign * (abs_comp * (1.0f + std::log(A_LAW_A))) / A_LAW_A;
|
||||
} else {
|
||||
sample = sign * std::exp(abs_comp * (1.0f + std::log(A_LAW_A)) - 1.0f) / A_LAW_A;
|
||||
}
|
||||
|
||||
return sample;
|
||||
}
|
||||
} // namespace trnr
|
||||
@@ -1,48 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace trnr {
|
||||
// mulaw companding based on code by Emilie Gillet / Mutable Instruments
|
||||
class mulaw {
|
||||
public:
|
||||
int8_t encode_samples(int16_t pcm_val)
|
||||
{
|
||||
int16_t mask;
|
||||
int16_t seg;
|
||||
uint8_t uval;
|
||||
pcm_val = pcm_val >> 2;
|
||||
if (pcm_val < 0) {
|
||||
pcm_val = -pcm_val;
|
||||
mask = 0x7f;
|
||||
} else {
|
||||
mask = 0xff;
|
||||
}
|
||||
if (pcm_val > 8159) pcm_val = 8159;
|
||||
pcm_val += (0x84 >> 2);
|
||||
|
||||
if (pcm_val <= 0x3f) seg = 0;
|
||||
else if (pcm_val <= 0x7f) seg = 1;
|
||||
else if (pcm_val <= 0xff) seg = 2;
|
||||
else if (pcm_val <= 0x1ff) seg = 3;
|
||||
else if (pcm_val <= 0x3ff) seg = 4;
|
||||
else if (pcm_val <= 0x7ff) seg = 5;
|
||||
else if (pcm_val <= 0xfff) seg = 6;
|
||||
else if (pcm_val <= 0x1fff) seg = 7;
|
||||
else seg = 8;
|
||||
if (seg >= 8) return static_cast<uint8_t>(0x7f ^ mask);
|
||||
else {
|
||||
uval = static_cast<uint8_t>((seg << 4) | ((pcm_val >> (seg + 1)) & 0x0f));
|
||||
return (uval ^ mask);
|
||||
}
|
||||
}
|
||||
|
||||
int16_t decode_samples(uint8_t u_val)
|
||||
{
|
||||
int16_t t;
|
||||
u_val = ~u_val;
|
||||
t = ((u_val & 0xf) << 3) + 0x84;
|
||||
t <<= ((unsigned)u_val & 0x70) >> 4;
|
||||
return ((u_val & 0x80) ? (0x84 - t) : (t - 0x84));
|
||||
}
|
||||
};
|
||||
} // namespace trnr
|
||||
@@ -1,108 +0,0 @@
|
||||
#pragma once
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace trnr {
|
||||
// ulaw compansion based on code by Chris Johnson
|
||||
class ulaw {
|
||||
public:
|
||||
ulaw()
|
||||
{
|
||||
fpd_l = 1.0;
|
||||
while (fpd_l < 16386) fpd_l = rand() * UINT32_MAX;
|
||||
fpd_r = 1.0;
|
||||
while (fpd_r < 16386) fpd_r = rand() * UINT32_MAX;
|
||||
}
|
||||
|
||||
void encode_samples(double& input_sample_l, double& input_sample_r)
|
||||
{
|
||||
|
||||
// ulaw encoding
|
||||
static int noisesource_l = 0;
|
||||
static int noisesource_r = 850010;
|
||||
int residue;
|
||||
double applyresidue;
|
||||
|
||||
noisesource_l = noisesource_l % 1700021;
|
||||
noisesource_l++;
|
||||
residue = noisesource_l * noisesource_l;
|
||||
residue = residue % 170003;
|
||||
residue *= residue;
|
||||
residue = residue % 17011;
|
||||
residue *= residue;
|
||||
residue = residue % 1709;
|
||||
residue *= residue;
|
||||
residue = residue % 173;
|
||||
residue *= residue;
|
||||
residue = residue % 17;
|
||||
applyresidue = residue;
|
||||
applyresidue *= 0.00000001;
|
||||
applyresidue *= 0.00000001;
|
||||
input_sample_l += applyresidue;
|
||||
if (input_sample_l < 1.2e-38 && -input_sample_l < 1.2e-38) { input_sample_l -= applyresidue; }
|
||||
|
||||
noisesource_r = noisesource_r % 1700021;
|
||||
noisesource_r++;
|
||||
residue = noisesource_r * noisesource_r;
|
||||
residue = residue % 170003;
|
||||
residue *= residue;
|
||||
residue = residue % 17011;
|
||||
residue *= residue;
|
||||
residue = residue % 1709;
|
||||
residue *= residue;
|
||||
residue = residue % 173;
|
||||
residue *= residue;
|
||||
residue = residue % 17;
|
||||
applyresidue = residue;
|
||||
applyresidue *= 0.00000001;
|
||||
applyresidue *= 0.00000001;
|
||||
input_sample_r += applyresidue;
|
||||
if (input_sample_r < 1.2e-38 && -input_sample_r < 1.2e-38) { input_sample_r -= applyresidue; }
|
||||
|
||||
if (input_sample_l > 1.0) input_sample_l = 1.0;
|
||||
if (input_sample_l < -1.0) input_sample_l = -1.0;
|
||||
|
||||
if (input_sample_r > 1.0) input_sample_r = 1.0;
|
||||
if (input_sample_r < -1.0) input_sample_r = -1.0;
|
||||
|
||||
if (input_sample_l > 0) input_sample_l = log(1.0 + (255 * fabs(input_sample_l))) / log(256);
|
||||
if (input_sample_l < 0) input_sample_l = -log(1.0 + (255 * fabs(input_sample_l))) / log(256);
|
||||
|
||||
if (input_sample_r > 0) input_sample_r = log(1.0 + (255 * fabs(input_sample_r))) / log(256);
|
||||
if (input_sample_r < 0) input_sample_r = -log(1.0 + (255 * fabs(input_sample_r))) / log(256);
|
||||
}
|
||||
|
||||
void decode_samples(double& input_sample_l, double& input_sample_r)
|
||||
{
|
||||
|
||||
// ulaw decoding
|
||||
if (fabs(input_sample_l) < 1.18e-23) input_sample_l = fpd_l * 1.18e-17;
|
||||
if (fabs(input_sample_r) < 1.18e-23) input_sample_r = fpd_r * 1.18e-17;
|
||||
|
||||
if (input_sample_l > 1.0) input_sample_l = 1.0;
|
||||
if (input_sample_l < -1.0) input_sample_l = -1.0;
|
||||
|
||||
if (input_sample_r > 1.0) input_sample_r = 1.0;
|
||||
if (input_sample_r < -1.0) input_sample_r = -1.0;
|
||||
|
||||
if (input_sample_l > 0) input_sample_l = (pow(256, fabs(input_sample_l)) - 1.0) / 255;
|
||||
if (input_sample_l < 0) input_sample_l = -(pow(256, fabs(input_sample_l)) - 1.0) / 255;
|
||||
|
||||
if (input_sample_r > 0) input_sample_r = (pow(256, fabs(input_sample_r)) - 1.0) / 255;
|
||||
if (input_sample_r < 0) input_sample_r = -(pow(256, fabs(input_sample_r)) - 1.0) / 255;
|
||||
|
||||
// 64 bit stereo floating point dither
|
||||
fpd_l ^= fpd_l << 13;
|
||||
fpd_l ^= fpd_l >> 17;
|
||||
fpd_l ^= fpd_l << 5;
|
||||
fpd_r ^= fpd_r << 13;
|
||||
fpd_r ^= fpd_r >> 17;
|
||||
fpd_r ^= fpd_r << 5;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t fpd_l;
|
||||
uint32_t fpd_r;
|
||||
};
|
||||
} // namespace trnr
|
||||
@@ -1,280 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstdlib>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace trnr {
|
||||
// compressor based on pop2 by Chris Johnson
|
||||
class aw_pop2 {
|
||||
public:
|
||||
aw_pop2()
|
||||
{
|
||||
samplerate = 44100;
|
||||
|
||||
A = 0.5;
|
||||
B = 0.5;
|
||||
C = 0.5;
|
||||
D = 0.5;
|
||||
E = 1.0;
|
||||
fpdL = 1.0;
|
||||
while (fpdL < 16386) fpdL = rand() * UINT32_MAX;
|
||||
fpdR = 1.0;
|
||||
while (fpdR < 16386) fpdR = rand() * UINT32_MAX;
|
||||
|
||||
lastSampleL = 0.0;
|
||||
wasPosClipL = false;
|
||||
wasNegClipL = false;
|
||||
lastSampleR = 0.0;
|
||||
wasPosClipR = false;
|
||||
wasNegClipR = false;
|
||||
for (int x = 0; x < 16; x++) {
|
||||
intermediateL[x] = 0.0;
|
||||
intermediateR[x] = 0.0;
|
||||
}
|
||||
|
||||
muVaryL = 0.0;
|
||||
muAttackL = 0.0;
|
||||
muNewSpeedL = 1000.0;
|
||||
muSpeedAL = 1000.0;
|
||||
muSpeedBL = 1000.0;
|
||||
muCoefficientAL = 1.0;
|
||||
muCoefficientBL = 1.0;
|
||||
|
||||
muVaryR = 0.0;
|
||||
muAttackR = 0.0;
|
||||
muNewSpeedR = 1000.0;
|
||||
muSpeedAR = 1000.0;
|
||||
muSpeedBR = 1000.0;
|
||||
muCoefficientAR = 1.0;
|
||||
muCoefficientBR = 1.0;
|
||||
|
||||
flip = false;
|
||||
// this is reset: values being initialized only once. Startup values, whatever they are.
|
||||
}
|
||||
|
||||
void set_compression(double value) { A = clamp(value); }
|
||||
|
||||
void set_attack(double value) { B = clamp(value); }
|
||||
|
||||
void set_release(double value) { C = clamp(value); }
|
||||
|
||||
void set_drive(double value) { D = clamp(value); }
|
||||
|
||||
void set_drywet(double value) { E = clamp(value); }
|
||||
|
||||
void set_samplerate(double _samplerate) { samplerate = _samplerate; }
|
||||
|
||||
template <typename t_sample>
|
||||
void process_block(t_sample** inputs, t_sample** outputs, long sampleframes)
|
||||
{
|
||||
t_sample* in1 = inputs[0];
|
||||
t_sample* in2 = inputs[1];
|
||||
t_sample* out1 = outputs[0];
|
||||
t_sample* out2 = outputs[1];
|
||||
|
||||
double overallscale = 1.0;
|
||||
overallscale /= 44100.0;
|
||||
overallscale *= samplerate;
|
||||
|
||||
int spacing = floor(overallscale); // should give us working basic scaling, usually 2 or 4
|
||||
if (spacing < 1) spacing = 1;
|
||||
if (spacing > 16) spacing = 16;
|
||||
|
||||
double threshold = 1.0 - ((1.0 - pow(1.0 - A, 2)) * 0.9);
|
||||
double attack = ((pow(B, 4) * 100000.0) + 10.0) * overallscale;
|
||||
double release = ((pow(C, 5) * 2000000.0) + 20.0) * overallscale;
|
||||
double maxRelease = release * 4.0;
|
||||
double muPreGain = 1.0 / threshold;
|
||||
double muMakeupGain = sqrt(1.0 / threshold) * D;
|
||||
double wet = E;
|
||||
// compressor section
|
||||
|
||||
while (--sampleframes >= 0) {
|
||||
double inputSampleL = *in1;
|
||||
double inputSampleR = *in2;
|
||||
if (fabs(inputSampleL) < 1.18e-23) inputSampleL = fpdL * 1.18e-17;
|
||||
if (fabs(inputSampleR) < 1.18e-23) inputSampleR = fpdR * 1.18e-17;
|
||||
double drySampleL = inputSampleL;
|
||||
double drySampleR = inputSampleR;
|
||||
|
||||
// begin compressor section
|
||||
inputSampleL *= muPreGain;
|
||||
inputSampleR *= muPreGain;
|
||||
// adjust coefficients for L
|
||||
if (flip) {
|
||||
if (fabs(inputSampleL) > threshold) {
|
||||
muVaryL = threshold / fabs(inputSampleL);
|
||||
muAttackL = sqrt(fabs(muSpeedAL));
|
||||
muCoefficientAL = muCoefficientAL * (muAttackL - 1.0);
|
||||
if (muVaryL < threshold) muCoefficientAL = muCoefficientAL + threshold;
|
||||
else muCoefficientAL = muCoefficientAL + muVaryL;
|
||||
muCoefficientAL = muCoefficientAL / muAttackL;
|
||||
muNewSpeedL = muSpeedAL * (muSpeedAL - 1.0);
|
||||
muNewSpeedL = muNewSpeedL + release;
|
||||
muSpeedAL = muNewSpeedL / muSpeedAL;
|
||||
if (muSpeedAL > maxRelease) muSpeedAL = maxRelease;
|
||||
} else {
|
||||
muCoefficientAL = muCoefficientAL * ((muSpeedAL * muSpeedAL) - 1.0);
|
||||
muCoefficientAL = muCoefficientAL + 1.0;
|
||||
muCoefficientAL = muCoefficientAL / (muSpeedAL * muSpeedAL);
|
||||
muNewSpeedL = muSpeedAL * (muSpeedAL - 1.0);
|
||||
muNewSpeedL = muNewSpeedL + attack;
|
||||
muSpeedAL = muNewSpeedL / muSpeedAL;
|
||||
}
|
||||
} else {
|
||||
if (fabs(inputSampleL) > threshold) {
|
||||
muVaryL = threshold / fabs(inputSampleL);
|
||||
muAttackL = sqrt(fabs(muSpeedBL));
|
||||
muCoefficientBL = muCoefficientBL * (muAttackL - 1);
|
||||
if (muVaryL < threshold) muCoefficientBL = muCoefficientBL + threshold;
|
||||
else muCoefficientBL = muCoefficientBL + muVaryL;
|
||||
muCoefficientBL = muCoefficientBL / muAttackL;
|
||||
muNewSpeedL = muSpeedBL * (muSpeedBL - 1.0);
|
||||
muNewSpeedL = muNewSpeedL + release;
|
||||
muSpeedBL = muNewSpeedL / muSpeedBL;
|
||||
if (muSpeedBL > maxRelease) muSpeedBL = maxRelease;
|
||||
} else {
|
||||
muCoefficientBL = muCoefficientBL * ((muSpeedBL * muSpeedBL) - 1.0);
|
||||
muCoefficientBL = muCoefficientBL + 1.0;
|
||||
muCoefficientBL = muCoefficientBL / (muSpeedBL * muSpeedBL);
|
||||
muNewSpeedL = muSpeedBL * (muSpeedBL - 1.0);
|
||||
muNewSpeedL = muNewSpeedL + attack;
|
||||
muSpeedBL = muNewSpeedL / muSpeedBL;
|
||||
}
|
||||
}
|
||||
// got coefficients, adjusted speeds for L
|
||||
|
||||
// adjust coefficients for R
|
||||
if (flip) {
|
||||
if (fabs(inputSampleR) > threshold) {
|
||||
muVaryR = threshold / fabs(inputSampleR);
|
||||
muAttackR = sqrt(fabs(muSpeedAR));
|
||||
muCoefficientAR = muCoefficientAR * (muAttackR - 1.0);
|
||||
if (muVaryR < threshold) muCoefficientAR = muCoefficientAR + threshold;
|
||||
else muCoefficientAR = muCoefficientAR + muVaryR;
|
||||
muCoefficientAR = muCoefficientAR / muAttackR;
|
||||
muNewSpeedR = muSpeedAR * (muSpeedAR - 1.0);
|
||||
muNewSpeedR = muNewSpeedR + release;
|
||||
muSpeedAR = muNewSpeedR / muSpeedAR;
|
||||
if (muSpeedAR > maxRelease) muSpeedAR = maxRelease;
|
||||
} else {
|
||||
muCoefficientAR = muCoefficientAR * ((muSpeedAR * muSpeedAR) - 1.0);
|
||||
muCoefficientAR = muCoefficientAR + 1.0;
|
||||
muCoefficientAR = muCoefficientAR / (muSpeedAR * muSpeedAR);
|
||||
muNewSpeedR = muSpeedAR * (muSpeedAR - 1.0);
|
||||
muNewSpeedR = muNewSpeedR + attack;
|
||||
muSpeedAR = muNewSpeedR / muSpeedAR;
|
||||
}
|
||||
} else {
|
||||
if (fabs(inputSampleR) > threshold) {
|
||||
muVaryR = threshold / fabs(inputSampleR);
|
||||
muAttackR = sqrt(fabs(muSpeedBR));
|
||||
muCoefficientBR = muCoefficientBR * (muAttackR - 1);
|
||||
if (muVaryR < threshold) muCoefficientBR = muCoefficientBR + threshold;
|
||||
else muCoefficientBR = muCoefficientBR + muVaryR;
|
||||
muCoefficientBR = muCoefficientBR / muAttackR;
|
||||
muNewSpeedR = muSpeedBR * (muSpeedBR - 1.0);
|
||||
muNewSpeedR = muNewSpeedR + release;
|
||||
muSpeedBR = muNewSpeedR / muSpeedBR;
|
||||
if (muSpeedBR > maxRelease) muSpeedBR = maxRelease;
|
||||
} else {
|
||||
muCoefficientBR = muCoefficientBR * ((muSpeedBR * muSpeedBR) - 1.0);
|
||||
muCoefficientBR = muCoefficientBR + 1.0;
|
||||
muCoefficientBR = muCoefficientBR / (muSpeedBR * muSpeedBR);
|
||||
muNewSpeedR = muSpeedBR * (muSpeedBR - 1.0);
|
||||
muNewSpeedR = muNewSpeedR + attack;
|
||||
muSpeedBR = muNewSpeedR / muSpeedBR;
|
||||
}
|
||||
}
|
||||
// got coefficients, adjusted speeds for R
|
||||
|
||||
if (flip) {
|
||||
inputSampleL *= pow(muCoefficientAL, 2);
|
||||
inputSampleR *= pow(muCoefficientAR, 2);
|
||||
} else {
|
||||
inputSampleL *= pow(muCoefficientBL, 2);
|
||||
inputSampleR *= pow(muCoefficientBR, 2);
|
||||
}
|
||||
inputSampleL *= muMakeupGain;
|
||||
inputSampleR *= muMakeupGain;
|
||||
flip = !flip;
|
||||
// end compressor section
|
||||
|
||||
if (wet < 1.0) {
|
||||
inputSampleL = (drySampleL * (1.0 - wet)) + (inputSampleL * wet);
|
||||
inputSampleR = (drySampleR * (1.0 - wet)) + (inputSampleR * wet);
|
||||
}
|
||||
|
||||
// begin 64 bit stereo floating point dither
|
||||
// int expon; frexp((double)inputSampleL, &expon);
|
||||
fpdL ^= fpdL << 13;
|
||||
fpdL ^= fpdL >> 17;
|
||||
fpdL ^= fpdL << 5;
|
||||
// inputSampleL += ((double(fpdL)-uint32_t(0x7fffffff)) * 1.1e-44l * pow(2,expon+62));
|
||||
// frexp((double)inputSampleR, &expon);
|
||||
fpdR ^= fpdR << 13;
|
||||
fpdR ^= fpdR >> 17;
|
||||
fpdR ^= fpdR << 5;
|
||||
// inputSampleR += ((double(fpdR)-uint32_t(0x7fffffff)) * 1.1e-44l * pow(2,expon+62));
|
||||
// end 64 bit stereo floating point dither
|
||||
|
||||
*out1 = inputSampleL;
|
||||
*out2 = inputSampleR;
|
||||
|
||||
in1++;
|
||||
in2++;
|
||||
out1++;
|
||||
out2++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate;
|
||||
|
||||
uint32_t fpdL;
|
||||
uint32_t fpdR;
|
||||
// default stuff
|
||||
|
||||
double muVaryL;
|
||||
double muAttackL;
|
||||
double muNewSpeedL;
|
||||
double muSpeedAL;
|
||||
double muSpeedBL;
|
||||
double muCoefficientAL;
|
||||
double muCoefficientBL;
|
||||
|
||||
double muVaryR;
|
||||
double muAttackR;
|
||||
double muNewSpeedR;
|
||||
double muSpeedAR;
|
||||
double muSpeedBR;
|
||||
double muCoefficientAR;
|
||||
double muCoefficientBR;
|
||||
|
||||
bool flip;
|
||||
|
||||
double lastSampleL;
|
||||
double intermediateL[16];
|
||||
bool wasPosClipL;
|
||||
bool wasNegClipL;
|
||||
double lastSampleR;
|
||||
double intermediateR[16];
|
||||
bool wasPosClipR;
|
||||
bool wasNegClipR; // Stereo ClipOnly2
|
||||
|
||||
float A;
|
||||
float B;
|
||||
float C;
|
||||
float D;
|
||||
float E; // parameters. Always 0-1, and we scale/alter them elsewhere.
|
||||
|
||||
double clamp(double& value)
|
||||
{
|
||||
if (value > 1) {
|
||||
value = 1;
|
||||
} else if (value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
||||
} // namespace trnr
|
||||
142
dynamics/oneknob.h
Normal file
142
dynamics/oneknob.h
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* oneknob.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../util/audio_math.h"
|
||||
#include "rms_detector.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace trnr {
|
||||
struct hp_filter {
|
||||
float a0, a1, b1;
|
||||
float z1; // filter state
|
||||
};
|
||||
|
||||
inline void hp_filter_init(hp_filter& f, float samplerate)
|
||||
{
|
||||
const float cutoff = 100.0f;
|
||||
float w0 = 2.0f * 3.14159265359f * cutoff / samplerate;
|
||||
float alpha = (1.0f - std::tan(w0 / 2.0f)) / (1.0f + std::tan(w0 / 2.0f));
|
||||
f.a0 = 0.5f * (1.0f + alpha);
|
||||
f.a1 = -0.5f * (1.0f + alpha);
|
||||
f.b1 = alpha;
|
||||
f.z1 = 0.0f;
|
||||
}
|
||||
|
||||
inline float hp_filter_process(hp_filter& f, float x)
|
||||
{
|
||||
float y = f.a0 * x + f.a1 * f.z1 - f.b1 * f.z1;
|
||||
f.z1 = x;
|
||||
return y;
|
||||
}
|
||||
|
||||
struct oneknob_comp {
|
||||
// params
|
||||
float amount = 0.f;
|
||||
bool multiplied = false;
|
||||
|
||||
// state
|
||||
rms_detector detector;
|
||||
hp_filter filter;
|
||||
float attack_coef;
|
||||
float release_coef;
|
||||
float envelope_level;
|
||||
float sidechain_in;
|
||||
double samplerate;
|
||||
};
|
||||
|
||||
inline void oneknob_init(oneknob_comp& c, double samplerate, float window_ms)
|
||||
{
|
||||
rms_init(c.detector, samplerate, window_ms);
|
||||
hp_filter_init(c.filter, samplerate);
|
||||
|
||||
const float attack_ms = 0.2f;
|
||||
const float release_ms = 150.f;
|
||||
|
||||
c.samplerate = samplerate;
|
||||
c.attack_coef = expf(-1.0f / (attack_ms * 1e-6 * samplerate));
|
||||
c.release_coef = expf(-1.0f / (release_ms * 1e-3 * samplerate));
|
||||
c.envelope_level = -60.f;
|
||||
c.sidechain_in = 0.f;
|
||||
}
|
||||
|
||||
template <typename sample>
|
||||
inline void oneknob_process_block(oneknob_comp& c, sample** audio, int frames)
|
||||
{
|
||||
const float min_user_ratio = 1.0f;
|
||||
const float max_user_ratio = 20.0f;
|
||||
const float threshold_db = -12.f;
|
||||
|
||||
const float amount = fmaxf(0.0f, fminf(powf(c.amount, 2.f), 1.0f));
|
||||
float ratio = min_user_ratio + amount * (max_user_ratio - min_user_ratio);
|
||||
|
||||
const float fast_attack = 0.1f;
|
||||
const float slow_attack = 0.6f;
|
||||
const float fast_release = 90.f;
|
||||
const float slow_release = 300.f;
|
||||
const float max_gr = 12.f;
|
||||
|
||||
for (int i = 0; i < frames; ++i) {
|
||||
float rms_value = rms_process<sample>(c.detector, c.sidechain_in);
|
||||
float envelope_in = lin_2_db(fmaxf(fabs(rms_value), 1e-20f));
|
||||
|
||||
// attack
|
||||
if (envelope_in > c.envelope_level) {
|
||||
c.envelope_level =
|
||||
envelope_in + c.attack_coef * (c.envelope_level - envelope_in);
|
||||
}
|
||||
// release
|
||||
else {
|
||||
c.envelope_level =
|
||||
envelope_in + c.release_coef * (c.envelope_level - envelope_in);
|
||||
}
|
||||
|
||||
float x = c.envelope_level;
|
||||
float y;
|
||||
|
||||
if (x < threshold_db) y = x;
|
||||
else y = threshold_db + (x - threshold_db) / ratio;
|
||||
|
||||
float gain_reduction_db = y - x;
|
||||
float gain_reduction_lin = db_2_lin(gain_reduction_db);
|
||||
|
||||
audio[0][i] *= gain_reduction_lin;
|
||||
audio[1][i] *= gain_reduction_lin;
|
||||
|
||||
// calculate program-dependent attack/release times
|
||||
float norm_gr = std::clamp(gain_reduction_db / max_gr, 0.f, 1.f);
|
||||
float release_ms = fast_release + (slow_release - fast_release) * (1.f - norm_gr);
|
||||
float attack_ms = fast_attack + (slow_attack - fast_attack) * (1.f - norm_gr);
|
||||
|
||||
c.attack_coef = expf(-1.0f / (attack_ms * 1e-6 * c.samplerate));
|
||||
c.release_coef = expf(-1.0f / (release_ms * 1e-3 * c.samplerate));
|
||||
|
||||
// feedback compression
|
||||
float sum = sqrtf(0.5f * (audio[0][i] * audio[0][i] + audio[1][i] * audio[1][i]));
|
||||
if (c.multiplied) sum *= 3.f;
|
||||
c.sidechain_in = hp_filter_process(c.filter, sum);
|
||||
}
|
||||
}
|
||||
} // namespace trnr
|
||||
215
dynamics/pump.h
215
dynamics/pump.h
@@ -1,63 +1,115 @@
|
||||
/*
|
||||
* pump.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../util/audio_math.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace trnr {
|
||||
struct pump {
|
||||
double samplerate;
|
||||
float threshold_db = 0.f;
|
||||
float attack_ms = 10.f;
|
||||
float release_ms = 100.f;
|
||||
float hp_filter = 80.f;
|
||||
float ratio = 1000.f;
|
||||
float filter_frq = 40000.f;
|
||||
float filter_exp = 0.f;
|
||||
float treble_boost = 0.f;
|
||||
|
||||
float filtered = 0.f;
|
||||
float filtered_l = 0.f;
|
||||
float filtered_r = 0.f;
|
||||
float boosted_l = 0.f;
|
||||
float boosted_r = 0.f;
|
||||
float attack_coef = 0.f;
|
||||
float release_coef = 0.f;
|
||||
float envelope_db = 0.f;
|
||||
};
|
||||
|
||||
enum pump_param {
|
||||
PUMP_THRESHOLD,
|
||||
PUMP_ATTACK,
|
||||
PUMP_RELEASE,
|
||||
PUMP_HP_FILTER,
|
||||
PUMP_RATIO,
|
||||
PUMP_FILTER_FRQ,
|
||||
PUMP_FILTER_EXP,
|
||||
PUMP_TREBLE_BOOST
|
||||
};
|
||||
|
||||
inline void pump_set_param(pump& p, pump_param param, float value)
|
||||
{
|
||||
switch (param) {
|
||||
case PUMP_THRESHOLD:
|
||||
p.threshold_db = value;
|
||||
break;
|
||||
case PUMP_ATTACK:
|
||||
p.attack_ms = value;
|
||||
p.attack_coef = exp(-1000.0 / (p.attack_ms * p.samplerate));
|
||||
break;
|
||||
case PUMP_RELEASE:
|
||||
p.release_ms = value;
|
||||
p.release_coef = exp(-1000.0 / (p.release_ms * p.samplerate));
|
||||
break;
|
||||
case PUMP_HP_FILTER:
|
||||
p.hp_filter = value;
|
||||
break;
|
||||
case PUMP_RATIO:
|
||||
p.ratio = value;
|
||||
break;
|
||||
case PUMP_FILTER_FRQ:
|
||||
p.filter_frq = value;
|
||||
break;
|
||||
case PUMP_FILTER_EXP:
|
||||
p.filter_exp = value;
|
||||
break;
|
||||
case PUMP_TREBLE_BOOST:
|
||||
p.treble_boost = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline void pump_init(pump& p, double samplerate)
|
||||
{
|
||||
p.samplerate = samplerate;
|
||||
pump_set_param(p, PUMP_ATTACK, p.attack_ms);
|
||||
pump_set_param(p, PUMP_RELEASE, p.release_ms);
|
||||
}
|
||||
|
||||
template <typename sample>
|
||||
class pump {
|
||||
public:
|
||||
pump()
|
||||
: attack_ms(10.f)
|
||||
, release_ms(100.f)
|
||||
, hp_filter(80.0f)
|
||||
, threshold_db(0.f)
|
||||
, ratio(1000.0f)
|
||||
, filter_frq(40000.f)
|
||||
, filter_exp(0.f)
|
||||
, treble_boost(0.f)
|
||||
{
|
||||
set_samplerate(44100);
|
||||
}
|
||||
|
||||
void set_samplerate(double _samplerate)
|
||||
{
|
||||
samplerate = _samplerate;
|
||||
set_attack(attack_ms);
|
||||
set_release(release_ms);
|
||||
}
|
||||
|
||||
void set_ratio(float value) { ratio = value; }
|
||||
|
||||
/* set threshold in db */
|
||||
void set_threshold(float value) { threshold_db = value; }
|
||||
|
||||
/* set attack in ms */
|
||||
void set_attack(float value)
|
||||
{
|
||||
attack_ms = value;
|
||||
attack_coef = exp(-1000.0 / (attack_ms * samplerate));
|
||||
}
|
||||
|
||||
/* set release in ms */
|
||||
void set_release(float value)
|
||||
{
|
||||
release_ms = value;
|
||||
release_coef = exp(-1000.0 / (release_ms * samplerate));
|
||||
}
|
||||
|
||||
/* lowpass filter frequency in hz */
|
||||
void set_filter_frq(float value) { filter_frq = value; }
|
||||
|
||||
/* accentuates the filter pumping effect */
|
||||
void set_filter_exp(float value) { filter_exp = value; }
|
||||
|
||||
void process_block(sample** audio, sample** sidechain, int frames)
|
||||
{
|
||||
inline void pump_process_block(pump& p, sample** audio, sample** sidechain, int frames)
|
||||
{
|
||||
// highpass filter coefficients
|
||||
float hp_x = std::exp(-2.0 * M_PI * hp_filter / samplerate);
|
||||
float hp_x = std::exp(-2.0 * M_PI * p.hp_filter / p.samplerate);
|
||||
float hp_a0 = 1.0 - hp_x;
|
||||
float hp_b1 = -hp_x;
|
||||
|
||||
// top end boost filter coefficients
|
||||
float bst_x = exp(-2.0 * M_PI * 5000.0 / samplerate);
|
||||
float bst_x = exp(-2.0 * M_PI * 5000.0 / p.samplerate);
|
||||
float bst_a0 = 1.0 - bst_x;
|
||||
float bst_b1 = -bst_x;
|
||||
|
||||
@@ -69,76 +121,55 @@ public:
|
||||
sample sidechain_in = (sidechain[0][i] + sidechain[1][i]) / 2.0;
|
||||
|
||||
// highpass filter sidechain signal
|
||||
filtered = hp_a0 * sidechain_in - hp_b1 * filtered;
|
||||
sidechain_in = sidechain_in - filtered;
|
||||
p.filtered = hp_a0 * sidechain_in - hp_b1 * p.filtered;
|
||||
sidechain_in = sidechain_in - p.filtered;
|
||||
|
||||
// rectify sidechain input for envelope following
|
||||
float link = std::fabs(sidechain_in);
|
||||
float linked_db = trnr::lin_2_db(link);
|
||||
|
||||
// cut envelope below threshold
|
||||
float overshoot_db = linked_db - (threshold_db - 10.0);
|
||||
float overshoot_db = linked_db - (p.threshold_db - 10.0);
|
||||
if (overshoot_db < 0.0) overshoot_db = 0.0;
|
||||
|
||||
// process envelope
|
||||
if (overshoot_db > envelope_db) {
|
||||
envelope_db = overshoot_db + attack_coef * (envelope_db - overshoot_db);
|
||||
if (overshoot_db > p.envelope_db) {
|
||||
p.envelope_db = overshoot_db + p.attack_coef * (p.envelope_db - overshoot_db);
|
||||
} else {
|
||||
envelope_db = overshoot_db + release_coef * (envelope_db - overshoot_db);
|
||||
p.envelope_db = overshoot_db + p.release_coef * (p.envelope_db - overshoot_db);
|
||||
}
|
||||
|
||||
float slope = 1.f / ratio;
|
||||
float slope = 1.f / p.ratio;
|
||||
|
||||
// transfer function
|
||||
float gain_reduction_db = envelope_db * (slope - 1.0);
|
||||
float gain_reduction_lin = trnr::db_2_lin(gain_reduction_db);
|
||||
float gain_reduction_db = p.envelope_db * (slope - 1.0);
|
||||
float gain_reduction_lin = db_2_lin(gain_reduction_db);
|
||||
|
||||
// compress left and right signals
|
||||
sample output_l = input_l * gain_reduction_lin;
|
||||
sample output_r = input_r * gain_reduction_lin;
|
||||
|
||||
if (filter_exp > 0.f) {
|
||||
if (p.filter_exp > 0.f) {
|
||||
// one pole lowpass filter with envelope applied to frequency for pumping effect
|
||||
float freq = filter_frq * pow(gain_reduction_lin, filter_exp);
|
||||
float lp_x = exp(-2.0 * M_PI * freq / samplerate);
|
||||
float freq = p.filter_frq * pow(gain_reduction_lin, p.filter_exp);
|
||||
float lp_x = exp(-2.0 * M_PI * freq / p.samplerate);
|
||||
float lp_a0 = 1.0 - lp_x;
|
||||
float lp_b1 = -lp_x;
|
||||
filtered_l = lp_a0 * output_l - lp_b1 * filtered_l;
|
||||
filtered_r = lp_a0 * output_r - lp_b1 * filtered_r;
|
||||
p.filtered_l = lp_a0 * output_l - lp_b1 * p.filtered_l;
|
||||
p.filtered_r = lp_a0 * output_r - lp_b1 * p.filtered_r;
|
||||
}
|
||||
|
||||
// top end boost
|
||||
boosted_l = bst_a0 * filtered_l - bst_b1 * boosted_l;
|
||||
boosted_r = bst_a0 * filtered_r - bst_b1 * boosted_r;
|
||||
output_l = filtered_l + (filtered_l - boosted_l) * treble_boost;
|
||||
output_r = filtered_r + (filtered_r - boosted_r) * treble_boost;
|
||||
p.boosted_l = bst_a0 * p.filtered_l - bst_b1 * p.boosted_l;
|
||||
p.boosted_r = bst_a0 * p.filtered_r - bst_b1 * p.boosted_r;
|
||||
output_l = p.filtered_l + (p.filtered_l - p.boosted_l) * p.treble_boost;
|
||||
output_r = p.filtered_r + (p.filtered_r - p.boosted_r) * p.treble_boost;
|
||||
|
||||
// calculate makeup gain
|
||||
float makeup_lin = trnr::db_2_lin(-threshold_db / 5.f);
|
||||
float makeup_lin = trnr::db_2_lin(-p.threshold_db / 5.f);
|
||||
|
||||
audio[0][i] = input_l * gain_reduction_lin * makeup_lin;
|
||||
audio[1][i] = input_r * gain_reduction_lin * makeup_lin;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate;
|
||||
float threshold_db = 0.f;
|
||||
float attack_ms;
|
||||
float release_ms;
|
||||
float hp_filter;
|
||||
float ratio;
|
||||
float filter_frq;
|
||||
float filter_exp;
|
||||
float treble_boost;
|
||||
|
||||
sample filtered;
|
||||
sample filtered_l = 0;
|
||||
sample filtered_r = 0;
|
||||
sample boosted_l = 0;
|
||||
sample boosted_r = 0;
|
||||
float attack_coef;
|
||||
float release_coef;
|
||||
float envelope_db = 0;
|
||||
};
|
||||
}
|
||||
} // namespace trnr
|
||||
47
dynamics/rms_detector.h
Normal file
47
dynamics/rms_detector.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* rms_detector.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace trnr {
|
||||
struct rms_detector {
|
||||
float alpha;
|
||||
float rms_squared;
|
||||
};
|
||||
|
||||
inline void rms_init(rms_detector& d, float samplerate, float window_ms)
|
||||
{
|
||||
float window_seconds = 0.001f * window_ms;
|
||||
d.alpha = 1.0f - expf(-1.0f / (samplerate * window_seconds));
|
||||
d.rms_squared = 0.0f;
|
||||
}
|
||||
|
||||
template <typename sample>
|
||||
inline sample rms_process(rms_detector& d, sample input)
|
||||
{
|
||||
d.rms_squared = (1.0f - d.alpha) * d.rms_squared + d.alpha * (input * input);
|
||||
return sqrtf(d.rms_squared);
|
||||
}
|
||||
} // namespace trnr
|
||||
649
filter/aw_eq.h
649
filter/aw_eq.h
@@ -1,649 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstdlib>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace trnr {
|
||||
// 3 band equalizer with high/lowpass filters based on EQ by Chris Johnson.
|
||||
class aw_eq {
|
||||
public:
|
||||
aw_eq()
|
||||
{
|
||||
samplerate = 44100;
|
||||
|
||||
A = 0.5; // Treble -12 to 12
|
||||
B = 0.5; // Mid -12 to 12
|
||||
C = 0.5; // Bass -12 to 12
|
||||
D = 1.0; // Lowpass 16.0K log 1 to 16 defaulting to 16K
|
||||
E = 0.4; // TrebFrq 6.0 log 1 to 16 defaulting to 6K
|
||||
F = 0.4; // BassFrq 100.0 log 30 to 1600 defaulting to 100 hz
|
||||
G = 0.0; // Hipass 30.0 log 30 to 1600 defaulting to 30
|
||||
H = 0.5; // OutGain -18 to 18
|
||||
|
||||
lastSampleL = 0.0;
|
||||
last2SampleL = 0.0;
|
||||
lastSampleR = 0.0;
|
||||
last2SampleR = 0.0;
|
||||
|
||||
iirHighSampleLA = 0.0;
|
||||
iirHighSampleLB = 0.0;
|
||||
iirHighSampleLC = 0.0;
|
||||
iirHighSampleLD = 0.0;
|
||||
iirHighSampleLE = 0.0;
|
||||
iirLowSampleLA = 0.0;
|
||||
iirLowSampleLB = 0.0;
|
||||
iirLowSampleLC = 0.0;
|
||||
iirLowSampleLD = 0.0;
|
||||
iirLowSampleLE = 0.0;
|
||||
iirHighSampleL = 0.0;
|
||||
iirLowSampleL = 0.0;
|
||||
|
||||
iirHighSampleRA = 0.0;
|
||||
iirHighSampleRB = 0.0;
|
||||
iirHighSampleRC = 0.0;
|
||||
iirHighSampleRD = 0.0;
|
||||
iirHighSampleRE = 0.0;
|
||||
iirLowSampleRA = 0.0;
|
||||
iirLowSampleRB = 0.0;
|
||||
iirLowSampleRC = 0.0;
|
||||
iirLowSampleRD = 0.0;
|
||||
iirLowSampleRE = 0.0;
|
||||
iirHighSampleR = 0.0;
|
||||
iirLowSampleR = 0.0;
|
||||
|
||||
tripletLA = 0.0;
|
||||
tripletLB = 0.0;
|
||||
tripletLC = 0.0;
|
||||
tripletFactorL = 0.0;
|
||||
|
||||
tripletRA = 0.0;
|
||||
tripletRB = 0.0;
|
||||
tripletRC = 0.0;
|
||||
tripletFactorR = 0.0;
|
||||
|
||||
lowpassSampleLAA = 0.0;
|
||||
lowpassSampleLAB = 0.0;
|
||||
lowpassSampleLBA = 0.0;
|
||||
lowpassSampleLBB = 0.0;
|
||||
lowpassSampleLCA = 0.0;
|
||||
lowpassSampleLCB = 0.0;
|
||||
lowpassSampleLDA = 0.0;
|
||||
lowpassSampleLDB = 0.0;
|
||||
lowpassSampleLE = 0.0;
|
||||
lowpassSampleLF = 0.0;
|
||||
lowpassSampleLG = 0.0;
|
||||
|
||||
lowpassSampleRAA = 0.0;
|
||||
lowpassSampleRAB = 0.0;
|
||||
lowpassSampleRBA = 0.0;
|
||||
lowpassSampleRBB = 0.0;
|
||||
lowpassSampleRCA = 0.0;
|
||||
lowpassSampleRCB = 0.0;
|
||||
lowpassSampleRDA = 0.0;
|
||||
lowpassSampleRDB = 0.0;
|
||||
lowpassSampleRE = 0.0;
|
||||
lowpassSampleRF = 0.0;
|
||||
lowpassSampleRG = 0.0;
|
||||
|
||||
highpassSampleLAA = 0.0;
|
||||
highpassSampleLAB = 0.0;
|
||||
highpassSampleLBA = 0.0;
|
||||
highpassSampleLBB = 0.0;
|
||||
highpassSampleLCA = 0.0;
|
||||
highpassSampleLCB = 0.0;
|
||||
highpassSampleLDA = 0.0;
|
||||
highpassSampleLDB = 0.0;
|
||||
highpassSampleLE = 0.0;
|
||||
highpassSampleLF = 0.0;
|
||||
|
||||
highpassSampleRAA = 0.0;
|
||||
highpassSampleRAB = 0.0;
|
||||
highpassSampleRBA = 0.0;
|
||||
highpassSampleRBB = 0.0;
|
||||
highpassSampleRCA = 0.0;
|
||||
highpassSampleRCB = 0.0;
|
||||
highpassSampleRDA = 0.0;
|
||||
highpassSampleRDB = 0.0;
|
||||
highpassSampleRE = 0.0;
|
||||
highpassSampleRF = 0.0;
|
||||
|
||||
flip = false;
|
||||
flipthree = 0;
|
||||
|
||||
fpdL = 1.0;
|
||||
while (fpdL < 16386) fpdL = rand() * UINT32_MAX;
|
||||
fpdR = 1.0;
|
||||
while (fpdR < 16386) fpdR = rand() * UINT32_MAX;
|
||||
// this is reset: values being initialized only once. Startup values, whatever they are.
|
||||
}
|
||||
|
||||
void set_treble(double value) { A = clamp(value); }
|
||||
|
||||
void set_mid(double value) { B = clamp(value); }
|
||||
|
||||
void set_bass(double value) { C = clamp(value); }
|
||||
|
||||
void set_lowpass(double value) { D = clamp(value); }
|
||||
|
||||
void set_treble_frq(double value) { E = clamp(value); }
|
||||
|
||||
void set_bass_frq(double value) { F = clamp(value); }
|
||||
|
||||
void set_hipass(double value) { G = clamp(value); }
|
||||
|
||||
void set_out_gain(double value) { H = clamp(value); }
|
||||
|
||||
void set_samplerate(double _samplerate) { samplerate = _samplerate; }
|
||||
|
||||
template <typename t_sample>
|
||||
void process_block(t_sample** inputs, t_sample** outputs, long sampleframes)
|
||||
{
|
||||
t_sample* in1 = inputs[0];
|
||||
t_sample* in2 = inputs[1];
|
||||
t_sample* out1 = outputs[0];
|
||||
t_sample* out2 = outputs[1];
|
||||
|
||||
double overallscale = 1.0;
|
||||
overallscale /= 44100.0;
|
||||
double compscale = overallscale;
|
||||
overallscale = samplerate;
|
||||
compscale = compscale * overallscale;
|
||||
// compscale is the one that's 1 or something like 2.2 for 96K rates
|
||||
|
||||
double inputSampleL;
|
||||
double inputSampleR;
|
||||
|
||||
double highSampleL = 0.0;
|
||||
double midSampleL = 0.0;
|
||||
double bassSampleL = 0.0;
|
||||
|
||||
double highSampleR = 0.0;
|
||||
double midSampleR = 0.0;
|
||||
double bassSampleR = 0.0;
|
||||
|
||||
double densityA = (A * 12.0) - 6.0;
|
||||
double densityB = (B * 12.0) - 6.0;
|
||||
double densityC = (C * 12.0) - 6.0;
|
||||
bool engageEQ = true;
|
||||
if ((0.0 == densityA) && (0.0 == densityB) && (0.0 == densityC)) engageEQ = false;
|
||||
|
||||
densityA = pow(10.0, densityA / 20.0) - 1.0;
|
||||
densityB = pow(10.0, densityB / 20.0) - 1.0;
|
||||
densityC = pow(10.0, densityC / 20.0) - 1.0;
|
||||
// convert to 0 to X multiplier with 1.0 being O db
|
||||
// minus one gives nearly -1 to ? (should top out at 1)
|
||||
// calibrate so that X db roughly equals X db with maximum topping out at 1 internally
|
||||
|
||||
double tripletIntensity = -densityA;
|
||||
|
||||
double iirAmountC = (((D * D * 15.0) + 1.0) * 0.0188) + 0.7;
|
||||
if (iirAmountC > 1.0) iirAmountC = 1.0;
|
||||
bool engageLowpass = false;
|
||||
if (((D * D * 15.0) + 1.0) < 15.99) engageLowpass = true;
|
||||
|
||||
double iirAmountA = (((E * E * 15.0) + 1.0) * 1000) / overallscale;
|
||||
double iirAmountB = (((F * F * 1570.0) + 30.0) * 10) / overallscale;
|
||||
double iirAmountD = (((G * G * 1570.0) + 30.0) * 1.0) / overallscale;
|
||||
bool engageHighpass = false;
|
||||
if (((G * G * 1570.0) + 30.0) > 30.01) engageHighpass = true;
|
||||
// bypass the highpass and lowpass if set to extremes
|
||||
double bridgerectifier;
|
||||
double outA = fabs(densityA);
|
||||
double outB = fabs(densityB);
|
||||
double outC = fabs(densityC);
|
||||
// end EQ
|
||||
double outputgain = pow(10.0, ((H * 36.0) - 18.0) / 20.0);
|
||||
|
||||
while (--sampleframes >= 0) {
|
||||
inputSampleL = *in1;
|
||||
inputSampleR = *in2;
|
||||
if (fabs(inputSampleL) < 1.18e-23) inputSampleL = fpdL * 1.18e-17;
|
||||
if (fabs(inputSampleR) < 1.18e-23) inputSampleR = fpdR * 1.18e-17;
|
||||
|
||||
last2SampleL = lastSampleL;
|
||||
lastSampleL = inputSampleL;
|
||||
|
||||
last2SampleR = lastSampleR;
|
||||
lastSampleR = inputSampleR;
|
||||
|
||||
flip = !flip;
|
||||
flipthree++;
|
||||
if (flipthree < 1 || flipthree > 3) flipthree = 1;
|
||||
// counters
|
||||
|
||||
// begin highpass
|
||||
if (engageHighpass) {
|
||||
if (flip) {
|
||||
highpassSampleLAA = (highpassSampleLAA * (1.0 - iirAmountD)) + (inputSampleL * iirAmountD);
|
||||
inputSampleL -= highpassSampleLAA;
|
||||
highpassSampleLBA = (highpassSampleLBA * (1.0 - iirAmountD)) + (inputSampleL * iirAmountD);
|
||||
inputSampleL -= highpassSampleLBA;
|
||||
highpassSampleLCA = (highpassSampleLCA * (1.0 - iirAmountD)) + (inputSampleL * iirAmountD);
|
||||
inputSampleL -= highpassSampleLCA;
|
||||
highpassSampleLDA = (highpassSampleLDA * (1.0 - iirAmountD)) + (inputSampleL * iirAmountD);
|
||||
inputSampleL -= highpassSampleLDA;
|
||||
} else {
|
||||
highpassSampleLAB = (highpassSampleLAB * (1.0 - iirAmountD)) + (inputSampleL * iirAmountD);
|
||||
inputSampleL -= highpassSampleLAB;
|
||||
highpassSampleLBB = (highpassSampleLBB * (1.0 - iirAmountD)) + (inputSampleL * iirAmountD);
|
||||
inputSampleL -= highpassSampleLBB;
|
||||
highpassSampleLCB = (highpassSampleLCB * (1.0 - iirAmountD)) + (inputSampleL * iirAmountD);
|
||||
inputSampleL -= highpassSampleLCB;
|
||||
highpassSampleLDB = (highpassSampleLDB * (1.0 - iirAmountD)) + (inputSampleL * iirAmountD);
|
||||
inputSampleL -= highpassSampleLDB;
|
||||
}
|
||||
highpassSampleLE = (highpassSampleLE * (1.0 - iirAmountD)) + (inputSampleL * iirAmountD);
|
||||
inputSampleL -= highpassSampleLE;
|
||||
highpassSampleLF = (highpassSampleLF * (1.0 - iirAmountD)) + (inputSampleL * iirAmountD);
|
||||
inputSampleL -= highpassSampleLF;
|
||||
|
||||
if (flip) {
|
||||
highpassSampleRAA = (highpassSampleRAA * (1.0 - iirAmountD)) + (inputSampleR * iirAmountD);
|
||||
inputSampleR -= highpassSampleRAA;
|
||||
highpassSampleRBA = (highpassSampleRBA * (1.0 - iirAmountD)) + (inputSampleR * iirAmountD);
|
||||
inputSampleR -= highpassSampleRBA;
|
||||
highpassSampleRCA = (highpassSampleRCA * (1.0 - iirAmountD)) + (inputSampleR * iirAmountD);
|
||||
inputSampleR -= highpassSampleRCA;
|
||||
highpassSampleRDA = (highpassSampleRDA * (1.0 - iirAmountD)) + (inputSampleR * iirAmountD);
|
||||
inputSampleR -= highpassSampleRDA;
|
||||
} else {
|
||||
highpassSampleRAB = (highpassSampleRAB * (1.0 - iirAmountD)) + (inputSampleR * iirAmountD);
|
||||
inputSampleR -= highpassSampleRAB;
|
||||
highpassSampleRBB = (highpassSampleRBB * (1.0 - iirAmountD)) + (inputSampleR * iirAmountD);
|
||||
inputSampleR -= highpassSampleRBB;
|
||||
highpassSampleRCB = (highpassSampleRCB * (1.0 - iirAmountD)) + (inputSampleR * iirAmountD);
|
||||
inputSampleR -= highpassSampleRCB;
|
||||
highpassSampleRDB = (highpassSampleRDB * (1.0 - iirAmountD)) + (inputSampleR * iirAmountD);
|
||||
inputSampleR -= highpassSampleRDB;
|
||||
}
|
||||
highpassSampleRE = (highpassSampleRE * (1 - iirAmountD)) + (inputSampleR * iirAmountD);
|
||||
inputSampleR -= highpassSampleRE;
|
||||
highpassSampleRF = (highpassSampleRF * (1 - iirAmountD)) + (inputSampleR * iirAmountD);
|
||||
inputSampleR -= highpassSampleRF;
|
||||
}
|
||||
// end highpass
|
||||
|
||||
// begin EQ
|
||||
if (engageEQ) {
|
||||
switch (flipthree) {
|
||||
case 1:
|
||||
tripletFactorL = last2SampleL - inputSampleL;
|
||||
tripletLA += tripletFactorL;
|
||||
tripletLC -= tripletFactorL;
|
||||
tripletFactorL = tripletLA * tripletIntensity;
|
||||
iirHighSampleLC = (iirHighSampleLC * (1.0 - iirAmountA)) + (inputSampleL * iirAmountA);
|
||||
highSampleL = inputSampleL - iirHighSampleLC;
|
||||
iirLowSampleLC = (iirLowSampleLC * (1.0 - iirAmountB)) + (inputSampleL * iirAmountB);
|
||||
bassSampleL = iirLowSampleLC;
|
||||
|
||||
tripletFactorR = last2SampleR - inputSampleR;
|
||||
tripletRA += tripletFactorR;
|
||||
tripletRC -= tripletFactorR;
|
||||
tripletFactorR = tripletRA * tripletIntensity;
|
||||
iirHighSampleRC = (iirHighSampleRC * (1.0 - iirAmountA)) + (inputSampleR * iirAmountA);
|
||||
highSampleR = inputSampleR - iirHighSampleRC;
|
||||
iirLowSampleRC = (iirLowSampleRC * (1.0 - iirAmountB)) + (inputSampleR * iirAmountB);
|
||||
bassSampleR = iirLowSampleRC;
|
||||
break;
|
||||
case 2:
|
||||
tripletFactorL = last2SampleL - inputSampleL;
|
||||
tripletLB += tripletFactorL;
|
||||
tripletLA -= tripletFactorL;
|
||||
tripletFactorL = tripletLB * tripletIntensity;
|
||||
iirHighSampleLD = (iirHighSampleLD * (1.0 - iirAmountA)) + (inputSampleL * iirAmountA);
|
||||
highSampleL = inputSampleL - iirHighSampleLD;
|
||||
iirLowSampleLD = (iirLowSampleLD * (1.0 - iirAmountB)) + (inputSampleL * iirAmountB);
|
||||
bassSampleL = iirLowSampleLD;
|
||||
|
||||
tripletFactorR = last2SampleR - inputSampleR;
|
||||
tripletRB += tripletFactorR;
|
||||
tripletRA -= tripletFactorR;
|
||||
tripletFactorR = tripletRB * tripletIntensity;
|
||||
iirHighSampleRD = (iirHighSampleRD * (1.0 - iirAmountA)) + (inputSampleR * iirAmountA);
|
||||
highSampleR = inputSampleR - iirHighSampleRD;
|
||||
iirLowSampleRD = (iirLowSampleRD * (1.0 - iirAmountB)) + (inputSampleR * iirAmountB);
|
||||
bassSampleR = iirLowSampleRD;
|
||||
break;
|
||||
case 3:
|
||||
tripletFactorL = last2SampleL - inputSampleL;
|
||||
tripletLC += tripletFactorL;
|
||||
tripletLB -= tripletFactorL;
|
||||
tripletFactorL = tripletLC * tripletIntensity;
|
||||
iirHighSampleLE = (iirHighSampleLE * (1.0 - iirAmountA)) + (inputSampleL * iirAmountA);
|
||||
highSampleL = inputSampleL - iirHighSampleLE;
|
||||
iirLowSampleLE = (iirLowSampleLE * (1.0 - iirAmountB)) + (inputSampleL * iirAmountB);
|
||||
bassSampleL = iirLowSampleLE;
|
||||
|
||||
tripletFactorR = last2SampleR - inputSampleR;
|
||||
tripletRC += tripletFactorR;
|
||||
tripletRB -= tripletFactorR;
|
||||
tripletFactorR = tripletRC * tripletIntensity;
|
||||
iirHighSampleRE = (iirHighSampleRE * (1.0 - iirAmountA)) + (inputSampleR * iirAmountA);
|
||||
highSampleR = inputSampleR - iirHighSampleRE;
|
||||
iirLowSampleRE = (iirLowSampleRE * (1.0 - iirAmountB)) + (inputSampleR * iirAmountB);
|
||||
bassSampleR = iirLowSampleRE;
|
||||
break;
|
||||
}
|
||||
tripletLA /= 2.0;
|
||||
tripletLB /= 2.0;
|
||||
tripletLC /= 2.0;
|
||||
highSampleL = highSampleL + tripletFactorL;
|
||||
|
||||
tripletRA /= 2.0;
|
||||
tripletRB /= 2.0;
|
||||
tripletRC /= 2.0;
|
||||
highSampleR = highSampleR + tripletFactorR;
|
||||
|
||||
if (flip) {
|
||||
iirHighSampleLA = (iirHighSampleLA * (1.0 - iirAmountA)) + (highSampleL * iirAmountA);
|
||||
highSampleL -= iirHighSampleLA;
|
||||
iirLowSampleLA = (iirLowSampleLA * (1.0 - iirAmountB)) + (bassSampleL * iirAmountB);
|
||||
bassSampleL = iirLowSampleLA;
|
||||
|
||||
iirHighSampleRA = (iirHighSampleRA * (1.0 - iirAmountA)) + (highSampleR * iirAmountA);
|
||||
highSampleR -= iirHighSampleRA;
|
||||
iirLowSampleRA = (iirLowSampleRA * (1.0 - iirAmountB)) + (bassSampleR * iirAmountB);
|
||||
bassSampleR = iirLowSampleRA;
|
||||
} else {
|
||||
iirHighSampleLB = (iirHighSampleLB * (1.0 - iirAmountA)) + (highSampleL * iirAmountA);
|
||||
highSampleL -= iirHighSampleLB;
|
||||
iirLowSampleLB = (iirLowSampleLB * (1.0 - iirAmountB)) + (bassSampleL * iirAmountB);
|
||||
bassSampleL = iirLowSampleLB;
|
||||
|
||||
iirHighSampleRB = (iirHighSampleRB * (1.0 - iirAmountA)) + (highSampleR * iirAmountA);
|
||||
highSampleR -= iirHighSampleRB;
|
||||
iirLowSampleRB = (iirLowSampleRB * (1.0 - iirAmountB)) + (bassSampleR * iirAmountB);
|
||||
bassSampleR = iirLowSampleRB;
|
||||
}
|
||||
|
||||
iirHighSampleL = (iirHighSampleL * (1.0 - iirAmountA)) + (highSampleL * iirAmountA);
|
||||
highSampleL -= iirHighSampleL;
|
||||
iirLowSampleL = (iirLowSampleL * (1.0 - iirAmountB)) + (bassSampleL * iirAmountB);
|
||||
bassSampleL = iirLowSampleL;
|
||||
|
||||
iirHighSampleR = (iirHighSampleR * (1.0 - iirAmountA)) + (highSampleR * iirAmountA);
|
||||
highSampleR -= iirHighSampleR;
|
||||
iirLowSampleR = (iirLowSampleR * (1.0 - iirAmountB)) + (bassSampleR * iirAmountB);
|
||||
bassSampleR = iirLowSampleR;
|
||||
|
||||
midSampleL = (inputSampleL - bassSampleL) - highSampleL;
|
||||
midSampleR = (inputSampleR - bassSampleR) - highSampleR;
|
||||
|
||||
// drive section
|
||||
highSampleL *= (densityA + 1.0);
|
||||
bridgerectifier = fabs(highSampleL) * 1.57079633;
|
||||
if (bridgerectifier > 1.57079633) bridgerectifier = 1.57079633;
|
||||
// max value for sine function
|
||||
if (densityA > 0) bridgerectifier = sin(bridgerectifier);
|
||||
else bridgerectifier = 1 - cos(bridgerectifier);
|
||||
// produce either boosted or starved version
|
||||
if (highSampleL > 0) highSampleL = (highSampleL * (1 - outA)) + (bridgerectifier * outA);
|
||||
else highSampleL = (highSampleL * (1 - outA)) - (bridgerectifier * outA);
|
||||
// blend according to densityA control
|
||||
|
||||
highSampleR *= (densityA + 1.0);
|
||||
bridgerectifier = fabs(highSampleR) * 1.57079633;
|
||||
if (bridgerectifier > 1.57079633) bridgerectifier = 1.57079633;
|
||||
// max value for sine function
|
||||
if (densityA > 0) bridgerectifier = sin(bridgerectifier);
|
||||
else bridgerectifier = 1 - cos(bridgerectifier);
|
||||
// produce either boosted or starved version
|
||||
if (highSampleR > 0) highSampleR = (highSampleR * (1 - outA)) + (bridgerectifier * outA);
|
||||
else highSampleR = (highSampleR * (1 - outA)) - (bridgerectifier * outA);
|
||||
// blend according to densityA control
|
||||
|
||||
midSampleL *= (densityB + 1.0);
|
||||
bridgerectifier = fabs(midSampleL) * 1.57079633;
|
||||
if (bridgerectifier > 1.57079633) bridgerectifier = 1.57079633;
|
||||
// max value for sine function
|
||||
if (densityB > 0) bridgerectifier = sin(bridgerectifier);
|
||||
else bridgerectifier = 1 - cos(bridgerectifier);
|
||||
// produce either boosted or starved version
|
||||
if (midSampleL > 0) midSampleL = (midSampleL * (1 - outB)) + (bridgerectifier * outB);
|
||||
else midSampleL = (midSampleL * (1 - outB)) - (bridgerectifier * outB);
|
||||
// blend according to densityB control
|
||||
|
||||
midSampleR *= (densityB + 1.0);
|
||||
bridgerectifier = fabs(midSampleR) * 1.57079633;
|
||||
if (bridgerectifier > 1.57079633) bridgerectifier = 1.57079633;
|
||||
// max value for sine function
|
||||
if (densityB > 0) bridgerectifier = sin(bridgerectifier);
|
||||
else bridgerectifier = 1 - cos(bridgerectifier);
|
||||
// produce either boosted or starved version
|
||||
if (midSampleR > 0) midSampleR = (midSampleR * (1 - outB)) + (bridgerectifier * outB);
|
||||
else midSampleR = (midSampleR * (1 - outB)) - (bridgerectifier * outB);
|
||||
// blend according to densityB control
|
||||
|
||||
bassSampleL *= (densityC + 1.0);
|
||||
bridgerectifier = fabs(bassSampleL) * 1.57079633;
|
||||
if (bridgerectifier > 1.57079633) bridgerectifier = 1.57079633;
|
||||
// max value for sine function
|
||||
if (densityC > 0) bridgerectifier = sin(bridgerectifier);
|
||||
else bridgerectifier = 1 - cos(bridgerectifier);
|
||||
// produce either boosted or starved version
|
||||
if (bassSampleL > 0) bassSampleL = (bassSampleL * (1 - outC)) + (bridgerectifier * outC);
|
||||
else bassSampleL = (bassSampleL * (1 - outC)) - (bridgerectifier * outC);
|
||||
// blend according to densityC control
|
||||
|
||||
bassSampleR *= (densityC + 1.0);
|
||||
bridgerectifier = fabs(bassSampleR) * 1.57079633;
|
||||
if (bridgerectifier > 1.57079633) bridgerectifier = 1.57079633;
|
||||
// max value for sine function
|
||||
if (densityC > 0) bridgerectifier = sin(bridgerectifier);
|
||||
else bridgerectifier = 1 - cos(bridgerectifier);
|
||||
// produce either boosted or starved version
|
||||
if (bassSampleR > 0) bassSampleR = (bassSampleR * (1 - outC)) + (bridgerectifier * outC);
|
||||
else bassSampleR = (bassSampleR * (1 - outC)) - (bridgerectifier * outC);
|
||||
// blend according to densityC control
|
||||
|
||||
inputSampleL = midSampleL;
|
||||
inputSampleL += highSampleL;
|
||||
inputSampleL += bassSampleL;
|
||||
|
||||
inputSampleR = midSampleR;
|
||||
inputSampleR += highSampleR;
|
||||
inputSampleR += bassSampleR;
|
||||
}
|
||||
// end EQ
|
||||
|
||||
// EQ lowpass is after all processing like the compressor that might produce hash
|
||||
if (engageLowpass) {
|
||||
if (flip) {
|
||||
lowpassSampleLAA = (lowpassSampleLAA * (1.0 - iirAmountC)) + (inputSampleL * iirAmountC);
|
||||
inputSampleL = lowpassSampleLAA;
|
||||
lowpassSampleLBA = (lowpassSampleLBA * (1.0 - iirAmountC)) + (inputSampleL * iirAmountC);
|
||||
inputSampleL = lowpassSampleLBA;
|
||||
lowpassSampleLCA = (lowpassSampleLCA * (1.0 - iirAmountC)) + (inputSampleL * iirAmountC);
|
||||
inputSampleL = lowpassSampleLCA;
|
||||
lowpassSampleLDA = (lowpassSampleLDA * (1.0 - iirAmountC)) + (inputSampleL * iirAmountC);
|
||||
inputSampleL = lowpassSampleLDA;
|
||||
lowpassSampleLE = (lowpassSampleLE * (1.0 - iirAmountC)) + (inputSampleL * iirAmountC);
|
||||
inputSampleL = lowpassSampleLE;
|
||||
|
||||
lowpassSampleRAA = (lowpassSampleRAA * (1.0 - iirAmountC)) + (inputSampleR * iirAmountC);
|
||||
inputSampleR = lowpassSampleRAA;
|
||||
lowpassSampleRBA = (lowpassSampleRBA * (1.0 - iirAmountC)) + (inputSampleR * iirAmountC);
|
||||
inputSampleR = lowpassSampleRBA;
|
||||
lowpassSampleRCA = (lowpassSampleRCA * (1.0 - iirAmountC)) + (inputSampleR * iirAmountC);
|
||||
inputSampleR = lowpassSampleRCA;
|
||||
lowpassSampleRDA = (lowpassSampleRDA * (1.0 - iirAmountC)) + (inputSampleR * iirAmountC);
|
||||
inputSampleR = lowpassSampleRDA;
|
||||
lowpassSampleRE = (lowpassSampleRE * (1.0 - iirAmountC)) + (inputSampleR * iirAmountC);
|
||||
inputSampleR = lowpassSampleRE;
|
||||
} else {
|
||||
lowpassSampleLAB = (lowpassSampleLAB * (1.0 - iirAmountC)) + (inputSampleL * iirAmountC);
|
||||
inputSampleL = lowpassSampleLAB;
|
||||
lowpassSampleLBB = (lowpassSampleLBB * (1.0 - iirAmountC)) + (inputSampleL * iirAmountC);
|
||||
inputSampleL = lowpassSampleLBB;
|
||||
lowpassSampleLCB = (lowpassSampleLCB * (1.0 - iirAmountC)) + (inputSampleL * iirAmountC);
|
||||
inputSampleL = lowpassSampleLCB;
|
||||
lowpassSampleLDB = (lowpassSampleLDB * (1.0 - iirAmountC)) + (inputSampleL * iirAmountC);
|
||||
inputSampleL = lowpassSampleLDB;
|
||||
lowpassSampleLF = (lowpassSampleLF * (1.0 - iirAmountC)) + (inputSampleL * iirAmountC);
|
||||
inputSampleL = lowpassSampleLF;
|
||||
|
||||
lowpassSampleRAB = (lowpassSampleRAB * (1.0 - iirAmountC)) + (inputSampleR * iirAmountC);
|
||||
inputSampleR = lowpassSampleRAB;
|
||||
lowpassSampleRBB = (lowpassSampleRBB * (1.0 - iirAmountC)) + (inputSampleR * iirAmountC);
|
||||
inputSampleR = lowpassSampleRBB;
|
||||
lowpassSampleRCB = (lowpassSampleRCB * (1.0 - iirAmountC)) + (inputSampleR * iirAmountC);
|
||||
inputSampleR = lowpassSampleRCB;
|
||||
lowpassSampleRDB = (lowpassSampleRDB * (1.0 - iirAmountC)) + (inputSampleR * iirAmountC);
|
||||
inputSampleR = lowpassSampleRDB;
|
||||
lowpassSampleRF = (lowpassSampleRF * (1.0 - iirAmountC)) + (inputSampleR * iirAmountC);
|
||||
inputSampleR = lowpassSampleRF;
|
||||
}
|
||||
lowpassSampleLG = (lowpassSampleLG * (1.0 - iirAmountC)) + (inputSampleL * iirAmountC);
|
||||
lowpassSampleRG = (lowpassSampleRG * (1.0 - iirAmountC)) + (inputSampleR * iirAmountC);
|
||||
|
||||
inputSampleL = (lowpassSampleLG * (1.0 - iirAmountC)) + (inputSampleL * iirAmountC);
|
||||
inputSampleR = (lowpassSampleRG * (1.0 - iirAmountC)) + (inputSampleR * iirAmountC);
|
||||
}
|
||||
|
||||
// built in output trim and dry/wet if desired
|
||||
if (outputgain != 1.0) {
|
||||
inputSampleL *= outputgain;
|
||||
inputSampleR *= outputgain;
|
||||
}
|
||||
|
||||
// begin 64 bit stereo floating point dither
|
||||
// int expon; frexp((double)inputSampleL, &expon);
|
||||
fpdL ^= fpdL << 13;
|
||||
fpdL ^= fpdL >> 17;
|
||||
fpdL ^= fpdL << 5;
|
||||
// inputSampleL += ((double(fpdL)-uint32_t(0x7fffffff)) * 1.1e-44l * pow(2,expon+62));
|
||||
// frexp((double)inputSampleR, &expon);
|
||||
fpdR ^= fpdR << 13;
|
||||
fpdR ^= fpdR >> 17;
|
||||
fpdR ^= fpdR << 5;
|
||||
// inputSampleR += ((double(fpdR)-uint32_t(0x7fffffff)) * 1.1e-44l * pow(2,expon+62));
|
||||
// end 64 bit stereo floating point dither
|
||||
|
||||
*out1 = inputSampleL;
|
||||
*out2 = inputSampleR;
|
||||
|
||||
*in1++;
|
||||
*in2++;
|
||||
*out1++;
|
||||
*out2++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate;
|
||||
|
||||
uint32_t fpdL;
|
||||
uint32_t fpdR;
|
||||
// default stuff
|
||||
|
||||
double lastSampleL;
|
||||
double last2SampleL;
|
||||
double lastSampleR;
|
||||
double last2SampleR;
|
||||
|
||||
// begin EQ
|
||||
double iirHighSampleLA;
|
||||
double iirHighSampleLB;
|
||||
double iirHighSampleLC;
|
||||
double iirHighSampleLD;
|
||||
double iirHighSampleLE;
|
||||
double iirLowSampleLA;
|
||||
double iirLowSampleLB;
|
||||
double iirLowSampleLC;
|
||||
double iirLowSampleLD;
|
||||
double iirLowSampleLE;
|
||||
double iirHighSampleL;
|
||||
double iirLowSampleL;
|
||||
|
||||
double iirHighSampleRA;
|
||||
double iirHighSampleRB;
|
||||
double iirHighSampleRC;
|
||||
double iirHighSampleRD;
|
||||
double iirHighSampleRE;
|
||||
double iirLowSampleRA;
|
||||
double iirLowSampleRB;
|
||||
double iirLowSampleRC;
|
||||
double iirLowSampleRD;
|
||||
double iirLowSampleRE;
|
||||
double iirHighSampleR;
|
||||
double iirLowSampleR;
|
||||
|
||||
double tripletLA;
|
||||
double tripletLB;
|
||||
double tripletLC;
|
||||
double tripletFactorL;
|
||||
|
||||
double tripletRA;
|
||||
double tripletRB;
|
||||
double tripletRC;
|
||||
double tripletFactorR;
|
||||
|
||||
double lowpassSampleLAA;
|
||||
double lowpassSampleLAB;
|
||||
double lowpassSampleLBA;
|
||||
double lowpassSampleLBB;
|
||||
double lowpassSampleLCA;
|
||||
double lowpassSampleLCB;
|
||||
double lowpassSampleLDA;
|
||||
double lowpassSampleLDB;
|
||||
double lowpassSampleLE;
|
||||
double lowpassSampleLF;
|
||||
double lowpassSampleLG;
|
||||
|
||||
double lowpassSampleRAA;
|
||||
double lowpassSampleRAB;
|
||||
double lowpassSampleRBA;
|
||||
double lowpassSampleRBB;
|
||||
double lowpassSampleRCA;
|
||||
double lowpassSampleRCB;
|
||||
double lowpassSampleRDA;
|
||||
double lowpassSampleRDB;
|
||||
double lowpassSampleRE;
|
||||
double lowpassSampleRF;
|
||||
double lowpassSampleRG;
|
||||
|
||||
double highpassSampleLAA;
|
||||
double highpassSampleLAB;
|
||||
double highpassSampleLBA;
|
||||
double highpassSampleLBB;
|
||||
double highpassSampleLCA;
|
||||
double highpassSampleLCB;
|
||||
double highpassSampleLDA;
|
||||
double highpassSampleLDB;
|
||||
double highpassSampleLE;
|
||||
double highpassSampleLF;
|
||||
|
||||
double highpassSampleRAA;
|
||||
double highpassSampleRAB;
|
||||
double highpassSampleRBA;
|
||||
double highpassSampleRBB;
|
||||
double highpassSampleRCA;
|
||||
double highpassSampleRCB;
|
||||
double highpassSampleRDA;
|
||||
double highpassSampleRDB;
|
||||
double highpassSampleRE;
|
||||
double highpassSampleRF;
|
||||
|
||||
bool flip;
|
||||
int flipthree;
|
||||
// end EQ
|
||||
|
||||
float A;
|
||||
float B;
|
||||
float C;
|
||||
float D;
|
||||
float E;
|
||||
float F;
|
||||
float G;
|
||||
float H;
|
||||
|
||||
double clamp(double& value)
|
||||
{
|
||||
if (value > 1) {
|
||||
value = 1;
|
||||
} else if (value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
||||
} // namespace trnr
|
||||
@@ -1,4 +1,28 @@
|
||||
/*
|
||||
* chebyshev.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <array>
|
||||
#include <math.h>
|
||||
|
||||
613
filter/spliteq.h
Normal file
613
filter/spliteq.h
Normal file
@@ -0,0 +1,613 @@
|
||||
/*
|
||||
* spliteq.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../util/audio_math.h"
|
||||
#include "../util/smoother.h"
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
namespace trnr {
|
||||
|
||||
// Filter type enum
|
||||
enum filter_type {
|
||||
LOWPASS = 0,
|
||||
HIGHPASS = 1
|
||||
};
|
||||
|
||||
struct cascade_filter {
|
||||
filter_type type;
|
||||
int stages; // Number of cascaded stages
|
||||
double cutoff; // Cutoff frequency (Hz)
|
||||
double samplerate; // Sample rate (Hz)
|
||||
double alpha; // Filter coefficient
|
||||
std::vector<double> state; // State per stage
|
||||
};
|
||||
|
||||
inline void cascade_filter_setup(cascade_filter& f, filter_type _type, int _stages,
|
||||
double _cutoff, double _samplerate)
|
||||
{
|
||||
f.type = _type;
|
||||
f.stages = _stages;
|
||||
f.cutoff = _cutoff;
|
||||
f.samplerate = _samplerate;
|
||||
|
||||
// alpha = iirAmount = exp(-2 * pi * cutoff / samplerate);
|
||||
double x = exp(-2.0 * M_PI * f.cutoff / f.samplerate);
|
||||
f.alpha = 1.0 - x;
|
||||
|
||||
f.state.resize(f.stages, 0.0);
|
||||
}
|
||||
|
||||
// Process one sample
|
||||
inline double cascade_filter_process(cascade_filter& f, double input)
|
||||
{
|
||||
double out = input;
|
||||
for (int i = 0; i < f.stages; ++i) {
|
||||
if (f.type == LOWPASS) {
|
||||
f.state[i] = (f.state[i] * (1.0 - f.alpha)) + (out * f.alpha);
|
||||
out = f.state[i];
|
||||
} else { // CASCADE_HIGHPASS
|
||||
f.state[i] = (f.state[i] * (1.0 - f.alpha)) + (out * f.alpha);
|
||||
out -= f.state[i];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// 2nd order Butterworth biquad filter
|
||||
struct butterworth {
|
||||
filter_type type;
|
||||
double cutoff;
|
||||
double a1, a2;
|
||||
double b0, b1, b2;
|
||||
double x1, x2; // previous inputs
|
||||
double y1, y2; // previous outputs
|
||||
};
|
||||
|
||||
// Biquad coefficient calculation
|
||||
inline void butterworth_biquad_coeffs(butterworth& b, double samplerate)
|
||||
{
|
||||
double omega = 2.0 * M_PI * b.cutoff / samplerate;
|
||||
double sin_omega = sin(omega);
|
||||
double cos_omega = cos(omega);
|
||||
|
||||
double Q = 1.0 / sqrt(2.0); // Butterworth Q for 2nd order
|
||||
double alpha = sin_omega / (2.0 * Q);
|
||||
double a0 = 1.0 + alpha;
|
||||
|
||||
switch (b.type) {
|
||||
case LOWPASS:
|
||||
b.b0 = (1.0 - cos_omega) / 2.0 / a0;
|
||||
b.b1 = (1.0 - cos_omega) / a0;
|
||||
b.b2 = (1.0 - cos_omega) / 2.0 / a0;
|
||||
b.a1 = -2.0 * cos_omega / a0;
|
||||
b.a2 = (1.0 - alpha) / a0;
|
||||
break;
|
||||
case HIGHPASS:
|
||||
b.b0 = (1.0 + cos_omega) / 2.0 / a0;
|
||||
b.b1 = -(1.0 + cos_omega) / a0;
|
||||
b.b2 = (1.0 + cos_omega) / 2.0 / a0;
|
||||
b.a1 = -2.0 * cos_omega / a0;
|
||||
b.a2 = (1.0 - alpha) / a0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Biquad sample processing
|
||||
inline double butterworth_biquad_process(butterworth& b, double input)
|
||||
{
|
||||
double y = b.b0 * input + b.b1 * b.x1 + b.b2 * b.x2 - b.a1 * b.y1 - b.a2 * b.y2;
|
||||
b.x2 = b.x1;
|
||||
b.x1 = input;
|
||||
b.y2 = b.y1;
|
||||
b.y1 = y;
|
||||
return y;
|
||||
}
|
||||
|
||||
struct aw_filter {
|
||||
filter_type type;
|
||||
float amount;
|
||||
bool flip;
|
||||
double sampleLAA;
|
||||
double sampleLAB;
|
||||
double sampleLBA;
|
||||
double sampleLBB;
|
||||
double sampleLCA;
|
||||
double sampleLCB;
|
||||
double sampleLDA;
|
||||
double sampleLDB;
|
||||
double sampleLE;
|
||||
double sampleLF;
|
||||
double sampleLG;
|
||||
double samplerate;
|
||||
};
|
||||
|
||||
inline void aw_filter_init(aw_filter& f, filter_type type, float amount,
|
||||
double samplerate)
|
||||
{
|
||||
f.type = type;
|
||||
f.amount = amount;
|
||||
f.samplerate = samplerate;
|
||||
|
||||
f.sampleLAA = 0.0;
|
||||
f.sampleLAB = 0.0;
|
||||
f.sampleLBA = 0.0;
|
||||
f.sampleLBB = 0.0;
|
||||
f.sampleLCA = 0.0;
|
||||
f.sampleLCB = 0.0;
|
||||
f.sampleLDA = 0.0;
|
||||
f.sampleLDB = 0.0;
|
||||
f.sampleLE = 0.0;
|
||||
f.sampleLF = 0.0;
|
||||
f.sampleLG = 0.0;
|
||||
|
||||
f.flip = false;
|
||||
}
|
||||
|
||||
inline void aw_filter_process_block(aw_filter& f, float* audio, int frames)
|
||||
{
|
||||
double overallscale = 1.0;
|
||||
overallscale /= 44100.0;
|
||||
double compscale = overallscale;
|
||||
overallscale = f.samplerate;
|
||||
compscale = compscale * overallscale;
|
||||
bool engage = false;
|
||||
|
||||
double iir_amt = 0.0;
|
||||
|
||||
if (f.type == LOWPASS) {
|
||||
iir_amt = (((f.amount * f.amount * 15.0) + 1.0) * 0.0188) + 0.7;
|
||||
if (iir_amt > 1.0) iir_amt = 1.0;
|
||||
if (((f.amount * f.amount * 15.0) + 1.0) < 15.99) engage = true;
|
||||
} else if (f.type == HIGHPASS) {
|
||||
iir_amt = (((f.amount * f.amount * 1570.0) + 30.0) * 1.0) / overallscale;
|
||||
if (((f.amount * f.amount * 1570.0) + 30.0) > 30.01) engage = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < frames; i++) {
|
||||
float input = audio[i];
|
||||
f.flip = !f.flip;
|
||||
|
||||
if (engage) {
|
||||
switch (f.type) {
|
||||
case LOWPASS:
|
||||
if (f.flip) {
|
||||
f.sampleLAA = (f.sampleLAA * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input = f.sampleLAA;
|
||||
f.sampleLBA = (f.sampleLBA * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input = f.sampleLBA;
|
||||
f.sampleLCA = (f.sampleLCA * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input = f.sampleLCA;
|
||||
f.sampleLDA = (f.sampleLDA * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input = f.sampleLDA;
|
||||
f.sampleLE = (f.sampleLE * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input = f.sampleLE;
|
||||
} else {
|
||||
f.sampleLAB = (f.sampleLAB * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input = f.sampleLAB;
|
||||
f.sampleLBB = (f.sampleLBB * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input = f.sampleLBB;
|
||||
f.sampleLCB = (f.sampleLCB * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input = f.sampleLCB;
|
||||
f.sampleLDB = (f.sampleLDB * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input = f.sampleLDB;
|
||||
f.sampleLF = (f.sampleLF * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input = f.sampleLF;
|
||||
}
|
||||
f.sampleLG = (f.sampleLG * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input = (f.sampleLG * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
break;
|
||||
case HIGHPASS:
|
||||
if (f.flip) {
|
||||
f.sampleLAA = (f.sampleLAA * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input -= f.sampleLAA;
|
||||
f.sampleLBA = (f.sampleLBA * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input -= f.sampleLBA;
|
||||
f.sampleLCA = (f.sampleLCA * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input -= f.sampleLCA;
|
||||
f.sampleLDA = (f.sampleLDA * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input -= f.sampleLDA;
|
||||
} else {
|
||||
f.sampleLAB = (f.sampleLAB * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input -= f.sampleLAB;
|
||||
f.sampleLBB = (f.sampleLBB * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input -= f.sampleLBB;
|
||||
f.sampleLCB = (f.sampleLCB * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input -= f.sampleLCB;
|
||||
f.sampleLDB = (f.sampleLDB * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input -= f.sampleLDB;
|
||||
}
|
||||
f.sampleLE = (f.sampleLE * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input -= f.sampleLE;
|
||||
f.sampleLF = (f.sampleLF * (1.0 - iir_amt)) + (input * iir_amt);
|
||||
input -= f.sampleLF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
audio[i] = input;
|
||||
}
|
||||
}
|
||||
|
||||
enum spliteq_mode {
|
||||
CASCADE_SUM,
|
||||
LINKWITZ_RILEY
|
||||
};
|
||||
|
||||
struct spliteq {
|
||||
aw_filter lp_l, lp_r, hp_l, hp_r; // lowpass and highpass filters
|
||||
|
||||
// cascaded filters
|
||||
cascade_filter bass_l, bass_r;
|
||||
cascade_filter treble_l, treble_r;
|
||||
|
||||
// butterworth biquads
|
||||
butterworth bass1_l, bass2_l, bass1_r, bass2_r;
|
||||
// Mid: two cascaded highpass THEN two cascaded lowpass per channel
|
||||
butterworth mid_hp1_l, mid_hp2_l, mid_lp1_l, mid_lp2_l;
|
||||
butterworth mid_hp1_r, mid_hp2_r, mid_lp1_r, mid_lp2_r;
|
||||
// Treble: two cascaded highpass filters per channel
|
||||
butterworth treble1_l, treble2_l, treble1_r, treble2_r;
|
||||
|
||||
double low_mid_crossover = 150.0; // Hz
|
||||
double mid_high_crossover = 1700.0; // Hz
|
||||
|
||||
// adjusted crossover frequencies for cascade filters
|
||||
double low_mid_crossover_adj = 150.0;
|
||||
double mid_high_crossover_adj = 1700.0;
|
||||
|
||||
double samplerate = 48000.0;
|
||||
|
||||
float bass_gain = 1.0f;
|
||||
float mid_gain = 1.0f;
|
||||
float treble_gain = 1.0f;
|
||||
|
||||
// adjusted gain for cascade filters
|
||||
float bass_gain_adj = 1.0f;
|
||||
float mid_gain_adj = 1.0f;
|
||||
float treble_gain_adj = 1.0f;
|
||||
|
||||
spliteq_mode current_mode = CASCADE_SUM;
|
||||
spliteq_mode target_mode = CASCADE_SUM;
|
||||
bool transitioning = false;
|
||||
smoother transition_smoother;
|
||||
};
|
||||
|
||||
inline void spliteq_init(spliteq& eq, double samplerate, double low_mid_crossover,
|
||||
double mid_high_crossover)
|
||||
{
|
||||
low_mid_crossover /= 2.0;
|
||||
mid_high_crossover /= 2.0;
|
||||
|
||||
eq.samplerate = samplerate;
|
||||
|
||||
eq.low_mid_crossover = low_mid_crossover;
|
||||
eq.mid_high_crossover = mid_high_crossover;
|
||||
eq.low_mid_crossover_adj = low_mid_crossover;
|
||||
eq.mid_high_crossover_adj = mid_high_crossover;
|
||||
|
||||
// initialize lp/hp filters
|
||||
aw_filter_init(eq.lp_l, LOWPASS, 1.0f, samplerate);
|
||||
aw_filter_init(eq.lp_r, LOWPASS, 1.0f, samplerate);
|
||||
aw_filter_init(eq.hp_l, HIGHPASS, 0.0f, samplerate);
|
||||
aw_filter_init(eq.hp_r, HIGHPASS, 0.0f, samplerate);
|
||||
|
||||
// init cascade filters
|
||||
cascade_filter_setup(eq.bass_l, LOWPASS, 2, low_mid_crossover, samplerate);
|
||||
cascade_filter_setup(eq.bass_r, LOWPASS, 2, low_mid_crossover, samplerate);
|
||||
cascade_filter_setup(eq.treble_l, HIGHPASS, 2, mid_high_crossover, samplerate);
|
||||
cascade_filter_setup(eq.treble_r, HIGHPASS, 2, mid_high_crossover, samplerate);
|
||||
|
||||
// init butterworth filters
|
||||
// bass filters
|
||||
eq.bass1_l.type = LOWPASS;
|
||||
eq.bass1_l.cutoff = low_mid_crossover;
|
||||
eq.bass1_l.x1 = eq.bass1_l.x2 = eq.bass1_l.y1 = eq.bass1_l.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.bass1_l, samplerate);
|
||||
|
||||
eq.bass2_l.type = LOWPASS;
|
||||
eq.bass2_l.cutoff = low_mid_crossover;
|
||||
eq.bass2_l.x1 = eq.bass2_l.x2 = eq.bass2_l.y1 = eq.bass2_l.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.bass2_l, samplerate);
|
||||
|
||||
eq.bass1_r.type = LOWPASS;
|
||||
eq.bass1_r.cutoff = low_mid_crossover;
|
||||
eq.bass1_r.x1 = eq.bass1_r.x2 = eq.bass1_r.y1 = eq.bass1_r.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.bass1_r, samplerate);
|
||||
|
||||
eq.bass2_r.type = LOWPASS;
|
||||
eq.bass2_r.cutoff = low_mid_crossover;
|
||||
eq.bass2_r.x1 = eq.bass2_r.x2 = eq.bass2_r.y1 = eq.bass2_r.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.bass2_r, samplerate);
|
||||
|
||||
// mid filters (HPF x2, then LPF x2)
|
||||
eq.mid_hp1_l.type = HIGHPASS;
|
||||
eq.mid_hp1_l.cutoff = low_mid_crossover;
|
||||
eq.mid_hp1_l.x1 = eq.mid_hp1_l.x2 = eq.mid_hp1_l.y1 = eq.mid_hp1_l.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.mid_hp1_l, samplerate);
|
||||
|
||||
eq.mid_hp2_l.type = HIGHPASS;
|
||||
eq.mid_hp2_l.cutoff = low_mid_crossover;
|
||||
eq.mid_hp2_l.x1 = eq.mid_hp2_l.x2 = eq.mid_hp2_l.y1 = eq.mid_hp2_l.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.mid_hp2_l, samplerate);
|
||||
|
||||
eq.mid_lp1_l.type = LOWPASS;
|
||||
eq.mid_lp1_l.cutoff = mid_high_crossover;
|
||||
eq.mid_lp1_l.x1 = eq.mid_lp1_l.x2 = eq.mid_lp1_l.y1 = eq.mid_lp1_l.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.mid_lp1_l, samplerate);
|
||||
|
||||
eq.mid_lp2_l.type = LOWPASS;
|
||||
eq.mid_lp2_l.cutoff = mid_high_crossover;
|
||||
eq.mid_lp2_l.x1 = eq.mid_lp2_l.x2 = eq.mid_lp2_l.y1 = eq.mid_lp2_l.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.mid_lp2_l, samplerate);
|
||||
|
||||
eq.mid_hp1_r.type = HIGHPASS;
|
||||
eq.mid_hp1_r.cutoff = low_mid_crossover;
|
||||
eq.mid_hp1_r.x1 = eq.mid_hp1_r.x2 = eq.mid_hp1_r.y1 = eq.mid_hp1_r.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.mid_hp1_r, samplerate);
|
||||
|
||||
eq.mid_hp2_r.type = HIGHPASS;
|
||||
eq.mid_hp2_r.cutoff = low_mid_crossover;
|
||||
eq.mid_hp2_r.x1 = eq.mid_hp2_r.x2 = eq.mid_hp2_r.y1 = eq.mid_hp2_r.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.mid_hp2_r, samplerate);
|
||||
|
||||
eq.mid_lp1_r.type = LOWPASS;
|
||||
eq.mid_lp1_r.cutoff = mid_high_crossover;
|
||||
eq.mid_lp1_r.x1 = eq.mid_lp1_r.x2 = eq.mid_lp1_r.y1 = eq.mid_lp1_r.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.mid_lp1_r, samplerate);
|
||||
|
||||
eq.mid_lp2_r.type = LOWPASS;
|
||||
eq.mid_lp2_r.cutoff = mid_high_crossover;
|
||||
eq.mid_lp2_r.x1 = eq.mid_lp2_r.x2 = eq.mid_lp2_r.y1 = eq.mid_lp2_r.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.mid_lp2_r, samplerate);
|
||||
|
||||
// treble filters
|
||||
eq.treble1_l.type = HIGHPASS;
|
||||
eq.treble1_l.cutoff = mid_high_crossover;
|
||||
eq.treble1_l.x1 = eq.treble1_l.x2 = eq.treble1_l.y1 = eq.treble1_l.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.treble1_l, samplerate);
|
||||
|
||||
eq.treble2_l.type = HIGHPASS;
|
||||
eq.treble2_l.cutoff = mid_high_crossover;
|
||||
eq.treble2_l.x1 = eq.treble2_l.x2 = eq.treble2_l.y1 = eq.treble2_l.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.treble2_l, samplerate);
|
||||
|
||||
eq.treble1_r.type = HIGHPASS;
|
||||
eq.treble1_r.cutoff = mid_high_crossover;
|
||||
eq.treble1_r.x1 = eq.treble1_r.x2 = eq.treble1_r.y1 = eq.treble1_r.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.treble1_r, samplerate);
|
||||
|
||||
eq.treble2_r.type = HIGHPASS;
|
||||
eq.treble2_r.cutoff = mid_high_crossover;
|
||||
eq.treble2_r.x1 = eq.treble2_r.x2 = eq.treble2_r.y1 = eq.treble2_r.y2 = 0.0;
|
||||
butterworth_biquad_coeffs(eq.treble2_r, samplerate);
|
||||
|
||||
smoother_init(eq.transition_smoother, samplerate, 50.0f, 1.0f);
|
||||
}
|
||||
|
||||
inline void spliteq_set_mode(spliteq& eq, spliteq_mode mode)
|
||||
{
|
||||
if (eq.target_mode != mode) {
|
||||
eq.target_mode = mode;
|
||||
eq.transitioning = true;
|
||||
smoother_set_target(eq.transition_smoother, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
inline void linkwitz_riley_process(spliteq& eq, float**& audio, int& i)
|
||||
{
|
||||
// left channel
|
||||
double input_l = audio[0][i];
|
||||
// bass
|
||||
double bass_l = butterworth_biquad_process(eq.bass1_l, input_l);
|
||||
bass_l = butterworth_biquad_process(eq.bass2_l, bass_l);
|
||||
// mid
|
||||
double mid_l = butterworth_biquad_process(eq.mid_hp1_l, input_l);
|
||||
mid_l = butterworth_biquad_process(eq.mid_hp2_l, mid_l);
|
||||
mid_l = butterworth_biquad_process(eq.mid_lp1_l, mid_l);
|
||||
mid_l = butterworth_biquad_process(eq.mid_lp2_l, mid_l);
|
||||
// treble
|
||||
double treble_l = butterworth_biquad_process(eq.treble1_l, input_l);
|
||||
treble_l = butterworth_biquad_process(eq.treble2_l, treble_l);
|
||||
|
||||
// apply gains
|
||||
bass_l *= eq.bass_gain;
|
||||
mid_l *= eq.mid_gain;
|
||||
treble_l *= eq.treble_gain;
|
||||
|
||||
// sum bands
|
||||
audio[0][i] = bass_l + mid_l + treble_l;
|
||||
|
||||
// right channel
|
||||
double input_r = audio[1][i];
|
||||
double bass_r = butterworth_biquad_process(eq.bass1_r, input_r);
|
||||
bass_r = butterworth_biquad_process(eq.bass2_r, bass_r);
|
||||
|
||||
double mid_r = butterworth_biquad_process(eq.mid_hp1_r, input_r);
|
||||
mid_r = butterworth_biquad_process(eq.mid_hp2_r, mid_r);
|
||||
mid_r = butterworth_biquad_process(eq.mid_lp1_r, mid_r);
|
||||
mid_r = butterworth_biquad_process(eq.mid_lp2_r, mid_r);
|
||||
|
||||
double treble_r = butterworth_biquad_process(eq.treble1_r, input_r);
|
||||
treble_r = butterworth_biquad_process(eq.treble2_r, treble_r);
|
||||
|
||||
bass_r *= eq.bass_gain;
|
||||
mid_r *= eq.mid_gain;
|
||||
treble_r *= eq.treble_gain;
|
||||
|
||||
audio[1][i] = bass_r + mid_r + treble_r;
|
||||
}
|
||||
|
||||
inline void cascade_sum_process(spliteq& eq, float**& audio, int& i)
|
||||
{
|
||||
double input_l = audio[0][i];
|
||||
double input_r = audio[1][i];
|
||||
|
||||
double bass_l = cascade_filter_process(eq.bass_l, input_l);
|
||||
double bass_r = cascade_filter_process(eq.bass_r, input_r);
|
||||
|
||||
double treble_l = cascade_filter_process(eq.treble_l, input_l);
|
||||
double treble_r = cascade_filter_process(eq.treble_r, input_r);
|
||||
|
||||
double mid_l = input_l - bass_l - treble_l;
|
||||
double mid_r = input_r - bass_r - treble_r;
|
||||
|
||||
// apply gains
|
||||
bass_l *= eq.bass_gain_adj;
|
||||
bass_r *= eq.bass_gain_adj;
|
||||
mid_l *= eq.mid_gain_adj;
|
||||
mid_r *= eq.mid_gain_adj;
|
||||
treble_l *= eq.treble_gain_adj;
|
||||
treble_r *= eq.treble_gain_adj;
|
||||
|
||||
// sum bands
|
||||
audio[0][i] = bass_l + mid_l + treble_l;
|
||||
audio[1][i] = bass_r + mid_r + treble_r;
|
||||
}
|
||||
|
||||
inline void spliteq_process_block(spliteq& eq, float** audio, int frames)
|
||||
{
|
||||
// highpass filters
|
||||
aw_filter_process_block(eq.hp_l, audio[0], frames);
|
||||
aw_filter_process_block(eq.hp_r, audio[1], frames);
|
||||
|
||||
for (int i = 0; i < frames; i++) {
|
||||
float smooth_gain = 1.0f;
|
||||
|
||||
if (eq.transitioning) {
|
||||
smooth_gain = smoother_process_sample(eq.transition_smoother);
|
||||
|
||||
if (smooth_gain == 0.f) {
|
||||
smoother_set_target(eq.transition_smoother, 1.0);
|
||||
eq.current_mode = eq.target_mode;
|
||||
} else if (smooth_gain == 1.f) {
|
||||
eq.transitioning = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (eq.current_mode == LINKWITZ_RILEY) {
|
||||
linkwitz_riley_process(eq, audio, i);
|
||||
} else if (eq.current_mode == CASCADE_SUM) {
|
||||
cascade_sum_process(eq, audio, i);
|
||||
}
|
||||
|
||||
audio[0][i] *= smooth_gain;
|
||||
audio[1][i] *= smooth_gain;
|
||||
}
|
||||
|
||||
// lowpass filters
|
||||
aw_filter_process_block(eq.lp_l, audio[0], frames);
|
||||
aw_filter_process_block(eq.lp_r, audio[1], frames);
|
||||
}
|
||||
|
||||
inline void spliteq_update(spliteq& eq, double hp_freq, double lp_freq,
|
||||
double low_mid_crossover, double mid_high_crossover,
|
||||
double bass_gain, double mid_gain, double treble_gain)
|
||||
{
|
||||
low_mid_crossover /= 2.0;
|
||||
mid_high_crossover /= 2.0;
|
||||
|
||||
eq.bass_gain = db_2_lin(bass_gain);
|
||||
eq.mid_gain = db_2_lin(mid_gain);
|
||||
eq.treble_gain = db_2_lin(treble_gain);
|
||||
|
||||
if (bass_gain > 0.f) {
|
||||
eq.bass_gain = eq.bass_gain_adj = db_2_lin(bass_gain * 0.85f);
|
||||
eq.low_mid_crossover_adj = low_mid_crossover;
|
||||
} else {
|
||||
eq.bass_gain_adj = db_2_lin(bass_gain);
|
||||
eq.low_mid_crossover_adj = low_mid_crossover * 2.0;
|
||||
}
|
||||
|
||||
if (mid_gain > 0.0f) eq.mid_gain_adj = db_2_lin(mid_gain * 0.85f);
|
||||
else eq.mid_gain_adj = db_2_lin(mid_gain * 0.74f);
|
||||
|
||||
if (treble_gain > 0.f) {
|
||||
eq.treble_gain_adj = db_2_lin(treble_gain * 1.1f);
|
||||
eq.mid_high_crossover_adj = mid_high_crossover;
|
||||
} else {
|
||||
eq.treble_gain_adj = db_2_lin(treble_gain);
|
||||
eq.mid_high_crossover_adj = mid_high_crossover / 2.0;
|
||||
}
|
||||
|
||||
eq.low_mid_crossover = low_mid_crossover;
|
||||
eq.mid_high_crossover = mid_high_crossover;
|
||||
|
||||
eq.hp_l.amount = hp_freq;
|
||||
eq.hp_r.amount = hp_freq;
|
||||
eq.lp_l.amount = lp_freq;
|
||||
eq.lp_r.amount = lp_freq;
|
||||
|
||||
cascade_filter_setup(eq.bass_l, LOWPASS, 2, eq.low_mid_crossover_adj, eq.samplerate);
|
||||
cascade_filter_setup(eq.bass_r, LOWPASS, 2, eq.low_mid_crossover_adj, eq.samplerate);
|
||||
cascade_filter_setup(eq.treble_l, HIGHPASS, 2, eq.mid_high_crossover_adj,
|
||||
eq.samplerate);
|
||||
cascade_filter_setup(eq.treble_r, HIGHPASS, 2, eq.mid_high_crossover_adj,
|
||||
eq.samplerate);
|
||||
|
||||
eq.bass1_l.cutoff = low_mid_crossover;
|
||||
butterworth_biquad_coeffs(eq.bass1_l, eq.samplerate);
|
||||
eq.bass2_l.cutoff = low_mid_crossover;
|
||||
butterworth_biquad_coeffs(eq.bass2_l, eq.samplerate);
|
||||
eq.bass1_r.cutoff = low_mid_crossover;
|
||||
butterworth_biquad_coeffs(eq.bass1_r, eq.samplerate);
|
||||
eq.bass2_r.cutoff = low_mid_crossover;
|
||||
butterworth_biquad_coeffs(eq.bass2_r, eq.samplerate);
|
||||
|
||||
eq.mid_hp1_l.cutoff = low_mid_crossover;
|
||||
butterworth_biquad_coeffs(eq.mid_hp1_l, eq.samplerate);
|
||||
eq.mid_hp2_l.cutoff = low_mid_crossover;
|
||||
butterworth_biquad_coeffs(eq.mid_hp2_l, eq.samplerate);
|
||||
eq.mid_lp1_l.cutoff = mid_high_crossover;
|
||||
butterworth_biquad_coeffs(eq.mid_lp1_l, eq.samplerate);
|
||||
eq.mid_lp2_l.cutoff = mid_high_crossover;
|
||||
butterworth_biquad_coeffs(eq.mid_lp2_l, eq.samplerate);
|
||||
|
||||
eq.mid_hp1_r.cutoff = low_mid_crossover;
|
||||
butterworth_biquad_coeffs(eq.mid_hp1_r, eq.samplerate);
|
||||
eq.mid_hp2_r.cutoff = low_mid_crossover;
|
||||
butterworth_biquad_coeffs(eq.mid_hp2_r, eq.samplerate);
|
||||
eq.mid_lp1_r.cutoff = mid_high_crossover;
|
||||
butterworth_biquad_coeffs(eq.mid_lp1_r, eq.samplerate);
|
||||
eq.mid_lp2_r.cutoff = mid_high_crossover;
|
||||
butterworth_biquad_coeffs(eq.mid_lp2_r, eq.samplerate);
|
||||
|
||||
eq.treble1_l.cutoff = mid_high_crossover;
|
||||
butterworth_biquad_coeffs(eq.treble1_l, eq.samplerate);
|
||||
eq.treble2_l.cutoff = mid_high_crossover;
|
||||
butterworth_biquad_coeffs(eq.treble2_l, eq.samplerate);
|
||||
eq.treble1_r.cutoff = mid_high_crossover;
|
||||
butterworth_biquad_coeffs(eq.treble1_r, eq.samplerate);
|
||||
eq.treble2_r.cutoff = mid_high_crossover;
|
||||
butterworth_biquad_coeffs(eq.treble2_r, eq.samplerate);
|
||||
}
|
||||
|
||||
inline void spliteq_update(spliteq& eq, double bass_gain, double mid_gain,
|
||||
double treble_gain)
|
||||
{
|
||||
trnr::spliteq_update(eq, eq.hp_l.amount, eq.lp_l.amount, eq.low_mid_crossover * 2.0,
|
||||
eq.mid_high_crossover * 2.0, bass_gain, mid_gain, treble_gain);
|
||||
}
|
||||
} // namespace trnr
|
||||
@@ -1,280 +0,0 @@
|
||||
#pragma once
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <array>
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
|
||||
namespace trnr {
|
||||
template <typename t_sample>
|
||||
// Bandpass filter based on YBandpass by Chris Johnson
|
||||
class ybandpass {
|
||||
public:
|
||||
ybandpass(double _samplerate)
|
||||
: samplerate {_samplerate}
|
||||
, A {0.1f}
|
||||
, B {1.0f}
|
||||
, C {0.0f}
|
||||
, D {0.1f}
|
||||
, E {0.9f}
|
||||
, F {1.0f}
|
||||
, fpdL {0}
|
||||
, fpdR {0}
|
||||
, biquad {0}
|
||||
{
|
||||
for (int x = 0; x < biq_total; x++) { biquad[x] = 0.0; }
|
||||
powFactorA = 1.0;
|
||||
powFactorB = 1.0;
|
||||
inTrimA = 0.1;
|
||||
inTrimB = 0.1;
|
||||
outTrimA = 1.0;
|
||||
outTrimB = 1.0;
|
||||
for (int x = 0; x < fix_total; x++) {
|
||||
fixA[x] = 0.0;
|
||||
fixB[x] = 0.0;
|
||||
}
|
||||
|
||||
fpdL = 1.0;
|
||||
while (fpdL < 16386) fpdL = rand() * UINT32_MAX;
|
||||
fpdR = 1.0;
|
||||
while (fpdR < 16386) fpdR = rand() * UINT32_MAX;
|
||||
}
|
||||
|
||||
void set_samplerate(double _samplerate) { samplerate = _samplerate; }
|
||||
|
||||
void set_drive(float value) { A = value * 0.9 + 0.1; }
|
||||
|
||||
void set_frequency(float value) { B = value; }
|
||||
|
||||
void set_resonance(float value) { C = value; }
|
||||
|
||||
void set_edge(float value) { D = value; }
|
||||
|
||||
void set_output(float value) { E = value; }
|
||||
|
||||
void set_mix(float value) { F = value; }
|
||||
|
||||
void processblock(t_sample** inputs, t_sample** outputs, int blockSize)
|
||||
{
|
||||
t_sample* in1 = inputs[0];
|
||||
t_sample* in2 = inputs[1];
|
||||
t_sample* out1 = outputs[0];
|
||||
t_sample* out2 = outputs[1];
|
||||
|
||||
int inFramesToProcess = blockSize;
|
||||
double overallscale = 1.0;
|
||||
overallscale /= 44100.0;
|
||||
overallscale *= samplerate;
|
||||
|
||||
inTrimA = inTrimB;
|
||||
inTrimB = A * 10.0;
|
||||
|
||||
biquad[biq_freq] = pow(B, 3) * 20000.0;
|
||||
if (biquad[biq_freq] < 15.0) biquad[biq_freq] = 15.0;
|
||||
biquad[biq_freq] /= samplerate;
|
||||
biquad[biq_reso] = (pow(C, 2) * 15.0) + 0.5571;
|
||||
biquad[biq_aA0] = biquad[biq_aB0];
|
||||
// biquad[biq_aA1] = biquad[biq_aB1];
|
||||
biquad[biq_aA2] = biquad[biq_aB2];
|
||||
biquad[biq_bA1] = biquad[biq_bB1];
|
||||
biquad[biq_bA2] = biquad[biq_bB2];
|
||||
// previous run through the buffer is still in the filter, so we move it
|
||||
// to the A section and now it's the new starting point.
|
||||
double K = tan(M_PI * biquad[biq_freq]);
|
||||
double norm = 1.0 / (1.0 + K / biquad[biq_reso] + K * K);
|
||||
biquad[biq_aB0] = K / biquad[biq_reso] * norm;
|
||||
// biquad[biq_aB1] = 0.0; //bandpass can simplify the biquad kernel: leave out this multiply
|
||||
biquad[biq_aB2] = -biquad[biq_aB0];
|
||||
biquad[biq_bB1] = 2.0 * (K * K - 1.0) * norm;
|
||||
biquad[biq_bB2] = (1.0 - K / biquad[biq_reso] + K * K) * norm;
|
||||
// for the coefficient-interpolated biquad filter
|
||||
|
||||
powFactorA = powFactorB;
|
||||
powFactorB = pow(D + 0.9, 4);
|
||||
|
||||
// 1.0 == target neutral
|
||||
|
||||
outTrimA = outTrimB;
|
||||
outTrimB = E;
|
||||
|
||||
double wet = F;
|
||||
|
||||
fixA[fix_freq] = fixB[fix_freq] = 20000.0 / samplerate;
|
||||
fixA[fix_reso] = fixB[fix_reso] = 0.7071; // butterworth Q
|
||||
|
||||
K = tan(M_PI * fixA[fix_freq]);
|
||||
norm = 1.0 / (1.0 + K / fixA[fix_reso] + K * K);
|
||||
fixA[fix_a0] = fixB[fix_a0] = K * K * norm;
|
||||
fixA[fix_a1] = fixB[fix_a1] = 2.0 * fixA[fix_a0];
|
||||
fixA[fix_a2] = fixB[fix_a2] = fixA[fix_a0];
|
||||
fixA[fix_b1] = fixB[fix_b1] = 2.0 * (K * K - 1.0) * norm;
|
||||
fixA[fix_b2] = fixB[fix_b2] = (1.0 - K / fixA[fix_reso] + K * K) * norm;
|
||||
// for the fixed-position biquad filter
|
||||
|
||||
for (int s = 0; s < blockSize; s++) {
|
||||
double inputSampleL = *in1;
|
||||
double inputSampleR = *in2;
|
||||
if (fabs(inputSampleL) < 1.18e-23) inputSampleL = fpdL * 1.18e-17;
|
||||
if (fabs(inputSampleR) < 1.18e-23) inputSampleR = fpdR * 1.18e-17;
|
||||
double drySampleL = inputSampleL;
|
||||
double drySampleR = inputSampleR;
|
||||
|
||||
double temp = (double)s / inFramesToProcess;
|
||||
biquad[biq_a0] = (biquad[biq_aA0] * temp) + (biquad[biq_aB0] * (1.0 - temp));
|
||||
// biquad[biq_a1] = (biquad[biq_aA1]*temp)+(biquad[biq_aB1]*(1.0-temp));
|
||||
biquad[biq_a2] = (biquad[biq_aA2] * temp) + (biquad[biq_aB2] * (1.0 - temp));
|
||||
biquad[biq_b1] = (biquad[biq_bA1] * temp) + (biquad[biq_bB1] * (1.0 - temp));
|
||||
biquad[biq_b2] = (biquad[biq_bA2] * temp) + (biquad[biq_bB2] * (1.0 - temp));
|
||||
// this is the interpolation code for the biquad
|
||||
double powFactor = (powFactorA * temp) + (powFactorB * (1.0 - temp));
|
||||
double inTrim = (inTrimA * temp) + (inTrimB * (1.0 - temp));
|
||||
double outTrim = (outTrimA * temp) + (outTrimB * (1.0 - temp));
|
||||
|
||||
inputSampleL *= inTrim;
|
||||
inputSampleR *= inTrim;
|
||||
|
||||
temp = (inputSampleL * fixA[fix_a0]) + fixA[fix_sL1];
|
||||
fixA[fix_sL1] = (inputSampleL * fixA[fix_a1]) - (temp * fixA[fix_b1]) + fixA[fix_sL2];
|
||||
fixA[fix_sL2] = (inputSampleL * fixA[fix_a2]) - (temp * fixA[fix_b2]);
|
||||
inputSampleL = temp; // fixed biquad filtering ultrasonics
|
||||
temp = (inputSampleR * fixA[fix_a0]) + fixA[fix_sR1];
|
||||
fixA[fix_sR1] = (inputSampleR * fixA[fix_a1]) - (temp * fixA[fix_b1]) + fixA[fix_sR2];
|
||||
fixA[fix_sR2] = (inputSampleR * fixA[fix_a2]) - (temp * fixA[fix_b2]);
|
||||
inputSampleR = temp; // fixed biquad filtering ultrasonics
|
||||
|
||||
// encode/decode courtesy of torridgristle under the MIT license
|
||||
if (inputSampleL > 1.0) inputSampleL = 1.0;
|
||||
else if (inputSampleL > 0.0) inputSampleL = 1.0 - pow(1.0 - inputSampleL, powFactor);
|
||||
if (inputSampleL < -1.0) inputSampleL = -1.0;
|
||||
else if (inputSampleL < 0.0) inputSampleL = -1.0 + pow(1.0 + inputSampleL, powFactor);
|
||||
if (inputSampleR > 1.0) inputSampleR = 1.0;
|
||||
else if (inputSampleR > 0.0) inputSampleR = 1.0 - pow(1.0 - inputSampleR, powFactor);
|
||||
if (inputSampleR < -1.0) inputSampleR = -1.0;
|
||||
else if (inputSampleR < 0.0) inputSampleR = -1.0 + pow(1.0 + inputSampleR, powFactor);
|
||||
|
||||
temp = (inputSampleL * biquad[biq_a0]) + biquad[biq_sL1];
|
||||
biquad[biq_sL1] = -(temp * biquad[biq_b1]) + biquad[biq_sL2];
|
||||
biquad[biq_sL2] = (inputSampleL * biquad[biq_a2]) - (temp * biquad[biq_b2]);
|
||||
inputSampleL = temp; // coefficient interpolating biquad filter
|
||||
temp = (inputSampleR * biquad[biq_a0]) + biquad[biq_sR1];
|
||||
biquad[biq_sR1] = -(temp * biquad[biq_b1]) + biquad[biq_sR2];
|
||||
biquad[biq_sR2] = (inputSampleR * biquad[biq_a2]) - (temp * biquad[biq_b2]);
|
||||
inputSampleR = temp; // coefficient interpolating biquad filter
|
||||
|
||||
// encode/decode courtesy of torridgristle under the MIT license
|
||||
if (inputSampleL > 1.0) inputSampleL = 1.0;
|
||||
else if (inputSampleL > 0.0) inputSampleL = 1.0 - pow(1.0 - inputSampleL, (1.0 / powFactor));
|
||||
if (inputSampleL < -1.0) inputSampleL = -1.0;
|
||||
else if (inputSampleL < 0.0) inputSampleL = -1.0 + pow(1.0 + inputSampleL, (1.0 / powFactor));
|
||||
if (inputSampleR > 1.0) inputSampleR = 1.0;
|
||||
else if (inputSampleR > 0.0) inputSampleR = 1.0 - pow(1.0 - inputSampleR, (1.0 / powFactor));
|
||||
if (inputSampleR < -1.0) inputSampleR = -1.0;
|
||||
else if (inputSampleR < 0.0) inputSampleR = -1.0 + pow(1.0 + inputSampleR, (1.0 / powFactor));
|
||||
|
||||
inputSampleL *= outTrim;
|
||||
inputSampleR *= outTrim;
|
||||
|
||||
temp = (inputSampleL * fixB[fix_a0]) + fixB[fix_sL1];
|
||||
fixB[fix_sL1] = (inputSampleL * fixB[fix_a1]) - (temp * fixB[fix_b1]) + fixB[fix_sL2];
|
||||
fixB[fix_sL2] = (inputSampleL * fixB[fix_a2]) - (temp * fixB[fix_b2]);
|
||||
inputSampleL = temp; // fixed biquad filtering ultrasonics
|
||||
temp = (inputSampleR * fixB[fix_a0]) + fixB[fix_sR1];
|
||||
fixB[fix_sR1] = (inputSampleR * fixB[fix_a1]) - (temp * fixB[fix_b1]) + fixB[fix_sR2];
|
||||
fixB[fix_sR2] = (inputSampleR * fixB[fix_a2]) - (temp * fixB[fix_b2]);
|
||||
inputSampleR = temp; // fixed biquad filtering ultrasonics
|
||||
|
||||
if (wet < 1.0) {
|
||||
inputSampleL = (inputSampleL * wet) + (drySampleL * (1.0 - wet));
|
||||
inputSampleR = (inputSampleR * wet) + (drySampleR * (1.0 - wet));
|
||||
}
|
||||
|
||||
// begin 32 bit stereo floating point dither
|
||||
int expon;
|
||||
frexpf((float)inputSampleL, &expon);
|
||||
fpdL ^= fpdL << 13;
|
||||
fpdL ^= fpdL >> 17;
|
||||
fpdL ^= fpdL << 5;
|
||||
inputSampleL += ((double(fpdL) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
|
||||
frexpf((float)inputSampleR, &expon);
|
||||
fpdR ^= fpdR << 13;
|
||||
fpdR ^= fpdR >> 17;
|
||||
fpdR ^= fpdR << 5;
|
||||
inputSampleR += ((double(fpdR) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
|
||||
// end 32 bit stereo floating point dither
|
||||
|
||||
*out1 = inputSampleL;
|
||||
*out2 = inputSampleR;
|
||||
|
||||
in1++;
|
||||
in2++;
|
||||
out1++;
|
||||
out2++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate;
|
||||
|
||||
enum {
|
||||
biq_freq,
|
||||
biq_reso,
|
||||
biq_a0,
|
||||
biq_a1,
|
||||
biq_a2,
|
||||
biq_b1,
|
||||
biq_b2,
|
||||
biq_aA0,
|
||||
biq_aA1,
|
||||
biq_aA2,
|
||||
biq_bA1,
|
||||
biq_bA2,
|
||||
biq_aB0,
|
||||
biq_aB1,
|
||||
biq_aB2,
|
||||
biq_bB1,
|
||||
biq_bB2,
|
||||
biq_sL1,
|
||||
biq_sL2,
|
||||
biq_sR1,
|
||||
biq_sR2,
|
||||
biq_total
|
||||
}; // coefficient interpolating biquad filter, stereo
|
||||
|
||||
std::array<double, biq_total> biquad;
|
||||
|
||||
double powFactorA;
|
||||
double powFactorB;
|
||||
double inTrimA;
|
||||
double inTrimB;
|
||||
double outTrimA;
|
||||
double outTrimB;
|
||||
|
||||
enum {
|
||||
fix_freq,
|
||||
fix_reso,
|
||||
fix_a0,
|
||||
fix_a1,
|
||||
fix_a2,
|
||||
fix_b1,
|
||||
fix_b2,
|
||||
fix_sL1,
|
||||
fix_sL2,
|
||||
fix_sR1,
|
||||
fix_sR2,
|
||||
fix_total
|
||||
}; // fixed frequency biquad filter for ultrasonics, stereo
|
||||
|
||||
std::array<double, fix_total> fixA;
|
||||
std::array<double, fix_total> fixB;
|
||||
|
||||
uint32_t fpdL;
|
||||
uint32_t fpdR;
|
||||
// default stuff
|
||||
|
||||
float A;
|
||||
float B;
|
||||
float C;
|
||||
float D;
|
||||
float E;
|
||||
float F; // parameters. Always 0-1, and we scale/alter them elsewhere.
|
||||
};
|
||||
} // namespace trnr
|
||||
@@ -1,280 +0,0 @@
|
||||
#pragma once
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <array>
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
|
||||
namespace trnr {
|
||||
template <typename t_sample>
|
||||
// Highpass filter based on YHighpass by Chris Johnson
|
||||
class yhighpass {
|
||||
public:
|
||||
yhighpass(double _samplerate)
|
||||
: samplerate {_samplerate}
|
||||
, A {0.1f}
|
||||
, B {1.0f}
|
||||
, C {0.0f}
|
||||
, D {0.1f}
|
||||
, E {0.9f}
|
||||
, F {1.0f}
|
||||
, fpdL {0}
|
||||
, fpdR {0}
|
||||
, biquad {0}
|
||||
{
|
||||
for (int x = 0; x < biq_total; x++) { biquad[x] = 0.0; }
|
||||
powFactorA = 1.0;
|
||||
powFactorB = 1.0;
|
||||
inTrimA = 0.1;
|
||||
inTrimB = 0.1;
|
||||
outTrimA = 1.0;
|
||||
outTrimB = 1.0;
|
||||
for (int x = 0; x < fix_total; x++) {
|
||||
fixA[x] = 0.0;
|
||||
fixB[x] = 0.0;
|
||||
}
|
||||
|
||||
fpdL = 1.0;
|
||||
while (fpdL < 16386) fpdL = rand() * UINT32_MAX;
|
||||
fpdR = 1.0;
|
||||
while (fpdR < 16386) fpdR = rand() * UINT32_MAX;
|
||||
}
|
||||
|
||||
void set_samplerate(double _samplerate) { samplerate = _samplerate; }
|
||||
|
||||
void set_drive(float value) { A = value * 0.9 + 0.1; }
|
||||
|
||||
void set_frequency(float value) { B = value; }
|
||||
|
||||
void set_resonance(float value) { C = value; }
|
||||
|
||||
void set_edge(float value) { D = value; }
|
||||
|
||||
void set_output(float value) { E = value; }
|
||||
|
||||
void set_mix(float value) { F = value; }
|
||||
|
||||
void processblock(t_sample** inputs, t_sample** outputs, int blockSize)
|
||||
{
|
||||
t_sample* in1 = inputs[0];
|
||||
t_sample* in2 = inputs[1];
|
||||
t_sample* out1 = outputs[0];
|
||||
t_sample* out2 = outputs[1];
|
||||
|
||||
int inFramesToProcess = blockSize;
|
||||
double overallscale = 1.0;
|
||||
overallscale /= 44100.0;
|
||||
overallscale *= samplerate;
|
||||
|
||||
inTrimA = inTrimB;
|
||||
inTrimB = A * 10.0;
|
||||
|
||||
biquad[biq_freq] = pow(B, 3) * 20000.0;
|
||||
if (biquad[biq_freq] < 15.0) biquad[biq_freq] = 15.0;
|
||||
biquad[biq_freq] /= samplerate;
|
||||
biquad[biq_reso] = (pow(C, 2) * 15.0) + 0.5571;
|
||||
biquad[biq_aA0] = biquad[biq_aB0];
|
||||
biquad[biq_aA1] = biquad[biq_aB1];
|
||||
biquad[biq_aA2] = biquad[biq_aB2];
|
||||
biquad[biq_bA1] = biquad[biq_bB1];
|
||||
biquad[biq_bA2] = biquad[biq_bB2];
|
||||
// previous run through the buffer is still in the filter, so we move it
|
||||
// to the A section and now it's the new starting point.
|
||||
double K = tan(M_PI * biquad[biq_freq]);
|
||||
double norm = 1.0 / (1.0 + K / biquad[biq_reso] + K * K);
|
||||
biquad[biq_aB0] = norm;
|
||||
biquad[biq_aB1] = -2.0 * biquad[biq_aB0];
|
||||
biquad[biq_aB2] = biquad[biq_aB0];
|
||||
biquad[biq_bB1] = 2.0 * (K * K - 1.0) * norm;
|
||||
biquad[biq_bB2] = (1.0 - K / biquad[biq_reso] + K * K) * norm;
|
||||
// for the coefficient-interpolated biquad filter
|
||||
|
||||
powFactorA = powFactorB;
|
||||
powFactorB = pow(D + 0.9, 4);
|
||||
|
||||
// 1.0 == target neutral
|
||||
|
||||
outTrimA = outTrimB;
|
||||
outTrimB = E;
|
||||
|
||||
double wet = F;
|
||||
|
||||
fixA[fix_freq] = fixB[fix_freq] = 20000.0 / samplerate;
|
||||
fixA[fix_reso] = fixB[fix_reso] = 0.7071; // butterworth Q
|
||||
|
||||
K = tan(M_PI * fixA[fix_freq]);
|
||||
norm = 1.0 / (1.0 + K / fixA[fix_reso] + K * K);
|
||||
fixA[fix_a0] = fixB[fix_a0] = K * K * norm;
|
||||
fixA[fix_a1] = fixB[fix_a1] = 2.0 * fixA[fix_a0];
|
||||
fixA[fix_a2] = fixB[fix_a2] = fixA[fix_a0];
|
||||
fixA[fix_b1] = fixB[fix_b1] = 2.0 * (K * K - 1.0) * norm;
|
||||
fixA[fix_b2] = fixB[fix_b2] = (1.0 - K / fixA[fix_reso] + K * K) * norm;
|
||||
// for the fixed-position biquad filter
|
||||
|
||||
for (int s = 0; s < blockSize; s++) {
|
||||
double inputSampleL = *in1;
|
||||
double inputSampleR = *in2;
|
||||
if (fabs(inputSampleL) < 1.18e-23) inputSampleL = fpdL * 1.18e-17;
|
||||
if (fabs(inputSampleR) < 1.18e-23) inputSampleR = fpdR * 1.18e-17;
|
||||
double drySampleL = inputSampleL;
|
||||
double drySampleR = inputSampleR;
|
||||
|
||||
double temp = (double)s / inFramesToProcess;
|
||||
biquad[biq_a0] = (biquad[biq_aA0] * temp) + (biquad[biq_aB0] * (1.0 - temp));
|
||||
biquad[biq_a1] = (biquad[biq_aA1] * temp) + (biquad[biq_aB1] * (1.0 - temp));
|
||||
biquad[biq_a2] = (biquad[biq_aA2] * temp) + (biquad[biq_aB2] * (1.0 - temp));
|
||||
biquad[biq_b1] = (biquad[biq_bA1] * temp) + (biquad[biq_bB1] * (1.0 - temp));
|
||||
biquad[biq_b2] = (biquad[biq_bA2] * temp) + (biquad[biq_bB2] * (1.0 - temp));
|
||||
// this is the interpolation code for the biquad
|
||||
double powFactor = (powFactorA * temp) + (powFactorB * (1.0 - temp));
|
||||
double inTrim = (inTrimA * temp) + (inTrimB * (1.0 - temp));
|
||||
double outTrim = (outTrimA * temp) + (outTrimB * (1.0 - temp));
|
||||
|
||||
inputSampleL *= inTrim;
|
||||
inputSampleR *= inTrim;
|
||||
|
||||
temp = (inputSampleL * fixA[fix_a0]) + fixA[fix_sL1];
|
||||
fixA[fix_sL1] = (inputSampleL * fixA[fix_a1]) - (temp * fixA[fix_b1]) + fixA[fix_sL2];
|
||||
fixA[fix_sL2] = (inputSampleL * fixA[fix_a2]) - (temp * fixA[fix_b2]);
|
||||
inputSampleL = temp; // fixed biquad filtering ultrasonics
|
||||
temp = (inputSampleR * fixA[fix_a0]) + fixA[fix_sR1];
|
||||
fixA[fix_sR1] = (inputSampleR * fixA[fix_a1]) - (temp * fixA[fix_b1]) + fixA[fix_sR2];
|
||||
fixA[fix_sR2] = (inputSampleR * fixA[fix_a2]) - (temp * fixA[fix_b2]);
|
||||
inputSampleR = temp; // fixed biquad filtering ultrasonics
|
||||
|
||||
// encode/decode courtesy of torridgristle under the MIT license
|
||||
if (inputSampleL > 1.0) inputSampleL = 1.0;
|
||||
else if (inputSampleL > 0.0) inputSampleL = 1.0 - pow(1.0 - inputSampleL, powFactor);
|
||||
if (inputSampleL < -1.0) inputSampleL = -1.0;
|
||||
else if (inputSampleL < 0.0) inputSampleL = -1.0 + pow(1.0 + inputSampleL, powFactor);
|
||||
if (inputSampleR > 1.0) inputSampleR = 1.0;
|
||||
else if (inputSampleR > 0.0) inputSampleR = 1.0 - pow(1.0 - inputSampleR, powFactor);
|
||||
if (inputSampleR < -1.0) inputSampleR = -1.0;
|
||||
else if (inputSampleR < 0.0) inputSampleR = -1.0 + pow(1.0 + inputSampleR, powFactor);
|
||||
|
||||
temp = (inputSampleL * biquad[biq_a0]) + biquad[biq_sL1];
|
||||
biquad[biq_sL1] = (inputSampleL * biquad[biq_a1]) - (temp * biquad[biq_b1]) + biquad[biq_sL2];
|
||||
biquad[biq_sL2] = (inputSampleL * biquad[biq_a2]) - (temp * biquad[biq_b2]);
|
||||
inputSampleL = temp; // coefficient interpolating biquad filter
|
||||
temp = (inputSampleR * biquad[biq_a0]) + biquad[biq_sR1];
|
||||
biquad[biq_sR1] = (inputSampleR * biquad[biq_a1]) - (temp * biquad[biq_b1]) + biquad[biq_sR2];
|
||||
biquad[biq_sR2] = (inputSampleR * biquad[biq_a2]) - (temp * biquad[biq_b2]);
|
||||
inputSampleR = temp; // coefficient interpolating biquad filter
|
||||
|
||||
// encode/decode courtesy of torridgristle under the MIT license
|
||||
if (inputSampleL > 1.0) inputSampleL = 1.0;
|
||||
else if (inputSampleL > 0.0) inputSampleL = 1.0 - pow(1.0 - inputSampleL, (1.0 / powFactor));
|
||||
if (inputSampleL < -1.0) inputSampleL = -1.0;
|
||||
else if (inputSampleL < 0.0) inputSampleL = -1.0 + pow(1.0 + inputSampleL, (1.0 / powFactor));
|
||||
if (inputSampleR > 1.0) inputSampleR = 1.0;
|
||||
else if (inputSampleR > 0.0) inputSampleR = 1.0 - pow(1.0 - inputSampleR, (1.0 / powFactor));
|
||||
if (inputSampleR < -1.0) inputSampleR = -1.0;
|
||||
else if (inputSampleR < 0.0) inputSampleR = -1.0 + pow(1.0 + inputSampleR, (1.0 / powFactor));
|
||||
|
||||
inputSampleL *= outTrim;
|
||||
inputSampleR *= outTrim;
|
||||
|
||||
temp = (inputSampleL * fixB[fix_a0]) + fixB[fix_sL1];
|
||||
fixB[fix_sL1] = (inputSampleL * fixB[fix_a1]) - (temp * fixB[fix_b1]) + fixB[fix_sL2];
|
||||
fixB[fix_sL2] = (inputSampleL * fixB[fix_a2]) - (temp * fixB[fix_b2]);
|
||||
inputSampleL = temp; // fixed biquad filtering ultrasonics
|
||||
temp = (inputSampleR * fixB[fix_a0]) + fixB[fix_sR1];
|
||||
fixB[fix_sR1] = (inputSampleR * fixB[fix_a1]) - (temp * fixB[fix_b1]) + fixB[fix_sR2];
|
||||
fixB[fix_sR2] = (inputSampleR * fixB[fix_a2]) - (temp * fixB[fix_b2]);
|
||||
inputSampleR = temp; // fixed biquad filtering ultrasonics
|
||||
|
||||
if (wet < 1.0) {
|
||||
inputSampleL = (inputSampleL * wet) + (drySampleL * (1.0 - wet));
|
||||
inputSampleR = (inputSampleR * wet) + (drySampleR * (1.0 - wet));
|
||||
}
|
||||
|
||||
// begin 32 bit stereo floating point dither
|
||||
int expon;
|
||||
frexpf((float)inputSampleL, &expon);
|
||||
fpdL ^= fpdL << 13;
|
||||
fpdL ^= fpdL >> 17;
|
||||
fpdL ^= fpdL << 5;
|
||||
inputSampleL += ((double(fpdL) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
|
||||
frexpf((float)inputSampleR, &expon);
|
||||
fpdR ^= fpdR << 13;
|
||||
fpdR ^= fpdR >> 17;
|
||||
fpdR ^= fpdR << 5;
|
||||
inputSampleR += ((double(fpdR) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
|
||||
// end 32 bit stereo floating point dither
|
||||
|
||||
*out1 = inputSampleL;
|
||||
*out2 = inputSampleR;
|
||||
|
||||
in1++;
|
||||
in2++;
|
||||
out1++;
|
||||
out2++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate;
|
||||
|
||||
enum {
|
||||
biq_freq,
|
||||
biq_reso,
|
||||
biq_a0,
|
||||
biq_a1,
|
||||
biq_a2,
|
||||
biq_b1,
|
||||
biq_b2,
|
||||
biq_aA0,
|
||||
biq_aA1,
|
||||
biq_aA2,
|
||||
biq_bA1,
|
||||
biq_bA2,
|
||||
biq_aB0,
|
||||
biq_aB1,
|
||||
biq_aB2,
|
||||
biq_bB1,
|
||||
biq_bB2,
|
||||
biq_sL1,
|
||||
biq_sL2,
|
||||
biq_sR1,
|
||||
biq_sR2,
|
||||
biq_total
|
||||
}; // coefficient interpolating biquad filter, stereo
|
||||
|
||||
std::array<double, biq_total> biquad;
|
||||
|
||||
double powFactorA;
|
||||
double powFactorB;
|
||||
double inTrimA;
|
||||
double inTrimB;
|
||||
double outTrimA;
|
||||
double outTrimB;
|
||||
|
||||
enum {
|
||||
fix_freq,
|
||||
fix_reso,
|
||||
fix_a0,
|
||||
fix_a1,
|
||||
fix_a2,
|
||||
fix_b1,
|
||||
fix_b2,
|
||||
fix_sL1,
|
||||
fix_sL2,
|
||||
fix_sR1,
|
||||
fix_sR2,
|
||||
fix_total
|
||||
}; // fixed frequency biquad filter for ultrasonics, stereo
|
||||
|
||||
std::array<double, fix_total> fixA;
|
||||
std::array<double, fix_total> fixB;
|
||||
|
||||
uint32_t fpdL;
|
||||
uint32_t fpdR;
|
||||
// default stuff
|
||||
|
||||
float A;
|
||||
float B;
|
||||
float C;
|
||||
float D;
|
||||
float E;
|
||||
float F; // parameters. Always 0-1, and we scale/alter them elsewhere.
|
||||
};
|
||||
} // namespace trnr
|
||||
@@ -1,280 +0,0 @@
|
||||
#pragma once
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <array>
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
|
||||
namespace trnr {
|
||||
template <typename t_sample>
|
||||
// Lowpass filter based on YLowpass by Chris Johnson
|
||||
class ylowpass {
|
||||
public:
|
||||
ylowpass(double _samplerate)
|
||||
: samplerate {_samplerate}
|
||||
, A {0.1f}
|
||||
, B {1.0f}
|
||||
, C {0.0f}
|
||||
, D {0.1f}
|
||||
, E {0.9f}
|
||||
, F {1.0f}
|
||||
, fpdL {0}
|
||||
, fpdR {0}
|
||||
, biquad {0}
|
||||
{
|
||||
for (int x = 0; x < biq_total; x++) { biquad[x] = 0.0; }
|
||||
powFactorA = 1.0;
|
||||
powFactorB = 1.0;
|
||||
inTrimA = 0.1;
|
||||
inTrimB = 0.1;
|
||||
outTrimA = 1.0;
|
||||
outTrimB = 1.0;
|
||||
for (int x = 0; x < fix_total; x++) {
|
||||
fixA[x] = 0.0;
|
||||
fixB[x] = 0.0;
|
||||
}
|
||||
|
||||
fpdL = 1.0;
|
||||
while (fpdL < 16386) fpdL = rand() * UINT32_MAX;
|
||||
fpdR = 1.0;
|
||||
while (fpdR < 16386) fpdR = rand() * UINT32_MAX;
|
||||
}
|
||||
|
||||
void set_samplerate(double _samplerate) { samplerate = _samplerate; }
|
||||
|
||||
void set_drive(float value) { A = value * 0.9 + 0.1; }
|
||||
|
||||
void set_frequency(float value) { B = value; }
|
||||
|
||||
void set_resonance(float value) { C = value; }
|
||||
|
||||
void set_edge(float value) { D = value; }
|
||||
|
||||
void set_output(float value) { E = value; }
|
||||
|
||||
void set_mix(float value) { F = value; }
|
||||
|
||||
void processblock(t_sample** inputs, t_sample** outputs, int blockSize)
|
||||
{
|
||||
t_sample* in1 = inputs[0];
|
||||
t_sample* in2 = inputs[1];
|
||||
t_sample* out1 = outputs[0];
|
||||
t_sample* out2 = outputs[1];
|
||||
|
||||
int inFramesToProcess = blockSize;
|
||||
double overallscale = 1.0;
|
||||
overallscale /= 44100.0;
|
||||
overallscale *= samplerate;
|
||||
|
||||
inTrimA = inTrimB;
|
||||
inTrimB = A * 10.0;
|
||||
|
||||
biquad[biq_freq] = pow(B, 3) * 20000.0;
|
||||
if (biquad[biq_freq] < 15.0) biquad[biq_freq] = 15.0;
|
||||
biquad[biq_freq] /= samplerate;
|
||||
biquad[biq_reso] = (pow(C, 2) * 15.0) + 0.5571;
|
||||
biquad[biq_aA0] = biquad[biq_aB0];
|
||||
biquad[biq_aA1] = biquad[biq_aB1];
|
||||
biquad[biq_aA2] = biquad[biq_aB2];
|
||||
biquad[biq_bA1] = biquad[biq_bB1];
|
||||
biquad[biq_bA2] = biquad[biq_bB2];
|
||||
// previous run through the buffer is still in the filter, so we move it
|
||||
// to the A section and now it's the new starting point.
|
||||
double K = tan(M_PI * biquad[biq_freq]);
|
||||
double norm = 1.0 / (1.0 + K / biquad[biq_reso] + K * K);
|
||||
biquad[biq_aB0] = K * K * norm;
|
||||
biquad[biq_aB1] = 2.0 * biquad[biq_aB0];
|
||||
biquad[biq_aB2] = biquad[biq_aB0];
|
||||
biquad[biq_bB1] = 2.0 * (K * K - 1.0) * norm;
|
||||
biquad[biq_bB2] = (1.0 - K / biquad[biq_reso] + K * K) * norm;
|
||||
// for the coefficient-interpolated biquad filter
|
||||
|
||||
powFactorA = powFactorB;
|
||||
powFactorB = pow(D + 0.9, 4);
|
||||
|
||||
// 1.0 == target neutral
|
||||
|
||||
outTrimA = outTrimB;
|
||||
outTrimB = E;
|
||||
|
||||
double wet = F;
|
||||
|
||||
fixA[fix_freq] = fixB[fix_freq] = 20000.0 / samplerate;
|
||||
fixA[fix_reso] = fixB[fix_reso] = 0.7071; // butterworth Q
|
||||
|
||||
K = tan(M_PI * fixA[fix_freq]);
|
||||
norm = 1.0 / (1.0 + K / fixA[fix_reso] + K * K);
|
||||
fixA[fix_a0] = fixB[fix_a0] = K * K * norm;
|
||||
fixA[fix_a1] = fixB[fix_a1] = 2.0 * fixA[fix_a0];
|
||||
fixA[fix_a2] = fixB[fix_a2] = fixA[fix_a0];
|
||||
fixA[fix_b1] = fixB[fix_b1] = 2.0 * (K * K - 1.0) * norm;
|
||||
fixA[fix_b2] = fixB[fix_b2] = (1.0 - K / fixA[fix_reso] + K * K) * norm;
|
||||
// for the fixed-position biquad filter
|
||||
|
||||
for (int s = 0; s < blockSize; s++) {
|
||||
double inputSampleL = *in1;
|
||||
double inputSampleR = *in2;
|
||||
if (fabs(inputSampleL) < 1.18e-23) inputSampleL = fpdL * 1.18e-17;
|
||||
if (fabs(inputSampleR) < 1.18e-23) inputSampleR = fpdR * 1.18e-17;
|
||||
double drySampleL = inputSampleL;
|
||||
double drySampleR = inputSampleR;
|
||||
|
||||
double temp = (double)s / inFramesToProcess;
|
||||
biquad[biq_a0] = (biquad[biq_aA0] * temp) + (biquad[biq_aB0] * (1.0 - temp));
|
||||
biquad[biq_a1] = (biquad[biq_aA1] * temp) + (biquad[biq_aB1] * (1.0 - temp));
|
||||
biquad[biq_a2] = (biquad[biq_aA2] * temp) + (biquad[biq_aB2] * (1.0 - temp));
|
||||
biquad[biq_b1] = (biquad[biq_bA1] * temp) + (biquad[biq_bB1] * (1.0 - temp));
|
||||
biquad[biq_b2] = (biquad[biq_bA2] * temp) + (biquad[biq_bB2] * (1.0 - temp));
|
||||
// this is the interpolation code for the biquad
|
||||
double powFactor = (powFactorA * temp) + (powFactorB * (1.0 - temp));
|
||||
double inTrim = (inTrimA * temp) + (inTrimB * (1.0 - temp));
|
||||
double outTrim = (outTrimA * temp) + (outTrimB * (1.0 - temp));
|
||||
|
||||
inputSampleL *= inTrim;
|
||||
inputSampleR *= inTrim;
|
||||
|
||||
temp = (inputSampleL * fixA[fix_a0]) + fixA[fix_sL1];
|
||||
fixA[fix_sL1] = (inputSampleL * fixA[fix_a1]) - (temp * fixA[fix_b1]) + fixA[fix_sL2];
|
||||
fixA[fix_sL2] = (inputSampleL * fixA[fix_a2]) - (temp * fixA[fix_b2]);
|
||||
inputSampleL = temp; // fixed biquad filtering ultrasonics
|
||||
temp = (inputSampleR * fixA[fix_a0]) + fixA[fix_sR1];
|
||||
fixA[fix_sR1] = (inputSampleR * fixA[fix_a1]) - (temp * fixA[fix_b1]) + fixA[fix_sR2];
|
||||
fixA[fix_sR2] = (inputSampleR * fixA[fix_a2]) - (temp * fixA[fix_b2]);
|
||||
inputSampleR = temp; // fixed biquad filtering ultrasonics
|
||||
|
||||
// encode/decode courtesy of torridgristle under the MIT license
|
||||
if (inputSampleL > 1.0) inputSampleL = 1.0;
|
||||
else if (inputSampleL > 0.0) inputSampleL = 1.0 - pow(1.0 - inputSampleL, powFactor);
|
||||
if (inputSampleL < -1.0) inputSampleL = -1.0;
|
||||
else if (inputSampleL < 0.0) inputSampleL = -1.0 + pow(1.0 + inputSampleL, powFactor);
|
||||
if (inputSampleR > 1.0) inputSampleR = 1.0;
|
||||
else if (inputSampleR > 0.0) inputSampleR = 1.0 - pow(1.0 - inputSampleR, powFactor);
|
||||
if (inputSampleR < -1.0) inputSampleR = -1.0;
|
||||
else if (inputSampleR < 0.0) inputSampleR = -1.0 + pow(1.0 + inputSampleR, powFactor);
|
||||
|
||||
temp = (inputSampleL * biquad[biq_a0]) + biquad[biq_sL1];
|
||||
biquad[biq_sL1] = (inputSampleL * biquad[biq_a1]) - (temp * biquad[biq_b1]) + biquad[biq_sL2];
|
||||
biquad[biq_sL2] = (inputSampleL * biquad[biq_a2]) - (temp * biquad[biq_b2]);
|
||||
inputSampleL = temp; // coefficient interpolating biquad filter
|
||||
temp = (inputSampleR * biquad[biq_a0]) + biquad[biq_sR1];
|
||||
biquad[biq_sR1] = (inputSampleR * biquad[biq_a1]) - (temp * biquad[biq_b1]) + biquad[biq_sR2];
|
||||
biquad[biq_sR2] = (inputSampleR * biquad[biq_a2]) - (temp * biquad[biq_b2]);
|
||||
inputSampleR = temp; // coefficient interpolating biquad filter
|
||||
|
||||
// encode/decode courtesy of torridgristle under the MIT license
|
||||
if (inputSampleL > 1.0) inputSampleL = 1.0;
|
||||
else if (inputSampleL > 0.0) inputSampleL = 1.0 - pow(1.0 - inputSampleL, (1.0 / powFactor));
|
||||
if (inputSampleL < -1.0) inputSampleL = -1.0;
|
||||
else if (inputSampleL < 0.0) inputSampleL = -1.0 + pow(1.0 + inputSampleL, (1.0 / powFactor));
|
||||
if (inputSampleR > 1.0) inputSampleR = 1.0;
|
||||
else if (inputSampleR > 0.0) inputSampleR = 1.0 - pow(1.0 - inputSampleR, (1.0 / powFactor));
|
||||
if (inputSampleR < -1.0) inputSampleR = -1.0;
|
||||
else if (inputSampleR < 0.0) inputSampleR = -1.0 + pow(1.0 + inputSampleR, (1.0 / powFactor));
|
||||
|
||||
inputSampleL *= outTrim;
|
||||
inputSampleR *= outTrim;
|
||||
|
||||
temp = (inputSampleL * fixB[fix_a0]) + fixB[fix_sL1];
|
||||
fixB[fix_sL1] = (inputSampleL * fixB[fix_a1]) - (temp * fixB[fix_b1]) + fixB[fix_sL2];
|
||||
fixB[fix_sL2] = (inputSampleL * fixB[fix_a2]) - (temp * fixB[fix_b2]);
|
||||
inputSampleL = temp; // fixed biquad filtering ultrasonics
|
||||
temp = (inputSampleR * fixB[fix_a0]) + fixB[fix_sR1];
|
||||
fixB[fix_sR1] = (inputSampleR * fixB[fix_a1]) - (temp * fixB[fix_b1]) + fixB[fix_sR2];
|
||||
fixB[fix_sR2] = (inputSampleR * fixB[fix_a2]) - (temp * fixB[fix_b2]);
|
||||
inputSampleR = temp; // fixed biquad filtering ultrasonics
|
||||
|
||||
if (wet < 1.0) {
|
||||
inputSampleL = (inputSampleL * wet) + (drySampleL * (1.0 - wet));
|
||||
inputSampleR = (inputSampleR * wet) + (drySampleR * (1.0 - wet));
|
||||
}
|
||||
|
||||
// begin 32 bit stereo floating point dither
|
||||
int expon;
|
||||
frexpf((float)inputSampleL, &expon);
|
||||
fpdL ^= fpdL << 13;
|
||||
fpdL ^= fpdL >> 17;
|
||||
fpdL ^= fpdL << 5;
|
||||
inputSampleL += ((double(fpdL) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
|
||||
frexpf((float)inputSampleR, &expon);
|
||||
fpdR ^= fpdR << 13;
|
||||
fpdR ^= fpdR >> 17;
|
||||
fpdR ^= fpdR << 5;
|
||||
inputSampleR += ((double(fpdR) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
|
||||
// end 32 bit stereo floating point dither
|
||||
|
||||
*out1 = inputSampleL;
|
||||
*out2 = inputSampleR;
|
||||
|
||||
in1++;
|
||||
in2++;
|
||||
out1++;
|
||||
out2++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate;
|
||||
|
||||
enum {
|
||||
biq_freq,
|
||||
biq_reso,
|
||||
biq_a0,
|
||||
biq_a1,
|
||||
biq_a2,
|
||||
biq_b1,
|
||||
biq_b2,
|
||||
biq_aA0,
|
||||
biq_aA1,
|
||||
biq_aA2,
|
||||
biq_bA1,
|
||||
biq_bA2,
|
||||
biq_aB0,
|
||||
biq_aB1,
|
||||
biq_aB2,
|
||||
biq_bB1,
|
||||
biq_bB2,
|
||||
biq_sL1,
|
||||
biq_sL2,
|
||||
biq_sR1,
|
||||
biq_sR2,
|
||||
biq_total
|
||||
}; // coefficient interpolating biquad filter, stereo
|
||||
|
||||
std::array<double, biq_total> biquad;
|
||||
|
||||
double powFactorA;
|
||||
double powFactorB;
|
||||
double inTrimA;
|
||||
double inTrimB;
|
||||
double outTrimA;
|
||||
double outTrimB;
|
||||
|
||||
enum {
|
||||
fix_freq,
|
||||
fix_reso,
|
||||
fix_a0,
|
||||
fix_a1,
|
||||
fix_a2,
|
||||
fix_b1,
|
||||
fix_b2,
|
||||
fix_sL1,
|
||||
fix_sL2,
|
||||
fix_sR1,
|
||||
fix_sR2,
|
||||
fix_total
|
||||
}; // fixed frequency biquad filter for ultrasonics, stereo
|
||||
|
||||
std::array<double, fix_total> fixA;
|
||||
std::array<double, fix_total> fixB;
|
||||
|
||||
uint32_t fpdL;
|
||||
uint32_t fpdR;
|
||||
// default stuff
|
||||
|
||||
float A;
|
||||
float B;
|
||||
float C;
|
||||
float D;
|
||||
float E;
|
||||
float F; // parameters. Always 0-1, and we scale/alter them elsewhere.
|
||||
};
|
||||
} // namespace trnr
|
||||
280
filter/ynotch.h
280
filter/ynotch.h
@@ -1,280 +0,0 @@
|
||||
#pragma once
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <array>
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
|
||||
namespace trnr {
|
||||
template <typename t_sample>
|
||||
// Notch filter based on YNotch by Chris Johnson
|
||||
class ynotch {
|
||||
public:
|
||||
ynotch(double _samplerate)
|
||||
: samplerate {_samplerate}
|
||||
, A {0.1f}
|
||||
, B {1.0f}
|
||||
, C {0.0f}
|
||||
, D {0.1f}
|
||||
, E {0.9f}
|
||||
, F {1.0f}
|
||||
, fpdL {0}
|
||||
, fpdR {0}
|
||||
, biquad {0}
|
||||
{
|
||||
for (int x = 0; x < biq_total; x++) { biquad[x] = 0.0; }
|
||||
powFactorA = 1.0;
|
||||
powFactorB = 1.0;
|
||||
inTrimA = 0.1;
|
||||
inTrimB = 0.1;
|
||||
outTrimA = 1.0;
|
||||
outTrimB = 1.0;
|
||||
for (int x = 0; x < fix_total; x++) {
|
||||
fixA[x] = 0.0;
|
||||
fixB[x] = 0.0;
|
||||
}
|
||||
|
||||
fpdL = 1.0;
|
||||
while (fpdL < 16386) fpdL = rand() * UINT32_MAX;
|
||||
fpdR = 1.0;
|
||||
while (fpdR < 16386) fpdR = rand() * UINT32_MAX;
|
||||
}
|
||||
|
||||
void set_samplerate(double _samplerate) { samplerate = _samplerate; }
|
||||
|
||||
void set_drive(float value) { A = value * 0.9 + 0.1; }
|
||||
|
||||
void set_frequency(float value) { B = value; }
|
||||
|
||||
void set_resonance(float value) { C = value; }
|
||||
|
||||
void set_edge(float value) { D = value; }
|
||||
|
||||
void set_output(float value) { E = value; }
|
||||
|
||||
void set_mix(float value) { F = value; }
|
||||
|
||||
void processblock(t_sample** inputs, t_sample** outputs, int blockSize)
|
||||
{
|
||||
t_sample* in1 = inputs[0];
|
||||
t_sample* in2 = inputs[1];
|
||||
t_sample* out1 = outputs[0];
|
||||
t_sample* out2 = outputs[1];
|
||||
|
||||
int inFramesToProcess = blockSize;
|
||||
double overallscale = 1.0;
|
||||
overallscale /= 44100.0;
|
||||
overallscale *= samplerate;
|
||||
|
||||
inTrimA = inTrimB;
|
||||
inTrimB = A * 10.0;
|
||||
|
||||
biquad[biq_freq] = pow(B, 3) * 20000.0;
|
||||
if (biquad[biq_freq] < 15.0) biquad[biq_freq] = 15.0;
|
||||
biquad[biq_freq] /= samplerate;
|
||||
biquad[biq_reso] = (pow(C, 2) * 15.0) + 0.0001;
|
||||
biquad[biq_aA0] = biquad[biq_aB0];
|
||||
biquad[biq_aA1] = biquad[biq_aB1];
|
||||
biquad[biq_aA2] = biquad[biq_aB2];
|
||||
biquad[biq_bA1] = biquad[biq_bB1];
|
||||
biquad[biq_bA2] = biquad[biq_bB2];
|
||||
// previous run through the buffer is still in the filter, so we move it
|
||||
// to the A section and now it's the new starting point.
|
||||
double K = tan(M_PI * biquad[biq_freq]);
|
||||
double norm = 1.0 / (1.0 + K / biquad[biq_reso] + K * K);
|
||||
biquad[biq_aB0] = (1.0 + K * K) * norm;
|
||||
biquad[biq_aB1] = 2.0 * (K * K - 1) * norm;
|
||||
biquad[biq_aB2] = biquad[biq_aB0];
|
||||
biquad[biq_bB1] = biquad[biq_aB1];
|
||||
biquad[biq_bB2] = (1.0 - K / biquad[biq_reso] + K * K) * norm;
|
||||
// for the coefficient-interpolated biquad filter
|
||||
|
||||
powFactorA = powFactorB;
|
||||
powFactorB = pow(D + 0.9, 4);
|
||||
|
||||
// 1.0 == target neutral
|
||||
|
||||
outTrimA = outTrimB;
|
||||
outTrimB = E;
|
||||
|
||||
double wet = F;
|
||||
|
||||
fixA[fix_freq] = fixB[fix_freq] = 20000.0 / samplerate;
|
||||
fixA[fix_reso] = fixB[fix_reso] = 0.7071; // butterworth Q
|
||||
|
||||
K = tan(M_PI * fixA[fix_freq]);
|
||||
norm = 1.0 / (1.0 + K / fixA[fix_reso] + K * K);
|
||||
fixA[fix_a0] = fixB[fix_a0] = K * K * norm;
|
||||
fixA[fix_a1] = fixB[fix_a1] = 2.0 * fixA[fix_a0];
|
||||
fixA[fix_a2] = fixB[fix_a2] = fixA[fix_a0];
|
||||
fixA[fix_b1] = fixB[fix_b1] = 2.0 * (K * K - 1.0) * norm;
|
||||
fixA[fix_b2] = fixB[fix_b2] = (1.0 - K / fixA[fix_reso] + K * K) * norm;
|
||||
// for the fixed-position biquad filter
|
||||
|
||||
for (int s = 0; s < blockSize; s++) {
|
||||
double inputSampleL = *in1;
|
||||
double inputSampleR = *in2;
|
||||
if (fabs(inputSampleL) < 1.18e-23) inputSampleL = fpdL * 1.18e-17;
|
||||
if (fabs(inputSampleR) < 1.18e-23) inputSampleR = fpdR * 1.18e-17;
|
||||
double drySampleL = inputSampleL;
|
||||
double drySampleR = inputSampleR;
|
||||
|
||||
double temp = (double)s / inFramesToProcess;
|
||||
biquad[biq_a0] = (biquad[biq_aA0] * temp) + (biquad[biq_aB0] * (1.0 - temp));
|
||||
biquad[biq_a1] = (biquad[biq_aA1] * temp) + (biquad[biq_aB1] * (1.0 - temp));
|
||||
biquad[biq_a2] = (biquad[biq_aA2] * temp) + (biquad[biq_aB2] * (1.0 - temp));
|
||||
biquad[biq_b1] = (biquad[biq_bA1] * temp) + (biquad[biq_bB1] * (1.0 - temp));
|
||||
biquad[biq_b2] = (biquad[biq_bA2] * temp) + (biquad[biq_bB2] * (1.0 - temp));
|
||||
// this is the interpolation code for the biquad
|
||||
double powFactor = (powFactorA * temp) + (powFactorB * (1.0 - temp));
|
||||
double inTrim = (inTrimA * temp) + (inTrimB * (1.0 - temp));
|
||||
double outTrim = (outTrimA * temp) + (outTrimB * (1.0 - temp));
|
||||
|
||||
inputSampleL *= inTrim;
|
||||
inputSampleR *= inTrim;
|
||||
|
||||
temp = (inputSampleL * fixA[fix_a0]) + fixA[fix_sL1];
|
||||
fixA[fix_sL1] = (inputSampleL * fixA[fix_a1]) - (temp * fixA[fix_b1]) + fixA[fix_sL2];
|
||||
fixA[fix_sL2] = (inputSampleL * fixA[fix_a2]) - (temp * fixA[fix_b2]);
|
||||
inputSampleL = temp; // fixed biquad filtering ultrasonics
|
||||
temp = (inputSampleR * fixA[fix_a0]) + fixA[fix_sR1];
|
||||
fixA[fix_sR1] = (inputSampleR * fixA[fix_a1]) - (temp * fixA[fix_b1]) + fixA[fix_sR2];
|
||||
fixA[fix_sR2] = (inputSampleR * fixA[fix_a2]) - (temp * fixA[fix_b2]);
|
||||
inputSampleR = temp; // fixed biquad filtering ultrasonics
|
||||
|
||||
// encode/decode courtesy of torridgristle under the MIT license
|
||||
if (inputSampleL > 1.0) inputSampleL = 1.0;
|
||||
else if (inputSampleL > 0.0) inputSampleL = 1.0 - pow(1.0 - inputSampleL, powFactor);
|
||||
if (inputSampleL < -1.0) inputSampleL = -1.0;
|
||||
else if (inputSampleL < 0.0) inputSampleL = -1.0 + pow(1.0 + inputSampleL, powFactor);
|
||||
if (inputSampleR > 1.0) inputSampleR = 1.0;
|
||||
else if (inputSampleR > 0.0) inputSampleR = 1.0 - pow(1.0 - inputSampleR, powFactor);
|
||||
if (inputSampleR < -1.0) inputSampleR = -1.0;
|
||||
else if (inputSampleR < 0.0) inputSampleR = -1.0 + pow(1.0 + inputSampleR, powFactor);
|
||||
|
||||
temp = (inputSampleL * biquad[biq_a0]) + biquad[biq_sL1];
|
||||
biquad[biq_sL1] = (inputSampleL * biquad[biq_a1]) - (temp * biquad[biq_b1]) + biquad[biq_sL2];
|
||||
biquad[biq_sL2] = (inputSampleL * biquad[biq_a2]) - (temp * biquad[biq_b2]);
|
||||
inputSampleL = temp; // coefficient interpolating biquad filter
|
||||
temp = (inputSampleR * biquad[biq_a0]) + biquad[biq_sR1];
|
||||
biquad[biq_sR1] = (inputSampleR * biquad[biq_a1]) - (temp * biquad[biq_b1]) + biquad[biq_sR2];
|
||||
biquad[biq_sR2] = (inputSampleR * biquad[biq_a2]) - (temp * biquad[biq_b2]);
|
||||
inputSampleR = temp; // coefficient interpolating biquad filter
|
||||
|
||||
// encode/decode courtesy of torridgristle under the MIT license
|
||||
if (inputSampleL > 1.0) inputSampleL = 1.0;
|
||||
else if (inputSampleL > 0.0) inputSampleL = 1.0 - pow(1.0 - inputSampleL, (1.0 / powFactor));
|
||||
if (inputSampleL < -1.0) inputSampleL = -1.0;
|
||||
else if (inputSampleL < 0.0) inputSampleL = -1.0 + pow(1.0 + inputSampleL, (1.0 / powFactor));
|
||||
if (inputSampleR > 1.0) inputSampleR = 1.0;
|
||||
else if (inputSampleR > 0.0) inputSampleR = 1.0 - pow(1.0 - inputSampleR, (1.0 / powFactor));
|
||||
if (inputSampleR < -1.0) inputSampleR = -1.0;
|
||||
else if (inputSampleR < 0.0) inputSampleR = -1.0 + pow(1.0 + inputSampleR, (1.0 / powFactor));
|
||||
|
||||
inputSampleL *= outTrim;
|
||||
inputSampleR *= outTrim;
|
||||
|
||||
temp = (inputSampleL * fixB[fix_a0]) + fixB[fix_sL1];
|
||||
fixB[fix_sL1] = (inputSampleL * fixB[fix_a1]) - (temp * fixB[fix_b1]) + fixB[fix_sL2];
|
||||
fixB[fix_sL2] = (inputSampleL * fixB[fix_a2]) - (temp * fixB[fix_b2]);
|
||||
inputSampleL = temp; // fixed biquad filtering ultrasonics
|
||||
temp = (inputSampleR * fixB[fix_a0]) + fixB[fix_sR1];
|
||||
fixB[fix_sR1] = (inputSampleR * fixB[fix_a1]) - (temp * fixB[fix_b1]) + fixB[fix_sR2];
|
||||
fixB[fix_sR2] = (inputSampleR * fixB[fix_a2]) - (temp * fixB[fix_b2]);
|
||||
inputSampleR = temp; // fixed biquad filtering ultrasonics
|
||||
|
||||
if (wet < 1.0) {
|
||||
inputSampleL = (inputSampleL * wet) + (drySampleL * (1.0 - wet));
|
||||
inputSampleR = (inputSampleR * wet) + (drySampleR * (1.0 - wet));
|
||||
}
|
||||
|
||||
// begin 32 bit stereo floating point dither
|
||||
int expon;
|
||||
frexpf((float)inputSampleL, &expon);
|
||||
fpdL ^= fpdL << 13;
|
||||
fpdL ^= fpdL >> 17;
|
||||
fpdL ^= fpdL << 5;
|
||||
inputSampleL += ((double(fpdL) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
|
||||
frexpf((float)inputSampleR, &expon);
|
||||
fpdR ^= fpdR << 13;
|
||||
fpdR ^= fpdR >> 17;
|
||||
fpdR ^= fpdR << 5;
|
||||
inputSampleR += ((double(fpdR) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
|
||||
// end 32 bit stereo floating point dither
|
||||
|
||||
*out1 = inputSampleL;
|
||||
*out2 = inputSampleR;
|
||||
|
||||
in1++;
|
||||
in2++;
|
||||
out1++;
|
||||
out2++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate;
|
||||
|
||||
enum {
|
||||
biq_freq,
|
||||
biq_reso,
|
||||
biq_a0,
|
||||
biq_a1,
|
||||
biq_a2,
|
||||
biq_b1,
|
||||
biq_b2,
|
||||
biq_aA0,
|
||||
biq_aA1,
|
||||
biq_aA2,
|
||||
biq_bA1,
|
||||
biq_bA2,
|
||||
biq_aB0,
|
||||
biq_aB1,
|
||||
biq_aB2,
|
||||
biq_bB1,
|
||||
biq_bB2,
|
||||
biq_sL1,
|
||||
biq_sL2,
|
||||
biq_sR1,
|
||||
biq_sR2,
|
||||
biq_total
|
||||
}; // coefficient interpolating biquad filter, stereo
|
||||
|
||||
std::array<double, biq_total> biquad;
|
||||
|
||||
double powFactorA;
|
||||
double powFactorB;
|
||||
double inTrimA;
|
||||
double inTrimB;
|
||||
double outTrimA;
|
||||
double outTrimB;
|
||||
|
||||
enum {
|
||||
fix_freq,
|
||||
fix_reso,
|
||||
fix_a0,
|
||||
fix_a1,
|
||||
fix_a2,
|
||||
fix_b1,
|
||||
fix_b2,
|
||||
fix_sL1,
|
||||
fix_sL2,
|
||||
fix_sR1,
|
||||
fix_sR2,
|
||||
fix_total
|
||||
}; // fixed frequency biquad filter for ultrasonics, stereo
|
||||
|
||||
std::array<double, fix_total> fixA;
|
||||
std::array<double, fix_total> fixB;
|
||||
|
||||
uint32_t fpdL;
|
||||
uint32_t fpdR;
|
||||
// default stuff
|
||||
|
||||
float A;
|
||||
float B;
|
||||
float C;
|
||||
float D;
|
||||
float E;
|
||||
float F; // parameters. Always 0-1, and we scale/alter them elsewhere.
|
||||
};
|
||||
} // namespace trnr
|
||||
1242
filter/ysvf.h
1242
filter/ysvf.h
File diff suppressed because it is too large
Load Diff
172
gfx/dice.h
Normal file
172
gfx/dice.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* dice.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace trnr {
|
||||
struct point {
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
struct rect {
|
||||
float x;
|
||||
float y;
|
||||
float width;
|
||||
float height;
|
||||
};
|
||||
|
||||
struct quad {
|
||||
point p1, p2, p3, p4;
|
||||
};
|
||||
|
||||
struct dice {
|
||||
int width;
|
||||
int height;
|
||||
array<point, 6> border_points;
|
||||
array<rect, 7> pips;
|
||||
array<quad, 6> segments_left;
|
||||
};
|
||||
|
||||
// caclulates coordinates for an isometric dice control with bar segments
|
||||
inline void dice_init(dice& d, float width, float height)
|
||||
{
|
||||
const float shortening = 0.866f;
|
||||
|
||||
float face_height, dice_width;
|
||||
face_height = dice_width = height / 2.f;
|
||||
float mid_x = width / 2.f;
|
||||
float face_half_height = face_height / 2.f;
|
||||
float face_width = dice_width * shortening;
|
||||
|
||||
// border points
|
||||
point b_p1 = point {mid_x, 0};
|
||||
point b_p2 = point {mid_x + face_width, face_half_height};
|
||||
point b_p3 = point {mid_x + face_width, face_half_height + face_height};
|
||||
point b_p4 = point {mid_x, height};
|
||||
point b_p5 = point {mid_x - face_width, face_half_height + face_height};
|
||||
point b_p6 = point {mid_x - face_width, face_half_height};
|
||||
|
||||
d.border_points = {b_p1, b_p2, b_p3, b_p4, b_p5, b_p6};
|
||||
|
||||
const float padding = face_height * 0.09f;
|
||||
const float pad_x = padding * shortening;
|
||||
const float pad_y = padding;
|
||||
const float padded_face_height = face_height - 2 * pad_y;
|
||||
|
||||
const int segments = d.segments_left.size();
|
||||
// define the ratio between segments and gaps as 3:1
|
||||
const int segment_parts = 3;
|
||||
const int gap_part = 1;
|
||||
// the number of parts per segment
|
||||
const int parts_per_segment = segment_parts + gap_part;
|
||||
const int parts = segments * parts_per_segment - 2; // remove last gap
|
||||
|
||||
const float segment_height = (padded_face_height / parts) * segment_parts;
|
||||
const float gap_height = padded_face_height / parts;
|
||||
|
||||
// calculate segments of the left face
|
||||
for (int i = 0; i < d.segments_left.size(); i++) {
|
||||
const point base_p1 =
|
||||
point {mid_x - face_width + pad_x, face_half_height + pad_y};
|
||||
const point base_p2 = point {mid_x - pad_x, face_height};
|
||||
float seg_y = i * (segment_height + gap_height);
|
||||
point p1 = {base_p1.x, base_p1.y + seg_y};
|
||||
point p2 = {base_p2.x, base_p2.y + seg_y};
|
||||
point p3 = {base_p2.x, base_p2.y + seg_y + segment_height};
|
||||
point p4 = {base_p1.x, base_p1.y + seg_y + segment_height};
|
||||
d.segments_left[i] = {p1, p2, p3, p4};
|
||||
}
|
||||
|
||||
// calculate pip positions for top face (30-degree isometric perspective)
|
||||
// correct center of the diamond face
|
||||
// the diamond spans from b_p1 (top) to the middle of the left face
|
||||
float diamond_center_x = mid_x;
|
||||
float diamond_center_y =
|
||||
face_half_height / 2.0f + face_half_height / 2.0f; // move down to actual center
|
||||
|
||||
// for 30-degree isometric, the grid directions are:
|
||||
float cos30 = 1.f; // cos(30°) = √3/2 ≈ 0.866
|
||||
float sin30 = 0.5f; // sin(30°) = 1/2
|
||||
|
||||
// scale the grid vectors to fit properly within the diamond
|
||||
float grid_scale = 0.25f; // smaller scale to fit within diamond bounds
|
||||
|
||||
// grid vector 1: 30 degrees from horizontal (toward top-left of diamond)
|
||||
float grid1_x = -cos30 * face_width * grid_scale;
|
||||
float grid1_y = -sin30 * face_height * grid_scale;
|
||||
|
||||
// grid vector 2: -30 degrees from horizontal (toward top-right of diamond)
|
||||
float grid2_x = cos30 * face_width * grid_scale;
|
||||
float grid2_y = -sin30 * face_height * grid_scale;
|
||||
|
||||
const float pip_h = face_height * 0.1f;
|
||||
const float pip_w = pip_h * 2 * shortening;
|
||||
|
||||
// position pips in the 30-degree isometric grid
|
||||
for (int i = 0; i < 7; i++) {
|
||||
float u = 0, v = 0; // grid coordinates
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
u = 0;
|
||||
v = 0;
|
||||
break; // center
|
||||
case 1:
|
||||
u = -1;
|
||||
v = 1;
|
||||
break; // top-left
|
||||
case 2:
|
||||
u = 1;
|
||||
v = -1;
|
||||
break; // bottom-right
|
||||
case 3:
|
||||
u = 1;
|
||||
v = 1;
|
||||
break; // top-right
|
||||
case 4:
|
||||
u = -1;
|
||||
v = -1;
|
||||
break; // bottom-left
|
||||
case 5:
|
||||
u = -1;
|
||||
v = 0;
|
||||
break; // center-left
|
||||
case 6:
|
||||
u = 1;
|
||||
v = 0;
|
||||
break; // center-right
|
||||
}
|
||||
|
||||
// convert grid coordinates to world coordinates using 30-degree vectors
|
||||
float pip_x = diamond_center_x + u * grid1_x + v * grid2_x;
|
||||
float pip_y = diamond_center_y + u * grid1_y + v * grid2_y;
|
||||
|
||||
d.pips[i] = {pip_x - pip_w / 2, pip_y - pip_h / 2, pip_w, pip_h};
|
||||
}
|
||||
}
|
||||
} // namespace trnr
|
||||
@@ -1,3 +1,26 @@
|
||||
/*
|
||||
* oversampler.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
@@ -7,7 +30,7 @@
|
||||
|
||||
namespace trnr {
|
||||
|
||||
template<typename sample>
|
||||
template <typename sample>
|
||||
class oversampler {
|
||||
public:
|
||||
oversampler()
|
||||
|
||||
64
sequencer/combine_seq.h
Normal file
64
sequencer/combine_seq.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* combine_seq.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "simple_seq.h"
|
||||
|
||||
namespace trnr {
|
||||
|
||||
struct combine_note {
|
||||
int probability;
|
||||
int pitch;
|
||||
int octave;
|
||||
int velocity;
|
||||
};
|
||||
|
||||
struct combine_seq {
|
||||
simple_seq probability_seq;
|
||||
simple_seq pitch_seq;
|
||||
simple_seq octave_seq;
|
||||
simple_seq velocity_seq;
|
||||
combine_note current_note;
|
||||
};
|
||||
|
||||
inline void combine_seq_init(combine_seq& c, size_t max_len)
|
||||
{
|
||||
simple_seq_init(c.probability_seq, max_len);
|
||||
simple_seq_init(c.pitch_seq, max_len);
|
||||
simple_seq_init(c.octave_seq, max_len);
|
||||
simple_seq_init(c.velocity_seq, max_len);
|
||||
}
|
||||
|
||||
inline combine_note& combine_seq_process_step(combine_seq& c)
|
||||
{
|
||||
c.current_note.probability = simple_seq_process_step(c.probability_seq);
|
||||
c.current_note.pitch = simple_seq_process_step(c.pitch_seq);
|
||||
c.current_note.octave = simple_seq_process_step(c.octave_seq);
|
||||
c.current_note.velocity = simple_seq_process_step(c.velocity_seq);
|
||||
|
||||
return c.current_note;
|
||||
}
|
||||
|
||||
struct timing_info {};
|
||||
} // namespace trnr
|
||||
89
sequencer/simple_seq.h
Normal file
89
sequencer/simple_seq.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* simple_seq.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace trnr {
|
||||
|
||||
enum playback_dir {
|
||||
PB_FORWARD,
|
||||
PB_BACKWARD,
|
||||
PB_PENDULUM,
|
||||
PB_RANDOM
|
||||
};
|
||||
|
||||
struct simple_seq {
|
||||
size_t current_pos;
|
||||
size_t length;
|
||||
playback_dir playback_dir;
|
||||
vector<int> data;
|
||||
bool pendulum_forward;
|
||||
};
|
||||
|
||||
inline void simple_seq_init(simple_seq& s, size_t length)
|
||||
{
|
||||
s.current_pos = 0;
|
||||
s.length = length;
|
||||
s.playback_dir = PB_FORWARD;
|
||||
s.data.resize(s.length);
|
||||
s.data.assign(s.length, 0);
|
||||
s.pendulum_forward = true;
|
||||
}
|
||||
|
||||
inline int simple_seq_process_step(simple_seq& s)
|
||||
{
|
||||
// play head advancement
|
||||
if (s.playback_dir == PB_FORWARD ||
|
||||
s.playback_dir == PB_PENDULUM && s.pendulum_forward)
|
||||
s.current_pos++;
|
||||
else if (s.playback_dir == PB_BACKWARD ||
|
||||
s.playback_dir == PB_PENDULUM && !s.pendulum_forward)
|
||||
s.current_pos--;
|
||||
else if (s.playback_dir == PB_RANDOM) s.current_pos = rand() % s.length;
|
||||
|
||||
// play head reset
|
||||
switch (s.playback_dir) {
|
||||
case PB_FORWARD:
|
||||
if (s.current_pos > s.length - 1) s.current_pos = 0;
|
||||
break;
|
||||
case PB_BACKWARD:
|
||||
if (s.current_pos < 0) s.current_pos = s.length - 1;
|
||||
break;
|
||||
case PB_PENDULUM:
|
||||
if (s.pendulum_forward && s.current_pos >= s.length - 1)
|
||||
s.pendulum_forward = false;
|
||||
else if (!s.pendulum_forward && s.current_pos <= 0) s.pendulum_forward = true;
|
||||
break;
|
||||
case PB_RANDOM:
|
||||
break;
|
||||
}
|
||||
|
||||
return s.data[s.current_pos];
|
||||
}
|
||||
} // namespace trnr
|
||||
@@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace trnr {
|
||||
template <typename t_sample>
|
||||
struct ivoice {
|
||||
virtual ~ivoice() = default;
|
||||
virtual void process_samples(t_sample** _outputs, int _start_index, int _block_size) = 0;
|
||||
virtual bool is_busy() = 0;
|
||||
virtual void set_samplerate(double samplerate) = 0;
|
||||
virtual void note_on(int _note, float _velocity) = 0;
|
||||
virtual void note_off() = 0;
|
||||
virtual void modulate_pitch(float _pitch) = 0;
|
||||
};
|
||||
|
||||
// check if a template derives from ivoice
|
||||
template <class derived, typename sample>
|
||||
struct is_convertible {
|
||||
template <class T>
|
||||
static char test(T*);
|
||||
|
||||
template <class T>
|
||||
static double test(...);
|
||||
|
||||
static const bool value = sizeof(test<ivoice<sample>>(static_cast<derived*>(0))) == 1;
|
||||
};
|
||||
} // namespace trnr
|
||||
@@ -1,48 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace trnr {
|
||||
|
||||
enum midi_event_type {
|
||||
note_on = 0,
|
||||
note_off,
|
||||
pitch_wheel,
|
||||
mod_wheel
|
||||
};
|
||||
|
||||
class midi_event {
|
||||
public:
|
||||
midi_event_type type;
|
||||
int offset = 0;
|
||||
int midi_note = 0;
|
||||
float velocity = 1.f;
|
||||
double data = 0;
|
||||
|
||||
void make_note_on(int _midi_note, float _velocity, int _offset = 0)
|
||||
{
|
||||
type = midi_event_type::note_on;
|
||||
midi_note = _midi_note;
|
||||
velocity = _velocity;
|
||||
offset = _offset;
|
||||
}
|
||||
|
||||
void make_note_off(int _midi_note, float _velocity, int _offset = 0)
|
||||
{
|
||||
type = midi_event_type::note_off;
|
||||
midi_note = _midi_note;
|
||||
velocity = _velocity;
|
||||
offset = _offset;
|
||||
}
|
||||
|
||||
void make_pitch_wheel(double _pitch, int _offset = 0)
|
||||
{
|
||||
type = midi_event_type::pitch_wheel;
|
||||
data = _pitch;
|
||||
}
|
||||
|
||||
void make_mod_wheel(double _mod, int _offset = 0)
|
||||
{
|
||||
type = midi_event_type::pitch_wheel;
|
||||
data = _mod;
|
||||
}
|
||||
};
|
||||
} // namespace trnr
|
||||
@@ -1,93 +0,0 @@
|
||||
#pragma once
|
||||
#include "ivoice.h"
|
||||
#include "midi_event.h"
|
||||
#include "voice_allocator.h"
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace trnr {
|
||||
|
||||
// a generic midi synth base class with sample accurate event handling.
|
||||
// the templated type t_voice must derive from ivoice
|
||||
template <typename t_voice, typename t_sample>
|
||||
class midi_synth : public voice_allocator<t_voice, t_sample> {
|
||||
public:
|
||||
midi_synth()
|
||||
: m_voices_active {false}
|
||||
{
|
||||
// checks whether template derives from ivoice
|
||||
typedef t_voice assert_at_compile_time[is_convertible<t_voice, t_sample>::value ? 1 : -1];
|
||||
}
|
||||
|
||||
void set_samplerate_blocksize(double _samplerate, int _block_size)
|
||||
{
|
||||
m_block_size = _block_size;
|
||||
voice_allocator<t_voice, t_sample>::set_samplerate(_samplerate);
|
||||
}
|
||||
|
||||
void process_block(t_sample** _outputs, int _n_frames)
|
||||
{
|
||||
// clear outputs
|
||||
for (auto i = 0; i < 2; i++) { memset(_outputs[i], 0, _n_frames * sizeof(t_sample)); }
|
||||
|
||||
// sample accurate event handling based on the iPlug2 synth by Oli Larkin
|
||||
if (m_voices_active || !m_event_queue.empty()) {
|
||||
int block_size = m_block_size;
|
||||
int samples_remaining = _n_frames;
|
||||
int start_index = 0;
|
||||
|
||||
while (samples_remaining > 0) {
|
||||
|
||||
if (samples_remaining < block_size) block_size = samples_remaining;
|
||||
|
||||
while (!m_event_queue.empty()) {
|
||||
midi_event event = m_event_queue.front();
|
||||
|
||||
// we assume the messages are in chronological order. If we find one later than the current block we
|
||||
// are done.
|
||||
if (event.offset > start_index + block_size) break;
|
||||
|
||||
// send performance messages to the voice allocator
|
||||
// message offset is relative to the start of this process_samples() block
|
||||
event.offset -= start_index;
|
||||
voice_allocator<t_voice, t_sample>::add_event(event);
|
||||
|
||||
m_event_queue.erase(m_event_queue.begin());
|
||||
}
|
||||
|
||||
voice_allocator<t_voice, t_sample>::process_samples(_outputs, start_index, block_size);
|
||||
|
||||
samples_remaining -= block_size;
|
||||
start_index += block_size;
|
||||
}
|
||||
|
||||
m_voices_active = voice_allocator<t_voice, t_sample>::voices_active();
|
||||
|
||||
flush_event_queue(_n_frames);
|
||||
} else {
|
||||
for (int s = 0; s < _n_frames; s++) {
|
||||
_outputs[0][s] = 0.;
|
||||
_outputs[1][s] = 0.;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void add_event(midi_event event)
|
||||
{
|
||||
if (event.type == midi_event_type::note_on) m_voices_active = true;
|
||||
|
||||
m_event_queue.push_back(event);
|
||||
}
|
||||
|
||||
void flush_event_queue(int frames)
|
||||
{
|
||||
for (int i = 0; i < m_event_queue.size(); i++) { m_event_queue.at(i).offset -= frames; }
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<midi_event> m_event_queue;
|
||||
int m_block_size;
|
||||
bool m_voices_active;
|
||||
};
|
||||
} // namespace trnr
|
||||
817
synth/triplex.h
Normal file
817
synth/triplex.h
Normal file
@@ -0,0 +1,817 @@
|
||||
/*
|
||||
* triplex.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../util/audio_buffer.h"
|
||||
#include "../util/audio_math.h"
|
||||
#include "voice_allocator.h"
|
||||
#include <cmath>
|
||||
#include <random>
|
||||
|
||||
namespace trnr {
|
||||
|
||||
//////////////
|
||||
// SINE OSC //
|
||||
//////////////
|
||||
|
||||
struct tx_sineosc {
|
||||
bool phase_reset;
|
||||
double samplerate;
|
||||
float phase_resolution;
|
||||
float phase;
|
||||
float history;
|
||||
};
|
||||
|
||||
inline void tx_randomize_phase(float& phase)
|
||||
{
|
||||
std::random_device random;
|
||||
std::mt19937 gen(random());
|
||||
std::uniform_real_distribution<> dis(0.0, 1.0);
|
||||
phase = dis(gen);
|
||||
}
|
||||
|
||||
inline void tx_sineosc_init(tx_sineosc& s, double samplerate)
|
||||
{
|
||||
s.phase_reset = false;
|
||||
s.samplerate = samplerate;
|
||||
s.phase_resolution = 16.f;
|
||||
s.phase = 0.f;
|
||||
s.history = 0.f;
|
||||
|
||||
tx_randomize_phase(s.phase);
|
||||
}
|
||||
|
||||
inline float tx_wrap(float& phase)
|
||||
{
|
||||
while (phase < 0.) phase += 1.;
|
||||
while (phase >= 1.) phase -= 1.;
|
||||
return phase;
|
||||
}
|
||||
|
||||
inline float tx_sineosc_process_sample(tx_sineosc& s, bool trigger, float frequency,
|
||||
float phase_modulation = 0.f)
|
||||
{
|
||||
if (trigger) {
|
||||
if (s.phase_reset) s.phase = 0.f;
|
||||
else tx_randomize_phase(s.phase);
|
||||
}
|
||||
|
||||
float lookup_phase = s.phase + phase_modulation;
|
||||
tx_wrap(lookup_phase);
|
||||
s.phase += frequency / s.samplerate;
|
||||
tx_wrap(s.phase);
|
||||
|
||||
// redux
|
||||
s.phase = static_cast<int>(s.phase * s.phase_resolution) / s.phase_resolution;
|
||||
|
||||
// x is scaled 0<=x<4096
|
||||
float output;
|
||||
float x = lookup_phase;
|
||||
const float a = -0.40319426317E-08;
|
||||
const float b = 0.21683205691E+03;
|
||||
const float c = 0.28463350538E-04;
|
||||
const float d = -0.30774648337E-02;
|
||||
float y;
|
||||
|
||||
bool negate = false;
|
||||
if (x > 2048) {
|
||||
negate = true;
|
||||
x -= 2048;
|
||||
}
|
||||
if (x > 1024) x = 2048 - x;
|
||||
y = (a + x) / (b + c * x * x) + d * x;
|
||||
if (negate) output = (-y);
|
||||
else output = y;
|
||||
|
||||
// filter
|
||||
output = 0.5 * (output + s.history);
|
||||
s.history = output;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
//////////////
|
||||
// ENVELOPE //
|
||||
//////////////
|
||||
|
||||
enum tx_env_state {
|
||||
idle = 0,
|
||||
attack1,
|
||||
attack2,
|
||||
hold,
|
||||
decay1,
|
||||
decay2,
|
||||
sustain,
|
||||
release1,
|
||||
release2
|
||||
};
|
||||
|
||||
struct tx_envelope {
|
||||
tx_env_state state = idle;
|
||||
float attack1_rate = 0;
|
||||
float attack1_level = 0;
|
||||
float attack2_rate = 0;
|
||||
float hold_rate = 0;
|
||||
float decay1_rate = 0;
|
||||
float decay1_level = 0;
|
||||
float decay2_rate = 0;
|
||||
float sustain_level = 0;
|
||||
float release1_rate = 0;
|
||||
float release1_level = 0;
|
||||
float release2_rate = 0;
|
||||
bool skip_sustain = false;
|
||||
|
||||
double samplerate = 44100.;
|
||||
size_t phase = 0;
|
||||
float level = 0.f;
|
||||
float start_level = 0.f;
|
||||
float h1 = 0.f;
|
||||
float h2 = 0.f;
|
||||
float h3 = 0.f;
|
||||
bool retrigger;
|
||||
};
|
||||
|
||||
inline void tx_envelope_init(tx_envelope& e, double samplerate, bool retrigger = false)
|
||||
{
|
||||
e.samplerate = samplerate;
|
||||
e.retrigger = retrigger;
|
||||
}
|
||||
|
||||
inline size_t tx_mtos(float ms, double samplerate)
|
||||
{
|
||||
return static_cast<size_t>(ms * samplerate / 1000.f);
|
||||
}
|
||||
|
||||
inline float tx_lerp(float x1, float y1, float x2, float y2, float x)
|
||||
{
|
||||
return y1 + (((x - x1) * (y2 - y1)) / (x2 - x1));
|
||||
}
|
||||
|
||||
inline float tx_envelope_process_sample(tx_envelope& e, bool gate, bool trigger,
|
||||
float _attack_mod = 0, float _decay_mod = 0)
|
||||
{
|
||||
size_t attack_mid_x1 = tx_mtos(e.attack1_rate + (float)_attack_mod, e.samplerate);
|
||||
size_t attack_mid_x2 = tx_mtos(e.attack2_rate + (float)_attack_mod, e.samplerate);
|
||||
size_t hold_samp = tx_mtos(e.hold_rate, e.samplerate);
|
||||
size_t decay_mid_x1 = tx_mtos(e.decay1_rate + (float)_decay_mod, e.samplerate);
|
||||
size_t decay_mid_x2 = tx_mtos(e.decay2_rate + (float)_decay_mod, e.samplerate);
|
||||
size_t release_mid_x1 = tx_mtos(e.release1_rate + (float)_decay_mod, e.samplerate);
|
||||
size_t release_mid_x2 = tx_mtos(e.release2_rate + (float)_decay_mod, e.samplerate);
|
||||
|
||||
// if note on is triggered, transition to attack phase
|
||||
if (trigger) {
|
||||
if (e.retrigger) e.start_level = 0.f;
|
||||
else e.start_level = e.level;
|
||||
e.phase = 0;
|
||||
e.state = attack1;
|
||||
}
|
||||
// attack 1st half
|
||||
if (e.state == attack1) {
|
||||
// while in attack phase
|
||||
if (e.phase < attack_mid_x1) {
|
||||
e.level = tx_lerp(0, e.start_level, (float)attack_mid_x1, e.attack1_level,
|
||||
(float)e.phase);
|
||||
e.phase += 1;
|
||||
}
|
||||
// reset phase if parameter was changed
|
||||
if (e.phase > attack_mid_x1) { e.phase = attack_mid_x1; }
|
||||
// if attack phase is done, transition to decay phase
|
||||
if (e.phase == attack_mid_x1) {
|
||||
e.state = attack2;
|
||||
e.phase = 0;
|
||||
}
|
||||
}
|
||||
// attack 2nd half
|
||||
if (e.state == attack2) {
|
||||
// while in attack phase
|
||||
if (e.phase < attack_mid_x2) {
|
||||
e.level =
|
||||
tx_lerp(0, e.attack1_level, (float)attack_mid_x2, 1, (float)e.phase);
|
||||
e.phase += 1;
|
||||
}
|
||||
// reset phase if parameter was changed
|
||||
if (e.phase > attack_mid_x2) { e.phase = attack_mid_x2; }
|
||||
// if attack phase is done, transition to decay phase
|
||||
if (e.phase == attack_mid_x2) {
|
||||
e.state = hold;
|
||||
e.phase = 0;
|
||||
}
|
||||
}
|
||||
// hold
|
||||
if (e.state == hold) {
|
||||
if (e.phase < hold_samp) {
|
||||
e.level = 1.0;
|
||||
e.phase += 1;
|
||||
}
|
||||
if (e.phase > hold_samp) { e.phase = hold_samp; }
|
||||
if (e.phase == hold_samp) {
|
||||
e.state = decay1;
|
||||
e.phase = 0;
|
||||
}
|
||||
}
|
||||
// decay 1st half
|
||||
if (e.state == decay1) {
|
||||
// while in decay phase
|
||||
if (e.phase < decay_mid_x1) {
|
||||
e.level = tx_lerp(0, 1, (float)decay_mid_x1, e.decay1_level, (float)e.phase);
|
||||
e.phase += 1;
|
||||
}
|
||||
// reset phase if parameter was changed
|
||||
if (e.phase > decay_mid_x1) { e.phase = decay_mid_x1; }
|
||||
// if decay phase is done, transition to sustain phase
|
||||
if (e.phase == decay_mid_x1) {
|
||||
e.state = decay2;
|
||||
e.phase = 0;
|
||||
}
|
||||
}
|
||||
// decay 2nd half
|
||||
if (e.state == decay2) {
|
||||
// while in decay phase
|
||||
if (e.phase < decay_mid_x2) {
|
||||
e.level = tx_lerp(0, e.decay1_level, (float)decay_mid_x2, e.sustain_level,
|
||||
(float)e.phase);
|
||||
e.phase += 1;
|
||||
}
|
||||
// reset phase if parameter was changed
|
||||
if (e.phase > decay_mid_x2) { e.phase = decay_mid_x2; }
|
||||
// if decay phase is done, transition to sustain phase
|
||||
if (e.phase == decay_mid_x2) {
|
||||
e.state = sustain;
|
||||
e.phase = 0;
|
||||
e.level = e.sustain_level;
|
||||
}
|
||||
}
|
||||
// while sustain phase: if note off is triggered, transition to release phase
|
||||
if (e.state == sustain && (!gate || e.skip_sustain)) {
|
||||
e.state = release1;
|
||||
e.level = e.sustain_level;
|
||||
}
|
||||
// release 1st half
|
||||
if (e.state == release1) {
|
||||
// while in release phase
|
||||
if (e.phase < release_mid_x1) {
|
||||
e.level = tx_lerp(0, e.sustain_level, (float)release_mid_x1, e.release1_level,
|
||||
(float)e.phase);
|
||||
e.phase += 1;
|
||||
}
|
||||
// reset phase if parameter was changed
|
||||
if (e.phase > release_mid_x1) { e.phase = release_mid_x1; }
|
||||
// transition to 2nd release half
|
||||
if (e.phase == release_mid_x1) {
|
||||
e.phase = 0;
|
||||
e.state = release2;
|
||||
}
|
||||
}
|
||||
// release 2nd half
|
||||
if (e.state == release2) {
|
||||
// while in release phase
|
||||
if (e.phase < release_mid_x2) {
|
||||
e.level =
|
||||
tx_lerp(0, e.release1_level, (float)release_mid_x2, 0, (float)e.phase);
|
||||
e.phase += 1;
|
||||
}
|
||||
// reset phase if parameter was changed
|
||||
if (e.phase > release_mid_x2) { e.phase = release_mid_x2; }
|
||||
// reset
|
||||
if (e.phase == release_mid_x2) {
|
||||
e.phase = 0;
|
||||
e.state = idle;
|
||||
e.level = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// smooth output
|
||||
e.h3 = e.h2;
|
||||
e.h2 = e.h1;
|
||||
e.h1 = e.level;
|
||||
|
||||
return (e.h1 + e.h2 + e.h3) / 3.f;
|
||||
}
|
||||
|
||||
//////////////
|
||||
// OPERATOR //
|
||||
//////////////
|
||||
|
||||
struct tx_operator {
|
||||
tx_envelope envelope;
|
||||
tx_sineosc oscillator;
|
||||
float ratio = 1.f;
|
||||
float amplitude = 1.f;
|
||||
};
|
||||
|
||||
inline void tx_operator_init(tx_operator& op, double samplerate)
|
||||
{
|
||||
tx_envelope_init(op.envelope, samplerate);
|
||||
tx_sineosc_init(op.oscillator, samplerate);
|
||||
}
|
||||
|
||||
inline float tx_operator_process_sample(tx_operator& op, bool gate, bool trigger,
|
||||
float frequency, float velocity, float pm = 0.f)
|
||||
{
|
||||
float env = tx_envelope_process_sample(op.envelope, gate, trigger);
|
||||
|
||||
// drifts and sounds better!
|
||||
if (op.envelope.state != idle) {
|
||||
float osc = tx_sineosc_process_sample(op.oscillator, trigger, frequency, pm);
|
||||
return osc * env * velocity;
|
||||
} else {
|
||||
return 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
////////////
|
||||
// VOICE //
|
||||
////////////
|
||||
|
||||
constexpr float MOD_INDEX_COEFF = 4.f;
|
||||
|
||||
struct tx_state {
|
||||
float additional_pitch_mod = 0.f; // modulates pitch in frequency
|
||||
int algorithm = 0;
|
||||
float pitch_env_amt = 0.f;
|
||||
float pitch_mod = 0.f;
|
||||
float feedback_amt = 0.f;
|
||||
float bit_resolution = 12.f;
|
||||
tx_sineosc feedback_osc;
|
||||
tx_envelope pitch_env;
|
||||
tx_operator op1;
|
||||
tx_operator op2;
|
||||
tx_operator op3;
|
||||
};
|
||||
|
||||
inline void tx_voice_init(tx_state& s, double samplerate)
|
||||
{
|
||||
tx_sineosc_init(s.feedback_osc, samplerate);
|
||||
tx_envelope_init(s.pitch_env, samplerate);
|
||||
tx_operator_init(s.op1, samplerate);
|
||||
tx_operator_init(s.op2, samplerate);
|
||||
tx_operator_init(s.op3, samplerate);
|
||||
}
|
||||
|
||||
inline void tx_voice_process_block(tx_state& t, voice_state& s, float** audio,
|
||||
size_t num_frames,
|
||||
const vector<audio_buffer<float>>& mods = {})
|
||||
{
|
||||
float frequency =
|
||||
midi_to_frequency(s.midi_note + t.pitch_mod + t.additional_pitch_mod);
|
||||
|
||||
for (int i = 0; i < num_frames; i++) {
|
||||
|
||||
voice_process_event_for_frame(s, i);
|
||||
|
||||
float pitch_env_signal =
|
||||
tx_envelope_process_sample(t.pitch_env, s.gate, s.trigger) * t.pitch_env_amt;
|
||||
float pitched_freq = frequency + pitch_env_signal;
|
||||
|
||||
float output = 0.f;
|
||||
|
||||
// mix operator signals according to selected algorithm
|
||||
if (t.algorithm == 0) {
|
||||
float fb_freq = frequency * t.op3.ratio;
|
||||
float fb_mod_index = (t.feedback_amt * MOD_INDEX_COEFF);
|
||||
float fb_signal =
|
||||
tx_sineosc_process_sample(t.feedback_osc, s.trigger, fb_freq) *
|
||||
fb_mod_index;
|
||||
|
||||
float op3_Freq = frequency * t.op3.ratio;
|
||||
float op3_mod_index = (t.op3.amplitude * MOD_INDEX_COEFF);
|
||||
float op3_signal =
|
||||
tx_operator_process_sample(t.op3, s.gate, s.trigger, op3_Freq, s.velocity,
|
||||
fb_signal) *
|
||||
op3_mod_index;
|
||||
|
||||
float op2_freq = frequency * t.op2.ratio;
|
||||
float op2_mod_index = (t.op2.amplitude * MOD_INDEX_COEFF);
|
||||
float op2_signal =
|
||||
tx_operator_process_sample(t.op2, s.gate, s.trigger, op2_freq, s.velocity,
|
||||
op3_signal) *
|
||||
op2_mod_index;
|
||||
|
||||
float op1_freq = frequency * t.op1.ratio;
|
||||
output = tx_operator_process_sample(t.op1, s.gate, s.trigger, op1_freq,
|
||||
s.velocity, op2_signal) *
|
||||
t.op1.amplitude;
|
||||
} else if (t.algorithm == 1) {
|
||||
float fb_freq = frequency * t.op3.ratio;
|
||||
float fb_mod_index = (t.feedback_amt * MOD_INDEX_COEFF);
|
||||
float fb_signal =
|
||||
tx_sineosc_process_sample(t.feedback_osc, s.trigger, fb_freq) *
|
||||
fb_mod_index;
|
||||
|
||||
float op3_freq = frequency * t.op3.ratio;
|
||||
float op3_signal =
|
||||
tx_operator_process_sample(t.op3, s.gate, s.trigger, op3_freq, s.velocity,
|
||||
fb_signal) *
|
||||
t.op3.amplitude;
|
||||
|
||||
float op2_freq = frequency * t.op2.ratio;
|
||||
float op2_mod_index = (t.op2.amplitude * MOD_INDEX_COEFF);
|
||||
float op2_signal = tx_operator_process_sample(t.op2, s.gate, s.trigger,
|
||||
op2_freq, s.velocity) *
|
||||
op2_mod_index;
|
||||
|
||||
float op1_freq = frequency * t.op1.ratio;
|
||||
float op1_signal =
|
||||
tx_operator_process_sample(t.op1, s.gate, s.trigger, op1_freq, s.velocity,
|
||||
op2_signal) *
|
||||
t.op1.amplitude;
|
||||
|
||||
output = op1_signal + op3_signal;
|
||||
} else if (t.algorithm == 2) {
|
||||
float fb_freq = frequency * t.op3.ratio;
|
||||
float fb_mod_index = (t.feedback_amt * MOD_INDEX_COEFF);
|
||||
float fb_signal =
|
||||
tx_sineosc_process_sample(t.feedback_osc, s.trigger, fb_freq) *
|
||||
fb_mod_index;
|
||||
|
||||
float op3_freq = frequency * t.op3.ratio;
|
||||
float op3_signal =
|
||||
tx_operator_process_sample(t.op3, s.gate, s.trigger, op3_freq, s.velocity,
|
||||
fb_signal) *
|
||||
t.op3.amplitude;
|
||||
|
||||
float op2_freq = frequency * t.op2.ratio;
|
||||
float op2_signal = tx_operator_process_sample(t.op2, s.gate, s.trigger,
|
||||
op2_freq, s.velocity) *
|
||||
t.op2.amplitude;
|
||||
|
||||
float op1_freq = frequency * t.op1.ratio;
|
||||
float op1_signal = tx_operator_process_sample(t.op1, s.gate, s.trigger,
|
||||
op1_freq, s.velocity) *
|
||||
t.op1.amplitude;
|
||||
|
||||
output = op1_signal + op2_signal + op3_signal;
|
||||
} else if (t.algorithm == 3) {
|
||||
float fb_freq = frequency * t.op3.ratio;
|
||||
float fb_mod_index = (t.feedback_amt * MOD_INDEX_COEFF);
|
||||
float fb_signal =
|
||||
tx_sineosc_process_sample(t.feedback_osc, s.trigger, fb_freq) *
|
||||
fb_mod_index;
|
||||
|
||||
float op3_freq = frequency * t.op3.ratio;
|
||||
float op3_mod_index = (t.op3.amplitude * MOD_INDEX_COEFF);
|
||||
float op3_signal =
|
||||
tx_operator_process_sample(t.op3, s.gate, s.trigger, op3_freq, s.velocity,
|
||||
fb_signal) *
|
||||
op3_mod_index;
|
||||
|
||||
float op2_freq = frequency * t.op2.ratio;
|
||||
float op2_mod_index = (t.op2.amplitude * MOD_INDEX_COEFF);
|
||||
float op2_signal = tx_operator_process_sample(t.op2, s.gate, s.trigger,
|
||||
op2_freq, s.velocity) *
|
||||
op2_mod_index;
|
||||
|
||||
float op1_freq = frequency * t.op1.ratio;
|
||||
output = tx_operator_process_sample(t.op1, s.gate, s.trigger, op1_freq,
|
||||
s.velocity, op2_signal + op3_signal) *
|
||||
t.op1.amplitude;
|
||||
}
|
||||
|
||||
// reset trigger
|
||||
s.trigger = false;
|
||||
|
||||
float res = powf(2, t.bit_resolution);
|
||||
output = roundf(output * res) / res;
|
||||
|
||||
audio[0][i] += output / 3.;
|
||||
audio[1][i] = audio[0][i];
|
||||
}
|
||||
}
|
||||
|
||||
enum tx_parameter {
|
||||
BIT_RESOLUTION = 0,
|
||||
FEEDBACKOSC_PHASE_RESOLUTION,
|
||||
FEEDBACK,
|
||||
ALGORITHM,
|
||||
|
||||
PITCH_ENVELOPE_AMOUNT,
|
||||
PITCH_ENVELOPE_SKIP_SUSTAIN,
|
||||
PITCH_ENVELOPE_ATTACK1_RATE,
|
||||
PITCH_ENVELOPE_ATTACK1_LEVEL,
|
||||
PITCH_ENVELOPE_ATTACK2_RATE,
|
||||
PITCH_ENVELOPE_HOLD_RATE,
|
||||
PITCH_ENVELOPE_DECAY1_RATE,
|
||||
PITCH_ENVELOPE_DECAY1_LEVEL,
|
||||
PITCH_ENVELOPE_DECAY2_RATE,
|
||||
PITCH_ENVELOPE_SUSTAIN_LEVEL,
|
||||
PITCH_ENVELOPE_RELEASE1_RATE,
|
||||
PITCH_ENVELOPE_RELEASE1_LEVEL,
|
||||
PITCH_ENVELOPE_RELEASE2_RATE,
|
||||
|
||||
OP1_RATIO,
|
||||
OP1_AMPLITUDE,
|
||||
OP1_PHASE_RESOLUTION,
|
||||
OP1_ENVELOPE_SKIP_SUSTAIN,
|
||||
OP1_ENVELOPE_ATTACK1_RATE,
|
||||
OP1_ENVELOPE_ATTACK1_LEVEL,
|
||||
OP1_ENVELOPE_ATTACK2_RATE,
|
||||
OP1_ENVELOPE_HOLD_RATE,
|
||||
OP1_ENVELOPE_DECAY1_RATE,
|
||||
OP1_ENVELOPE_DECAY1_LEVEL,
|
||||
OP1_ENVELOPE_DECAY2_RATE,
|
||||
OP1_ENVELOPE_SUSTAIN_LEVEL,
|
||||
OP1_ENVELOPE_RELEASE1_RATE,
|
||||
OP1_ENVELOPE_RELEASE1_LEVEL,
|
||||
OP1_ENVELOPE_RELEASE2_RATE,
|
||||
|
||||
OP2_RATIO,
|
||||
OP2_AMPLITUDE,
|
||||
OP2_PHASE_RESOLUTION,
|
||||
OP2_ENVELOPE_SKIP_SUSTAIN,
|
||||
OP2_ENVELOPE_ATTACK1_RATE,
|
||||
OP2_ENVELOPE_ATTACK1_LEVEL,
|
||||
OP2_ENVELOPE_ATTACK2_RATE,
|
||||
OP2_ENVELOPE_HOLD_RATE,
|
||||
OP2_ENVELOPE_DECAY1_RATE,
|
||||
OP2_ENVELOPE_DECAY1_LEVEL,
|
||||
OP2_ENVELOPE_DECAY2_RATE,
|
||||
OP2_ENVELOPE_SUSTAIN_LEVEL,
|
||||
OP2_ENVELOPE_RELEASE1_RATE,
|
||||
OP2_ENVELOPE_RELEASE1_LEVEL,
|
||||
OP2_ENVELOPE_RELEASE2_RATE,
|
||||
|
||||
OP3_RATIO,
|
||||
OP3_AMPLITUDE,
|
||||
OP3_PHASE_RESOLUTION,
|
||||
OP3_ENVELOPE_SKIP_SUSTAIN,
|
||||
OP3_ENVELOPE_ATTACK1_RATE,
|
||||
OP3_ENVELOPE_ATTACK1_LEVEL,
|
||||
OP3_ENVELOPE_ATTACK2_RATE,
|
||||
OP3_ENVELOPE_HOLD_RATE,
|
||||
OP3_ENVELOPE_DECAY1_RATE,
|
||||
OP3_ENVELOPE_DECAY1_LEVEL,
|
||||
OP3_ENVELOPE_DECAY2_RATE,
|
||||
OP3_ENVELOPE_SUSTAIN_LEVEL,
|
||||
OP3_ENVELOPE_RELEASE1_RATE,
|
||||
OP3_ENVELOPE_RELEASE1_LEVEL,
|
||||
OP3_ENVELOPE_RELEASE2_RATE,
|
||||
};
|
||||
|
||||
struct tx_parameter_mapping {
|
||||
float range_min;
|
||||
float range_max;
|
||||
float exponent;
|
||||
tx_parameter parameter;
|
||||
|
||||
float apply(float _input) const
|
||||
{
|
||||
if (range_min == range_max && exponent == 1.f) return _input;
|
||||
|
||||
return powf(_input, exponent) * (range_max - range_min) + range_min;
|
||||
}
|
||||
};
|
||||
|
||||
struct tx_synth {
|
||||
voice_allocator allocator;
|
||||
array<tx_state, MAX_VOICES> voices;
|
||||
};
|
||||
|
||||
inline void tx_synth_init(tx_synth& s, double samplerate)
|
||||
{
|
||||
voice_allocator_init(s.allocator);
|
||||
for (int i = 0; i < MAX_VOICES; i++) { tx_voice_init(s.voices[i], samplerate); }
|
||||
}
|
||||
|
||||
inline void tx_synth_process_block(tx_synth& s, float** audio, size_t num_frames,
|
||||
const vector<midi_event>& midi_events,
|
||||
const vector<audio_buffer<float>>& mods = {})
|
||||
{
|
||||
for (int i = 0; i < num_frames; i++) {
|
||||
audio[0][i] = audio[1][i] = 0.f;
|
||||
} // clear audio buffers
|
||||
|
||||
voice_allocator_process_block(s.allocator, midi_events);
|
||||
|
||||
for (int i = 0; i < s.allocator.active_voice_count; i++) {
|
||||
tx_voice_process_block(s.voices[i], s.allocator.voices[i], audio, num_frames,
|
||||
mods);
|
||||
}
|
||||
}
|
||||
|
||||
inline void tx_apply_parameter_mapping(array<tx_state, MAX_VOICES>& v,
|
||||
tx_parameter_mapping& m, float value)
|
||||
{
|
||||
if (m.range_min != m.range_max || m.exponent != 1.f)
|
||||
value = powf(value, m.exponent) * (m.range_max - m.range_min) + m.range_min;
|
||||
|
||||
for (int i = 0; i < MAX_VOICES; i++) {
|
||||
tx_state& s = v[i];
|
||||
|
||||
switch (m.parameter) {
|
||||
case tx_parameter::BIT_RESOLUTION:
|
||||
s.bit_resolution = value;
|
||||
break;
|
||||
case tx_parameter::FEEDBACKOSC_PHASE_RESOLUTION:
|
||||
s.feedback_osc.phase_resolution = value;
|
||||
break;
|
||||
case tx_parameter::FEEDBACK:
|
||||
s.feedback_amt = value;
|
||||
break;
|
||||
case tx_parameter::ALGORITHM:
|
||||
s.algorithm = value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_AMOUNT:
|
||||
s.pitch_env_amt = value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_SKIP_SUSTAIN:
|
||||
s.pitch_env.skip_sustain = value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_ATTACK1_RATE:
|
||||
s.pitch_env.attack1_rate = value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_ATTACK1_LEVEL:
|
||||
s.pitch_env.attack1_level = value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_ATTACK2_RATE:
|
||||
s.pitch_env.attack2_rate = value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_HOLD_RATE:
|
||||
s.pitch_env.hold_rate = value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_DECAY1_RATE:
|
||||
s.pitch_env.decay1_rate = value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_DECAY1_LEVEL:
|
||||
s.pitch_env.decay1_level = value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_DECAY2_RATE:
|
||||
s.pitch_env.decay2_rate = value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_SUSTAIN_LEVEL:
|
||||
s.pitch_env.sustain_level = value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_RELEASE1_RATE:
|
||||
s.pitch_env.release1_rate = value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_RELEASE1_LEVEL:
|
||||
s.pitch_env.release1_level = value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_RELEASE2_RATE:
|
||||
s.pitch_env.release2_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP1_RATIO:
|
||||
s.op1.ratio = value;
|
||||
break;
|
||||
case tx_parameter::OP1_AMPLITUDE:
|
||||
s.op1.amplitude = value;
|
||||
break;
|
||||
case tx_parameter::OP1_PHASE_RESOLUTION:
|
||||
s.op1.oscillator.phase_resolution = value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_SKIP_SUSTAIN:
|
||||
s.op1.envelope.skip_sustain = value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_ATTACK1_RATE:
|
||||
s.op1.envelope.attack1_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_ATTACK1_LEVEL:
|
||||
s.op1.envelope.attack1_level = value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_ATTACK2_RATE:
|
||||
s.op1.envelope.attack2_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_HOLD_RATE:
|
||||
s.op1.envelope.hold_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_DECAY1_RATE:
|
||||
s.op1.envelope.decay1_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_DECAY1_LEVEL:
|
||||
s.op1.envelope.decay1_level = value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_DECAY2_RATE:
|
||||
s.op1.envelope.decay2_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_SUSTAIN_LEVEL:
|
||||
s.op1.envelope.sustain_level = value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_RELEASE1_RATE:
|
||||
s.op1.envelope.release1_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_RELEASE1_LEVEL:
|
||||
s.op1.envelope.release1_level = value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_RELEASE2_RATE:
|
||||
s.op1.envelope.release2_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP2_RATIO:
|
||||
s.op2.ratio = value;
|
||||
break;
|
||||
case tx_parameter::OP2_AMPLITUDE:
|
||||
s.op2.amplitude = value;
|
||||
break;
|
||||
case tx_parameter::OP2_PHASE_RESOLUTION:
|
||||
s.op2.oscillator.phase_resolution = value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_SKIP_SUSTAIN:
|
||||
s.op2.envelope.skip_sustain = value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_ATTACK1_RATE:
|
||||
s.op2.envelope.attack1_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_ATTACK1_LEVEL:
|
||||
s.op2.envelope.attack1_level = value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_ATTACK2_RATE:
|
||||
s.op2.envelope.attack2_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_HOLD_RATE:
|
||||
s.op2.envelope.hold_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_DECAY1_RATE:
|
||||
s.op2.envelope.decay1_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_DECAY1_LEVEL:
|
||||
s.op2.envelope.decay1_level = value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_DECAY2_RATE:
|
||||
s.op2.envelope.decay2_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_SUSTAIN_LEVEL:
|
||||
s.op2.envelope.sustain_level = value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_RELEASE1_RATE:
|
||||
s.op2.envelope.release1_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_RELEASE1_LEVEL:
|
||||
s.op2.envelope.release1_level = value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_RELEASE2_RATE:
|
||||
s.op2.envelope.release2_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP3_RATIO:
|
||||
s.op3.ratio = value;
|
||||
break;
|
||||
case tx_parameter::OP3_AMPLITUDE:
|
||||
s.op3.amplitude = value;
|
||||
break;
|
||||
case tx_parameter::OP3_PHASE_RESOLUTION:
|
||||
s.op3.oscillator.phase_resolution = value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_SKIP_SUSTAIN:
|
||||
s.op3.envelope.skip_sustain = value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_ATTACK1_RATE:
|
||||
s.op3.envelope.attack1_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_ATTACK1_LEVEL:
|
||||
s.op3.envelope.attack1_level = value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_ATTACK2_RATE:
|
||||
s.op3.envelope.attack2_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_HOLD_RATE:
|
||||
s.op3.envelope.hold_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_DECAY1_RATE:
|
||||
s.op3.envelope.decay1_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_DECAY1_LEVEL:
|
||||
s.op3.envelope.decay1_level = value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_DECAY2_RATE:
|
||||
s.op3.envelope.decay2_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_SUSTAIN_LEVEL:
|
||||
s.op3.envelope.sustain_level = value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_RELEASE1_RATE:
|
||||
s.op3.envelope.release1_rate = value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_RELEASE1_LEVEL:
|
||||
s.op3.envelope.release1_level = value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_RELEASE2_RATE:
|
||||
s.op3.envelope.release2_rate = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void tx_apply_parameter_mappings(array<tx_state, MAX_VOICES>& v,
|
||||
std::vector<tx_parameter_mapping>& m, float value)
|
||||
{
|
||||
for (int i = 0; i < m.size(); i++) { tx_apply_parameter_mapping(v, m[i], value); }
|
||||
}
|
||||
} // namespace trnr
|
||||
@@ -1,238 +0,0 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
|
||||
namespace trnr {
|
||||
|
||||
enum env_state {
|
||||
idle = 0,
|
||||
attack1,
|
||||
attack2,
|
||||
hold,
|
||||
decay1,
|
||||
decay2,
|
||||
sustain,
|
||||
release1,
|
||||
release2
|
||||
};
|
||||
|
||||
class tx_envelope {
|
||||
public:
|
||||
env_state state = idle;
|
||||
float attack1_rate = 0;
|
||||
float attack1_level = 0;
|
||||
float attack2_rate = 0;
|
||||
float hold_rate = 0;
|
||||
float decay1_rate = 0;
|
||||
float decay1_level = 0;
|
||||
float decay2_rate = 0;
|
||||
float sustain_level = 0;
|
||||
float release1_rate = 0;
|
||||
float release1_level = 0;
|
||||
float release2_rate = 0;
|
||||
bool skip_sustain = false;
|
||||
|
||||
tx_envelope(bool _retrigger = false)
|
||||
: retrigger {_retrigger}
|
||||
{
|
||||
}
|
||||
|
||||
float process_sample(bool gate, bool trigger) { return process_sample<float>(gate, trigger, 0, 0); }
|
||||
|
||||
template <typename t_sample>
|
||||
float process_sample(bool gate, bool trigger, t_sample _attack_mod, t_sample _decay_mod)
|
||||
{
|
||||
size_t attack_mid_x1 = ms_to_samples(attack1_rate + (float)_attack_mod);
|
||||
size_t attack_mid_x2 = ms_to_samples(attack2_rate + (float)_attack_mod);
|
||||
size_t hold_samp = ms_to_samples(hold_rate);
|
||||
size_t decay_mid_x1 = ms_to_samples(decay1_rate + (float)_decay_mod);
|
||||
size_t decay_mid_x2 = ms_to_samples(decay2_rate + (float)_decay_mod);
|
||||
size_t release_mid_x1 = ms_to_samples(release1_rate + (float)_decay_mod);
|
||||
size_t release_mid_x2 = ms_to_samples(release2_rate + (float)_decay_mod);
|
||||
|
||||
// if note on is triggered, transition to attack phase
|
||||
if (trigger) {
|
||||
if (retrigger) start_level = 0.f;
|
||||
else start_level = level;
|
||||
phase = 0;
|
||||
state = attack1;
|
||||
}
|
||||
// attack 1st half
|
||||
if (state == attack1) {
|
||||
// while in attack phase
|
||||
if (phase < attack_mid_x1) {
|
||||
level = lerp(0, start_level, (float)attack_mid_x1, attack1_level, (float)phase);
|
||||
phase += 1;
|
||||
}
|
||||
// reset phase if parameter was changed
|
||||
if (phase > attack_mid_x1) { phase = attack_mid_x1; }
|
||||
// if attack phase is done, transition to decay phase
|
||||
if (phase == attack_mid_x1) {
|
||||
state = attack2;
|
||||
phase = 0;
|
||||
}
|
||||
}
|
||||
// attack 2nd half
|
||||
if (state == attack2) {
|
||||
// while in attack phase
|
||||
if (phase < attack_mid_x2) {
|
||||
level = lerp(0, attack1_level, (float)attack_mid_x2, 1, (float)phase);
|
||||
phase += 1;
|
||||
}
|
||||
// reset phase if parameter was changed
|
||||
if (phase > attack_mid_x2) { phase = attack_mid_x2; }
|
||||
// if attack phase is done, transition to decay phase
|
||||
if (phase == attack_mid_x2) {
|
||||
state = hold;
|
||||
phase = 0;
|
||||
}
|
||||
}
|
||||
// hold
|
||||
if (state == hold) {
|
||||
if (phase < hold_samp) {
|
||||
level = 1.0;
|
||||
phase += 1;
|
||||
}
|
||||
if (phase > hold_samp) { phase = hold_samp; }
|
||||
if (phase == hold_samp) {
|
||||
state = decay1;
|
||||
phase = 0;
|
||||
}
|
||||
}
|
||||
// decay 1st half
|
||||
if (state == decay1) {
|
||||
// while in decay phase
|
||||
if (phase < decay_mid_x1) {
|
||||
level = lerp(0, 1, (float)decay_mid_x1, decay1_level, (float)phase);
|
||||
phase += 1;
|
||||
}
|
||||
// reset phase if parameter was changed
|
||||
if (phase > decay_mid_x1) { phase = decay_mid_x1; }
|
||||
// if decay phase is done, transition to sustain phase
|
||||
if (phase == decay_mid_x1) {
|
||||
state = decay2;
|
||||
phase = 0;
|
||||
}
|
||||
}
|
||||
// decay 2nd half
|
||||
if (state == decay2) {
|
||||
// while in decay phase
|
||||
if (phase < decay_mid_x2) {
|
||||
level = lerp(0, decay1_level, (float)decay_mid_x2, sustain_level, (float)phase);
|
||||
phase += 1;
|
||||
}
|
||||
// reset phase if parameter was changed
|
||||
if (phase > decay_mid_x2) { phase = decay_mid_x2; }
|
||||
// if decay phase is done, transition to sustain phase
|
||||
if (phase == decay_mid_x2) {
|
||||
state = sustain;
|
||||
phase = 0;
|
||||
level = sustain_level;
|
||||
}
|
||||
}
|
||||
// while sustain phase: if note off is triggered, transition to release phase
|
||||
if (state == sustain && (!gate || skip_sustain)) {
|
||||
state = release1;
|
||||
level = sustain_level;
|
||||
}
|
||||
// release 1st half
|
||||
if (state == release1) {
|
||||
// while in release phase
|
||||
if (phase < release_mid_x1) {
|
||||
level = lerp(0, sustain_level, (float)release_mid_x1, release1_level, (float)phase);
|
||||
phase += 1;
|
||||
}
|
||||
// reset phase if parameter was changed
|
||||
if (phase > release_mid_x1) { phase = release_mid_x1; }
|
||||
// transition to 2nd release half
|
||||
if (phase == release_mid_x1) {
|
||||
phase = 0;
|
||||
state = release2;
|
||||
}
|
||||
}
|
||||
// release 2nd half
|
||||
if (state == release2) {
|
||||
// while in release phase
|
||||
if (phase < release_mid_x2) {
|
||||
level = lerp(0, release1_level, (float)release_mid_x2, 0, (float)phase);
|
||||
phase += 1;
|
||||
}
|
||||
// reset phase if parameter was changed
|
||||
if (phase > release_mid_x2) { phase = release_mid_x2; }
|
||||
// reset
|
||||
if (phase == release_mid_x2) {
|
||||
phase = 0;
|
||||
state = idle;
|
||||
level = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return smooth(level);
|
||||
}
|
||||
|
||||
bool is_busy() { return state != 0; }
|
||||
|
||||
void set_samplerate(double sampleRate) { this->samplerate = sampleRate; }
|
||||
|
||||
// converts the x/y coordinates of the envelope points as a list for graphical representation.
|
||||
std::array<float, 18> calc_coordinates(float _max_attack, float _max_decay, float _max_release)
|
||||
{
|
||||
|
||||
auto scale = [](float _value, float _max) { return powf(_value / _max, 0.25) * _max; };
|
||||
|
||||
float a_x = 0;
|
||||
float a_y = 0;
|
||||
|
||||
float b_x = scale(attack1_rate, _max_attack / 2);
|
||||
float b_y = attack1_level;
|
||||
|
||||
float c_x = b_x + scale(attack2_rate, _max_attack / 2);
|
||||
float c_y = 1;
|
||||
|
||||
float d_x = c_x + hold_rate;
|
||||
float d_y = 1;
|
||||
|
||||
float e_x = d_x + scale(decay1_rate, _max_decay / 2);
|
||||
float e_y = decay1_level;
|
||||
|
||||
float f_x = e_x + scale(decay2_rate, _max_decay / 2);
|
||||
float f_y = sustain_level;
|
||||
|
||||
float g_x = _max_attack + _max_decay;
|
||||
float g_y = sustain_level;
|
||||
|
||||
float h_x = g_x + scale(release1_rate, _max_decay / 2);
|
||||
float h_y = release1_level;
|
||||
|
||||
float i_x = h_x + scale(release2_rate, _max_decay / 2);
|
||||
float i_y = 0;
|
||||
|
||||
float total = _max_attack + _max_decay + _max_release;
|
||||
|
||||
return {a_x, a_y, b_x / total, b_y, c_x / total, c_y, d_x / total, d_y, e_x / total, e_y,
|
||||
f_x / total, f_y, g_x / total, g_y, h_x / total, h_y, i_x / total, i_y};
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate = 44100.;
|
||||
size_t phase = 0;
|
||||
float level = 0.f;
|
||||
float start_level = 0.f;
|
||||
float h1 = 0.f;
|
||||
float h2 = 0.f;
|
||||
float h3 = 0.f;
|
||||
bool retrigger;
|
||||
|
||||
float lerp(float x1, float y1, float x2, float y2, float x) { return y1 + (((x - x1) * (y2 - y1)) / (x2 - x1)); }
|
||||
|
||||
float smooth(float sample)
|
||||
{
|
||||
h3 = h2;
|
||||
h2 = h1;
|
||||
h1 = sample;
|
||||
|
||||
return (h1 + h2 + h3) / 3.f;
|
||||
}
|
||||
|
||||
size_t ms_to_samples(float ms) { return static_cast<size_t>(ms * samplerate / 1000.f); }
|
||||
};
|
||||
} // namespace trnr
|
||||
@@ -1,49 +0,0 @@
|
||||
#pragma once
|
||||
#include "../clip/wavefolder.h"
|
||||
#include "tx_envelope.h"
|
||||
#include "tx_sineosc.h"
|
||||
|
||||
namespace trnr {
|
||||
class tx_operator {
|
||||
public:
|
||||
tx_operator()
|
||||
: ratio {1}
|
||||
, amplitude {1.0f}
|
||||
, envelope_enabled {true}
|
||||
, velocity_enabled {true}
|
||||
{
|
||||
}
|
||||
|
||||
tx_envelope envelope;
|
||||
tx_sineosc oscillator;
|
||||
wavefolder folder;
|
||||
float ratio;
|
||||
float amplitude;
|
||||
bool envelope_enabled;
|
||||
bool velocity_enabled;
|
||||
|
||||
float process_sample(const bool& gate, const bool& trigger, const float& frequency, const float& velocity,
|
||||
const float& pm = 0)
|
||||
{
|
||||
float env = 1.f;
|
||||
|
||||
if (envelope_enabled) {
|
||||
env = envelope.process_sample(gate, trigger);
|
||||
if (!envelope.is_busy()) return 0.f;
|
||||
}
|
||||
|
||||
float osc = oscillator.process_sample(trigger, frequency, pm);
|
||||
folder.process_sample(osc);
|
||||
|
||||
float adjusted_velocity = velocity_enabled ? velocity : 1.f;
|
||||
|
||||
return osc * env * adjusted_velocity;
|
||||
}
|
||||
|
||||
void set_samplerate(double samplerate)
|
||||
{
|
||||
this->envelope.set_samplerate(samplerate);
|
||||
this->oscillator.set_samplerate(samplerate);
|
||||
}
|
||||
};
|
||||
} // namespace trnr
|
||||
@@ -1,89 +0,0 @@
|
||||
#include <cmath>
|
||||
namespace trnr {
|
||||
enum tx_parameter {
|
||||
BIT_RESOLUTION = 0,
|
||||
FEEDBACKOSC_PHASE_RESOLUTION,
|
||||
FEEDBACK,
|
||||
ALGORITHM,
|
||||
|
||||
PITCH_ENVELOPE_AMOUNT,
|
||||
PITCH_ENVELOPE_SKIP_SUSTAIN,
|
||||
PITCH_ENVELOPE_ATTACK1_RATE,
|
||||
PITCH_ENVELOPE_ATTACK1_LEVEL,
|
||||
PITCH_ENVELOPE_ATTACK2_RATE,
|
||||
PITCH_ENVELOPE_HOLD_RATE,
|
||||
PITCH_ENVELOPE_DECAY1_RATE,
|
||||
PITCH_ENVELOPE_DECAY1_LEVEL,
|
||||
PITCH_ENVELOPE_DECAY2_RATE,
|
||||
PITCH_ENVELOPE_SUSTAIN_LEVEL,
|
||||
PITCH_ENVELOPE_RELEASE1_RATE,
|
||||
PITCH_ENVELOPE_RELEASE1_LEVEL,
|
||||
PITCH_ENVELOPE_RELEASE2_RATE,
|
||||
|
||||
OP1_RATIO,
|
||||
OP1_AMPLITUDE,
|
||||
OP1_PHASE_RESOLUTION,
|
||||
OP1_ENVELOPE_SKIP_SUSTAIN,
|
||||
OP1_ENVELOPE_ATTACK1_RATE,
|
||||
OP1_ENVELOPE_ATTACK1_LEVEL,
|
||||
OP1_ENVELOPE_ATTACK2_RATE,
|
||||
OP1_ENVELOPE_HOLD_RATE,
|
||||
OP1_ENVELOPE_DECAY1_RATE,
|
||||
OP1_ENVELOPE_DECAY1_LEVEL,
|
||||
OP1_ENVELOPE_DECAY2_RATE,
|
||||
OP1_ENVELOPE_SUSTAIN_LEVEL,
|
||||
OP1_ENVELOPE_RELEASE1_RATE,
|
||||
OP1_ENVELOPE_RELEASE1_LEVEL,
|
||||
OP1_ENVELOPE_RELEASE2_RATE,
|
||||
|
||||
OP2_RATIO,
|
||||
OP2_AMPLITUDE,
|
||||
OP2_PHASE_RESOLUTION,
|
||||
OP2_ENVELOPE_SKIP_SUSTAIN,
|
||||
OP2_ENVELOPE_ATTACK1_RATE,
|
||||
OP2_ENVELOPE_ATTACK1_LEVEL,
|
||||
OP2_ENVELOPE_ATTACK2_RATE,
|
||||
OP2_ENVELOPE_HOLD_RATE,
|
||||
OP2_ENVELOPE_DECAY1_RATE,
|
||||
OP2_ENVELOPE_DECAY1_LEVEL,
|
||||
OP2_ENVELOPE_DECAY2_RATE,
|
||||
OP2_ENVELOPE_SUSTAIN_LEVEL,
|
||||
OP2_ENVELOPE_RELEASE1_RATE,
|
||||
OP2_ENVELOPE_RELEASE1_LEVEL,
|
||||
OP2_ENVELOPE_RELEASE2_RATE,
|
||||
|
||||
OP3_RATIO,
|
||||
OP3_AMPLITUDE,
|
||||
OP3_PHASE_RESOLUTION,
|
||||
OP3_ENVELOPE_SKIP_SUSTAIN,
|
||||
OP3_ENVELOPE_ATTACK1_RATE,
|
||||
OP3_ENVELOPE_ATTACK1_LEVEL,
|
||||
OP3_ENVELOPE_ATTACK2_RATE,
|
||||
OP3_ENVELOPE_HOLD_RATE,
|
||||
OP3_ENVELOPE_DECAY1_RATE,
|
||||
OP3_ENVELOPE_DECAY1_LEVEL,
|
||||
OP3_ENVELOPE_DECAY2_RATE,
|
||||
OP3_ENVELOPE_SUSTAIN_LEVEL,
|
||||
OP3_ENVELOPE_RELEASE1_RATE,
|
||||
OP3_ENVELOPE_RELEASE1_LEVEL,
|
||||
OP3_ENVELOPE_RELEASE2_RATE,
|
||||
};
|
||||
|
||||
struct tx_parameter_mapping {
|
||||
float range_min;
|
||||
float range_max;
|
||||
float exponent;
|
||||
tx_parameter parameter;
|
||||
|
||||
tx_parameter_mapping(float _range_min, float _range_max, float _exponent, tx_parameter _parameter)
|
||||
: range_min(_range_min), range_max(_range_max), exponent(_exponent), parameter(_parameter)
|
||||
{}
|
||||
|
||||
float apply(float _input) const
|
||||
{
|
||||
if (range_min == range_max && exponent == 1.f) return _input;
|
||||
|
||||
return powf(_input, exponent) * (range_max - range_min) + range_min;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
#pragma once
|
||||
#include <cmath>
|
||||
#include <random>
|
||||
|
||||
namespace trnr {
|
||||
|
||||
class tx_sineosc {
|
||||
public:
|
||||
bool phase_reset;
|
||||
|
||||
tx_sineosc()
|
||||
: samplerate {44100}
|
||||
, phase_resolution {16.f}
|
||||
, phase {0.}
|
||||
, history {0.}
|
||||
, phase_reset {false}
|
||||
{
|
||||
randomize_phase();
|
||||
}
|
||||
|
||||
void set_phase_resolution(float res) { phase_resolution = powf(2, res); }
|
||||
|
||||
float process_sample(bool trigger, float frequency, float phase_modulation = 0.f)
|
||||
{
|
||||
if (trigger) {
|
||||
if (phase_reset) phase = 0.f;
|
||||
else randomize_phase();
|
||||
}
|
||||
|
||||
float lookup_phase = phase + phase_modulation;
|
||||
wrap(lookup_phase);
|
||||
phase += frequency / samplerate;
|
||||
wrap(phase);
|
||||
|
||||
redux(lookup_phase);
|
||||
|
||||
float output = sine(lookup_phase * 4096.);
|
||||
|
||||
filter(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
void set_samplerate(double _samplerate) { this->samplerate = _samplerate; }
|
||||
|
||||
void randomize_phase()
|
||||
{
|
||||
std::random_device random;
|
||||
std::mt19937 gen(random());
|
||||
std::uniform_real_distribution<> dis(0.0, 1.0);
|
||||
phase = dis(gen);
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate;
|
||||
float phase_resolution;
|
||||
float phase;
|
||||
float history;
|
||||
|
||||
float sine(float x)
|
||||
{
|
||||
// x is scaled 0<=x<4096
|
||||
const float a = -0.40319426317E-08;
|
||||
const float b = 0.21683205691E+03;
|
||||
const float c = 0.28463350538E-04;
|
||||
const float d = -0.30774648337E-02;
|
||||
float y;
|
||||
|
||||
bool negate = false;
|
||||
if (x > 2048) {
|
||||
negate = true;
|
||||
x -= 2048;
|
||||
}
|
||||
if (x > 1024) x = 2048 - x;
|
||||
y = (a + x) / (b + c * x * x) + d * x;
|
||||
if (negate) return (float)(-y);
|
||||
else return (float)y;
|
||||
}
|
||||
|
||||
float wrap(float& phase)
|
||||
{
|
||||
while (phase < 0.) phase += 1.;
|
||||
|
||||
while (phase >= 1.) phase -= 1.;
|
||||
|
||||
return phase;
|
||||
}
|
||||
|
||||
float filter(float& value)
|
||||
{
|
||||
value = 0.5 * (value + history);
|
||||
history = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
float redux(float& value)
|
||||
{
|
||||
value = static_cast<int>(value * phase_resolution) / phase_resolution;
|
||||
return value;
|
||||
}
|
||||
};
|
||||
} // namespace trnr
|
||||
389
synth/tx_voice.h
389
synth/tx_voice.h
@@ -1,389 +0,0 @@
|
||||
#pragma once
|
||||
#include "../util/audio_math.h"
|
||||
#include "ivoice.h"
|
||||
#include "tx_envelope.h"
|
||||
#include "tx_operator.h"
|
||||
#include "tx_sineosc.h"
|
||||
#include "tx_parameter_mapping.h"
|
||||
|
||||
namespace trnr {
|
||||
|
||||
enum mod_dest {
|
||||
mod_dest_out = 0,
|
||||
mod_dest_fm,
|
||||
mod_dest_am
|
||||
};
|
||||
|
||||
template <typename t_sample>
|
||||
class tx_voice : public ivoice<t_sample> {
|
||||
public:
|
||||
tx_voice()
|
||||
: pitch_env_amt {0.f}
|
||||
, feedback_amt {0.f}
|
||||
, bit_resolution(12.f)
|
||||
{
|
||||
set_glide_time(0.f);
|
||||
}
|
||||
|
||||
bool gate = false;
|
||||
bool trigger = false;
|
||||
int midi_note = 0;
|
||||
float velocity = 1.f;
|
||||
float additional_pitch_mod = 0.f; // modulates pitch in frequency
|
||||
|
||||
mod_dest op2_dest = mod_dest_fm;
|
||||
mod_dest op3_dest = mod_dest_fm;
|
||||
|
||||
float pitch_env_amt;
|
||||
float feedback_amt;
|
||||
float bit_resolution;
|
||||
tx_sineosc feedback_osc;
|
||||
tx_envelope pitch_env;
|
||||
tx_operator op1;
|
||||
tx_operator op2;
|
||||
tx_operator op3;
|
||||
|
||||
void set_glide_time(float time_ms) { glide = 1 - exp(-1.0 / (time_ms * samplerate / 1000.f)); }
|
||||
|
||||
void note_on(int _note, float _velocity) override
|
||||
{
|
||||
this->gate = true;
|
||||
this->trigger = true;
|
||||
midi_note = _note;
|
||||
velocity = _velocity;
|
||||
}
|
||||
|
||||
void note_off() override { this->gate = false; }
|
||||
|
||||
// modulates the pitch in semitones
|
||||
void modulate_pitch(float _pitch) override { this->pitch_mod = _pitch; }
|
||||
|
||||
void process_samples(t_sample** _outputs, int _start_index, int _block_size) override
|
||||
{
|
||||
target_frequency = midi_to_frequency(midi_note + pitch_mod + additional_pitch_mod);
|
||||
|
||||
for (int s = _start_index; s < _start_index + _block_size; s++) {
|
||||
|
||||
// calculate moving average for portamento
|
||||
current_frequency = (1 - glide) * current_frequency + glide * target_frequency;
|
||||
|
||||
float pitch_env_signal = pitch_env.process_sample(gate, trigger) * pitch_env_amt;
|
||||
float pitched_freq = current_frequency + pitch_env_signal;
|
||||
|
||||
float signal = process_operators(pitched_freq);
|
||||
|
||||
// reset trigger
|
||||
trigger = false;
|
||||
|
||||
redux(signal, bit_resolution);
|
||||
|
||||
_outputs[0][s] += signal / 3.;
|
||||
_outputs[1][s] = _outputs[0][s];
|
||||
}
|
||||
}
|
||||
|
||||
bool is_busy() override
|
||||
{
|
||||
return gate || op1.envelope.is_busy() || op2.envelope.is_busy() || op3.envelope.is_busy();
|
||||
}
|
||||
|
||||
void set_samplerate(double _samplerate) override
|
||||
{
|
||||
samplerate = _samplerate;
|
||||
pitch_env.set_samplerate(_samplerate);
|
||||
feedback_osc.set_samplerate(_samplerate);
|
||||
op1.set_samplerate(_samplerate);
|
||||
op2.set_samplerate(_samplerate);
|
||||
op3.set_samplerate(_samplerate);
|
||||
}
|
||||
|
||||
void set_phase_reset(bool phase_reset)
|
||||
{
|
||||
op1.oscillator.phase_reset = phase_reset;
|
||||
op2.oscillator.phase_reset = phase_reset;
|
||||
op3.oscillator.phase_reset = phase_reset;
|
||||
feedback_osc.phase_reset = phase_reset;
|
||||
}
|
||||
|
||||
void update_parameters(float _value, const std::vector<tx_parameter_mapping>& _mappings)
|
||||
{
|
||||
for (const tx_parameter_mapping& mapping : _mappings) {
|
||||
float normalized = mapping.apply(_value);
|
||||
map_parameter(mapping, normalized);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate;
|
||||
const float MOD_INDEX_COEFF = 4.f;
|
||||
float pitch_mod = 0.f; // modulates pitch in semi-tones
|
||||
|
||||
float current_frequency;
|
||||
float target_frequency;
|
||||
float glide;
|
||||
|
||||
float process_op3(const float frequency)
|
||||
{
|
||||
float fb_freq = frequency * op3.ratio;
|
||||
float fb_mod_index = feedback_amt * MOD_INDEX_COEFF;
|
||||
float fb_signal = feedback_osc.process_sample(trigger, fb_freq) * fb_mod_index;
|
||||
|
||||
float op3_Freq = frequency * op3.ratio;
|
||||
return op3.process_sample(gate, trigger, op3_Freq, velocity, fb_signal) * op3.amplitude;
|
||||
}
|
||||
|
||||
float process_op2(const float frequency, const float modulator)
|
||||
{
|
||||
// if patched, op3 modulates the phase of op2
|
||||
float pm = op3_dest == mod_dest_fm ? modulator : 0.f;
|
||||
|
||||
float adjusted_freq = frequency * op2.ratio;
|
||||
float signal = op2.process_sample(gate, trigger, adjusted_freq, velocity, pm * MOD_INDEX_COEFF) * op2.amplitude;
|
||||
|
||||
// if patched, op3 modulated the amplitude of op2
|
||||
if (op3_dest == mod_dest_am) ring_mod(signal, modulator, op3.amplitude);
|
||||
|
||||
return signal;
|
||||
}
|
||||
|
||||
float process_op1(const float frequency, const float modulator)
|
||||
{
|
||||
// if patched, op2 modulates the phase of op1
|
||||
float pm = op2_dest == mod_dest_fm ? modulator : 0.f;
|
||||
|
||||
float op1_freq = frequency * op1.ratio;
|
||||
float signal = op1.process_sample(gate, trigger, op1_freq, velocity, pm * MOD_INDEX_COEFF) * op1.amplitude;
|
||||
|
||||
// if patched, op2 modulates the amplitude of op1
|
||||
if (op2_dest == mod_dest_am) ring_mod(signal, modulator, op2.amplitude);
|
||||
|
||||
return signal;
|
||||
}
|
||||
|
||||
float process_operators(float frequency)
|
||||
{
|
||||
float op3_signal = process_op3(frequency);
|
||||
|
||||
float op2_signal = process_op2(frequency, op3_signal);
|
||||
|
||||
float op1_signal = process_op1(frequency, op2_signal);
|
||||
|
||||
float signal_mix = op1_signal;
|
||||
if (op3_dest == mod_dest_out) { signal_mix += op3_signal; }
|
||||
if (op2_dest == mod_dest_out) { signal_mix += op2_signal; }
|
||||
|
||||
return signal_mix;
|
||||
}
|
||||
|
||||
void ring_mod(float& carrier, float modulator, float blend)
|
||||
{
|
||||
float dry_lvl = 1.f - blend;
|
||||
float wet_lvl = blend;
|
||||
|
||||
float dry_signal = carrier;
|
||||
float wet_signal = carrier * modulator * 2.0f;
|
||||
|
||||
carrier = dry_lvl * dry_signal + wet_lvl * wet_signal;
|
||||
}
|
||||
|
||||
float redux(float& value, float resolution)
|
||||
{
|
||||
float res = powf(2, resolution);
|
||||
value = roundf(value * res) / res;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void map_parameter(const tx_parameter_mapping& _mapping, const float _value)
|
||||
{
|
||||
switch (_mapping.parameter) {
|
||||
case tx_parameter::BIT_RESOLUTION:
|
||||
bit_resolution = _value;
|
||||
break;
|
||||
case tx_parameter::FEEDBACKOSC_PHASE_RESOLUTION:
|
||||
feedback_osc.set_phase_resolution(_value);
|
||||
break;
|
||||
case tx_parameter::FEEDBACK:
|
||||
feedback_amt = _value;
|
||||
break;
|
||||
case tx_parameter::ALGORITHM:
|
||||
algorithm = _value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_AMOUNT:
|
||||
pitch_env_amt = _value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_SKIP_SUSTAIN:
|
||||
pitch_env.skip_sustain = _value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_ATTACK1_RATE:
|
||||
pitch_env.attack1_rate = _value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_ATTACK1_LEVEL:
|
||||
pitch_env.attack1_level = _value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_ATTACK2_RATE:
|
||||
pitch_env.attack2_rate = _value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_HOLD_RATE:
|
||||
pitch_env.hold_rate = _value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_DECAY1_RATE:
|
||||
pitch_env.decay1_rate = _value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_DECAY1_LEVEL:
|
||||
pitch_env.decay1_level = _value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_DECAY2_RATE:
|
||||
pitch_env.decay2_rate = _value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_SUSTAIN_LEVEL:
|
||||
pitch_env.sustain_level = _value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_RELEASE1_RATE:
|
||||
pitch_env.release1_rate = _value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_RELEASE1_LEVEL:
|
||||
pitch_env.release1_level = _value;
|
||||
break;
|
||||
case tx_parameter::PITCH_ENVELOPE_RELEASE2_RATE:
|
||||
pitch_env.release2_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_RATIO:
|
||||
op1.ratio = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_AMPLITUDE:
|
||||
op1.amplitude = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_PHASE_RESOLUTION:
|
||||
op1.oscillator.set_phase_resolution(_value);
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_SKIP_SUSTAIN:
|
||||
op1.envelope.skip_sustain = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_ATTACK1_RATE:
|
||||
op1.envelope.attack1_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_ATTACK1_LEVEL:
|
||||
op1.envelope.attack1_level = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_ATTACK2_RATE:
|
||||
op1.envelope.attack2_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_HOLD_RATE:
|
||||
op1.envelope.hold_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_DECAY1_RATE:
|
||||
op1.envelope.decay1_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_DECAY1_LEVEL:
|
||||
op1.envelope.decay1_level = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_DECAY2_RATE:
|
||||
op1.envelope.decay2_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_SUSTAIN_LEVEL:
|
||||
op1.envelope.sustain_level = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_RELEASE1_RATE:
|
||||
op1.envelope.release1_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_RELEASE1_LEVEL:
|
||||
op1.envelope.release1_level = _value;
|
||||
break;
|
||||
case tx_parameter::OP1_ENVELOPE_RELEASE2_RATE:
|
||||
op1.envelope.release2_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_RATIO:
|
||||
op2.ratio = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_AMPLITUDE:
|
||||
op2.amplitude = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_PHASE_RESOLUTION:
|
||||
op2.oscillator.set_phase_resolution(_value);
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_SKIP_SUSTAIN:
|
||||
op2.envelope.skip_sustain = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_ATTACK1_RATE:
|
||||
op2.envelope.attack1_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_ATTACK1_LEVEL:
|
||||
op2.envelope.attack1_level = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_ATTACK2_RATE:
|
||||
op2.envelope.attack2_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_HOLD_RATE:
|
||||
op2.envelope.hold_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_DECAY1_RATE:
|
||||
op2.envelope.decay1_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_DECAY1_LEVEL:
|
||||
op2.envelope.decay1_level = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_DECAY2_RATE:
|
||||
op2.envelope.decay2_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_SUSTAIN_LEVEL:
|
||||
op2.envelope.sustain_level = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_RELEASE1_RATE:
|
||||
op2.envelope.release1_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_RELEASE1_LEVEL:
|
||||
op2.envelope.release1_level = _value;
|
||||
break;
|
||||
case tx_parameter::OP2_ENVELOPE_RELEASE2_RATE:
|
||||
op2.envelope.release2_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_RATIO:
|
||||
op3.ratio = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_AMPLITUDE:
|
||||
op3.amplitude = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_PHASE_RESOLUTION:
|
||||
op3.oscillator.set_phase_resolution(_value);
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_SKIP_SUSTAIN:
|
||||
op3.envelope.skip_sustain = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_ATTACK1_RATE:
|
||||
op3.envelope.attack1_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_ATTACK1_LEVEL:
|
||||
op3.envelope.attack1_level = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_ATTACK2_RATE:
|
||||
op3.envelope.attack2_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_HOLD_RATE:
|
||||
op3.envelope.hold_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_DECAY1_RATE:
|
||||
op3.envelope.decay1_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_DECAY1_LEVEL:
|
||||
op3.envelope.decay1_level = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_DECAY2_RATE:
|
||||
op3.envelope.decay2_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_SUSTAIN_LEVEL:
|
||||
op3.envelope.sustain_level = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_RELEASE1_RATE:
|
||||
op3.envelope.release1_rate = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_RELEASE1_LEVEL:
|
||||
op3.envelope.release1_level = _value;
|
||||
break;
|
||||
case tx_parameter::OP3_ENVELOPE_RELEASE2_RATE:
|
||||
op3.envelope.release2_rate = _value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace trnr
|
||||
@@ -1,167 +1,194 @@
|
||||
/*
|
||||
* voice_allocator.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "ivoice.h"
|
||||
#include "midi_event.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace trnr {
|
||||
|
||||
template <typename t_voice, typename t_sample>
|
||||
class voice_allocator {
|
||||
public:
|
||||
std::vector<std::shared_ptr<t_voice>> voicePtrs;
|
||||
|
||||
voice_allocator()
|
||||
{
|
||||
// checks whether template derives from ivoice
|
||||
typedef t_voice assert_at_compile_time[is_convertible<t_voice, t_sample>::value ? 1 : -1];
|
||||
|
||||
voicePtrs.reserve(8);
|
||||
}
|
||||
|
||||
void set_voice_count(const int& voice_count)
|
||||
{
|
||||
if (voice_count > voicePtrs.size()) {
|
||||
for (int i = voicePtrs.size(); i < voice_count; ++i) {
|
||||
if (voicePtrs.size() > 0) {
|
||||
voicePtrs.emplace_back(std::make_shared<t_voice>(*voicePtrs.at(0)));
|
||||
} else {
|
||||
voicePtrs.emplace_back(std::make_shared<t_voice>());
|
||||
}
|
||||
}
|
||||
} else if (voice_count < voicePtrs.size()) {
|
||||
voicePtrs.resize(voice_count);
|
||||
}
|
||||
}
|
||||
|
||||
void note_on(const midi_event& event)
|
||||
{
|
||||
auto voice = get_free_voice(event.midi_note);
|
||||
|
||||
if (!voice) { voice = steal_voice(); }
|
||||
|
||||
if (voice) { voice->note_on(event.midi_note, event.velocity); }
|
||||
}
|
||||
|
||||
void note_off(const midi_event& event)
|
||||
{
|
||||
for (const auto& v : voicePtrs) {
|
||||
if (v->midi_note == event.midi_note) v->note_off();
|
||||
}
|
||||
}
|
||||
|
||||
void access(std::function<void(t_voice*)> f)
|
||||
{
|
||||
std::for_each(voicePtrs.begin(), voicePtrs.end(), [&](std::shared_ptr<t_voice> ptr) {
|
||||
if (ptr) {
|
||||
f(ptr.get()); // Call the function with the raw pointer
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void process_samples(t_sample** _outputs, int _start_index, int _block_size)
|
||||
{
|
||||
for (int b = _start_index; b < _start_index + _block_size; b += internal_block_size) {
|
||||
|
||||
// process all events in the block (introduces potential inaccuracy of up to 16 samples)
|
||||
process_events(b, internal_block_size);
|
||||
|
||||
for (const auto& v : voicePtrs) { v->process_samples(_outputs, b, internal_block_size); }
|
||||
}
|
||||
}
|
||||
|
||||
void add_event(midi_event event) { input_queue.push_back(event); }
|
||||
|
||||
bool voices_active()
|
||||
{
|
||||
bool voices_active = false;
|
||||
|
||||
for (const auto& v : voicePtrs) {
|
||||
bool busy = v->is_busy();
|
||||
voices_active |= busy;
|
||||
}
|
||||
|
||||
return voices_active;
|
||||
}
|
||||
|
||||
void set_samplerate(double _samplerate)
|
||||
{
|
||||
for (const auto& v : voicePtrs) { v->set_samplerate(_samplerate); }
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<midi_event> input_queue;
|
||||
int index_to_steal = 0;
|
||||
const int internal_block_size = 16;
|
||||
|
||||
std::shared_ptr<t_voice> get_free_voice(float frequency)
|
||||
{
|
||||
std::shared_ptr<t_voice> voice = nullptr;
|
||||
|
||||
for (const auto& v : voicePtrs) {
|
||||
|
||||
if (!v->is_busy()) voice = v;
|
||||
break;
|
||||
}
|
||||
|
||||
return voice;
|
||||
}
|
||||
|
||||
std::shared_ptr<t_voice> steal_voice()
|
||||
{
|
||||
std::shared_ptr<t_voice> free_voice = nullptr;
|
||||
|
||||
for (const auto& v : voicePtrs) {
|
||||
if (!v->gate) {
|
||||
free_voice = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!free_voice) {
|
||||
free_voice = voicePtrs.at(index_to_steal);
|
||||
|
||||
if (index_to_steal < voicePtrs.size() - 1) {
|
||||
index_to_steal++;
|
||||
} else {
|
||||
index_to_steal = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return free_voice;
|
||||
}
|
||||
|
||||
void process_events(int _start_index, int _block_size)
|
||||
{
|
||||
for (int s = _start_index; s < _start_index + _block_size; s++) {
|
||||
auto iterator = input_queue.begin();
|
||||
while (iterator != input_queue.end()) {
|
||||
|
||||
midi_event& event = *iterator;
|
||||
if (event.offset == s) {
|
||||
|
||||
switch (event.type) {
|
||||
case midi_event_type::note_on:
|
||||
note_on(event);
|
||||
break;
|
||||
case midi_event_type::note_off:
|
||||
note_off(event);
|
||||
break;
|
||||
case midi_event_type::pitch_wheel:
|
||||
access([&event](t_voice* voice) { voice->modulate_pitch(event.data); });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
iterator = input_queue.erase(iterator);
|
||||
} else {
|
||||
iterator++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
enum midi_event_type {
|
||||
NOTE_ON = 0,
|
||||
NOTE_OFF,
|
||||
PITCH_WHEEL,
|
||||
MOD_WHEEL
|
||||
};
|
||||
|
||||
struct midi_event {
|
||||
midi_event_type type;
|
||||
int offset;
|
||||
int midi_note;
|
||||
float velocity;
|
||||
double data;
|
||||
};
|
||||
|
||||
inline void make_note_on(midi_event& ev, int _midi_note, float _velocity, int _offset = 0)
|
||||
{
|
||||
ev.type = midi_event_type::NOTE_ON;
|
||||
ev.midi_note = _midi_note;
|
||||
ev.velocity = _velocity;
|
||||
ev.offset = _offset;
|
||||
}
|
||||
|
||||
inline void make_note_off(midi_event& ev, int _midi_note, float _velocity,
|
||||
int _offset = 0)
|
||||
{
|
||||
ev.type = midi_event_type::NOTE_OFF;
|
||||
ev.midi_note = _midi_note;
|
||||
ev.velocity = _velocity;
|
||||
ev.offset = _offset;
|
||||
}
|
||||
|
||||
inline void make_pitch_wheel(midi_event& ev, double _pitch, int _offset = 0)
|
||||
{
|
||||
ev.type = midi_event_type::PITCH_WHEEL;
|
||||
ev.data = _pitch;
|
||||
}
|
||||
|
||||
inline void make_mod_wheel(midi_event& ev, double _mod, int _offset = 0)
|
||||
{
|
||||
ev.type = midi_event_type::PITCH_WHEEL;
|
||||
ev.data = _mod;
|
||||
}
|
||||
|
||||
#ifndef MAX_VOICES
|
||||
#define MAX_VOICES 16
|
||||
#endif
|
||||
|
||||
#ifndef MAX_EVENTS_PER_VOICE
|
||||
#define MAX_EVENTS_PER_VOICE 32
|
||||
#endif
|
||||
|
||||
struct voice_state {
|
||||
int midi_note;
|
||||
bool is_busy = false;
|
||||
bool gate = false;
|
||||
bool trigger = false;
|
||||
float velocity = 0.0f;
|
||||
array<midi_event, MAX_EVENTS_PER_VOICE> events;
|
||||
size_t event_count = 0;
|
||||
};
|
||||
|
||||
struct voice_allocator {
|
||||
array<voice_state, MAX_VOICES> voices;
|
||||
int active_voice_count = 1;
|
||||
size_t index_to_steal = 0;
|
||||
};
|
||||
|
||||
inline void voice_allocator_init(voice_allocator& va)
|
||||
{
|
||||
for (size_t i = 0; i < MAX_VOICES; ++i) { va.voices[i].event_count = 0; }
|
||||
}
|
||||
|
||||
inline void voice_allocator_process_block(voice_allocator& va,
|
||||
const vector<midi_event>& midi_events)
|
||||
{
|
||||
// reset voice events and counts
|
||||
for (int i = 0; i < MAX_VOICES; i++) {
|
||||
for (int j = 0; j < MAX_EVENTS_PER_VOICE; j++) {
|
||||
va.voices[i].events[j] = midi_event {};
|
||||
}
|
||||
va.voices[i].event_count = 0;
|
||||
}
|
||||
|
||||
for (const auto& ev : midi_events) {
|
||||
switch (ev.type) {
|
||||
case NOTE_ON: {
|
||||
bool found = false;
|
||||
// attempt to find a free voice
|
||||
for (size_t i = 0; i < va.active_voice_count; ++i) {
|
||||
if (!va.voices[i].is_busy) {
|
||||
voice_state& found_voice = va.voices[i];
|
||||
found_voice.is_busy = true;
|
||||
found_voice.midi_note = ev.midi_note;
|
||||
found_voice.velocity = ev.velocity;
|
||||
found_voice.events[va.voices[i].event_count++] = ev;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) break;
|
||||
|
||||
// if all voices are busy, steal one round-robin
|
||||
voice_state& found_voice = va.voices[va.index_to_steal];
|
||||
found_voice.is_busy = true;
|
||||
found_voice.midi_note = ev.midi_note;
|
||||
found_voice.velocity = ev.velocity;
|
||||
found_voice.events[va.voices[va.index_to_steal].event_count++] = ev;
|
||||
va.index_to_steal++;
|
||||
if (va.index_to_steal >= va.active_voice_count) va.index_to_steal = 0;
|
||||
break;
|
||||
}
|
||||
case NOTE_OFF: {
|
||||
for (size_t i = 0; i < va.active_voice_count; ++i) {
|
||||
if (va.voices[i].midi_note == ev.midi_note)
|
||||
va.voices[i].events[va.voices[i].event_count++] = ev;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PITCH_WHEEL:
|
||||
case MOD_WHEEL: {
|
||||
for (size_t i = 0; i < va.active_voice_count; ++i) {
|
||||
va.voices[i].events[va.voices[i].event_count++] = ev;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void voice_process_event_for_frame(voice_state& v, size_t frame)
|
||||
{
|
||||
const midi_event* best_event = nullptr;
|
||||
|
||||
for (int i = 0; i < v.event_count; i++) {
|
||||
const midi_event& ev = v.events[i];
|
||||
if (ev.offset == frame) {
|
||||
best_event = &ev;
|
||||
if (ev.type == NOTE_ON) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_event) switch (best_event->type) {
|
||||
case NOTE_ON:
|
||||
v.midi_note = best_event->midi_note;
|
||||
v.velocity = best_event->velocity;
|
||||
v.is_busy = true;
|
||||
v.gate = true;
|
||||
v.trigger = true;
|
||||
break;
|
||||
case NOTE_OFF:
|
||||
v.gate = false;
|
||||
break;
|
||||
// TODO: handle pitch wheel and mod wheel events
|
||||
case PITCH_WHEEL:
|
||||
case MOD_WHEEL:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace trnr
|
||||
|
||||
57
util/audio_buffer.h
Normal file
57
util/audio_buffer.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* audio_buffer.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace trnr {
|
||||
template <typename t_sample>
|
||||
struct audio_buffer {
|
||||
size_t channels;
|
||||
size_t frames;
|
||||
|
||||
vector<t_sample> flat_data;
|
||||
vector<t_sample*> channel_ptrs;
|
||||
};
|
||||
|
||||
template <typename t_sample>
|
||||
void audio_buffer_init(audio_buffer<t_sample>& a, size_t channels, size_t frames)
|
||||
{
|
||||
a.channels = channels;
|
||||
a.frames = frames;
|
||||
a.flat_data.resize(channels * frames);
|
||||
a.channel_ptrs.resize(channels);
|
||||
audio_buffer_update_ptrs(a);
|
||||
}
|
||||
|
||||
template <typename t_sample>
|
||||
void audio_buffer_update_ptrs(audio_buffer<t_sample>& a)
|
||||
{
|
||||
for (int ch = 0; ch < a.channels; ++ch) {
|
||||
a.channel_ptrs[ch] = a.flat_data.data() + ch * a.frames;
|
||||
}
|
||||
}
|
||||
} // namespace trnr
|
||||
@@ -1,17 +1,47 @@
|
||||
/*
|
||||
* audio_math.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
|
||||
namespace trnr {
|
||||
static inline double lin_2_db(double lin) {
|
||||
return 20 * log(lin);
|
||||
}
|
||||
|
||||
static inline double db_2_lin(double db) {
|
||||
return pow(10, db/20);
|
||||
}
|
||||
|
||||
static inline float midi_to_frequency(float midi_note) {
|
||||
return 440.0 * powf(2.0, ((float)midi_note - 69.0) / 12.0);
|
||||
}
|
||||
inline double lin_2_db(double lin)
|
||||
{
|
||||
if (lin <= 1e-20) lin = 1e-20; // avoid log(0)
|
||||
return 20.0 * log10(lin);
|
||||
}
|
||||
|
||||
inline double db_2_lin(double db) { return pow(10.0, db / 20.0); }
|
||||
|
||||
inline float midi_to_frequency(float midi_note)
|
||||
{
|
||||
return 440.0 * powf(2.0, ((float)midi_note - 69.0) / 12.0);
|
||||
}
|
||||
|
||||
inline float ms_to_samples(float ms, double sample_rate)
|
||||
{
|
||||
return (ms * 0.001f) * (float)sample_rate;
|
||||
}
|
||||
} // namespace trnr
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
|
||||
namespace trnr {
|
||||
template <typename t_sample, unsigned int sample_size>
|
||||
class averager {
|
||||
public:
|
||||
averager() { samples.fill(0); }
|
||||
|
||||
t_sample process_sample(t_sample& _sample)
|
||||
{
|
||||
t_sample sum = t_sample(0);
|
||||
|
||||
for (unsigned int i = 0; i < sample_size; i++) {
|
||||
|
||||
if (i < sample_size - 1) {
|
||||
// shift to the left
|
||||
samples[i] = samples[i + 1];
|
||||
} else {
|
||||
// put new sample last
|
||||
samples[i] = _sample;
|
||||
}
|
||||
|
||||
sum += samples[i];
|
||||
}
|
||||
|
||||
return sum / sample_size;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<t_sample, sample_size> samples;
|
||||
};
|
||||
} // namespace trnr
|
||||
@@ -1,38 +1,66 @@
|
||||
/*
|
||||
* demo_noise.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#define _USE_MATH_DEFINES
|
||||
|
||||
#include <math.h>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace trnr {
|
||||
template <typename t_sample>
|
||||
class demo_noise {
|
||||
public:
|
||||
void set_samplerate(double _samplerate) {
|
||||
samplerate = _samplerate;
|
||||
}
|
||||
|
||||
void process_block(t_sample** samples, long sample_frames) {
|
||||
|
||||
for (int s = 0; s < sample_frames; s++) {
|
||||
demo_counter++;
|
||||
|
||||
if (demo_counter == samplerate * 20) {
|
||||
demo_counter = 0;
|
||||
}
|
||||
if (demo_counter > samplerate * 17) {
|
||||
t_sample r1 = static_cast<t_sample>(rand()) / static_cast<t_sample>(RAND_MAX);
|
||||
t_sample r2 = static_cast<t_sample>(rand()) / static_cast<t_sample>(RAND_MAX);
|
||||
|
||||
t_sample noise = static_cast<t_sample>(sqrt(-2.0 * log(r1)) * cos(2.0 * M_PI * r2));
|
||||
|
||||
samples[0][s] = noise / 10.0;
|
||||
samples[1][s] = noise / 10.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
double samplerate = 44100;
|
||||
int demo_counter = 0;
|
||||
struct demo_noise {
|
||||
double samplerate;
|
||||
int counter = 0;
|
||||
// initialize with sane defaults
|
||||
int noise_len_sec = 3;
|
||||
int pause_len_sec = 17;
|
||||
float noise_gain = 0.1f;
|
||||
};
|
||||
|
||||
inline void demo_nose_init(demo_noise& d, double samplerate)
|
||||
{
|
||||
d.samplerate = samplerate;
|
||||
d.counter = 0;
|
||||
}
|
||||
|
||||
// overwrites the input buffer with noise in the specified time frame
|
||||
inline void process_block(demo_noise& d, float** samples, int blocksize)
|
||||
{
|
||||
int noise_len_samples = d.noise_len_sec * d.samplerate;
|
||||
int pause_len_samples = d.pause_len_sec * d.samplerate;
|
||||
int total_len_samples = noise_len_samples + pause_len_samples;
|
||||
|
||||
for (int s = 0; s < blocksize; s++) {
|
||||
d.counter++;
|
||||
|
||||
if (d.counter > total_len_samples) { d.counter = 0; }
|
||||
if (d.counter > pause_len_samples) {
|
||||
float r1 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
|
||||
float r2 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
|
||||
|
||||
float noise = static_cast<float>(sqrt(-2.0 * log(r1)) * cos(2.0 * M_PI * r2));
|
||||
|
||||
samples[0][s] = samples[1][s] = noise * d.noise_gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace trnr
|
||||
|
||||
77
util/format.h
Normal file
77
util/format.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* format.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdarg>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace trnr {
|
||||
|
||||
// Formats a string (like printf) using a format string and variable arguments.
|
||||
inline string format(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vector<char> buf(256);
|
||||
int needed = vsnprintf(buf.data(), buf.size(), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (needed < 0) { return {}; }
|
||||
if (static_cast<size_t>(needed) < buf.size()) { return string(buf.data(), needed); }
|
||||
|
||||
// Resize and try again if the buffer was too small
|
||||
buf.resize(needed + 1);
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf.data(), buf.size(), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
return string(buf.data(), needed);
|
||||
}
|
||||
|
||||
inline string float_to_string_trimmed(float value)
|
||||
{
|
||||
ostringstream out;
|
||||
out << fixed << setprecision(2) << value;
|
||||
string str = out.str();
|
||||
|
||||
// Remove trailing zeros
|
||||
str.erase(str.find_last_not_of('0') + 1, string::npos);
|
||||
// If the last character is a decimal point, remove it as well
|
||||
if (!str.empty() && str.back() == '.') { str.pop_back(); }
|
||||
return str;
|
||||
}
|
||||
|
||||
inline string to_upper(string& str)
|
||||
{
|
||||
std::transform(str.begin(), str.end(), str.begin(),
|
||||
[](unsigned char c) { return std::toupper(c); });
|
||||
return str;
|
||||
}
|
||||
} // namespace trnr
|
||||
@@ -1,8 +1,30 @@
|
||||
/*
|
||||
* retro_buf.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../companding/alaw.h"
|
||||
#include "../filter/chebyshev.h"
|
||||
#include "../companding/ulaw.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace trnr {
|
||||
|
||||
@@ -19,35 +41,33 @@ struct retro_buf_modulation {
|
||||
double deviation;
|
||||
};
|
||||
|
||||
// base class for accessing a sample buffer with adjustable samplerate, bitrate and other options.
|
||||
// base class for accessing a sample buffer with adjustable samplerate, bitrate and other
|
||||
// options.
|
||||
class retro_buf {
|
||||
public:
|
||||
void set_host_samplerate(double _samplerate) {
|
||||
void set_host_samplerate(double _samplerate)
|
||||
{
|
||||
m_host_samplerate = _samplerate;
|
||||
m_imaging_filter_l.set_samplerate(_samplerate);
|
||||
m_imaging_filter_r.set_samplerate(_samplerate);
|
||||
}
|
||||
|
||||
void set_buf_samplerate(double _samplerate) {
|
||||
m_buf_samplerate = _samplerate;
|
||||
}
|
||||
void set_buf_samplerate(double _samplerate) { m_buf_samplerate = _samplerate; }
|
||||
|
||||
void set_buffer_size(size_t _buffer_size) {
|
||||
m_buffer_size = _buffer_size;
|
||||
}
|
||||
void set_buffer_size(size_t _buffer_size) { m_buffer_size = _buffer_size; }
|
||||
|
||||
void set_channel_count(size_t _channel_count) {
|
||||
m_channel_count = _channel_count;
|
||||
}
|
||||
void set_channel_count(size_t _channel_count) { m_channel_count = _channel_count; }
|
||||
|
||||
void start_playback() {
|
||||
void start_playback()
|
||||
{
|
||||
if (m_modulation.reset || (!m_modulation.reset && m_playback_pos == -1)) {
|
||||
m_playback_pos = (double)m_modulation.start;
|
||||
}
|
||||
}
|
||||
|
||||
// @return is active
|
||||
bool process_block(double** _outputs, size_t _block_size, retro_buf_modulation _mod) {
|
||||
bool process_block(double** _outputs, size_t _block_size, retro_buf_modulation _mod)
|
||||
{
|
||||
|
||||
m_modulation = _mod;
|
||||
|
||||
@@ -60,12 +80,19 @@ public:
|
||||
|
||||
// quantize index
|
||||
double samplerate_divisor = m_host_samplerate / _mod.samplerate;
|
||||
size_t quantized_index = static_cast<size_t>(static_cast<size_t>(m_playback_pos / samplerate_divisor) * samplerate_divisor);
|
||||
size_t quantized_index = static_cast<size_t>(
|
||||
static_cast<size_t>(m_playback_pos / samplerate_divisor) *
|
||||
samplerate_divisor);
|
||||
|
||||
// get sample for each channel
|
||||
output_l = get_sample((size_t)wrap(quantized_index + jitterize(_mod.jitter), m_buffer_size), 0);
|
||||
output_l = get_sample(
|
||||
(size_t)wrap(quantized_index + jitterize(_mod.jitter), m_buffer_size),
|
||||
0);
|
||||
if (m_channel_count > 0) {
|
||||
output_r = get_sample((size_t)wrap(quantized_index + jitterize(_mod.jitter), m_buffer_size), 1);
|
||||
output_r =
|
||||
get_sample((size_t)wrap(quantized_index + jitterize(_mod.jitter),
|
||||
m_buffer_size),
|
||||
1);
|
||||
} else {
|
||||
output_r = output_l;
|
||||
}
|
||||
@@ -77,13 +104,14 @@ public:
|
||||
reduce_bitrate(output_l, output_r, _mod.bitrate);
|
||||
|
||||
// calculate imaging filter frequency + deviation
|
||||
double filter_frequency = ((_mod.samplerate / 2) * note_ratio) * ((_mod.deviation * 9) + 1);
|
||||
double filter_frequency =
|
||||
((_mod.samplerate / 2) * note_ratio) * ((_mod.deviation * 9) + 1);
|
||||
|
||||
m_imaging_filter_l.process_sample(output_l, filter_frequency);
|
||||
m_imaging_filter_r.process_sample(output_r, filter_frequency);
|
||||
}
|
||||
// else if loop
|
||||
else if(_mod.looping) {
|
||||
else if (_mod.looping) {
|
||||
// loop
|
||||
m_playback_pos = (double)_mod.start;
|
||||
}
|
||||
@@ -111,15 +139,16 @@ private:
|
||||
|
||||
chebyshev m_imaging_filter_l;
|
||||
chebyshev m_imaging_filter_r;
|
||||
ulaw m_compander;
|
||||
retro_buf_modulation m_modulation;
|
||||
|
||||
float midi_to_ratio(double midi_note) {
|
||||
float midi_to_ratio(double midi_note)
|
||||
{
|
||||
return powf(powf(2, (float)midi_note - 60.f), 1.f / 12.f);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T clamp(T& value, T min, T max) {
|
||||
T clamp(T& value, T min, T max)
|
||||
{
|
||||
if (value < min) {
|
||||
value = min;
|
||||
} else if (value > max) {
|
||||
@@ -128,14 +157,14 @@ private:
|
||||
return value;
|
||||
}
|
||||
|
||||
double wrap(double value, double max) {
|
||||
while (value > max) {
|
||||
value =- max;
|
||||
}
|
||||
double wrap(double value, double max)
|
||||
{
|
||||
while (value > max) { value -= max; }
|
||||
return value;
|
||||
}
|
||||
|
||||
int jitterize(int jitter) {
|
||||
int jitterize(int jitter)
|
||||
{
|
||||
if (jitter > 0) {
|
||||
return static_cast<int>(rand() % jitter);
|
||||
} else {
|
||||
@@ -143,14 +172,17 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void reduce_bitrate(double& value1, double& value2, double bit) {
|
||||
m_compander.encode_samples(value1, value2);
|
||||
void reduce_bitrate(double& value1, double& value2, double bit)
|
||||
{
|
||||
value1 = alaw_encode(value1);
|
||||
value2 = alaw_encode(value2);
|
||||
|
||||
float resolution = powf(2, bit);
|
||||
value1 = round(value1 * resolution) / resolution;
|
||||
value2 = round(value2 * resolution) / resolution;
|
||||
|
||||
m_compander.decode_samples(value1, value2);
|
||||
value1 = alaw_decode(value1);
|
||||
value2 = alaw_decode(value2);
|
||||
}
|
||||
};
|
||||
}
|
||||
} // namespace trnr
|
||||
|
||||
104
util/sample.h
104
util/sample.h
@@ -1,104 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace trnr {
|
||||
class sample {
|
||||
public:
|
||||
sample(int16_t initial_value = 0) {
|
||||
set_value(initial_value);
|
||||
instances.push_back(this); // track this instance
|
||||
}
|
||||
|
||||
~sample() {
|
||||
// remove this instance from the tracking vector
|
||||
auto it = std::find(instances.begin(), instances.end(), this);
|
||||
if (it != instances.end()) {
|
||||
instances.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
// set value while ensuring the global bit depth
|
||||
void set_value(int16_t new_value) {
|
||||
int16_t max_val = (1 << (global_bit_depth - 1)) - 1; // Max value for signed bit depth
|
||||
int16_t min_val = -(1 << (global_bit_depth - 1)); // Min value for signed bit depth
|
||||
|
||||
// Clamp the value to the allowed range
|
||||
if (new_value > max_val) {
|
||||
value = max_val;
|
||||
} else if (new_value < min_val) {
|
||||
value = min_val;
|
||||
} else {
|
||||
value = new_value;
|
||||
}
|
||||
}
|
||||
|
||||
int16_t get_value() const {
|
||||
return value;
|
||||
}
|
||||
|
||||
static void set_global_bit_depth(int depth) {
|
||||
if (depth < 1 || depth > 16) {
|
||||
throw std::invalid_argument("Bit depth must be between 1 and 16.");
|
||||
}
|
||||
|
||||
// rescale all existing values if the bit depth changes
|
||||
float scaling_factor = get_scaling_factor(previous_bit_depth, global_bit_depth);
|
||||
|
||||
// Rescale all existing instances
|
||||
for (auto* instance : instances) {
|
||||
instance->value = std::round(instance->value * scaling_factor);
|
||||
}
|
||||
|
||||
previous_bit_depth = global_bit_depth; // Store the old bit depth
|
||||
global_bit_depth = depth; // Update the global bit depth
|
||||
}
|
||||
|
||||
static int get_global_bit_depth() {
|
||||
return global_bit_depth;
|
||||
}
|
||||
|
||||
// arithmetic operators (all respect global bit depth)
|
||||
sample operator+(const sample& other) const {
|
||||
return sample(this->value + other.value);
|
||||
}
|
||||
|
||||
sample operator-(const sample& other) const {
|
||||
return sample(this->value - other.value);
|
||||
}
|
||||
|
||||
sample operator*(const sample& other) const {
|
||||
return sample(this->value * other.value);
|
||||
}
|
||||
|
||||
sample operator/(const sample& other) const {
|
||||
if (other.value == 0) {
|
||||
throw std::runtime_error("division by zero.");
|
||||
}
|
||||
return sample(this->value / other.value);
|
||||
}
|
||||
|
||||
// Cast to int16_t for convenience
|
||||
operator int16_t() const {
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
int16_t value;
|
||||
static int global_bit_depth; // global bit depth
|
||||
static int previous_bit_depth; // previous bit depth
|
||||
static std::vector<sample*> instances; // track all instances for rescaling
|
||||
|
||||
// helper function to calculate the scaling factor
|
||||
static float get_scaling_factor(int old_bit_depth, int new_bit_depth) {
|
||||
if (old_bit_depth == new_bit_depth) {
|
||||
return 1.0f; // No scaling needed
|
||||
}
|
||||
float old_max = (1 << (old_bit_depth - 1)) - 1;
|
||||
float new_max = (1 << (new_bit_depth - 1)) - 1;
|
||||
return new_max / old_max;
|
||||
}
|
||||
};
|
||||
}
|
||||
84
util/smoother.h
Normal file
84
util/smoother.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* smoother.h
|
||||
* Copyright (c) 2025 Christopher Herb
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_math.h"
|
||||
|
||||
namespace trnr {
|
||||
|
||||
struct smoother {
|
||||
float samplerate;
|
||||
float time_samples;
|
||||
|
||||
float current;
|
||||
float target;
|
||||
float increment;
|
||||
int32_t remaining;
|
||||
};
|
||||
|
||||
inline void smoother_init(smoother& s, double samplerate, float time_ms,
|
||||
float initial_value = 0.0f)
|
||||
{
|
||||
s.samplerate = fmax(0.0, samplerate);
|
||||
s.time_samples = ms_to_samples(time_ms, s.samplerate);
|
||||
s.current = initial_value;
|
||||
s.target = initial_value;
|
||||
s.increment = 0.0f;
|
||||
s.remaining = 0;
|
||||
}
|
||||
|
||||
inline void smoother_set_target(smoother& s, float newTarget)
|
||||
{
|
||||
s.target = newTarget;
|
||||
|
||||
// immediate if time is zero or too short
|
||||
if (s.time_samples <= 1.0f) {
|
||||
s.current = s.target;
|
||||
s.increment = 0.0f;
|
||||
s.remaining = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t n = static_cast<int32_t>(fmax(1.0f, ceilf(s.time_samples)));
|
||||
s.remaining = n;
|
||||
// protect against denormals / tiny differences, but we want exact reach at the end
|
||||
s.increment = (s.target - s.current) / static_cast<float>(s.remaining);
|
||||
}
|
||||
|
||||
// process a single sample and return the smoothed value
|
||||
inline float smoother_process_sample(smoother& s)
|
||||
{
|
||||
if (s.remaining > 0) {
|
||||
s.current += s.increment;
|
||||
--s.remaining;
|
||||
if (s.remaining == 0) {
|
||||
// ensure exact target at the end to avoid FP drift
|
||||
s.current = s.target;
|
||||
s.increment = 0.0f;
|
||||
}
|
||||
}
|
||||
return s.current;
|
||||
}
|
||||
|
||||
} // namespace trnr
|
||||
Reference in New Issue
Block a user