#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
};
SampleRateConverter<2> inputSrc;
SampleRateConverter<2> outputSrc;
DoubleRingBuffer, 256> inputBuffer;
DoubleRingBuffer, 256> outputBuffer;
uint8_t *block_mem;
uint8_t *block_ccm;
clouds::GranularProcessor *processor;
bool triggered = false;
SchmittTrigger freezeTrigger;
bool freeze = false;
SchmittTrigger blendTrigger;
int blendMode = 0;
clouds::PlaybackMode playback;
int quality = 0;
Clouds();
~Clouds();
void step() override;
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);
}
}
};
Clouds::Clouds() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
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::~Clouds() {
delete processor;
delete[] block_mem;
delete[] block_ccm;
}
void Clouds::step() {
// Get input
Frame<2> inputFrame = {};
if (!inputBuffer.full()) {
inputFrame.samples[0] = inputs[IN_L_INPUT].value * params[IN_GAIN_PARAM].value / 5.0;
inputFrame.samples[1] = inputs[IN_R_INPUT].active ? inputs[IN_R_INPUT].value * params[IN_GAIN_PARAM].value / 5.0 : inputFrame.samples[0];
inputBuffer.push(inputFrame);
}
if (freezeTrigger.process(params[FREEZE_PARAM].value)) {
freeze ^= true;
}
if (blendTrigger.process(params[MODE_PARAM].value)) {
blendMode = (blendMode + 1) % 4;
}
// Trigger
if (inputs[TRIG_INPUT].value >= 1.0) {
triggered = true;
}
// Render frames
if (outputBuffer.empty()) {
clouds::ShortFrame input[32] = {};
// Convert input buffer
{
inputSrc.setRates(engineGetSampleRate(), 32000);
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].value >= 1.0);
p->position = clamp(params[POSITION_PARAM].value + inputs[POSITION_INPUT].value / 5.0f, 0.0f, 1.0f);
p->size = clamp(params[SIZE_PARAM].value + inputs[SIZE_INPUT].value / 5.0f, 0.0f, 1.0f);
p->pitch = clamp((params[PITCH_PARAM].value + inputs[PITCH_INPUT].value) * 12.0f, -48.0f, 48.0f);
p->density = clamp(params[DENSITY_PARAM].value + inputs[DENSITY_INPUT].value / 5.0f, 0.0f, 1.0f);
p->texture = clamp(params[TEXTURE_PARAM].value + inputs[TEXTURE_INPUT].value / 5.0f, 0.0f, 1.0f);
p->dry_wet = params[BLEND_PARAM].value;
p->stereo_spread = params[SPREAD_PARAM].value;
p->feedback = params[FEEDBACK_PARAM].value;
// TODO
// Why doesn't dry audio get reverbed?
p->reverb = params[REVERB_PARAM].value;
float blend = inputs[BLEND_INPUT].value / 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
{
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, engineGetSampleRate());
int inLen = 32;
int outLen = outputBuffer.capacity();
outputSrc.process(outputFrames, &inLen, outputBuffer.endData(), &outLen);
outputBuffer.endIncr(outLen);
}
triggered = false;
}
// Set output
Frame<2> outputFrame = {};
if (!outputBuffer.empty()) {
outputFrame = outputBuffer.shift();
outputs[OUT_L_OUTPUT].value = 5.0 * outputFrame.samples[0];
outputs[OUT_R_OUTPUT].value = 5.0 * outputFrame.samples[1];
}
// Lights
clouds::Parameters *p = processor->mutable_parameters();
VUMeter vuMeter;
vuMeter.dBInterval = 6.0;
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].setBrightnessSmooth(vuMeter.getBrightness(3));
lights[PAN_GREEN_LIGHT].setBrightnessSmooth(vuMeter.getBrightness(2));
lights[FEEDBACK_GREEN_LIGHT].setBrightnessSmooth(vuMeter.getBrightness(1));
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].setBrightnessSmooth(vuMeter.getBrightness(1));
lights[REVERB_RED_LIGHT].setBrightnessSmooth(vuMeter.getBrightness(0));
}
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) : ModuleWidget(module) {
setPanel(SVG::load(assetPlugin(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, 0.0, 1.0, 0.5));
addParam(createParam(Vec(108, 93), module, Clouds::SIZE_PARAM, 0.0, 1.0, 0.5));
addParam(createParam(Vec(190, 93), module, Clouds::PITCH_PARAM, -2.0, 2.0, 0.0));
addParam(createParam(Vec(14, 180), module, Clouds::IN_GAIN_PARAM, 0.0, 1.0, 0.5));
addParam(createParam(Vec(81, 180), module, Clouds::DENSITY_PARAM, 0.0, 1.0, 0.5));
addParam(createParam(Vec(146, 180), module, Clouds::TEXTURE_PARAM, 0.0, 1.0, 0.5));
blendParam = createParam(Vec(213, 180), module, Clouds::BLEND_PARAM, 0.0, 1.0, 0.5);
addParam(blendParam);
spreadParam = createParam(Vec(213, 180), module, Clouds::SPREAD_PARAM, 0.0, 1.0, 0.0);
addParam(spreadParam);
feedbackParam = createParam(Vec(213, 180), module, Clouds::FEEDBACK_PARAM, 0.0, 1.0, 0.0);
addParam(feedbackParam);
reverbParam = createParam(Vec(213, 180), module, Clouds::REVERB_PARAM, 0.0, 1.0, 0.0);
addParam(reverbParam);
addParam(createParam(Vec(12, 43), module, Clouds::FREEZE_PARAM, 0.0, 1.0, 0.0));
addParam(createParam(Vec(211, 50), module, Clouds::MODE_PARAM, 0.0, 1.0, 0.0));
addParam(createParam(Vec(239, 50), module, Clouds::LOAD_PARAM, 0.0, 1.0, 0.0));
addInput(createPort(Vec(15, 274), PortWidget::INPUT, module, Clouds::FREEZE_INPUT));
addInput(createPort(Vec(58, 274), PortWidget::INPUT, module, Clouds::TRIG_INPUT));
addInput(createPort(Vec(101, 274), PortWidget::INPUT, module, Clouds::POSITION_INPUT));
addInput(createPort(Vec(144, 274), PortWidget::INPUT, module, Clouds::SIZE_INPUT));
addInput(createPort(Vec(188, 274), PortWidget::INPUT, module, Clouds::PITCH_INPUT));
addInput(createPort(Vec(230, 274), PortWidget::INPUT, module, Clouds::BLEND_INPUT));
addInput(createPort(Vec(15, 317), PortWidget::INPUT, module, Clouds::IN_L_INPUT));
addInput(createPort(Vec(58, 317), PortWidget::INPUT, module, Clouds::IN_R_INPUT));
addInput(createPort(Vec(101, 317), PortWidget::INPUT, module, Clouds::DENSITY_INPUT));
addInput(createPort(Vec(144, 317), PortWidget::INPUT, module, Clouds::TEXTURE_INPUT));
addOutput(createPort(Vec(188, 317), PortWidget::OUTPUT, module, Clouds::OUT_L_OUTPUT));
addOutput(createPort(Vec(230, 317), PortWidget::OUTPUT, 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);
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");