#include "AudibleInstruments.hpp" #include "clouds/dsp/granular_processor.h" struct Clouds : Module { enum ParamIds { FREEZE_PARAM, MODE_PARAM, LOAD_PARAM, POSITION_PARAM, SIZE_PARAM, PITCH_PARAM, IN_GAIN_PARAM, DENSITY_PARAM, TEXTURE_PARAM, BLEND_PARAM, SPREAD_PARAM, FEEDBACK_PARAM, REVERB_PARAM, NUM_PARAMS }; enum InputIds { FREEZE_INPUT, TRIG_INPUT, POSITION_INPUT, SIZE_INPUT, PITCH_INPUT, BLEND_INPUT, IN_L_INPUT, IN_R_INPUT, DENSITY_INPUT, TEXTURE_INPUT, NUM_INPUTS }; enum OutputIds { OUT_L_OUTPUT, OUT_R_OUTPUT, NUM_OUTPUTS }; enum LightIds { FREEZE_LIGHT, MIX_GREEN_LIGHT, MIX_RED_LIGHT, PAN_GREEN_LIGHT, PAN_RED_LIGHT, FEEDBACK_GREEN_LIGHT, FEEDBACK_RED_LIGHT, REVERB_GREEN_LIGHT, REVERB_RED_LIGHT, NUM_LIGHTS }; dsp::SampleRateConverter<2> inputSrc; dsp::SampleRateConverter<2> outputSrc; dsp::DoubleRingBuffer, 256> inputBuffer; dsp::DoubleRingBuffer, 256> outputBuffer; uint8_t *block_mem; uint8_t *block_ccm; clouds::GranularProcessor *processor; bool triggered = false; dsp::SchmittTrigger freezeTrigger; bool freeze = false; dsp::SchmittTrigger blendTrigger; int blendMode = 0; clouds::PlaybackMode playback; int quality = 0; Clouds() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); configParam(POSITION_PARAM, 0.0, 1.0, 0.5, "Grain position"); configParam(SIZE_PARAM, 0.0, 1.0, 0.5, "Grain size"); configParam(PITCH_PARAM, -2.0, 2.0, 0.0, "Grain pitch"); configParam(IN_GAIN_PARAM, 0.0, 1.0, 0.5, "Audio input gain"); configParam(DENSITY_PARAM, 0.0, 1.0, 0.5, "Grain density"); configParam(TEXTURE_PARAM, 0.0, 1.0, 0.5, "Grain texture"); configParam(BLEND_PARAM, 0.0, 1.0, 0.5, "Dry/wet"); configParam(SPREAD_PARAM, 0.0, 1.0, 0.0, "Stereo spread"); configParam(FEEDBACK_PARAM, 0.0, 1.0, 0.0, "Feedback amount"); configParam(REVERB_PARAM, 0.0, 1.0, 0.0, "Reverb amount"); configParam(FREEZE_PARAM, 0.0, 1.0, 0.0, "Freeze"); configParam(MODE_PARAM, 0.0, 1.0, 0.0, "Mode"); configParam(LOAD_PARAM, 0.0, 1.0, 0.0, "Load/save"); const int memLen = 118784; const int ccmLen = 65536 - 128; block_mem = new uint8_t[memLen](); block_ccm = new uint8_t[ccmLen](); processor = new clouds::GranularProcessor(); memset(processor, 0, sizeof(*processor)); processor->Init(block_mem, memLen, block_ccm, ccmLen); onReset(); } ~Clouds() { delete processor; delete[] block_mem; delete[] block_ccm; } void process(const ProcessArgs &args) override { // Get input dsp::Frame<2> inputFrame = {}; if (!inputBuffer.full()) { inputFrame.samples[0] = inputs[IN_L_INPUT].getVoltage() * params[IN_GAIN_PARAM].getValue() / 5.0; inputFrame.samples[1] = inputs[IN_R_INPUT].isConnected() ? inputs[IN_R_INPUT].getVoltage() * params[IN_GAIN_PARAM].getValue() / 5.0 : inputFrame.samples[0]; inputBuffer.push(inputFrame); } if (freezeTrigger.process(params[FREEZE_PARAM].getValue())) { freeze ^= true; } if (blendTrigger.process(params[MODE_PARAM].getValue())) { blendMode = (blendMode + 1) % 4; } // Trigger if (inputs[TRIG_INPUT].getVoltage() >= 1.0) { triggered = true; } // Render frames if (outputBuffer.empty()) { clouds::ShortFrame input[32] = {}; // Convert input buffer { inputSrc.setRates(args.sampleRate, 32000); dsp::Frame<2> inputFrames[32]; int inLen = inputBuffer.size(); int outLen = 32; inputSrc.process(inputBuffer.startData(), &inLen, inputFrames, &outLen); inputBuffer.startIncr(inLen); // We might not fill all of the input buffer if there is a deficiency, but this cannot be avoided due to imprecisions between the input and output SRC. for (int i = 0; i < outLen; i++) { input[i].l = clamp(inputFrames[i].samples[0] * 32767.0f, -32768.0f, 32767.0f); input[i].r = clamp(inputFrames[i].samples[1] * 32767.0f, -32768.0f, 32767.0f); } } // Set up processor processor->set_playback_mode(playback); processor->set_quality(quality); processor->Prepare(); clouds::Parameters *p = processor->mutable_parameters(); p->trigger = triggered; p->gate = triggered; p->freeze = freeze || (inputs[FREEZE_INPUT].getVoltage() >= 1.0); p->position = clamp(params[POSITION_PARAM].getValue() + inputs[POSITION_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); p->size = clamp(params[SIZE_PARAM].getValue() + inputs[SIZE_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); p->pitch = clamp((params[PITCH_PARAM].getValue() + inputs[PITCH_INPUT].getVoltage()) * 12.0f, -48.0f, 48.0f); p->density = clamp(params[DENSITY_PARAM].getValue() + inputs[DENSITY_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); p->texture = clamp(params[TEXTURE_PARAM].getValue() + inputs[TEXTURE_INPUT].getVoltage() / 5.0f, 0.0f, 1.0f); p->dry_wet = params[BLEND_PARAM].getValue(); p->stereo_spread = params[SPREAD_PARAM].getValue(); p->feedback = params[FEEDBACK_PARAM].getValue(); // TODO // Why doesn't dry audio get reverbed? p->reverb = params[REVERB_PARAM].getValue(); float blend = inputs[BLEND_INPUT].getVoltage() / 5.0f; switch (blendMode) { case 0: p->dry_wet += blend; p->dry_wet = clamp(p->dry_wet, 0.0f, 1.0f); break; case 1: p->stereo_spread += blend; p->stereo_spread = clamp(p->stereo_spread, 0.0f, 1.0f); break; case 2: p->feedback += blend; p->feedback = clamp(p->feedback, 0.0f, 1.0f); break; case 3: p->reverb += blend; p->reverb = clamp(p->reverb, 0.0f, 1.0f); break; } clouds::ShortFrame output[32]; processor->Process(input, output, 32); // Convert output buffer { dsp::Frame<2> outputFrames[32]; for (int i = 0; i < 32; i++) { outputFrames[i].samples[0] = output[i].l / 32768.0; outputFrames[i].samples[1] = output[i].r / 32768.0; } outputSrc.setRates(32000, args.sampleRate); int inLen = 32; int outLen = outputBuffer.capacity(); outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen); outputBuffer.endIncr(outLen); } triggered = false; } // Set output dsp::Frame<2> outputFrame = {}; if (!outputBuffer.empty()) { outputFrame = outputBuffer.shift(); outputs[OUT_L_OUTPUT].setVoltage(5.0 * outputFrame.samples[0]); outputs[OUT_R_OUTPUT].setVoltage(5.0 * outputFrame.samples[1]); } // Lights clouds::Parameters *p = processor->mutable_parameters(); dsp::VuMeter vuMeter; vuMeter.dBInterval = 6.0; dsp::Frame<2> lightFrame = p->freeze ? outputFrame : inputFrame; vuMeter.setValue(fmaxf(fabsf(lightFrame.samples[0]), fabsf(lightFrame.samples[1]))); lights[FREEZE_LIGHT].setBrightness(p->freeze ? 0.75 : 0.0); lights[MIX_GREEN_LIGHT].setSmoothBrightness(vuMeter.getBrightness(3), args.sampleTime); lights[PAN_GREEN_LIGHT].setSmoothBrightness(vuMeter.getBrightness(2), args.sampleTime); lights[FEEDBACK_GREEN_LIGHT].setSmoothBrightness(vuMeter.getBrightness(1), args.sampleTime); lights[REVERB_GREEN_LIGHT].setBrightness(0.0); lights[MIX_RED_LIGHT].setBrightness(0.0); lights[PAN_RED_LIGHT].setBrightness(0.0); lights[FEEDBACK_RED_LIGHT].setSmoothBrightness(vuMeter.getBrightness(1), args.sampleTime); lights[REVERB_RED_LIGHT].setSmoothBrightness(vuMeter.getBrightness(0), args.sampleTime); } void onReset() override { freeze = false; blendMode = 0; playback = clouds::PLAYBACK_MODE_GRANULAR; quality = 0; } json_t *dataToJson() override { json_t *rootJ = json_object(); json_object_set_new(rootJ, "playback", json_integer((int) playback)); json_object_set_new(rootJ, "quality", json_integer(quality)); json_object_set_new(rootJ, "blendMode", json_integer(blendMode)); return rootJ; } void dataFromJson(json_t *rootJ) override { json_t *playbackJ = json_object_get(rootJ, "playback"); if (playbackJ) { playback = (clouds::PlaybackMode) json_integer_value(playbackJ); } json_t *qualityJ = json_object_get(rootJ, "quality"); if (qualityJ) { quality = json_integer_value(qualityJ); } json_t *blendModeJ = json_object_get(rootJ, "blendMode"); if (blendModeJ) { blendMode = json_integer_value(blendModeJ); } } }; struct FreezeLight : YellowLight { FreezeLight() { box.size = Vec(28-6, 28-6); bgColor = color::BLACK_TRANSPARENT; } }; struct CloudsBlendItem : MenuItem { Clouds *module; int blendMode; void onAction(const event::Action &e) override { module->blendMode = blendMode; } void step() override { rightText = (module->blendMode == blendMode) ? "✔" : ""; MenuItem::step(); } }; struct CloudsPlaybackItem : MenuItem { Clouds *module; clouds::PlaybackMode playback; void onAction(const event::Action &e) override { module->playback = playback; } void step() override { rightText = (module->playback == playback) ? "✔" : ""; MenuItem::step(); } }; struct CloudsQualityItem : MenuItem { Clouds *module; int quality; void onAction(const event::Action &e) override { module->quality = quality; } void step() override { rightText = (module->quality == quality) ? "✔" : ""; MenuItem::step(); } }; struct CloudsWidget : ModuleWidget { ParamWidget *blendParam; ParamWidget *spreadParam; ParamWidget *feedbackParam; ParamWidget *reverbParam; CloudsWidget(Clouds *module) { setModule(module); setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Clouds.svg"))); addChild(createWidget(Vec(15, 0))); addChild(createWidget(Vec(240, 0))); addChild(createWidget(Vec(15, 365))); addChild(createWidget(Vec(240, 365))); addParam(createParam(Vec(27, 93), module, Clouds::POSITION_PARAM)); addParam(createParam(Vec(108, 93), module, Clouds::SIZE_PARAM)); addParam(createParam(Vec(190, 93), module, Clouds::PITCH_PARAM)); addParam(createParam(Vec(14, 180), module, Clouds::IN_GAIN_PARAM)); addParam(createParam(Vec(81, 180), module, Clouds::DENSITY_PARAM)); addParam(createParam(Vec(146, 180), module, Clouds::TEXTURE_PARAM)); blendParam = createParam(Vec(213, 180), module, Clouds::BLEND_PARAM); addParam(blendParam); spreadParam = createParam(Vec(213, 180), module, Clouds::SPREAD_PARAM); addParam(spreadParam); feedbackParam = createParam(Vec(213, 180), module, Clouds::FEEDBACK_PARAM); addParam(feedbackParam); reverbParam = createParam(Vec(213, 180), module, Clouds::REVERB_PARAM); addParam(reverbParam); addParam(createParam(Vec(12, 43), module, Clouds::FREEZE_PARAM)); addParam(createParam(Vec(211, 50), module, Clouds::MODE_PARAM)); addParam(createParam(Vec(239, 50), module, Clouds::LOAD_PARAM)); addInput(createInput(Vec(15, 274), module, Clouds::FREEZE_INPUT)); addInput(createInput(Vec(58, 274), module, Clouds::TRIG_INPUT)); addInput(createInput(Vec(101, 274), module, Clouds::POSITION_INPUT)); addInput(createInput(Vec(144, 274), module, Clouds::SIZE_INPUT)); addInput(createInput(Vec(188, 274), module, Clouds::PITCH_INPUT)); addInput(createInput(Vec(230, 274), module, Clouds::BLEND_INPUT)); addInput(createInput(Vec(15, 317), module, Clouds::IN_L_INPUT)); addInput(createInput(Vec(58, 317), module, Clouds::IN_R_INPUT)); addInput(createInput(Vec(101, 317), module, Clouds::DENSITY_INPUT)); addInput(createInput(Vec(144, 317), module, Clouds::TEXTURE_INPUT)); addOutput(createOutput(Vec(188, 317), module, Clouds::OUT_L_OUTPUT)); addOutput(createOutput(Vec(230, 317), module, Clouds::OUT_R_OUTPUT)); addChild(createLight(Vec(12+3, 43+3), module, Clouds::FREEZE_LIGHT)); addChild(createLight>(Vec(82.5, 53), module, Clouds::MIX_GREEN_LIGHT)); addChild(createLight>(Vec(114.5, 53), module, Clouds::PAN_GREEN_LIGHT)); addChild(createLight>(Vec(145.5, 53), module, Clouds::FEEDBACK_GREEN_LIGHT)); addChild(createLight>(Vec(177.5, 53), module, Clouds::REVERB_GREEN_LIGHT)); } void step() override { Clouds *module = dynamic_cast(this->module); if (module) { blendParam->visible = (module->blendMode == 0); spreadParam->visible = (module->blendMode == 1); feedbackParam->visible = (module->blendMode == 2); reverbParam->visible = (module->blendMode == 3); } ModuleWidget::step(); } void appendContextMenu(Menu *menu) override { Clouds *module = dynamic_cast(this->module); assert(module); menu->addChild(construct()); menu->addChild(construct(&MenuLabel::text, "Blend knob")); menu->addChild(construct(&MenuItem::text, "Wet/dry", &CloudsBlendItem::module, module, &CloudsBlendItem::blendMode, 0)); menu->addChild(construct(&MenuItem::text, "Spread", &CloudsBlendItem::module, module, &CloudsBlendItem::blendMode, 1)); menu->addChild(construct(&MenuItem::text, "Feedback", &CloudsBlendItem::module, module, &CloudsBlendItem::blendMode, 2)); menu->addChild(construct(&MenuItem::text, "Reverb", &CloudsBlendItem::module, module, &CloudsBlendItem::blendMode, 3)); menu->addChild(construct()); menu->addChild(construct(&MenuLabel::text, "Alternative mode")); menu->addChild(construct(&MenuItem::text, "Granular", &CloudsPlaybackItem::module, module, &CloudsPlaybackItem::playback, clouds::PLAYBACK_MODE_GRANULAR)); menu->addChild(construct(&MenuItem::text, "Pitch-shifter/time-stretcher", &CloudsPlaybackItem::module, module, &CloudsPlaybackItem::playback, clouds::PLAYBACK_MODE_STRETCH)); menu->addChild(construct(&MenuItem::text, "Looping delay", &CloudsPlaybackItem::module, module, &CloudsPlaybackItem::playback, clouds::PLAYBACK_MODE_LOOPING_DELAY)); menu->addChild(construct(&MenuItem::text, "Spectral madness", &CloudsPlaybackItem::module, module, &CloudsPlaybackItem::playback, clouds::PLAYBACK_MODE_SPECTRAL)); menu->addChild(construct()); menu->addChild(construct(&MenuLabel::text, "Quality")); menu->addChild(construct(&MenuItem::text, "1s 32kHz 16-bit stereo", &CloudsQualityItem::module, module, &CloudsQualityItem::quality, 0)); menu->addChild(construct(&MenuItem::text, "2s 32kHz 16-bit mono", &CloudsQualityItem::module, module, &CloudsQualityItem::quality, 1)); menu->addChild(construct(&MenuItem::text, "4s 16kHz 8-bit µ-law stereo", &CloudsQualityItem::module, module, &CloudsQualityItem::quality, 2)); menu->addChild(construct(&MenuItem::text, "8s 16kHz 8-bit µ-law mono", &CloudsQualityItem::module, module, &CloudsQualityItem::quality, 3)); } }; Model *modelClouds = createModel("Clouds");