BogaudioModules

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

commit ccf9c8adb24a6c90d02ecf1dc3be8bec73f9c04c
parent bfb2c4cb52f4dcb089b39482e2af72fc0b77d0cc
Author: Matt Demanett <matt@demanett.net>
Date:   Sat, 28 Dec 2019 18:35:52 -0600

ARP: add mode to use updated notes only when the arpeggio restarts, make this the default.

Diffstat:
Mres-src/Arp-src.svg | 4++--
Mres/Arp.svg | 0
Msrc/Arp.cpp | 369+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/Arp.hpp | 59+++++++++++++++++++++++++++++++++++++++++++----------------
4 files changed, 265 insertions(+), 167 deletions(-)

diff --git a/res-src/Arp-src.svg b/res-src/Arp-src.svg @@ -98,7 +98,7 @@ </g> <g transform="translate(0 20)"> <use id="UP_DOWN_LIGHT" xlink:href="#light-small" transform="translate(0 0)" /> - <text font-size="5pt" letter-spacing="1px" transform="translate(8 5.7)">P</text> + <text font-size="5pt" letter-spacing="1px" transform="translate(8 5.7)">PD</text> </g> <g transform="translate(21 20)"> <use id="UP_DOWN_REPEAT_LIGHT" xlink:href="#light-small" transform="translate(0 0)" /> @@ -110,7 +110,7 @@ </g> <g transform="translate(21 30)"> <use id="RANDOM_LIGHT" xlink:href="#light-small" transform="translate(0 0)" /> - <text font-size="5pt" letter-spacing="1px" transform="translate(8 5.7)">R</text> + <text font-size="5pt" letter-spacing="1px" transform="translate(8 5.7)">RD</text> </g> <use id="MODE_PARAM" xlink:href="#button-small" transform="translate(15 40)" /> </g> diff --git a/res/Arp.svg b/res/Arp.svg Binary files differ. diff --git a/src/Arp.cpp b/src/Arp.cpp @@ -1,35 +1,222 @@ #include "Arp.hpp" -void Arp::Note::reset() { +#define NOTES_IMMEDIATE_MODE "notes_immediate" + +void Arp::NoteSet::Note::reset() { pitch = 0.0f; channel = -1; } +bool Arp::NoteSet::nextPitch(Mode mode, float& pitchOut) { + if (_syncNext) { + _syncNext = false; + sync(); + } + if (_noteCount <= 0) { + return false; + } + + switch (mode) { + case UP_MODE: { + _playIndex = (_playIndex + 1) % _noteCount; + _syncNext = _syncTo && _playIndex == _noteCount - 1; + pitchOut = _notesByPitch[_playIndex].pitch; + return true; + } + + case DOWN_MODE: { + --_playIndex; + if (_playIndex < 0) { + _playIndex = _noteCount - 1; + } + _syncNext = _syncTo && _playIndex == 0; + pitchOut = _notesByPitch[_playIndex].pitch; + return true; + } + + case UP_DOWN_MODE: { + if (_up) { + ++_playIndex; + if (_playIndex >= _noteCount) { + _playIndex = std::max(0, _noteCount - 2); + _up = false; + } + } + else { + --_playIndex; + if (_playIndex < 0) { + _playIndex = 1 % _noteCount; + _up = true; + } + _syncNext = _syncTo && (_playIndex == 0 || _playIndex == 1); + } + pitchOut = _notesByPitch[_playIndex].pitch; + return true; + } + + case UP_DOWN_REPEAT_MODE: { + if (_up) { + ++_playIndex; + if (_playIndex >= _noteCount) { + _playIndex = _noteCount - 1; + _up = false; + } + } + else { + --_playIndex; + if (_playIndex < 0) { + _playIndex = 0; + _up = true; + } + _syncNext = _syncTo && _playIndex == 0; + } + pitchOut = _notesByPitch[_playIndex].pitch; + return true; + } + + case IN_ORDER_MODE: { + _playIndex = (_playIndex + 1) % _noteCount; + _syncNext = _syncTo && _playIndex == _noteCount - 1; + pitchOut = _notesAsPlayed[_playIndex].pitch; + return true; + } + + case RANDOM_MODE: { + _playIndex = (_playIndex + 1) % _noteCount; + _syncNext = _syncTo && _playIndex == _noteCount - 1; + pitchOut = _notesAsPlayed[random::u32() % _noteCount].pitch; + return true; + } + } + + assert(false); + return 0.0f; +} + +void Arp::NoteSet::reset() { + resetSequence(); + _notesDirty = false; + for (int c = 0; c < maxChannels; ++c) { + _noteOn[c] = false; + _notesAsPlayed[c].reset(); + _notesByPitch[c].reset(); + } +} + +void Arp::NoteSet::resetSequence() { + _noteCount = 0; + _playIndex = -1; + _up = true; +} + +void Arp::NoteSet::addNote(int c, float pitch) { + dropNote(c); + _noteOn[c] = true; + _notesDirty = true; + + Note n; + n.pitch = pitch; + n.channel = c; + + _notesAsPlayed[_noteCount] = n; + + int i = 0; + while (n.pitch >= _notesByPitch[i].pitch && i < _noteCount) { + ++i; + } + assert(i <= _noteCount); + shuffleUp(_notesByPitch, i); + _notesByPitch[i] = n; + + ++_noteCount; + assert(_noteCount <= maxChannels); +} + +void Arp::NoteSet::dropNote(int c) { + if (!_noteOn[c]) { + return; + } + _noteOn[c] = false; + _notesDirty = true; + + int i = 0; + while (_notesAsPlayed[i].channel != c && i < _noteCount) { + ++i; + } + assert(i < _noteCount); + shuffleDown(_notesAsPlayed, i); + _notesAsPlayed[_noteCount].reset(); + + i = 0; + while (_notesByPitch[i].channel != c && i < _noteCount) { + ++i; + } + assert(i < _noteCount); + shuffleDown(_notesByPitch, i); + _notesByPitch[_noteCount].reset(); + + --_noteCount; + assert(_noteCount >= 0); +} + +void Arp::NoteSet::shuffleUp(Note* notes, int index) { + for (int i = _noteCount; i > index; --i) { + notes[i] = notes[i - 1]; + } +} + +void Arp::NoteSet::shuffleDown(Note* notes, int index) { + for (int n = _noteCount - 1; index < n; ++index) { + notes[index] = notes[index + 1]; + } +} + +void Arp::NoteSet::sync() { + if (!_syncTo || !_syncTo->_notesDirty) { + return; + } + + _noteCount = _syncTo->_noteCount; + _playIndex = -1; + std::copy(_syncTo->_noteOn, _syncTo->_noteOn + maxChannels, _noteOn); + std::copy(_syncTo->_notesAsPlayed, _syncTo->_notesAsPlayed + _noteCount, _notesAsPlayed); + std::copy(_syncTo->_notesByPitch, _syncTo->_notesByPitch + _noteCount, _notesByPitch); + _syncTo->_notesDirty = false; +} + void Arp::reset() { _clockTrigger.reset(); _resetTrigger.reset(); _anyHigh = false; - _noteCount = 0; - _playIndex = -1; - _pitchOut = 0.0f; - _up = true; _secondsSinceLastClock = -1.0f; _clockSeconds = 0.1f; _gateGenerator.process(1000.0f); for (int c = 0; c < maxChannels; ++c) { _gateTrigger[c].reset(); _gateHigh[c] = false; - _noteOn[c] = false; - _notesAsPlayed[c].reset(); - _notesByPitch[c].reset(); } + _currentNotes->reset(); + _playbackNotes->reset(); } void Arp::sampleRateChange() { _sampleTime = APP->engine->getSampleTime(); } +json_t* Arp::dataToJson() { + json_t* root = json_object(); + json_object_set_new(root, NOTES_IMMEDIATE_MODE, json_boolean(_notesImmediate)); + return root; +} + +void Arp::dataFromJson(json_t* root) { + json_t* ni = json_object_get(root, NOTES_IMMEDIATE_MODE); + if (ni) { + _notesImmediate = json_is_true(ni); + } +} + int Arp::channels() { return inputs[GATE_INPUT].getChannels(); } @@ -39,9 +226,7 @@ void Arp::addChannel(int c) { } void Arp::removeChannel(int c) { - if (_noteOn[c]) { - dropNote(c); - } + _currentNotes->dropNote(c); } void Arp::modulate() { @@ -64,8 +249,8 @@ void Arp::processAll(const ProcessArgs& args) { lights[RANDOM_LIGHT].value = _mode == RANDOM_MODE; if (_resetTrigger.process(inputs[RESET_INPUT].getVoltage())) { - _playIndex = -1; - _up = true; + _currentNotes->resetSequence(); + _playbackNotes->resetSequence(); } bool wasAnyHigh = _anyHigh; @@ -78,13 +263,16 @@ void Arp::processAll(const ProcessArgs& args) { } _anyHigh = true; _gateHigh[c] = true; - addNote(c); + _currentNotes->addNote(c, inputs[PITCH_INPUT].getPolyVoltage(c)); + if (_currentNotes->noteCount() == 1) { + _playbackNotes->sync(); + } } else if (_gateHigh[c]) { if (!_gateTrigger[c].isHigh()) { _gateHigh[c] = false; if (!_hold) { - dropNote(c); + _currentNotes->dropNote(c); } } else { @@ -105,152 +293,24 @@ void Arp::processAll(const ProcessArgs& args) { _secondsSinceLastClock += _sampleTime; } - if (clock && _noteCount > 0) { - _gateGenerator.trigger(0.001f + _gateLength * _clockSeconds); - - switch (_mode) { - case UP_MODE: { - _playIndex = (_playIndex + 1) % _noteCount; - _pitchOut = _notesByPitch[_playIndex].pitch; - break; - } - - case DOWN_MODE: { - --_playIndex; - if (_playIndex < 0) { - _playIndex = _noteCount - 1; - } - _pitchOut = _notesByPitch[_playIndex].pitch; - break; - } - - case UP_DOWN_MODE: { - if (_up) { - ++_playIndex; - if (_playIndex >= _noteCount) { - _playIndex = std::max(0, _noteCount - 2); - _up = false; - } - } - else { - --_playIndex; - if (_playIndex < 0) { - _playIndex = 1 % _noteCount; - _up = true; - } - } - _pitchOut = _notesByPitch[_playIndex].pitch; - break; - } - - case UP_DOWN_REPEAT_MODE: { - if (_up) { - ++_playIndex; - if (_playIndex >= _noteCount) { - _playIndex = _noteCount - 1; - _up = false; - } - } - else { - --_playIndex; - if (_playIndex < 0) { - _playIndex = 0; - _up = true; - } - } - _pitchOut = _notesByPitch[_playIndex].pitch; - break; - } - - case IN_ORDER_MODE: { - _playIndex = (_playIndex + 1) % _noteCount; - _pitchOut = _notesAsPlayed[_playIndex].pitch; - break; - } - - case RANDOM_MODE: { - _playIndex = (_playIndex + 1) % _noteCount; - _pitchOut = _notesAsPlayed[random::u32() % _noteCount].pitch; - break; - } - - default: { - assert(false); - } + NoteSet* notes = _notesImmediate ? _currentNotes : _playbackNotes; + if (clock) { + if (notes->nextPitch(_mode, _pitchOut)) { + _gateGenerator.trigger(0.001f + _gateLength * _clockSeconds); } } - outputs[PITCH_OUTPUT].setVoltage(_pitchOut); outputs[GATE_OUTPUT].setVoltage(_gateGenerator.process(_sampleTime) * 5.0f); } -void Arp::addNote(int c) { - if (_noteOn[c]) { - dropNote(c); - } - _noteOn[c] = true; - - Note n; - n.pitch = inputs[PITCH_INPUT].getPolyVoltage(c); - n.channel = c; - - _notesAsPlayed[_noteCount] = n; - - int i = 0; - while (n.pitch >= _notesByPitch[i].pitch && i < _noteCount) { - ++i; - } - assert(i <= _noteCount); - shuffleUp(_notesByPitch, i); - _notesByPitch[i] = n; - - ++_noteCount; - assert(_noteCount <= maxChannels); -} - -void Arp::dropNote(int c) { - _noteOn[c] = false; - - int i = 0; - while (_notesAsPlayed[i].channel != c && i < _noteCount) { - ++i; - } - assert(i < _noteCount); - shuffleDown(_notesAsPlayed, i); - _notesAsPlayed[_noteCount].reset(); - - i = 0; - while (_notesByPitch[i].channel != c && i < _noteCount) { - ++i; - } - assert(i < _noteCount); - shuffleDown(_notesByPitch, i); - _notesByPitch[_noteCount].reset(); - - --_noteCount; - assert(_noteCount >= 0); -} - void Arp::dropAllNotes() { for (int c = 0; c < _channels; ++c) { - if (_noteOn[c] && !_gateHigh[c]) { - dropNote(c); + if (!_gateHigh[c]) { + _currentNotes->dropNote(c); } } } -void Arp::shuffleUp(Note* notes, int index) { - for (int i = _noteCount; i > index; --i) { - notes[i] = notes[i - 1]; - } -} - -void Arp::shuffleDown(Note* notes, int index) { - for (int n = _noteCount - 1; index < n; ++index) { - notes[index] = notes[index + 1]; - } -} - struct ArpWidget : ModuleWidget { static constexpr int hp = 3; @@ -308,6 +368,17 @@ struct ArpWidget : ModuleWidget { addChild(createLight<SmallLight<GreenLight>>(inOrderLightPosition, module, Arp::IN_ORDER_LIGHT)); addChild(createLight<SmallLight<GreenLight>>(randomLightPosition, module, Arp::RANDOM_LIGHT)); } + + void appendContextMenu(Menu* menu) override { + Arp* m = dynamic_cast<Arp*>(module); + assert(m); + menu->addChild(new MenuLabel()); + + OptionsMenuItem* ni = new OptionsMenuItem("Use new notes"); + 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); + } }; Model* modelArp = createModel<Arp, ArpWidget>("Bogaudio-Arp", "ARP", "Poly-input arpeggiator", "Arpeggiator", "Polyphonic"); diff --git a/src/Arp.hpp b/src/Arp.hpp @@ -47,32 +47,54 @@ struct Arp : BGModule { RANDOM_MODE }; - struct Note { - float pitch; - int channel; + struct NoteSet { + struct Note { + float pitch; + int channel; - Note() { - reset(); - } + Note() { + reset(); + } + void reset(); + }; + + + bool _noteOn[maxChannels] {}; + int _noteCount = 0; + Note _notesAsPlayed[maxChannels]; + Note _notesByPitch[maxChannels]; + bool _notesDirty = false; + int _playIndex = -1; + bool _up = true; + NoteSet* _syncTo; + bool _syncNext = true; + + NoteSet(NoteSet* syncTo = NULL) : _syncTo(syncTo) {} + + inline int noteCount() { return _noteCount; } + bool nextPitch(Mode mode, float& pitchOut); void reset(); + void resetSequence(); + void addNote(int c, float pitch); + void dropNote(int c); + void shuffleUp(Note* notes, int index); + void shuffleDown(Note* notes, int index); + void sync(); }; Mode _mode = UP_MODE; float _gateLength = 0.5f; bool _hold = false; + bool _notesImmediate = false; Trigger _clockTrigger; Trigger _resetTrigger; Trigger _gateTrigger[maxChannels]; bool _anyHigh = false; bool _gateHigh[maxChannels] {}; - bool _noteOn[maxChannels] {}; - int _noteCount = 0; - Note _notesAsPlayed[maxChannels]; - Note _notesByPitch[maxChannels]; - int _playIndex = -1; + NoteSet* _currentNotes; + NoteSet* _playbackNotes; float _pitchOut = 0.0f; - bool _up = true; float _sampleTime = 0.001f; float _secondsSinceLastClock = -1.0f; float _clockSeconds = 0.1f; @@ -83,21 +105,26 @@ struct Arp : BGModule { configParam(MODE_PARAM, 0.0f, 5.0f, 0.0f, "Playback mode"); configParam(GATE_LENGTH_PARAM, 0.0f, 1.0f, 0.5f, "Gate length", "%", 0.0f, 100.0f); configParam(HOLD_PARAM, 0.0f, 1.0f, 0.0f, "Hold/latch"); + + _currentNotes = new NoteSet(); + _playbackNotes = new NoteSet(_currentNotes); + } + virtual ~Arp() { + delete _currentNotes; + delete _playbackNotes; } void reset() override; void sampleRateChange() override; + json_t* dataToJson() override; + void dataFromJson(json_t* root) override; int channels() override; void addChannel(int c) override; void removeChannel(int c) override; void modulate() override; void processAll(const ProcessArgs& args) override; void processChannel(const ProcessArgs& args, int c) override {} - void addNote(int c); - void dropNote(int c); void dropAllNotes(); - void shuffleUp(Note* notes, int index); - void shuffleDown(Note* notes, int index); }; } // namespace bogaudio