| @@ -2,7 +2,6 @@ | |||||
| #include "Common.hpp" | #include "Common.hpp" | ||||
| #include "ChowDSP.hpp" | #include "ChowDSP.hpp" | ||||
| static const size_t BUF_LEN = 32; | |||||
| struct ChoppingKinky : Module { | struct ChoppingKinky : Module { | ||||
| enum ParamIds { | enum ParamIds { | ||||
| @@ -40,7 +39,7 @@ struct ChoppingKinky : Module { | |||||
| NUM_CHANNELS | NUM_CHANNELS | ||||
| }; | }; | ||||
| static const int WAVESHAPE_CACHE_SIZE = 128; | |||||
| static const int WAVESHAPE_CACHE_SIZE = 256; | |||||
| float waveshapeA[WAVESHAPE_CACHE_SIZE + 1] = {0.f}; | float waveshapeA[WAVESHAPE_CACHE_SIZE + 1] = {0.f}; | ||||
| float waveshapeBPositive[WAVESHAPE_CACHE_SIZE + 1] = {0.f}; | float waveshapeBPositive[WAVESHAPE_CACHE_SIZE + 1] = {0.f}; | ||||
| float waveshapeBNegative[WAVESHAPE_CACHE_SIZE + 1] = {0.f}; | float waveshapeBNegative[WAVESHAPE_CACHE_SIZE + 1] = {0.f}; | ||||
| @@ -48,9 +47,9 @@ struct ChoppingKinky : Module { | |||||
| 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; // default is 2^oversamplingIndex == x4 oversampling | |||||
| dsp::BiquadFilter blockDCFilter; | dsp::BiquadFilter blockDCFilter; | ||||
| bool blockDC = false; | bool blockDC = false; | ||||
| @@ -68,9 +67,8 @@ struct ChoppingKinky : Module { | |||||
| onSampleRateChange(); | onSampleRateChange(); | ||||
| } | } | ||||
| void onSampleRateChange() override { | |||||
| // SampleRateConverter needs integer value | |||||
| int sampleRate = APP->engine->getSampleRate(); | |||||
| void onSampleRateChange() override { | |||||
| float 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); | ||||
| @@ -80,43 +78,27 @@ struct ChoppingKinky : Module { | |||||
| } | } | ||||
| } | } | ||||
| void process(const ProcessArgs& args) override { | void process(const ProcessArgs& args) override { | ||||
| float gainA = params[FOLD_A_PARAM].getValue(); | float gainA = params[FOLD_A_PARAM].getValue(); | ||||
| if (inputs[CV_A_INPUT].isConnected()) { | |||||
| gainA += params[CV_A_PARAM].getValue() * inputs[CV_A_INPUT].getVoltage() / 10.f; | |||||
| } | |||||
| if (inputs[VCA_CV_A_INPUT].isConnected()) { | |||||
| gainA += inputs[VCA_CV_A_INPUT].getVoltage() / 10.f; | |||||
| } | |||||
| gainA += params[CV_A_PARAM].getValue() * inputs[CV_A_INPUT].getVoltage() / 10.f; | |||||
| gainA += inputs[VCA_CV_A_INPUT].getVoltage() / 10.f; | |||||
| // CV_B_INPUT is normalled to CV_A_INPUT (input with attenuverter) | |||||
| float gainB = params[FOLD_B_PARAM].getValue(); | float gainB = params[FOLD_B_PARAM].getValue(); | ||||
| if (inputs[CV_B_INPUT].isConnected()) { | |||||
| gainB += params[CV_B_PARAM].getValue() * inputs[CV_B_INPUT].getVoltage() / 10.f; | |||||
| } | |||||
| if (inputs[VCA_CV_B_INPUT].isConnected()) { | |||||
| gainB += inputs[VCA_CV_B_INPUT].getVoltage() / 10.f; | |||||
| } | |||||
| gainB += params[CV_B_PARAM].getValue() * inputs[CV_B_INPUT].getNormalVoltage(inputs[CV_A_INPUT].getVoltage()) / 10.f; | |||||
| gainB += inputs[VCA_CV_B_INPUT].getVoltage() / 10.f; | |||||
| float inA = 0; | |||||
| float inB = 0; | |||||
| if (inputs[IN_A_INPUT].isConnected()) { | |||||
| inA = inputs[IN_A_INPUT].getVoltage(); | |||||
| } | |||||
| if (inputs[IN_B_INPUT].isConnected()) { | |||||
| inB = inputs[IN_B_INPUT].getVoltage(); | |||||
| } | |||||
| else if (inputs[IN_A_INPUT].isConnected()) { | |||||
| inB = inA; | |||||
| } | |||||
| const float inA = inputs[IN_A_INPUT].getVoltage(); | |||||
| const float inB = inputs[IN_B_INPUT].getNormalVoltage(inputs[IN_A_INPUT].getVoltage()); | |||||
| // if the CHOPP gate is wired in, do chop logic | // if the CHOPP gate is wired in, do chop logic | ||||
| if (inputs[IN_GATE_INPUT].isConnected()) { | if (inputs[IN_GATE_INPUT].isConnected()) { | ||||
| // TODO: check rescale? | // TODO: check rescale? | ||||
| trigger.process(rescale(inputs[IN_GATE_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f)); | trigger.process(rescale(inputs[IN_GATE_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f)); | ||||
| outputAToChopp = trigger.isHigh(); | outputAToChopp = trigger.isHigh(); | ||||
| } | |||||
| } | |||||
| // else zero-crossing detector on input A switches between A and B | |||||
| else { | else { | ||||
| if (previousA > 0 && inA < 0) { | if (previousA > 0 && inA < 0) { | ||||
| outputAToChopp = false; | outputAToChopp = false; | ||||
| @@ -127,9 +109,9 @@ struct ChoppingKinky : Module { | |||||
| } | } | ||||
| previousA = inA; | previousA = inA; | ||||
| bool choppIsRequired = outputs[OUT_CHOPP_OUTPUT].isConnected(); | |||||
| bool aIsRequired = outputs[OUT_A_OUTPUT].isConnected() || choppIsRequired; | |||||
| bool bIsRequired = outputs[OUT_B_OUTPUT].isConnected() || choppIsRequired; | |||||
| const bool choppIsRequired = outputs[OUT_CHOPP_OUTPUT].isConnected(); | |||||
| const bool aIsRequired = outputs[OUT_A_OUTPUT].isConnected() || choppIsRequired; | |||||
| const bool bIsRequired = outputs[OUT_B_OUTPUT].isConnected() || choppIsRequired; | |||||
| if (aIsRequired) { | if (aIsRequired) { | ||||
| oversampler[CHANNEL_A].upsample(inA * gainA); | oversampler[CHANNEL_A].upsample(inA * gainA); | ||||
| @@ -138,8 +120,7 @@ struct ChoppingKinky : Module { | |||||
| oversampler[CHANNEL_B].upsample(inB * gainB); | 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(outputAToChopp ? 1.f : 0.f); | |||||
| } | } | ||||
| float* osBufferA = oversampler[CHANNEL_A].getOSBuffer(); | float* osBufferA = oversampler[CHANNEL_A].getOSBuffer(); | ||||
| @@ -148,12 +129,10 @@ 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) { | ||||
| // TODO: replace with LUT of measured wavefolder response | |||||
| //osBufferA[i] = wavefolderAResponse(osBufferA[i]); | //osBufferA[i] = wavefolderAResponse(osBufferA[i]); | ||||
| osBufferA[i] = wavefolderAResponseCached(osBufferA[i]); | osBufferA[i] = wavefolderAResponseCached(osBufferA[i]); | ||||
| } | } | ||||
| if (bIsRequired) { | if (bIsRequired) { | ||||
| // TODO: replace with LUT of measured wavefolder response | |||||
| //osBufferB[i] = wavefolderBResponse(osBufferB[i]); | //osBufferB[i] = wavefolderBResponse(osBufferB[i]); | ||||
| osBufferB[i] = wavefolderBResponseCached(osBufferB[i]); | osBufferB[i] = wavefolderBResponseCached(osBufferB[i]); | ||||
| } | } | ||||
| @@ -279,20 +258,17 @@ struct ChoppingKinky : Module { | |||||
| } | } | ||||
| } | } | ||||
| // functional form for waveshapers uses a lot of transcendental functions, so we cache | |||||
| // the response in a LUT | |||||
| void cacheWaveshaperResponses() { | void cacheWaveshaperResponses() { | ||||
| for (int i = 0; i < WAVESHAPE_CACHE_SIZE; ++i) { | for (int i = 0; i < WAVESHAPE_CACHE_SIZE; ++i) { | ||||
| float x = rescale(i, 0, WAVESHAPE_CACHE_SIZE - 1, 0.0, 10.f); | float x = rescale(i, 0, WAVESHAPE_CACHE_SIZE - 1, 0.0, 10.f); | ||||
| waveshapeA[i] = wavefolderAResponse(x); | 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); | waveshapeBPositive[i] = wavefolderBResponse(+x); | ||||
| waveshapeBNegative[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(); | ||||
| json_object_set_new(rootJ, "filterDC", json_boolean(blockDC)); | json_object_set_new(rootJ, "filterDC", json_boolean(blockDC)); | ||||
| @@ -357,9 +333,9 @@ struct ChoppingKinkyWidget : ModuleWidget { | |||||
| struct ModeItem : MenuItem { | struct ModeItem : MenuItem { | ||||
| ChoppingKinky* module; | ChoppingKinky* module; | ||||
| int mode; | |||||
| int oversamplingIndex; | |||||
| void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
| module->oversamplingIndex = mode; | |||||
| module->oversamplingIndex = oversamplingIndex; | |||||
| module->onSampleRateChange(); | module->onSampleRateChange(); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -380,7 +356,7 @@ struct ChoppingKinkyWidget : ModuleWidget { | |||||
| ModeItem* modeItem = createMenuItem<ModeItem>(std::to_string(int (1 << i)) + "x"); | ModeItem* modeItem = createMenuItem<ModeItem>(std::to_string(int (1 << i)) + "x"); | ||||
| modeItem->rightText = CHECKMARK(module->oversamplingIndex == i); | modeItem->rightText = CHECKMARK(module->oversamplingIndex == i); | ||||
| modeItem->module = module; | modeItem->module = module; | ||||
| modeItem->mode = i; | |||||
| modeItem->oversamplingIndex = i; | |||||
| menu->addChild(modeItem); | menu->addChild(modeItem); | ||||
| } | } | ||||
| } | } | ||||