#include #include "Southpole.hpp" #include "dsp/samplerate.hpp" #include "dsp/digital.hpp" #include "tides/generator.h" struct Splash : Module { enum ParamIds { MODE_PARAM, RANGE_PARAM, FREQUENCY_PARAM, FM_PARAM, SHAPE_PARAM, SLOPE_PARAM, SMOOTHNESS_PARAM, NUM_PARAMS }; enum InputIds { SHAPE_INPUT, SLOPE_INPUT, SMOOTHNESS_INPUT, TRIG_INPUT, FREEZE_INPUT, PITCH_INPUT, FM_INPUT, LEVEL_INPUT, CLOCK_INPUT, NUM_INPUTS }; enum OutputIds { HIGH_OUTPUT, LOW_OUTPUT, UNI_OUTPUT, BI_OUTPUT, NUM_OUTPUTS }; enum LightIds { MODE_GREEN_LIGHT, MODE_RED_LIGHT, PHASE_GREEN_LIGHT, PHASE_RED_LIGHT, RANGE_GREEN_LIGHT, RANGE_RED_LIGHT, NUM_LIGHTS }; bool sheep; tides::Generator generator; int frame = 0; uint8_t lastGate; SchmittTrigger modeTrigger; SchmittTrigger rangeTrigger; Splash(); void step() override; void reset() override { generator.set_range(tides::GENERATOR_RANGE_MEDIUM); generator.set_mode(tides::GENERATOR_MODE_LOOPING); sheep = false; } void randomize() override { generator.set_range((tides::GeneratorRange) (randomu32() % 3)); generator.set_mode((tides::GeneratorMode) (randomu32() % 3)); } json_t *toJson() override { json_t *rootJ = json_object(); json_object_set_new(rootJ, "mode", json_integer((int) generator.mode())); json_object_set_new(rootJ, "range", json_integer((int) generator.range())); json_object_set_new(rootJ, "sheep", json_boolean(sheep)); return rootJ; } void fromJson(json_t *rootJ) override { json_t *modeJ = json_object_get(rootJ, "mode"); if (modeJ) { generator.set_mode((tides::GeneratorMode) json_integer_value(modeJ)); } json_t *rangeJ = json_object_get(rootJ, "range"); if (rangeJ) { generator.set_range((tides::GeneratorRange) json_integer_value(rangeJ)); } json_t *sheepJ = json_object_get(rootJ, "sheep"); if (sheepJ) { sheep = json_boolean_value(sheepJ); } } }; Splash::Splash() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { memset(&generator, 0, sizeof(generator)); generator.Init(); generator.set_sync(false); reset(); } void Splash::step() { tides::GeneratorMode mode = generator.mode(); if (modeTrigger.process(params[MODE_PARAM].value)) { mode = (tides::GeneratorMode) (((int)mode - 1 + 3) % 3); generator.set_mode(mode); } lights[MODE_GREEN_LIGHT].value = (mode == 2) ? 1.0 : 0.0; lights[MODE_GREEN_LIGHT].value = (mode == 0) ? 0.0 : 1.0; lights[MODE_RED_LIGHT].value = (mode == 0) ? 1.0 : 0.0; lights[MODE_RED_LIGHT].value = (mode == 2) ? 0.0 : 1.0; tides::GeneratorRange range = generator.range(); if (rangeTrigger.process(params[RANGE_PARAM].value)) { range = (tides::GeneratorRange) (((int)range - 1 + 3) % 3); generator.set_range(range); } lights[RANGE_GREEN_LIGHT].value = (range == 2) ? 1.0 : 0.0; lights[RANGE_GREEN_LIGHT].value = (range == 0) ? 0.0 : 1.0; lights[RANGE_RED_LIGHT].value = (range == 0) ? 1.0 : 0.0; lights[RANGE_RED_LIGHT].value = (range == 2) ? 0.0 : 1.0; // Buffer loop if (++frame >= 16) { frame = 0; // Pitch float pitch = params[FREQUENCY_PARAM].value; pitch += 12.0 * inputs[PITCH_INPUT].value; pitch += params[FM_PARAM].value * inputs[FM_INPUT].normalize(0.1) / 5.0; pitch += 60.0; // Scale to the global sample rate pitch += log2f(48000.0 / engineGetSampleRate()) * 12.0; generator.set_pitch(clampf(pitch * 0x80, -0x8000, 0x7fff)); // Slope, smoothness, pitch int16_t shape = clampf(params[SHAPE_PARAM].value + inputs[SHAPE_INPUT].value / 5.0, -1.0, 1.0) * 0x7fff; int16_t slope = clampf(params[SLOPE_PARAM].value + inputs[SLOPE_INPUT].value / 5.0, -1.0, 1.0) * 0x7fff; int16_t smoothness = clampf(params[SMOOTHNESS_PARAM].value + inputs[SMOOTHNESS_INPUT].value / 5.0, -1.0, 1.0) * 0x7fff; generator.set_shape(shape); generator.set_slope(slope); generator.set_smoothness(smoothness); // Sync // Slight deviation from spec here. // Instead of toggling sync by holding the range button, just enable it if the clock port is plugged in. generator.set_sync(inputs[CLOCK_INPUT].active); // Generator generator.Process(sheep); } // Level uint16_t level = clampf(inputs[LEVEL_INPUT].normalize(8.0) / 8.0, 0.0, 1.0) * 0xffff; if (level < 32) level = 0; uint8_t gate = 0; if (inputs[FREEZE_INPUT].value >= 0.7) gate |= tides::CONTROL_FREEZE; if (inputs[TRIG_INPUT].value >= 0.7) gate |= tides::CONTROL_GATE; if (inputs[CLOCK_INPUT].value >= 0.7) gate |= tides::CONTROL_CLOCK; if (!(lastGate & tides::CONTROL_CLOCK) && (gate & tides::CONTROL_CLOCK)) gate |= tides::CONTROL_GATE_RISING; if (!(lastGate & tides::CONTROL_GATE) && (gate & tides::CONTROL_GATE)) gate |= tides::CONTROL_GATE_RISING; if ((lastGate & tides::CONTROL_GATE) && !(gate & tides::CONTROL_GATE)) gate |= tides::CONTROL_GATE_FALLING; lastGate = gate; const tides::GeneratorSample& sample = generator.Process(gate); uint32_t uni = sample.unipolar; int32_t bi = sample.bipolar; uni = uni * level >> 16; bi = -bi * level >> 16; float unif = (float) uni / 0xffff; float bif = (float) bi / 0x8000; outputs[HIGH_OUTPUT].value = sample.flags & tides::FLAG_END_OF_ATTACK ? 0.0 : 5.0; outputs[LOW_OUTPUT].value = sample.flags & tides::FLAG_END_OF_RELEASE ? 0.0 : 5.0; outputs[UNI_OUTPUT].value = unif * 8.0; outputs[BI_OUTPUT].value = bif * 5.0; if (sample.flags & tides::FLAG_END_OF_ATTACK) unif *= -1.0; lights[PHASE_GREEN_LIGHT].setBrightnessSmooth(fmaxf(0.0, unif)); lights[PHASE_RED_LIGHT].setBrightnessSmooth(fmaxf(0.0, -unif)); } SplashWidget::SplashWidget() { Splash *module = new Splash(); setModule(module); box.size = Vec(15 * 8, 380); { tidesPanel = new SVGPanel(); tidesPanel->setBackground(SVG::load(assetPlugin(plugin, "res/Splash.svg"))); tidesPanel->box.size = box.size; addChild(tidesPanel); } { sheepPanel = new SVGPanel(); sheepPanel->setBackground(SVG::load(assetPlugin(plugin, "res/Lambs.svg"))); sheepPanel->box.size = box.size; addChild(sheepPanel); } const float x1 = 0.5*RACK_GRID_WIDTH; const float x2 = 3.25*RACK_GRID_WIDTH; const float x3 = 5.75*RACK_GRID_WIDTH; const float y1 = 40.0f; const float y2 = 25.0f; const float yh = 38.0f; addParam(createParam(Vec(x3-3,y1-3), module, Splash::MODE_PARAM, 0.0, 1.0, 0.0)); addChild(createLight>(Vec(x3+7, y1+7), module, Splash::MODE_GREEN_LIGHT)); addParam(createParam(Vec(x3-3,y1+1.45*yh), module, Splash::RANGE_PARAM, 0.0, 1.0, 0.0)); addChild(createLight>(Vec(x3+7, y1+2*yh-10), module, Splash::RANGE_GREEN_LIGHT)); addChild(createLight>(Vec(x2-20, y2+2*yh), module, Splash::PHASE_GREEN_LIGHT)); addParam(createParam(Vec(x2-7,y2+1.75*yh), module, Splash::FREQUENCY_PARAM, -48.0, 48.0, 0.0)); addParam(createParam(Vec(x3, y2+4*yh), module, Splash::SHAPE_PARAM, -1.0, 1.0, 0.0)); addParam(createParam(Vec(x3, y2+4.75*yh), module, Splash::SLOPE_PARAM, -1.0, 1.0, 0.0)); addParam(createParam(Vec(x3, y2+5.5*yh), module, Splash::SMOOTHNESS_PARAM, -1.0, 1.0, 0.0)); addInput(createInput(Vec(x1, y1), module, Splash::TRIG_INPUT)); addInput(createInput(Vec(x2, y1), module, Splash::FREEZE_INPUT)); addInput(createInput(Vec(x1, y2+2*yh), module, Splash::PITCH_INPUT)); addInput(createInput(Vec(x1, y2+3.25*yh), module, Splash::FM_INPUT)); addParam(createParam(Vec(x2,y2+3.25*yh), module, Splash::FM_PARAM, -12.0, 12.0, 0.0)); addInput(createInput(Vec(x1, y2+4*yh), module, Splash::SHAPE_INPUT)); addInput(createInput(Vec(x1, y2+4.75*yh), module, Splash::SLOPE_INPUT)); addInput(createInput(Vec(x1, y2+5.5*yh), module, Splash::SMOOTHNESS_INPUT)); addInput(createInput(Vec(x3, y1+5.9*yh), module, Splash::LEVEL_INPUT)); addInput(createInput(Vec(x1, y1+5.9*yh), module, Splash::CLOCK_INPUT)); addOutput(createOutput(Vec(x1, y1+7.125*yh), module, Splash::HIGH_OUTPUT)); addOutput(createOutput(Vec(x1+1*28., y1+7.125*yh), module, Splash::LOW_OUTPUT)); addOutput(createOutput(Vec(x1+2*28., y1+7.125*yh), module, Splash::UNI_OUTPUT)); addOutput(createOutput(Vec(x1+3*28., y1+7.125*yh), module, Splash::BI_OUTPUT)); } void SplashWidget::step() { Splash *tides = dynamic_cast(module); assert(tides); tidesPanel->visible = !tides->sheep; sheepPanel->visible = tides->sheep; ModuleWidget::step(); } struct SplashSheepItem : MenuItem { Splash *tides; void onAction(EventAction &e) override { tides->sheep ^= true; } void step() override { rightText = (tides->sheep) ? "✔" : ""; MenuItem::step(); } }; Menu *SplashWidget::createContextMenu() { Menu *menu = ModuleWidget::createContextMenu(); Splash *tides = dynamic_cast(module); assert(tides); menu->addChild(construct()); menu->addChild(construct(&MenuEntry::text, "Lambs", &SplashSheepItem::tides, tides)); return menu; }