From 9ae62cbb381adb10f68921543a45ea9aa921a513 Mon Sep 17 00:00:00 2001 From: hemmer <915048+hemmer@users.noreply.github.com> Date: Fri, 8 Apr 2022 09:03:24 +0100 Subject: [PATCH] Initial commit from cschol PR --- plugin.json | 10 + src/Peaks.cpp | 697 +++++++++++++++++++++++++++++++++++++++++++++++++ src/plugin.cpp | 1 + src/plugin.hpp | 2 +- 4 files changed, 709 insertions(+), 1 deletion(-) create mode 100644 src/Peaks.cpp diff --git a/plugin.json b/plugin.json index 82c467c..ef77e2f 100644 --- a/plugin.json +++ b/plugin.json @@ -253,6 +253,16 @@ "Low-pass gate", "Polyphonic" ] + }, + { + "slug": "Peaks", + "name": "Peaks", + "description": "Based on Mutable Instruments Peaks", + "tags": [ + "Oscillator", + "LFO", + "Hardware clone" + ] } ] } \ No newline at end of file diff --git a/src/Peaks.cpp b/src/Peaks.cpp new file mode 100644 index 0000000..011166c --- /dev/null +++ b/src/Peaks.cpp @@ -0,0 +1,697 @@ +#include +#include + +#include "dsp/digital.hpp" +#include "dsp/samplerate.hpp" +#include "dsp/ringbuffer.hpp" + +#include "peaks/processors.h" + +#include "AudibleInstruments.hpp" + + +enum SwitchIndex { + SWITCH_TWIN_MODE, + SWITCH_FUNCTION, + SWITCH_GATE_TRIG_1, + SWITCH_GATE_TRIG_2 +}; + +enum EditMode { + EDIT_MODE_TWIN, + EDIT_MODE_SPLIT, + EDIT_MODE_FIRST, + EDIT_MODE_SECOND, + EDIT_MODE_LAST +}; + +enum Function { + FUNCTION_ENVELOPE, + FUNCTION_LFO, + FUNCTION_TAP_LFO, + FUNCTION_DRUM_GENERATOR, + FUNCTION_MINI_SEQUENCER, + FUNCTION_PULSE_SHAPER, + FUNCTION_PULSE_RANDOMIZER, + FUNCTION_FM_DRUM_GENERATOR, + FUNCTION_LAST, + FUNCTION_FIRST_ALTERNATE_FUNCTION = FUNCTION_MINI_SEQUENCER +}; + +struct Settings { + uint8_t edit_mode; + uint8_t function[2]; + uint8_t pot_value[8]; + bool snap_mode; +}; + + +static const size_t kNumBlocks = 2; +static const size_t kNumChannels = 2; +static const size_t kBlockSize = 4; +static const int32_t kLongPressDuration = 600; +static const uint8_t kNumAdcChannels = 4; +static const uint16_t kAdcThresholdUnlocked = 1 << (16 - 10); // 10 bits +static const uint16_t kAdcThresholdLocked = 1 << (16 - 8); // 8 bits + + +struct Peaks : Module { + enum ParamIds { + KNOB_1_PARAM, + KNOB_2_PARAM, + KNOB_3_PARAM, + KNOB_4_PARAM, + BUTTON_1_PARAM, + BUTTON_2_PARAM, + TRIG_1_PARAM, + TRIG_2_PARAM, + NUM_PARAMS + }; + enum InputIds { + GATE_1_INPUT, + GATE_2_INPUT, + NUM_INPUTS + }; + enum OutputIds { + OUT_1_OUTPUT, + OUT_2_OUTPUT, + NUM_OUTPUTS + }; + enum LightIds { + TRIG_1_LIGHT, + TRIG_2_LIGHT, + TWIN_MODE_LIGHT, + FUNC_1_LIGHT, + FUNC_2_LIGHT, + FUNC_3_LIGHT, + FUNC_4_LIGHT, + NUM_LIGHTS + }; + + static const peaks::ProcessorFunction function_table_[FUNCTION_LAST][2]; + + EditMode edit_mode_ = EDIT_MODE_TWIN; + Function function_[2] = {FUNCTION_ENVELOPE, FUNCTION_ENVELOPE}; + Settings settings_; + + uint8_t pot_value_[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + + bool snap_mode_ = false; + bool snapped_[4] = {false, false, false, false}; + + int32_t adc_lp_[kNumAdcChannels] = {0, 0, 0, 0}; + int32_t adc_value_[kNumAdcChannels] = {0, 0, 0, 0}; + int32_t adc_threshold_[kNumAdcChannels] = {0, 0, 0, 0}; + long long press_time_[2] = {0, 0}; + + peaks::Processors processors[2]; + + int16_t output[kBlockSize]; + int16_t brightness[kNumChannels] = {0, 0}; + + SchmittTrigger switches_[2]; + + peaks::GateFlags gate_flags[2] = {0, 0}; + + SampleRateConverter<2> outputSrc; + DoubleRingBuffer, 256> outputBuffer; + + bool initNumberStation = false; + + struct Block { + peaks::GateFlags input[kNumChannels][kBlockSize]; + uint16_t output[kNumChannels][kBlockSize]; + }; + + struct Slice { + Block* block; + size_t frame_index; + }; + + Block block_[kNumBlocks]; + size_t io_frame_ = 0; + size_t io_block_ = 0; + size_t render_block_ = kNumBlocks / 2; + + + Peaks() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + settings_.edit_mode = EDIT_MODE_TWIN; + settings_.function[0] = FUNCTION_ENVELOPE; + settings_.function[1] = FUNCTION_ENVELOPE; + settings_.snap_mode = false; + std::fill(&settings_.pot_value[0], &settings_.pot_value[8], 0); + + memset(&processors[0], 0, sizeof(processors[0])); + memset(&processors[1], 0, sizeof(processors[1])); + processors[0].Init(0); + processors[1].Init(1); + } + + void onReset() override { + init(); + } + + void init() { + std::fill(&pot_value_[0], &pot_value_[8], 0); + std::fill(&press_time_[0], &press_time_[1], 0); + std::fill(&brightness[0], &brightness[1], 0); + std::fill(&adc_lp_[0], &adc_lp_[kNumAdcChannels], 0); + std::fill(&adc_value_[0], &adc_value_[kNumAdcChannels], 0); + std::fill(&adc_threshold_[0], &adc_threshold_[kNumAdcChannels], 0); + std::fill(&snapped_[0], &snapped_[kNumAdcChannels], false); + + edit_mode_ = static_cast(settings_.edit_mode); + function_[0] = static_cast(settings_.function[0]); + function_[1] = static_cast(settings_.function[1]); + std::copy(&settings_.pot_value[0], &settings_.pot_value[8], &pot_value_[0]); + + if (edit_mode_ == EDIT_MODE_FIRST || edit_mode_ == EDIT_MODE_SECOND) { + lockPots(); + for (uint8_t i = 0; i < 4; ++i) { + processors[0].set_parameter( + i, + static_cast(pot_value_[i]) << 8); + processors[1].set_parameter( + i, + static_cast(pot_value_[i + 4]) << 8); + } + } + + snap_mode_ = settings_.snap_mode; + + changeControlMode(); + setFunction(0, function_[0]); + setFunction(1, function_[1]); + } + + json_t* toJson() override { + + saveState(); + + json_t* rootJ = json_object(); + + json_object_set_new(rootJ, "edit_mode", json_integer((int)settings_.edit_mode)); + json_object_set_new(rootJ, "fcn_channel_1", json_integer((int)settings_.function[0])); + json_object_set_new(rootJ, "fcn_channel_2", json_integer((int)settings_.function[1])); + + json_t* potValuesJ = json_array(); + for (int p : pot_value_) { + json_t* pJ = json_integer(p); + json_array_append_new(potValuesJ, pJ); + } + json_object_set_new(rootJ, "pot_values", potValuesJ); + + json_object_set_new(rootJ, "snap_mode", json_boolean(settings_.snap_mode)); + + return rootJ; + } + + void fromJson(json_t* rootJ) override { + json_t* editModeJ = json_object_get(rootJ, "edit_mode"); + if (editModeJ) { + settings_.edit_mode = static_cast(json_integer_value(editModeJ)); + } + + json_t* fcnChannel1J = json_object_get(rootJ, "fcn_channel_1"); + if (fcnChannel1J) { + settings_.function[0] = static_cast(json_integer_value(fcnChannel1J)); + } + + json_t* fcnChannel2J = json_object_get(rootJ, "fcn_channel_2"); + if (fcnChannel2J) { + settings_.function[1] = static_cast(json_integer_value(fcnChannel2J)); + } + + json_t* snapModeJ = json_object_get(rootJ, "snap_mode"); + if (snapModeJ) { + settings_.snap_mode = json_boolean_value(snapModeJ); + } + + json_t* potValuesJ = json_object_get(rootJ, "pot_values"); + size_t potValueId; + json_t* pJ; + json_array_foreach(potValuesJ, potValueId, pJ) { + if (potValueId < sizeof(pot_value_) / sizeof(pot_value_)[0]) { + settings_.pot_value[potValueId] = json_integer_value(pJ); + } + } + + // Update module internal state from settings. + init(); + } + + void step() override { + poll(); + pollPots(); + + // Initialize "secret" number station mode. + if (initNumberStation) { + processors[0].set_function(peaks::PROCESSOR_FUNCTION_NUMBER_STATION); + processors[1].set_function(peaks::PROCESSOR_FUNCTION_NUMBER_STATION); + initNumberStation = false; + } + + if (outputBuffer.empty()) { + + while (render_block_ != io_block_) { + process(&block_[render_block_], kBlockSize); + render_block_ = (render_block_ + 1) % kNumBlocks; + } + + uint32_t external_gate_inputs = 0; + external_gate_inputs |= (inputs[GATE_1_INPUT].value ? 1 : 0); + external_gate_inputs |= (inputs[GATE_2_INPUT].value ? 2 : 0); + + uint32_t buttons = 0; + buttons |= (params[TRIG_1_PARAM].value ? 1 : 0); + buttons |= (params[TRIG_2_PARAM].value ? 2 : 0); + + uint32_t gate_inputs = external_gate_inputs | buttons; + + // Prepare sample rate conversion. + // Peaks is sampling at 48kHZ. + outputSrc.setRates(48000, engineGetSampleRate()); + int inLen = kBlockSize; + int outLen = outputBuffer.capacity(); + Frame<2> f[kBlockSize]; + + // Process an entire block of data from the IOBuffer. + for (size_t k = 0; k < kBlockSize; ++k) { + + Slice slice = NextSlice(1); + + for (size_t i = 0; i < kNumChannels; ++i) { + gate_flags[i] = peaks::ExtractGateFlags( + gate_flags[i], + gate_inputs & (1 << i)); + + f[k].samples[i] = slice.block->output[i][slice.frame_index]; + } + + // A hack to make channel 1 aware of what's going on in channel 2. Used to + // reset the sequencer. + slice.block->input[0][slice.frame_index] = gate_flags[0] | (gate_flags[1] << 4) | (buttons & 8 ? peaks::GATE_FLAG_FROM_BUTTON : 0); + + slice.block->input[1][slice.frame_index] = gate_flags[1] | (buttons & 2 ? peaks::GATE_FLAG_FROM_BUTTON : 0); + } + + outputSrc.process(f, &inLen, outputBuffer.endData(), &outLen); + outputBuffer.endIncr(outLen); + } + + // Update outputs. + if (!outputBuffer.empty()) { + Frame<2> f = outputBuffer.shift(); + + // Peaks manual says output spec is 0..8V for envelopes and 10Vpp for audio/CV. + // TODO Check the output values against an actual device. + outputs[OUT_1_OUTPUT].value = rescale(static_cast(f.samples[0]), 0.0f, 65535.f, -8.0f, 8.0f); + outputs[OUT_2_OUTPUT].value = rescale(static_cast(f.samples[1]), 0.0f, 65535.f, -8.0f, 8.0f); + } + } + + inline Slice NextSlice(size_t size) { + Slice s; + s.block = &block_[io_block_]; + s.frame_index = io_frame_; + io_frame_ += size; + if (io_frame_ >= kBlockSize) { + io_frame_ -= kBlockSize; + io_block_ = (io_block_ + 1) % kNumBlocks; + } + return s; + } + + inline Function function() const { + return edit_mode_ == EDIT_MODE_SECOND ? function_[1] : function_[0]; + } + + inline void set_led_brightness(int channel, int16_t value) { + brightness[channel] = value; + } + + inline void process(Block* block, size_t size) { + for (size_t i = 0; i < kNumChannels; ++i) { + processors[i].Process(block->input[i], output, size); + set_led_brightness(i, output[0]); + for (size_t j = 0; j < size; ++j) { + // From calibration_data.h, shifting signed to unsigned values. + int32_t shifted_value = 32767 + static_cast(output[j]); + CONSTRAIN(shifted_value, 0, 65535); + block->output[i][j] = static_cast(shifted_value); + } + } + } + + void changeControlMode(); + void setFunction(uint8_t index, Function f); + void onPotChanged(uint16_t id, uint16_t value); + void onSwitchReleased(uint16_t id, uint16_t data); + void saveState(); + void lockPots(); + void poll(); + void pollPots(); + void refreshLeds(); + + long long getSystemTimeMs(); +}; + +const peaks::ProcessorFunction Peaks::function_table_[FUNCTION_LAST][2] = { + { peaks::PROCESSOR_FUNCTION_ENVELOPE, peaks::PROCESSOR_FUNCTION_ENVELOPE }, + { peaks::PROCESSOR_FUNCTION_LFO, peaks::PROCESSOR_FUNCTION_LFO }, + { peaks::PROCESSOR_FUNCTION_TAP_LFO, peaks::PROCESSOR_FUNCTION_TAP_LFO }, + { peaks::PROCESSOR_FUNCTION_BASS_DRUM, peaks::PROCESSOR_FUNCTION_SNARE_DRUM }, + + { peaks::PROCESSOR_FUNCTION_MINI_SEQUENCER, peaks::PROCESSOR_FUNCTION_MINI_SEQUENCER }, + { peaks::PROCESSOR_FUNCTION_PULSE_SHAPER, peaks::PROCESSOR_FUNCTION_PULSE_SHAPER }, + { peaks::PROCESSOR_FUNCTION_PULSE_RANDOMIZER, peaks::PROCESSOR_FUNCTION_PULSE_RANDOMIZER }, + { peaks::PROCESSOR_FUNCTION_FM_DRUM, peaks::PROCESSOR_FUNCTION_FM_DRUM }, +}; + + +void Peaks::changeControlMode() { + uint16_t parameters[4]; + for (int i = 0; i < 4; ++i) { + parameters[i] = adc_value_[i]; + } + + if (edit_mode_ == EDIT_MODE_SPLIT) { + processors[0].CopyParameters(¶meters[0], 2); + processors[1].CopyParameters(¶meters[2], 2); + processors[0].set_control_mode(peaks::CONTROL_MODE_HALF); + processors[1].set_control_mode(peaks::CONTROL_MODE_HALF); + } + else if (edit_mode_ == EDIT_MODE_TWIN) { + processors[0].CopyParameters(¶meters[0], 4); + processors[1].CopyParameters(¶meters[0], 4); + processors[0].set_control_mode(peaks::CONTROL_MODE_FULL); + processors[1].set_control_mode(peaks::CONTROL_MODE_FULL); + } + else { + processors[0].set_control_mode(peaks::CONTROL_MODE_FULL); + processors[1].set_control_mode(peaks::CONTROL_MODE_FULL); + } +} + +void Peaks::setFunction(uint8_t index, Function f) { + if (edit_mode_ == EDIT_MODE_SPLIT || edit_mode_ == EDIT_MODE_TWIN) { + function_[0] = function_[1] = f; + processors[0].set_function(function_table_[f][0]); + processors[1].set_function(function_table_[f][1]); + } + else { + function_[index] = f; + processors[index].set_function(function_table_[f][index]); + } +} + +void Peaks::onSwitchReleased(uint16_t id, uint16_t data) { + switch (id) { + case SWITCH_TWIN_MODE: + if (data > kLongPressDuration) { + edit_mode_ = static_cast( + (edit_mode_ + EDIT_MODE_FIRST) % EDIT_MODE_LAST); + function_[0] = function_[1]; + processors[0].set_function(function_table_[function_[0]][0]); + processors[1].set_function(function_table_[function_[0]][1]); + lockPots(); + } + else { + if (edit_mode_ <= EDIT_MODE_SPLIT) { + edit_mode_ = static_cast(EDIT_MODE_SPLIT - edit_mode_); + } + else { + edit_mode_ = static_cast(EDIT_MODE_SECOND - (edit_mode_ & 1)); + lockPots(); + } + } + changeControlMode(); + saveState(); + break; + + case SWITCH_FUNCTION: { + Function f = function(); + if (data > kLongPressDuration) { + f = static_cast((f + FUNCTION_FIRST_ALTERNATE_FUNCTION) % FUNCTION_LAST); + } + else { + if (f <= FUNCTION_DRUM_GENERATOR) { + f = static_cast((f + 1) & 3); + } + else { + f = static_cast(((f + 1) & 3) + FUNCTION_FIRST_ALTERNATE_FUNCTION); + } + } + setFunction(edit_mode_ - EDIT_MODE_FIRST, f); + saveState(); + } + break; + + case SWITCH_GATE_TRIG_1: + // no-op + break; + + case SWITCH_GATE_TRIG_2: + // no-op + break; + } +} + +void Peaks::lockPots() { + std::fill( + &adc_threshold_[0], + &adc_threshold_[kNumAdcChannels], + kAdcThresholdLocked); + std::fill(&snapped_[0], &snapped_[kNumAdcChannels], false); +} + +void Peaks::pollPots() { + for (uint8_t i = 0; i < kNumAdcChannels; ++i) { + adc_lp_[i] = (int32_t(params[KNOB_1_PARAM + i].value) + adc_lp_[i] * 7) >> 3; + int32_t value = adc_lp_[i]; + int32_t current_value = adc_value_[i]; + if (value >= current_value + adc_threshold_[i] || + value <= current_value - adc_threshold_[i] || + !adc_threshold_[i]) { + onPotChanged(i, value); + adc_value_[i] = value; + adc_threshold_[i] = kAdcThresholdUnlocked; + } + } +} + +void Peaks::onPotChanged(uint16_t id, uint16_t value) { + switch (edit_mode_) { + case EDIT_MODE_TWIN: + processors[0].set_parameter(id, value); + processors[1].set_parameter(id, value); + pot_value_[id] = value >> 8; + break; + + case EDIT_MODE_SPLIT: + if (id < 2) { + processors[0].set_parameter(id, value); + } + else { + processors[1].set_parameter(id - 2, value); + } + pot_value_[id] = value >> 8; + break; + + case EDIT_MODE_FIRST: + case EDIT_MODE_SECOND: { + uint8_t index = id + (edit_mode_ - EDIT_MODE_FIRST) * 4; + peaks::Processors* p = &processors[edit_mode_ - EDIT_MODE_FIRST]; + + int16_t delta = static_cast(pot_value_[index]) - \ + static_cast(value >> 8); + if (delta < 0) { + delta = -delta; + } + + if (!snap_mode_ || snapped_[id] || delta <= 2) { + p->set_parameter(id, value); + pot_value_[index] = value >> 8; + snapped_[id] = true; + } + } + break; + + case EDIT_MODE_LAST: + break; + } +} + +long long Peaks::getSystemTimeMs() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch() + ).count(); +} + +void Peaks::poll() { + for (uint8_t i = 0; i < 2; ++i) { + if (switches_[i].process(params[BUTTON_1_PARAM + i].value)) { + press_time_[i] = getSystemTimeMs(); + } + + if (switches_[i].isHigh() && press_time_[i] != 0) { + int32_t pressed_time = getSystemTimeMs() - press_time_[i]; + if (pressed_time > kLongPressDuration) { + onSwitchReleased(SWITCH_TWIN_MODE + i, pressed_time); + press_time_[i] = 0; // Inhibit next release event + } + } + if (!switches_[i].isHigh() && press_time_[i] != 0) { + int32_t delta = getSystemTimeMs() - press_time_[i] + 1; + onSwitchReleased(SWITCH_TWIN_MODE + i, delta); + press_time_[i] = 0; // Not in original code! + } + } + + refreshLeds(); +} + +void Peaks::saveState() { + settings_.edit_mode = edit_mode_; + settings_.function[0] = function_[0]; + settings_.function[1] = function_[1]; + std::copy(&pot_value_[0], &pot_value_[8], &settings_.pot_value[0]); + settings_.snap_mode = snap_mode_; +} + +void Peaks::refreshLeds() { + uint8_t flash = (getSystemTimeMs() >> 7) & 7; + switch (edit_mode_) { + case EDIT_MODE_FIRST: + lights[TWIN_MODE_LIGHT].value = (flash == 1) ? 1.0f : 0.0f; + break; + case EDIT_MODE_SECOND: + lights[TWIN_MODE_LIGHT].value = (flash == 1 || flash == 3) ? 1.0f : 0.0f; + break; + default: + lights[TWIN_MODE_LIGHT].value = (edit_mode_ & 1) ? 1.0f : 0.0f; + break; + } + if ((getSystemTimeMs() & 256) && function() >= FUNCTION_FIRST_ALTERNATE_FUNCTION) { + for (size_t i = 0; i < 4; ++i) { + lights[FUNC_1_LIGHT + i].value = 0.0f; + } + } + else { + for (size_t i = 0; i < 4; ++i) { + lights[FUNC_1_LIGHT + i].value = ((function() & 3) == i) ? 1.0f : 0.0f; + } + } + + uint8_t b[2]; + for (uint8_t i = 0; i < 2; ++i) { + switch (function_[i]) { + case FUNCTION_DRUM_GENERATOR: + case FUNCTION_FM_DRUM_GENERATOR: + b[i] = (int16_t) abs(brightness[i]) >> 8; + b[i] = b[i] >= 255 ? 255 : b[i]; + break; + case FUNCTION_LFO: + case FUNCTION_TAP_LFO: + case FUNCTION_MINI_SEQUENCER: { + int32_t brightnessVal = int32_t(brightness[i]) * 409 >> 8; + brightnessVal += 32768; + brightnessVal >>= 8; + CONSTRAIN(brightnessVal, 0, 255); + b[i] = brightnessVal; + } + break; + default: + b[i] = brightness[i] >> 7; + break; + } + } + + if (processors[0].function() == peaks::PROCESSOR_FUNCTION_NUMBER_STATION) { + uint8_t pattern = processors[0].number_station().digit() + ^ processors[1].number_station().digit(); + for (size_t i = 0; i < 4; ++i) { + lights[FUNC_1_LIGHT + i].value = (pattern & 1) ? 1.0f : 0.0f; + pattern = pattern >> 1; + } + b[0] = processors[0].number_station().gate() ? 255 : 0; + b[1] = processors[1].number_station().gate() ? 255 : 0; + } + + lights[TRIG_1_LIGHT].value = rescale(static_cast(b[0]), 0.0f, 255.0f, 0.0f, 1.0f); + lights[TRIG_2_LIGHT].value = rescale(static_cast(b[1]), 0.0f, 255.0f, 0.0f, 1.0f); +} + + +struct PeaksWidget : ModuleWidget { + PeaksWidget(Peaks* module) : ModuleWidget(module) { + setPanel(SVG::load(assetPlugin(plugin, "res/Peaks.svg"))); + + addChild(Widget::create(Vec(15, 0))); + addChild(Widget::create(Vec(15, 365))); + + addParam(ParamWidget::create(Vec(8.5, 52), module, Peaks::BUTTON_1_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(11.88, 74), module, Peaks::TWIN_MODE_LIGHT)); + addParam(ParamWidget::create(Vec(8.5, 89), module, Peaks::BUTTON_2_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(11.88, 111), module, Peaks::FUNC_1_LIGHT)); + addChild(ModuleLightWidget::create>(Vec(11.88, 126.75), module, Peaks::FUNC_2_LIGHT)); + addChild(ModuleLightWidget::create>(Vec(11.88, 142.5), module, Peaks::FUNC_3_LIGHT)); + addChild(ModuleLightWidget::create>(Vec(11.88, 158), module, Peaks::FUNC_4_LIGHT)); + + addParam(ParamWidget::create(Vec(61, 51), module, Peaks::KNOB_1_PARAM, 0.0f, 65535.0f, 16384.0f)); + addParam(ParamWidget::create(Vec(61, 115), module, Peaks::KNOB_2_PARAM, 0.0f, 65535.0f, 16384.0f)); + addParam(ParamWidget::create(Vec(61, 179), module, Peaks::KNOB_3_PARAM, 0.0f, 65535.0f, 32678.0f)); + addParam(ParamWidget::create(Vec(61, 244), module, Peaks::KNOB_4_PARAM, 0.0f, 65535.0f, 32678.0f)); + + addParam(ParamWidget::create(Vec(11, 188), module, Peaks::TRIG_1_PARAM, 0.0f, 1.0f, 0.0f)); + addParam(ParamWidget::create(Vec(11, 273), module, Peaks::TRIG_2_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(11, 188).plus(mm2px(Vec(0.75, 0.75))), module, Peaks::TRIG_1_LIGHT)); + addChild(ModuleLightWidget::create>(Vec(11, 273).plus(mm2px(Vec(0.75, 0.75))), module, Peaks::TRIG_2_LIGHT)); + + addInput(Port::create(Vec(10, 230), Port::INPUT, module, Peaks::GATE_1_INPUT)); + addInput(Port::create(Vec(10, 315), Port::INPUT, module, Peaks::GATE_2_INPUT)); + + addOutput(Port::create(Vec(53, 315), Port::OUTPUT, module, Peaks::OUT_1_OUTPUT)); + addOutput(Port::create(Vec(86, 315), Port::OUTPUT, module, Peaks::OUT_2_OUTPUT)); + } + + Menu* createContextMenu() override { + Menu* menu = ModuleWidget::createContextMenu(); + Peaks* peaks = dynamic_cast(this->module); + + struct SnapModeItem : MenuItem { + Peaks* peaks; + void onAction(EventAction& e) override { + peaks->snap_mode_ = !peaks->snap_mode_; + } + void step() override { + rightText = (peaks->snap_mode_) ? "✔" : ""; + MenuItem::step(); + } + }; + + struct NumberStationItem : MenuItem { + Peaks* peaks; + void onAction(EventAction& e) override { + peaks->initNumberStation = true; + } + void step() override { + rightText = (peaks->processors[0].function() == peaks::PROCESSOR_FUNCTION_NUMBER_STATION) ? "✔" : ""; + MenuItem::step(); + } + }; + + menu->addChild(construct()); + menu->addChild(construct(&SnapModeItem::text, "Snap Mode", &SnapModeItem::peaks, peaks)); + + menu->addChild(construct()); + menu->addChild(construct(&MenuLabel::text, "Secret Modes")); + menu->addChild(construct(&NumberStationItem::text, "Number Station", &NumberStationItem::peaks, peaks)); + + return menu; + } +}; + + + +Model* modelPeaks = Model::create("Audible Instruments", "Peaks", "Percussive Synthesizer", UTILITY_TAG, LFO_TAG, DRUM_TAG); diff --git a/src/plugin.cpp b/src/plugin.cpp index 3a6dd30..095e2ad 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -26,4 +26,5 @@ void init(rack::Plugin* p) { p->addModel(modelRipples); p->addModel(modelShelves); p->addModel(modelStreams); + p->addModel(modelPeaks); } diff --git a/src/plugin.hpp b/src/plugin.hpp index 8819248..fcd7bbc 100644 --- a/src/plugin.hpp +++ b/src/plugin.hpp @@ -26,7 +26,7 @@ extern Model* modelMarbles; extern Model* modelRipples; extern Model* modelShelves; extern Model* modelStreams; - +extern Model *modelPeaks; template struct Rogan6PSLight : Base {