#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" 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 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 // Global scope, so variables can be accessed by process() function. int16_t gOutputBuffer[peaks::kBlockSize]; int16_t gBrightness[2] = {0, 0}; static 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(). static void process(peaks::IOBuffer::Block* block, size_t size) { for (size_t i = 0; i < peaks::kNumChannels; ++i) { // TODO // 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); } } } 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}; SchmittTrigger switches_[2]; peaks::IOBuffer ioBuffer; peaks::GateFlags gate_flags[2] = {0, 0}; SampleRateConverter<2> outputSrc; DoubleRingBuffer, 256> outputBuffer; bool initNumberStation = false; peaks::Processors processors[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(&ioBuffer, 0, sizeof(ioBuffer)); memset(&processors[0], 0, sizeof(processors[0])); memset(&processors[1], 0, sizeof(processors[1])); ioBuffer.Init(); 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(&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]); } 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()) { 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 = peaks::kBlockSize; int outLen = outputBuffer.capacity(); Frame<2> f[peaks::kBlockSize]; // Process an entire block of data from the IOBuffer. for (size_t k = 0; k < peaks::kBlockSize; ++k) { peaks::IOBuffer::Slice slice = ioBuffer.NextSlice(1); for (size_t i = 0; i < peaks::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 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(); }; 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) 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() == 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) { #ifdef USE_VST2 setPanel(SVG::load(assetStaticPlugin("AudibleInstruments", "res/Peaks.svg"))); #else setPanel(SVG::load(assetPlugin(plugin, "res/Peaks.svg"))); #endif // USE_VST2 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; } }; 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);