BogaudioModules

BogaudioModules for VCV Rack
Log | Files | Refs | README | LICENSE

commit 8fd8fcc8a7131117ad4606fc46ea27c33eb12aaf
parent 288e35c34cae9cc7b4b1e6ec7c4ed2414d54e6df
Author: Matt Demanett <matt@demanett.net>
Date:   Tue, 24 Mar 2020 22:41:57 -0400

ARP: fix gate out when stopping and starting the clock input; add "Max gate length" context option to avoid clock weirdness entirely. #102

Diffstat:
MREADME.md | 2+-
Msrc/Arp.cpp | 22+++++++++++++++++++++-
Msrc/Arp.hpp | 33+++++++++++++++++++++++++++++++--
3 files changed, 53 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md @@ -681,7 +681,7 @@ MODE controls how the arpeggio plays back: - RD (random): play random notes from the arpeggio; notes may repeat. - SH (shuffle): on each arpeggio, play each note once, but in random order. Repeated notes may still occur, but only when the last note of a sequence happens to be the same as the randomly-selected first note of the next sequence. -The GATE knob sets the output gate length for each played note, as a proportion of the time between the last two clock pulses (which can potentially cause odd behaviors if you use an irregular clock). The minimum gate pulse is 1ms; if the knob is turned all the way up, the gate does not drop between notes. +The GATE knob sets the output gate length for each played note, as a proportion of the time between the last two clock pulses. The minimum gate pulse is 1ms; if the knob is turned all the way up, the gate does not drop between notes. Using an irregular clock, or starting and stopping the clock, will confuse the calculation of the clock rate and produce odd results. To avoid these problems, if they come up, the context menu option "Max gate length" may be set to "Fixed": in this mode, the gate length is set by the GATE knob to a value from 1ms up to 500ms, without reference to the clock. HOLD latches the arpeggio, such that it keeps playing even when all input gates are low. Once all gates are low, a new gate will start adding notes to a new arpeggio. Notes will be added to the current arpeggio as long as any gate is high. diff --git a/src/Arp.cpp b/src/Arp.cpp @@ -2,6 +2,7 @@ #include "Arp.hpp" #define NOTES_IMMEDIATE_MODE "notes_immediate" +#define FIXED_GATE_MODE "fixed_gate" void Arp::NoteSet::Note::reset() { pitch = 0.0f; @@ -229,6 +230,7 @@ void Arp::sampleRateChange() { json_t* Arp::dataToJson() { json_t* root = json_object(); json_object_set_new(root, NOTES_IMMEDIATE_MODE, json_boolean(_notesImmediate)); + json_object_set_new(root, FIXED_GATE_MODE, json_boolean(_fixedGate)); return root; } @@ -237,6 +239,11 @@ void Arp::dataFromJson(json_t* root) { if (ni) { _notesImmediate = json_is_true(ni); } + + json_t* fg = json_object_get(root, FIXED_GATE_MODE); + if (fg) { + _fixedGate = json_is_true(fg); + } } int Arp::channels() { @@ -320,7 +327,15 @@ void Arp::processAll(const ProcessArgs& args) { NoteSet* notes = _notesImmediate ? _currentNotes : _playbackNotes; if (clock) { if (notes->nextPitch(_mode, _pitchOut)) { - _gateGenerator.trigger(0.001f + _gateLength * _clockSeconds); + _gateGenerator.reset(); + float gl = _gateLength; + if (_fixedGate) { + gl *= 0.5f; + } + else { + gl *= _clockSeconds; + } + _gateGenerator.trigger(std::max(0.001f, gl)); } } outputs[PITCH_OUTPUT].setVoltage(_pitchOut); @@ -404,6 +419,11 @@ struct ArpWidget : ModuleWidget { ni->addItem(OptionMenuItem("On arpeggio restart", [m]() { return !m->_notesImmediate; }, [m]() { m->_notesImmediate = false; })); ni->addItem(OptionMenuItem("Immediately", [m]() { return m->_notesImmediate; }, [m]() { m->_notesImmediate = true; })); OptionsMenuItem::addToMenu(ni, menu); + + OptionsMenuItem* fg = new OptionsMenuItem("Max gate length"); + fg->addItem(OptionMenuItem("Clock interval", [m]() { return !m->_fixedGate; }, [m]() { m->_fixedGate = false; })); + fg->addItem(OptionMenuItem("Fixed (500ms)", [m]() { return m->_fixedGate; }, [m]() { m->_fixedGate = true; })); + OptionsMenuItem::addToMenu(fg, menu); } }; diff --git a/src/Arp.hpp b/src/Arp.hpp @@ -86,10 +86,39 @@ struct Arp : BGModule { void sync(); }; + struct GateLengthParamQuantity : ParamQuantity { + float getDisplayValue() override { + float v = getValue(); + if (!module) { + return v; + } + + if (dynamic_cast<Arp*>(module)->_fixedGate) { + unit = " ms"; + return v * 500.0f; + } + unit = "%"; + return v * 100.0f; + } + + void setDisplayValue(float displayValue) override { + if (!module) { + return; + } + if (dynamic_cast<Arp*>(module)->_fixedGate) { + setValue(displayValue / 500.0f); + } + else { + setValue(displayValue / 100.0f); + } + } + }; + Mode _mode = UP_MODE; float _gateLength = 0.5f; bool _hold = false; bool _notesImmediate = false; + bool _fixedGate = false; Trigger _clockTrigger; Trigger _resetTrigger; Trigger _gateTrigger[maxChannels]; @@ -99,14 +128,14 @@ struct Arp : BGModule { NoteSet* _playbackNotes; float _pitchOut = 0.0f; float _sampleTime = 0.001f; - float _secondsSinceLastClock = -1.0f; + float _secondsSinceLastClock = 0.0f; float _clockSeconds = 0.1f; rack::dsp::PulseGenerator _gateGenerator; Arp() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); configParam(MODE_PARAM, 0.0f, 6.0f, 0.0f, "Playback mode"); - configParam(GATE_LENGTH_PARAM, 0.0f, 1.0f, 0.5f, "Gate length", "%", 0.0f, 100.0f); + configParam<GateLengthParamQuantity>(GATE_LENGTH_PARAM, 0.0f, 1.0f, 0.5f, "Gate length"); configParam(HOLD_PARAM, 0.0f, 1.0f, 0.0f, "Hold/latch"); _currentNotes = new NoteSet();