Browse Source

Added measured waveshaper responses for chopping kinky

tags/v1.1.0^2
hemmer 3 years ago
parent
commit
9d854fe0ec
2 changed files with 138 additions and 36 deletions
  1. +136
    -34
      src/ChoppingKinky.cpp
  2. +2
    -2
      src/Common.hpp

+ 136
- 34
src/ChoppingKinky.cpp View File

@@ -4,20 +4,6 @@
static const size_t BUF_LEN = 32; static const size_t BUF_LEN = 32;
static float foldResponse(float inputGain, float G) {
return std::tanh(inputGain) + G * std::sin(M_PI * inputGain);
//return tanh_pade(inputGain) + G * sin2pi_pade_05_5_4(inputGain / 2);
}
static float foldResponseSin(float inputGain) {
return std::sin(2 * M_PI * inputGain / 2.5);
//return sin2pi_pade_05_5_4(inputGain / 2.5);
//return (std::abs(inputGain) < 1.0) ? inputGain : 1.0f;
}
struct ChoppingKinky : Module { struct ChoppingKinky : Module {
enum ParamIds { enum ParamIds {
FOLD_A_PARAM, FOLD_A_PARAM,
@@ -54,14 +40,19 @@ struct ChoppingKinky : Module {
NUM_CHANNELS NUM_CHANNELS
}; };
static const int WAVESHAPE_CACHE_SIZE = 128;
float waveshapeA[WAVESHAPE_CACHE_SIZE + 1] = {0.f};
float waveshapeBPositive[WAVESHAPE_CACHE_SIZE + 1] = {0.f};
float waveshapeBNegative[WAVESHAPE_CACHE_SIZE + 1] = {0.f};
dsp::SchmittTrigger trigger; dsp::SchmittTrigger trigger;
bool outputAToChopp; bool outputAToChopp;
float previousA = 0.0; float previousA = 0.0;
chowdsp::VariableOversampling<> oversampler[NUM_CHANNELS]; chowdsp::VariableOversampling<> oversampler[NUM_CHANNELS];
int oversamplingIndex = 2; int oversamplingIndex = 2;
dsp::BiquadFilter blockDCFilter;
dsp::BiquadFilter blockDCFilter;
bool blockDC = false; bool blockDC = false;
ChoppingKinky() { ChoppingKinky() {
@@ -71,6 +62,8 @@ struct ChoppingKinky : Module {
configParam(CV_A_PARAM, -1.f, 1.f, 0.f, ""); configParam(CV_A_PARAM, -1.f, 1.f, 0.f, "");
configParam(CV_B_PARAM, -1.f, 1.f, 0.f, ""); configParam(CV_B_PARAM, -1.f, 1.f, 0.f, "");
cacheWaveshaperResponses();
// calculate up/downsampling rates // calculate up/downsampling rates
onSampleRateChange(); onSampleRateChange();
} }
@@ -78,11 +71,11 @@ struct ChoppingKinky : Module {
void onSampleRateChange() override { void onSampleRateChange() override {
// SampleRateConverter needs integer value // SampleRateConverter needs integer value
int sampleRate = APP->engine->getSampleRate(); int sampleRate = APP->engine->getSampleRate();
blockDCFilter.setParameters(dsp::BiquadFilter::HIGHPASS, 10.3f / sampleRate, M_SQRT1_2, 1.0f); blockDCFilter.setParameters(dsp::BiquadFilter::HIGHPASS, 10.3f / sampleRate, M_SQRT1_2, 1.0f);
for (int channel_idx = 0; channel_idx < NUM_CHANNELS; channel_idx++) {
oversampler[channel_idx].setOversamplingIndex(oversamplingIndex);
for (int channel_idx = 0; channel_idx < NUM_CHANNELS; channel_idx++) {
oversampler[channel_idx].setOversamplingIndex(oversamplingIndex);
oversampler[channel_idx].reset(sampleRate); oversampler[channel_idx].reset(sampleRate);
} }
} }
@@ -109,10 +102,10 @@ struct ChoppingKinky : Module {
float inA = 0; float inA = 0;
float inB = 0; float inB = 0;
if (inputs[IN_A_INPUT].isConnected()) { if (inputs[IN_A_INPUT].isConnected()) {
inA = inputs[IN_A_INPUT].getVoltage() / 5.0f;
inA = inputs[IN_A_INPUT].getVoltage();
} }
if (inputs[IN_B_INPUT].isConnected()) { if (inputs[IN_B_INPUT].isConnected()) {
inB = inputs[IN_B_INPUT].getVoltage() / 5.0f;
inB = inputs[IN_B_INPUT].getVoltage();
} }
else if (inputs[IN_A_INPUT].isConnected()) { else if (inputs[IN_A_INPUT].isConnected()) {
inB = inA; inB = inA;
@@ -132,22 +125,20 @@ struct ChoppingKinky : Module {
outputAToChopp = true; outputAToChopp = true;
} }
} }
previousA = inA;
bool choppIsRequired = outputs[OUT_CHOPP_OUTPUT].isConnected(); bool choppIsRequired = outputs[OUT_CHOPP_OUTPUT].isConnected();
bool aIsRequired = outputs[OUT_A_OUTPUT].isConnected() || choppIsRequired; bool aIsRequired = outputs[OUT_A_OUTPUT].isConnected() || choppIsRequired;
bool bIsRequired = outputs[OUT_B_OUTPUT].isConnected() || choppIsRequired; bool bIsRequired = outputs[OUT_B_OUTPUT].isConnected() || choppIsRequired;
inA = inA * gainA;
inB = inB * gainB;
float inChopp = outputAToChopp ? 1.f : 0.f;
if (aIsRequired) { if (aIsRequired) {
oversampler[CHANNEL_A].upsample(inA);
oversampler[CHANNEL_A].upsample(inA * gainA);
} }
if (bIsRequired) { if (bIsRequired) {
oversampler[CHANNEL_B].upsample(inB);
oversampler[CHANNEL_B].upsample(inB * gainB);
} }
if (choppIsRequired) { if (choppIsRequired) {
float inChopp = outputAToChopp ? 1.f : 0.f;
oversampler[CHANNEL_CHOPP].upsample(inChopp); oversampler[CHANNEL_CHOPP].upsample(inChopp);
} }
@@ -157,10 +148,14 @@ struct ChoppingKinky : Module {
for (int i = 0; i < oversampler[0].getOversamplingRatio(); i++) { for (int i = 0; i < oversampler[0].getOversamplingRatio(); i++) {
if (aIsRequired) { if (aIsRequired) {
osBufferA[i] = foldResponse(4.0f * osBufferA[i], -0.2);
// TODO: replace with LUT of measured wavefolder response
//osBufferA[i] = wavefolderAResponse(osBufferA[i]);
osBufferA[i] = wavefolderAResponseCached(osBufferA[i]);
} }
if (bIsRequired) { if (bIsRequired) {
osBufferB[i] = foldResponseSin(4.0f * osBufferB[i]);
// TODO: replace with LUT of measured wavefolder response
//osBufferB[i] = wavefolderBResponse(osBufferB[i]);
osBufferB[i] = wavefolderBResponseCached(osBufferB[i]);
} }
if (choppIsRequired) { if (choppIsRequired) {
osBufferChopp[i] = osBufferChopp[i] * osBufferA[i] + (1.f - osBufferChopp[i]) * osBufferB[i]; osBufferChopp[i] = osBufferChopp[i] * osBufferA[i] + (1.f - osBufferChopp[i]) * osBufferB[i];
@@ -172,13 +167,12 @@ struct ChoppingKinky : Module {
float outChopp = choppIsRequired ? oversampler[CHANNEL_CHOPP].downsample() : 0.f; float outChopp = choppIsRequired ? oversampler[CHANNEL_CHOPP].downsample() : 0.f;
if (blockDC) { if (blockDC) {
outChopp = blockDCFilter.process(outChopp);
outChopp = blockDCFilter.process(outChopp);
} }
previousA = inA;
outputs[OUT_A_OUTPUT].setVoltage(outA * 5.0f);
outputs[OUT_B_OUTPUT].setVoltage(outB * 5.0f);
outputs[OUT_CHOPP_OUTPUT].setVoltage(outChopp * 5.0f);
outputs[OUT_A_OUTPUT].setVoltage(outA);
outputs[OUT_B_OUTPUT].setVoltage(outB);
outputs[OUT_CHOPP_OUTPUT].setVoltage(outChopp);
if (inputs[IN_GATE_INPUT].isConnected()) { if (inputs[IN_GATE_INPUT].isConnected()) {
lights[LED_A_LIGHT].setSmoothBrightness((float) outputAToChopp, args.sampleTime); lights[LED_A_LIGHT].setSmoothBrightness((float) outputAToChopp, args.sampleTime);
@@ -190,6 +184,114 @@ struct ChoppingKinky : Module {
} }
} }
float wavefolderAResponseCached(float x) {
if (x >= 0) {
float j = rescale(clamp(x, 0.f, 10.f), 0.f, 10.f, 0, WAVESHAPE_CACHE_SIZE - 1);
return interpolateLinear(waveshapeA, j);
}
else {
return -wavefolderAResponseCached(-x);
}
}
float wavefolderBResponseCached(float x) {
if (x >= 0) {
float j = rescale(clamp(x, 0.f, 10.f), 0.f, 10.f, 0, WAVESHAPE_CACHE_SIZE - 1);
return interpolateLinear(waveshapeBPositive, j);
}
else {
float j = rescale(clamp(-x, 0.f, 10.f), 0.f, 10.f, 0, WAVESHAPE_CACHE_SIZE - 1);
return interpolateLinear(waveshapeBNegative, j);
}
}
static float wavefolderAResponse(float x) {
if (x < 0) {
return -wavefolderAResponse(-x);
}
float xScaleFactor = 1.f / 20.f;
float yScaleFactor = 12.5f;
x = x * xScaleFactor;
float piecewiseX1 = 0.087;
float piecewiseX2 = 0.245;
float piecewiseX3 = 0.3252;
if (x < piecewiseX1) {
float x_ = x / piecewiseX1;
return -0.38 * yScaleFactor * (std::sin(M_PI * std::pow(x_, 0.8)) + 1.0 / (3 * 1.6) * std::sin(3 * M_PI * std::pow(x_, 0.8)));
}
else if (x < piecewiseX2) {
float x_ = x - piecewiseX1;
return -yScaleFactor * (-0.2 * std::sin(0.5 * M_PI * 12.69 * x_) - 0.24 * std::sin(1.5 * M_PI * 12.69 * x_));
}
else if (x < piecewiseX3) {
float x_ = 9.8 * (x - piecewiseX2);
return -0.33 * yScaleFactor * std::sin(x_ / 0.165) * (1 + 0.9 * std::pow(x_, 3) / (1.0 + 2.0 * std::pow(x_, 6)));
}
else {
float x_ = (x - piecewiseX3) / 0.05;
return yScaleFactor * ((0.4274 - 0.031) * std::exp(-std::pow(x_, 2.0)) + 0.031);
}
}
static float wavefolderBResponse(float x) {
float xScaleFactor = 1.f / 20.f;
float yScaleFactor = 12.5f;
x = x * xScaleFactor;
// assymetric response
if (x > 0) {
float piecewiseX1 = 0.117;
float piecewiseX2 = 0.2837;
if (x < piecewiseX1) {
float x_ = x / piecewiseX1;
return -0.3 * yScaleFactor * (std::sin(M_PI * std::pow(x_, 0.67)) + 1.0 / (3 * 0.8) * std::sin(3 * M_PI * std::pow(x_, 0.67)));
}
else if (x < piecewiseX2) {
float x_ = x - piecewiseX1;
return 0.35 * yScaleFactor * std::sin(12. * M_PI * x_);
}
else {
float x_ = (x - piecewiseX2);
return 0.57 * yScaleFactor * std::tanh(x_ / 0.03);
}
}
else {
float piecewiseX1 = -0.105;
float piecewiseX2 = -0.20722;
if (x > piecewiseX1) {
float x_ = x / piecewiseX1;
return 0.37 * yScaleFactor * (std::sin(M_PI * std::pow(x_, 0.65)) + 1.0 / (3 * 1.2) * std::sin(3 * M_PI * std::pow(x_, 0.65)));
}
else if (x > piecewiseX2) {
float x_ = x - piecewiseX1;
return 0.2 * yScaleFactor * std::sin(15 * M_PI * x_) * (1.0 - 10.f * x_);
}
else {
float x_ = (x - piecewiseX2) / 0.07;
return yScaleFactor * ((0.4022 - 0.065) * std::exp(-std::pow(x_, 2)) + 0.065);
}
}
}
void cacheWaveshaperResponses() {
for (int i = 0; i < WAVESHAPE_CACHE_SIZE; ++i) {
float x = rescale(i, 0, WAVESHAPE_CACHE_SIZE - 1, 0.0, 10.f);
waveshapeA[i] = wavefolderAResponse(x);
float j = rescale(x, 0.0, 10., 0, WAVESHAPE_CACHE_SIZE - 1);
float lookupResult = math::interpolateLinear(waveshapeA, j);
waveshapeBPositive[i] = wavefolderBResponse(+x);
waveshapeBNegative[i] = wavefolderBResponse(-x);
}
}
json_t* dataToJson() override { json_t* dataToJson() override {
json_t* rootJ = json_object(); json_t* rootJ = json_object();


+ 2
- 2
src/Common.hpp View File

@@ -72,7 +72,7 @@ struct ADEnvelope {
float env = 0.f; float env = 0.f;
float attackTime = 0.1, decayTime = 0.1; float attackTime = 0.1, decayTime = 0.1;
float attackShape = 1.0, decayShape = 1.0; float attackShape = 1.0, decayShape = 1.0;
ADEnvelope() { }; ADEnvelope() { };


void process(const float& sampleTime) { void process(const float& sampleTime) {
@@ -81,7 +81,7 @@ struct ADEnvelope {
env = envLinear = 0.0f; env = envLinear = 0.0f;
} }
else if (stage == STAGE_ATTACK) { else if (stage == STAGE_ATTACK) {
envLinear += sampleTime / attackTime;
envLinear += sampleTime / attackTime;
env = std::pow(envLinear, attackShape); env = std::pow(envLinear, attackShape);
} }
else if (stage == STAGE_DECAY) { else if (stage == STAGE_DECAY) {


Loading…
Cancel
Save