#include "plugin.hpp" #include "ChowDSP.hpp" struct Kickall : Module { enum ParamIds { TUNE_PARAM, TRIGG_BUTTON_PARAM, SHAPE_PARAM, DECAY_PARAM, TIME_PARAM, BEND_PARAM, NUM_PARAMS }; enum InputIds { TRIGG_INPUT, VOLUME_INPUT, TUNE_INPUT, SHAPE_INPUT, DECAY_INPUT, NUM_INPUTS }; enum OutputIds { OUT_OUTPUT, NUM_OUTPUTS }; enum LightIds { ENV_LIGHT, NUM_LIGHTS }; static constexpr float FREQ_A0 = 27.5f; static constexpr float FREQ_B2 = 123.471f; static constexpr float minVolumeDecay = 0.075f; static constexpr float maxVolumeDecay = 4.f; static constexpr float minPitchDecay = 0.0075f; static constexpr float maxPitchDecay = 1.f; static constexpr float bendRange = 10000; float phase = 0.f; ADEnvelope volume; ADEnvelope pitch; dsp::SchmittTrigger trigger; static const int UPSAMPLE = 8; chowdsp::Oversampling oversampler; Kickall() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); // TODO: review this mapping, using displayBase multiplier seems more normal configParam(TUNE_PARAM, FREQ_A0, FREQ_B2, 0.5 * (FREQ_A0 + FREQ_B2), "Tune", "Hz"); configParam(TRIGG_BUTTON_PARAM, 0.f, 1.f, 0.f, "Manual trigger"); configParam(SHAPE_PARAM, 0.f, 1.f, 0.f, "Wave shape"); configParam(DECAY_PARAM, 0.f, 1.f, 0.01f, "VCA Envelope decay time"); configParam(TIME_PARAM, 0.f, 1.0f, 0.f, "Pitch envelope decay time"); configParam(BEND_PARAM, 0.f, 1.f, 0.f, "Pitch envelope attenuator"); volume.attackTime = 0.01; volume.attackShape = 0.5; volume.decayShape = 3.0; pitch.attackTime = 0.00165; pitch.decayShape = 3.0; // calculate up/downsampling rates onSampleRateChange(); } void onSampleRateChange() override { oversampler.reset(APP->engine->getSampleRate()); } void process(const ProcessArgs& args) override { // TODO: check values if (trigger.process(inputs[TRIGG_INPUT].getVoltage() / 2.0f + params[TRIGG_BUTTON_PARAM].getValue() * 10.0)) { volume.trigger(); pitch.trigger(); } const float vcaGain = clamp(inputs[VOLUME_INPUT].getNormalVoltage(10.f) / 10.f, 0.f, 1.0f); // pitch envelope const float bend = bendRange * std::pow(params[BEND_PARAM].getValue(), 3.0); pitch.decayTime = rescale(params[TIME_PARAM].getValue(), 0.f, 1.0f, minPitchDecay, maxPitchDecay); pitch.process(args.sampleTime); // volume envelope const float volumeDecay = minVolumeDecay * std::pow(2.f, params[DECAY_PARAM].getValue() * std::log2(maxVolumeDecay / minVolumeDecay)); volume.decayTime = clamp(volumeDecay + inputs[DECAY_INPUT].getVoltage() * 0.1f, 0.01, 10.0); volume.process(args.sampleTime); float freq = params[TUNE_PARAM].getValue(); freq *= std::pow(2.f, inputs[TUNE_INPUT].getVoltage()); const float kickFrequency = std::max(10.0f, freq + bend * pitch.env); const float phaseInc = clamp(args.sampleTime * kickFrequency / UPSAMPLE, 1e-6, 0.35f); const float shape = clamp(inputs[SHAPE_INPUT].getVoltage() / 10.f + params[SHAPE_PARAM].getValue(), 0.0f, 1.0f) * 0.99f; const float shapeB = (1.0f - shape) / (1.0f + shape); const float shapeA = (4.0f * shape) / ((1.0f - shape) * (1.0f + shape)); float* inputBuf = oversampler.getOSBuffer(); for (int i = 0; i < UPSAMPLE; ++i) { phase += phaseInc; phase -= std::floor(phase); inputBuf[i] = sin2pi_pade_05_5_4(phase); inputBuf[i] = inputBuf[i] * (shapeA + shapeB) / ((std::abs(inputBuf[i]) * shapeA) + shapeB); } const float out = volume.env * oversampler.downsample() * 5.0f * vcaGain; outputs[OUT_OUTPUT].setVoltage(out); lights[ENV_LIGHT].setBrightness(volume.env); } }; struct KickallWidget : ModuleWidget { KickallWidget(Kickall* module) { setModule(module); setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Kickall.svg"))); addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); addParam(createParamCentered(mm2px(Vec(8.472, 28.97)), module, Kickall::TUNE_PARAM)); addParam(createParamCentered(mm2px(Vec(22.409, 29.159)), module, Kickall::TRIGG_BUTTON_PARAM)); addParam(createParamCentered(mm2px(Vec(15.526, 49.292)), module, Kickall::SHAPE_PARAM)); addParam(createParam(mm2px(Vec(19.667, 63.897)), module, Kickall::DECAY_PARAM)); addParam(createParamCentered(mm2px(Vec(8.521, 71.803)), module, Kickall::TIME_PARAM)); addParam(createParamCentered(mm2px(Vec(8.521, 93.517)), module, Kickall::BEND_PARAM)); addInput(createInputCentered(mm2px(Vec(15.501, 14.508)), module, Kickall::VOLUME_INPUT)); addInput(createInputCentered(mm2px(Vec(5.499, 14.536)), module, Kickall::TRIGG_INPUT)); addInput(createInputCentered(mm2px(Vec(25.525, 113.191)), module, Kickall::DECAY_INPUT)); addInput(createInputCentered(mm2px(Vec(5.499, 113.208)), module, Kickall::TUNE_INPUT)); addInput(createInputCentered(mm2px(Vec(15.485, 113.208)), module, Kickall::SHAPE_INPUT)); addOutput(createOutputCentered(mm2px(Vec(25.525, 14.52)), module, Kickall::OUT_OUTPUT)); addChild(createLightCentered>(mm2px(Vec(15.535, 34.943)), module, Kickall::ENV_LIGHT)); } }; Model* modelKickall = createModel("Kickall");