From 1d13b99bf3d60ef5fab3d4b2f1af93cf1830ac77 Mon Sep 17 00:00:00 2001 From: Christoph Scholtes Date: Thu, 8 Feb 2018 23:38:13 -0700 Subject: [PATCH] Initial version of Peaks. --- Makefile | 11 + res/Peaks.png | Bin 0 -> 13279 bytes src/AudibleInstruments.cpp | 3 +- src/AudibleInstruments.hpp | 5 + src/Peaks.cpp | 686 +++++++++++++++++++++++++++++++++++++ 5 files changed, 704 insertions(+), 1 deletion(-) create mode 100644 res/Peaks.png create mode 100644 src/Peaks.cpp diff --git a/Makefile b/Makefile index da9d7af..0b7900d 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,17 @@ SOURCES += eurorack/warps/resources.cc SOURCES += eurorack/frames/keyframer.cc SOURCES += eurorack/frames/resources.cc SOURCES += eurorack/frames/poly_lfo.cc +SOURCES += eurorack/peaks/processors.cc +SOURCES += eurorack/peaks/resources.cc +SOURCES += eurorack/peaks/drums/bass_drum.cc +SOURCES += eurorack/peaks/drums/fm_drum.cc +SOURCES += eurorack/peaks/drums/high_hat.cc +SOURCES += eurorack/peaks/drums/snare_drum.cc +SOURCES += eurorack/peaks/modulations/lfo.cc +SOURCES += eurorack/peaks/modulations/multistage_envelope.cc +SOURCES += eurorack/peaks/pulse_processor/pulse_shaper.cc +SOURCES += eurorack/peaks/pulse_processor/pulse_randomizer.cc +SOURCES += eurorack/peaks/number_station/number_station.cc DISTRIBUTABLES += $(wildcard LICENSE*) res diff --git a/res/Peaks.png b/res/Peaks.png new file mode 100644 index 0000000000000000000000000000000000000000..2b41609ce41e464489aa889ee5ea263516caf442 GIT binary patch literal 13279 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 z z9(_BvFka@LznQFVGUvj7@fz+0w}J>o_QS>mbM! zbPF!cq-vh7d9oS;ZxOXP(nOJXDy9F@kTt~LKrZcS=berBk$!oE`(N;^1xhFO8rE3J zQ-(8+E}?^@8WsH4fx%z{m2MkBrz6^MhW{Q6F3B3Wd7644b0~meFCzdy`t~)eP<{^)T>8ILYtYW8(sP;I$YKQCrDQa5K4zAz&N1Xg3R!`ATHDyUlviqTlT` z7$#`;egTaMjojaYCR|lETqo^kt*;SsipQqEAKD?ikK73VfhHue*U%vSN}~MAGF4P- z9<{fSO!alOb&eM#1qyb@i-H|r@A%3M6h)gGC~%IEKxFTADh2jLGxhkgdT;aj1tj2u z9Up$YHk#k|jPILa(Y<7ObOU)5CPo!m=Nu$FB)RkV%Z%soA zC1WQ;aaC2dNd8sY^zL6t zGGL&veHs7O4-LEKvW~rY%7vJLrE^*f7KBk>!7?m5b2f9ul;^i4rT-0KRw94gqOp4J z%R_X%6lc+n4)#-JgxZH#={+Lq8stREm%TmOV37nuo}k?yT_GwN0aP2|xFya3M)`*lQkwer2mlA*%~w-})> zBvCL@AjAa2_6uCQl3Uhu@p8qvPVw1kFn=Sr!i&ntFD-fP%;Z=+(X%o>@IdGQ8LXCV zu0v%WSu~N!GLJhsuz3YQ7Uwh;QAgpuoIk{tE$D9ewQ+p!@|0s}g4SH`j!41Z83!Q! z7Iik@H-SBJB8{Byl`nvF>ixHFUAR;wi9w8^vXNi1>jhfJxeK zJ@0Md|GjTy&d+wx&8SYZ!NGsSV*6dq?4jqqiDhC3k(!D7?9+FpC{)j=u+Fd+$}4q9 zi;)dk|My4x@t0_Ib~$-tKRPHcgn|7gd4>N!x1apKZCpV?gFT~*5(hXCyrISbHsv5F M$*ap%%b11!A5>YFKmY&$ literal 0 HcmV?d00001 diff --git a/src/AudibleInstruments.cpp b/src/AudibleInstruments.cpp index c958c7e..c3cbf66 100644 --- a/src/AudibleInstruments.cpp +++ b/src/AudibleInstruments.cpp @@ -21,4 +21,5 @@ void init(rack::Plugin *p) { p->addModel(createModel("Audible Instruments", "Blinds", "Quad VC-polarizer", MIXER_TAG, ATTENUATOR_TAG)); p->addModel(createModel("Audible Instruments", "Veils", "Quad VCA", MIXER_TAG)); p->addModel(createModel("Audible Instruments", "Frames", "Keyframer/Mixer", MIXER_TAG, ATTENUATOR_TAG, LFO_TAG)); -} + p->addModel(createModel("Audible Instruments", "Peaks", "Percussive Synthesizer", UTILITY_TAG, LFO_TAG, DRUM_TAG)); +} \ No newline at end of file diff --git a/src/AudibleInstruments.hpp b/src/AudibleInstruments.hpp index 1f1b40a..0709f4b 100644 --- a/src/AudibleInstruments.hpp +++ b/src/AudibleInstruments.hpp @@ -80,3 +80,8 @@ struct FramesWidget : ModuleWidget { FramesWidget(); Menu *createContextMenu() override; }; + +struct PeaksWidget : ModuleWidget { + PeaksWidget(); + Menu *createContextMenu() override; +}; diff --git a/src/Peaks.cpp b/src/Peaks.cpp new file mode 100644 index 0000000..962c135 --- /dev/null +++ b/src/Peaks.cpp @@ -0,0 +1,686 @@ +#include +#include + +#include "dsp/digital.hpp" +#include "dsp/samplerate.hpp" +#include "dsp/ringbuffer.hpp" + +#include "peaks/io_buffer.h" +#include "peaks/processors.h" + +#include "AudibleInstruments.hpp" + +using namespace peaks; + +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; +}; + +const int32_t kLongPressDuration = 600; +const uint8_t kNumAdcChannels = 4; +const uint16_t kAdcThresholdUnlocked = 1 << (16 - 10); // 10 bits +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 + }; + + Peaks(); + ~Peaks(); + + void step() override; + void onReset() override { + init(); + } + + void init(); + + 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(); + } + + inline Function function() const { + return edit_mode_ == EDIT_MODE_SECOND ? function_[1] : function_[0]; + } + + 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(); + + static const 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}; + + SchmittTrigger switches_[2]; + + std::shared_ptr ioBuffer; + + GateFlags gate_flags[2] = {0,0}; + + SampleRateConverter<2> outputSrc; + DoubleRingBuffer, 256> outputBuffer; + + bool initNumberStation = false; +}; + +const ProcessorFunction Peaks::function_table_[FUNCTION_LAST][2] = { + { PROCESSOR_FUNCTION_ENVELOPE, PROCESSOR_FUNCTION_ENVELOPE }, + { PROCESSOR_FUNCTION_LFO, PROCESSOR_FUNCTION_LFO }, + { PROCESSOR_FUNCTION_TAP_LFO, PROCESSOR_FUNCTION_TAP_LFO }, + { PROCESSOR_FUNCTION_BASS_DRUM, PROCESSOR_FUNCTION_SNARE_DRUM }, + + { PROCESSOR_FUNCTION_MINI_SEQUENCER, PROCESSOR_FUNCTION_MINI_SEQUENCER }, + { PROCESSOR_FUNCTION_PULSE_SHAPER, PROCESSOR_FUNCTION_PULSE_SHAPER }, + { PROCESSOR_FUNCTION_PULSE_RANDOMIZER, PROCESSOR_FUNCTION_PULSE_RANDOMIZER }, + { PROCESSOR_FUNCTION_FM_DRUM, PROCESSOR_FUNCTION_FM_DRUM }, +}; + +Peaks::Peaks() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + ioBuffer = std::make_shared(); + + 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); + + ioBuffer->Init(); + processors[0].Init(0); + processors[1].Init(1); +} + +Peaks::~Peaks() { +} + + +// Global scope, so variables can be accessed by process() function. +int16_t gOutputBuffer[kBlockSize]; +int16_t gBrightness[2] = {0,0}; + +void set_led_brightness(int channel, int16_t value) { + gBrightness[channel] = value; +} + +// File scope because of IOBuffer function signature. +// It cannot refer to a member function of class Peaks(). +void process(IOBuffer::Block* block, size_t size) { + for (size_t i = 0; i < kNumChannels; ++i) { + processors[i].Process(block->input[i], gOutputBuffer, size); + set_led_brightness(i, gOutputBuffer[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(gOutputBuffer[j]); + CONSTRAIN(shifted_value, 0, 65535); + block->output[i][j] = static_cast(shifted_value); + } + } +} + +void Peaks::init() { + std::fill(&pot_value_[0], &pot_value_[8], 0); + std::fill(&press_time_[0], &press_time_[1], 0); + std::fill(&gBrightness[0], &gBrightness[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]); +} + +void Peaks::step() { + + poll(); + pollPots(); + + // Initialize "secret" number station mode. + if (initNumberStation) { + processors[0].set_function(PROCESSOR_FUNCTION_NUMBER_STATION); + processors[1].set_function(PROCESSOR_FUNCTION_NUMBER_STATION); + initNumberStation = false; + } + + if (outputBuffer.empty()) { + + ioBuffer->Process(process); + + 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) { + + IOBuffer::Slice slice = ioBuffer->NextSlice(1); + + for (size_t i = 0; i < kNumChannels; ++i) { + gate_flags[i] = 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 ? GATE_FLAG_FROM_BUTTON : 0); + + slice.block->input[1][slice.frame_index] = gate_flags[1] \ + | (buttons & 2 ? 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); + } +} + +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(CONTROL_MODE_HALF); + processors[1].set_control_mode(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(CONTROL_MODE_FULL); + processors[1].set_control_mode(CONTROL_MODE_FULL); + } else { + processors[0].set_control_mode(CONTROL_MODE_FULL); + processors[1].set_control_mode(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; + 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] = std::abs(gBrightness[i]) >> 8; + b[i] = b[i] >= 255 ? 255 : b[i]; + break; + case FUNCTION_LFO: + case FUNCTION_TAP_LFO: + case FUNCTION_MINI_SEQUENCER: + { + int32_t brightness = int32_t(gBrightness[i]) * 409 >> 8; + brightness += 32768; + brightness >>= 8; + CONSTRAIN(brightness, 0, 255); + b[i] = brightness; + } + break; + default: + b[i] = gBrightness[i] >> 7; + break; + } + } + + if (processors[0].function() == 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); +} + +template +struct SwitchLED : BASE { + SwitchLED() { + this->box.size = mm2px(Vec(8.25, 8.25)); + } +}; + +PeaksWidget::PeaksWidget() { + Peaks *module = new Peaks(); + setModule(module); + box.size = Vec(15*8, 380); + + { + Panel *panel = new LightPanel(); + panel->backgroundImage = Image::load(assetPlugin(plugin, "res/Peaks.png")); + panel->box.size = box.size; + addChild(panel); + } + + addChild(createScrew(Vec(15, 0))); + addChild(createScrew(Vec(15, 365))); + + addParam(createParam(Vec(8.5, 52), module, Peaks::BUTTON_1_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(createLight>(Vec(11.88, 74), module, Peaks::TWIN_MODE_LIGHT)); + addParam(createParam(Vec(8.5, 89), module, Peaks::BUTTON_2_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(createLight>(Vec(11.88, 111), module, Peaks::FUNC_1_LIGHT)); + addChild(createLight>(Vec(11.88, 126.75), module, Peaks::FUNC_2_LIGHT)); + addChild(createLight>(Vec(11.88, 142.5), module, Peaks::FUNC_3_LIGHT)); + addChild(createLight>(Vec(11.88, 158), module, Peaks::FUNC_4_LIGHT)); + + addParam(createParam(Vec(61, 51), module, Peaks::KNOB_1_PARAM, 0.0f, 65535.0f, 16384.0f)); + addParam(createParam(Vec(61, 115), module, Peaks::KNOB_2_PARAM, 0.0f, 65535.0f, 16384.0f)); + addParam(createParam(Vec(61, 179), module, Peaks::KNOB_3_PARAM, 0.0f, 65535.0f, 32678.0f)); + addParam(createParam(Vec(61, 244), module, Peaks::KNOB_4_PARAM, 0.0f, 65535.0f, 32678.0f)); + + addParam(createParam(Vec(10.32, 188), module, Peaks::TRIG_1_PARAM, 0.0f, 1.0f, 0.0f)); + addParam(createParam(Vec(10.32, 273.5), module, Peaks::TRIG_2_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(createLight>(Vec(10.32, 188), module, Peaks::TRIG_1_LIGHT)); + addChild(createLight>(Vec(10.32, 273.5), module, Peaks::TRIG_2_LIGHT)); + + addInput(createInput(Vec(10, 230), module, Peaks::GATE_1_INPUT)); + addInput(createInput(Vec(10, 315), module, Peaks::GATE_2_INPUT)); + + addOutput(createOutput(Vec(53, 315), module, Peaks::OUT_1_OUTPUT)); + addOutput(createOutput(Vec(86, 315), module, Peaks::OUT_2_OUTPUT)); +} + + +struct PeaksSnapModeItem : MenuItem { + Peaks *peaks; + void onAction(EventAction &e) override { + peaks->snap_mode_ = !peaks->snap_mode_; + } + void step() override { + rightText = (peaks->snap_mode_) ? "✔" : ""; + MenuItem::step(); + } +}; + + +struct PeaksNumberStationItem : MenuItem { + Peaks *peaks; + void onAction(EventAction &e) override { + peaks->initNumberStation = true; + } +}; + + +Menu *PeaksWidget::createContextMenu() { + Menu *menu = ModuleWidget::createContextMenu(); + Peaks *peaks = dynamic_cast(this->module); + + menu->addChild(construct()); + menu->addChild(construct(&MenuEntry::text, "Snap Mode", &PeaksSnapModeItem::peaks, peaks)); + + menu->addChild(construct()); + menu->addChild(construct(&MenuEntry::text, "Secret Modes")); + menu->addChild(construct(&MenuEntry::text, "Number Station", &PeaksNumberStationItem::peaks, peaks)); + + return menu; +} \ No newline at end of file