diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 495add0e..a0025b61 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: mebitek +github: # patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f6d6a07..90df22be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +# v0.2.2 (20 March 2024) +- Logic Track + - per step gate logic operators + - per step note logic operators +- fix shift steps feature [@glibersat](https://github.com/glibersat) +- step recorder "move step forward" shortcuts +- step recorder "current step" cv routable, respond to gate (5ms) +- curve track cv contrallable min and max +- Overview page improvements +- add trigger curve shape +- add filter note parameter + +> **testers** : +> +> mebitek, Jil, Guillaume Libersat, Andreas Hieninger, P.M. Lenneskog + # v0.2.1 (4 March 2024) - issue #80 - Repeat Function Issue - Metropolix Mode - issue #82 - Fatal error when pressing STEP button and turning encoder @@ -8,6 +24,10 @@ - issue #90 - the first note is never recorded - fix user settings +> **testers** : +> +> mebitek, Jil, Andreas Hieninger + # v0.2.0 (29 Febrary 2024) - Stochastic Track - global octave modifier diff --git a/src/apps/sequencer/CMakeLists.txt b/src/apps/sequencer/CMakeLists.txt index 8cc0f9f1..411f512a 100644 --- a/src/apps/sequencer/CMakeLists.txt +++ b/src/apps/sequencer/CMakeLists.txt @@ -15,6 +15,7 @@ set(sources engine/MidiOutputEngine.cpp engine/NoteTrackEngine.cpp engine/StochasticEngine.cpp + engine/LogicTrackEngine.cpp engine/RoutingEngine.cpp engine/SequenceState.cpp # engine/generators @@ -41,6 +42,8 @@ set(sources model/NoteTrack.cpp model/StochasticSequence.cpp model/StochasticTrack.cpp + model/LogicSequence.cpp + model/LogicTrack.cpp model/PlayState.cpp model/Project.cpp model/Routing.cpp @@ -89,6 +92,8 @@ set(sources ui/pages/NoteSequencePage.cpp ui/pages/StochasticSequencePage.cpp ui/pages/StochasticSequenceEditPage.cpp + ui/pages/LogicSequenceEditPage.cpp + ui/pages/LogicSequencePage.cpp ui/pages/OverviewPage.cpp ui/pages/PatternPage.cpp ui/pages/PerformerPage.cpp diff --git a/src/apps/sequencer/Config.h b/src/apps/sequencer/Config.h index fcc4a3e8..5f2cdded 100644 --- a/src/apps/sequencer/Config.h +++ b/src/apps/sequencer/Config.h @@ -7,7 +7,7 @@ #define CONFIG_VERSION_NAME "PER|FORMER SEQUENCER" #define CONFIG_VERSION_MAJOR 0 #define CONFIG_VERSION_MINOR 2 -#define CONFIG_VERSION_REVISION 1 +#define CONFIG_VERSION_REVISION 2 // Task priorities #define CONFIG_DRIVER_TASK_PRIORITY 5 diff --git a/src/apps/sequencer/Sequencer.cpp b/src/apps/sequencer/Sequencer.cpp index 273f2000..215b065d 100644 --- a/src/apps/sequencer/Sequencer.cpp +++ b/src/apps/sequencer/Sequencer.cpp @@ -150,7 +150,7 @@ static void assert_handler(const char *filename, int line, const char *msg) { int main(void) { System::init(); - System::startWatchdog(1000); + System::startWatchdog(500); Console::init(); HighResolutionTimer::init(); diff --git a/src/apps/sequencer/engine/CurveTrackEngine.cpp b/src/apps/sequencer/engine/CurveTrackEngine.cpp index 06c34b68..e4f3e960 100644 --- a/src/apps/sequencer/engine/CurveTrackEngine.cpp +++ b/src/apps/sequencer/engine/CurveTrackEngine.cpp @@ -228,7 +228,12 @@ void CurveTrackEngine::updateOutput(uint32_t relativeTick, uint32_t divisor) { float value = evalStepShape(step, _shapeVariation || fillVariation, fillInvert, _currentStepFraction, _sequenceState.direction()); value = range.denormalize(value); - _cvOutputTarget = value; + + float min = float(_curveTrack.min()) / CurveSequence::Min::Max; + float max = float(_curveTrack.max()) / CurveSequence::Max::Max; + + + _cvOutputTarget = min + value * (max - min); } _engine.midiOutputEngine().sendCv(_track.trackIndex(), _cvOutputTarget); diff --git a/src/apps/sequencer/engine/CurveTrackEngine.h b/src/apps/sequencer/engine/CurveTrackEngine.h index 1c102ab1..e0d8c361 100644 --- a/src/apps/sequencer/engine/CurveTrackEngine.h +++ b/src/apps/sequencer/engine/CurveTrackEngine.h @@ -9,7 +9,7 @@ class CurveTrackEngine : public TrackEngine { public: - CurveTrackEngine(Engine &engine, const Model &model, Track &track, const TrackEngine *linkedTrackEngine) : + CurveTrackEngine(Engine &engine, Model &model, Track &track, const TrackEngine *linkedTrackEngine) : TrackEngine(engine, model, track, linkedTrackEngine), _curveTrack(track.curveTrack()) { diff --git a/src/apps/sequencer/engine/Engine.cpp b/src/apps/sequencer/engine/Engine.cpp index e4ef1614..ce889499 100644 --- a/src/apps/sequencer/engine/Engine.cpp +++ b/src/apps/sequencer/engine/Engine.cpp @@ -1,13 +1,16 @@ #include "Engine.h" #include "Config.h" +#include "LogicTrackEngine.h" #include "MidiUtils.h" +#include "NoteTrackEngine.h" #include "core/Debug.h" #include "core/midi/MidiMessage.h" #include "ui/ControllerManager.h" #include "os/os.h" +#include Engine::Engine(Model &model, ClockTimer &clockTimer, Adc &adc, Dac &dac, Dio &dio, GateOutput &gateOutput, Midi &midi, UsbMidi &usbMidi) : _model(model), @@ -409,7 +412,6 @@ void Engine::updateTrackSetups() { auto &track = _project.track(trackIndex); int linkTrack = track.linkTrack(); const TrackEngine *linkedTrackEngine = linkTrack >= 0 ? &trackEngine(linkTrack) : nullptr; - FixedStringBuilder<16> str("TRACK %d", trackIndex+1); if (!_trackEngines[trackIndex] || _trackEngines[trackIndex]->trackMode() != track.trackMode()) { auto &trackEngine = _trackEngines[trackIndex]; @@ -418,27 +420,18 @@ void Engine::updateTrackSetups() { switch (track.trackMode()) { case Track::TrackMode::Note: trackEngine = trackContainer.create(*this, _model, track, linkedTrackEngine); - if (sizeof(track.noteTrack().name()==0)) { - track.noteTrack().setName(str); - } break; case Track::TrackMode::Curve: trackEngine = trackContainer.create(*this, _model, track, linkedTrackEngine); - if (sizeof(track.noteTrack().name()==0)) { - track.curveTrack().setName(str); - } break; case Track::TrackMode::MidiCv: trackEngine = trackContainer.create(*this, _model, track, linkedTrackEngine); - if (sizeof(track.noteTrack().name()==0)) { - track.midiCvTrack().setName(str); - } break; case Track::TrackMode::Stochastic: trackEngine = trackContainer.create(*this, _model, track, linkedTrackEngine); - if (sizeof(track.stochasticTrack().name()==0)) { - track.stochasticTrack().setName(str); - } + break; + case Track::TrackMode::Logic: + trackEngine = trackContainer.create(*this, _model, track, linkedTrackEngine); break; case Track::TrackMode::Last: break; diff --git a/src/apps/sequencer/engine/Engine.h b/src/apps/sequencer/engine/Engine.h index c5d1e571..234689df 100644 --- a/src/apps/sequencer/engine/Engine.h +++ b/src/apps/sequencer/engine/Engine.h @@ -13,6 +13,7 @@ #include "RoutingEngine.h" #include "MidiOutputEngine.h" #include "StochasticEngine.h" +#include "LogicTrackEngine.h" #include "MidiPort.h" #include "MidiLearn.h" #include "CvGateToMidiConverter.h" @@ -34,7 +35,7 @@ class Engine : private Clock::Listener { public: - typedef Container TrackEngineContainer; + typedef Container TrackEngineContainer; typedef std::array TrackEngineContainerArray; typedef std::array TrackEngineArray; typedef std::array, CONFIG_TRACK_COUNT> TrackUpdateReducerArray; diff --git a/src/apps/sequencer/engine/LogicTrackEngine.cpp b/src/apps/sequencer/engine/LogicTrackEngine.cpp new file mode 100644 index 00000000..3c8cfaf0 --- /dev/null +++ b/src/apps/sequencer/engine/LogicTrackEngine.cpp @@ -0,0 +1,636 @@ +#include "LogicTrackEngine.h" + +#include "Engine.h" +#include "Groove.h" +#include "NoteTrackEngine.h" +#include "Slide.h" +#include "SequenceUtils.h" + +#include "core/Debug.h" +#include "core/utils/Random.h" +#include "core/math/Math.h" + +#include "model/LogicSequence.h" +#include "model/Scale.h" +#include "ui/MatrixMap.h" +#include +#include +#include + +static Random rng; + +// evaluate if step gate is active +static bool evalStepGate(const LogicSequence::Step &step, int probabilityBias) { + int probability = clamp(step.gateProbability() + probabilityBias, -1, LogicSequence::GateProbability::Max); + return step.gate() && int(rng.nextRange(LogicSequence::GateProbability::Range)) <= probability; +} + +// evaluate step condition +static bool evalStepCondition(const LogicSequence::Step &step, int iteration, bool fill, bool &prevCondition) { + auto condition = step.condition(); + switch (condition) { + case Types::Condition::Off: return true; + case Types::Condition::Fill: prevCondition = fill; return prevCondition; + case Types::Condition::NotFill: prevCondition = !fill; return prevCondition; + case Types::Condition::Pre: return prevCondition; + case Types::Condition::NotPre: return !prevCondition; + case Types::Condition::First: prevCondition = iteration == 0; return prevCondition; + case Types::Condition::NotFirst: prevCondition = iteration != 0; return prevCondition; + default: + int index = int(condition); + if (index >= int(Types::Condition::Loop) && index < int(Types::Condition::Last)) { + auto loop = Types::conditionLoop(condition); + prevCondition = iteration % loop.base == loop.offset; + if (loop.invert) prevCondition = !prevCondition; + return prevCondition; + } + } + return true; +} + +// evaluate step retrigger count +static int evalStepRetrigger(const LogicSequence::Step &step, int probabilityBias) { + int probability = clamp(step.retriggerProbability() + probabilityBias, -1, LogicSequence::RetriggerProbability::Max); + return int(rng.nextRange(LogicSequence::RetriggerProbability::Range)) <= probability ? step.retrigger() + 1 : 1; +} + +// evaluate step length +static int evalStepLength(const LogicSequence::Step &step, int lengthBias) { + int length = LogicSequence::Length::clamp(step.length() + lengthBias) + 1; + int probability = step.lengthVariationProbability(); + if (int(rng.nextRange(LogicSequence::LengthVariationProbability::Range)) <= probability) { + int offset = step.lengthVariationRange() == 0 ? 0 : rng.nextRange(std::abs(step.lengthVariationRange()) + 1); + if (step.lengthVariationRange() < 0) { + offset = -offset; + } + length = clamp(length + offset, 0, LogicSequence::Length::Range); + } + return length; +} + +// evaluate transposition +static int evalTransposition(const Scale &scale, int octave, int transpose) { + return octave * scale.notesPerOctave() + transpose; +} + +// evaluate note voltage +static float evalStepNote(const LogicSequence::Step &step, int probabilityBias, const Scale &scale, int rootNote, int octave, int transpose, int note1, int note2, bool useVariation = true) { + + auto stepNote = step.note(); + switch (step.noteLogic()) { + case LogicSequence::NOne: + stepNote = note1; + break; + case LogicSequence::NTwo: + stepNote = note2; + break; + case LogicSequence::Min: + stepNote = std::min(note1, note2); + break; + case LogicSequence::Max: + stepNote = std::max(note1, note2); + break; + case LogicSequence::Sum: + stepNote = note1 + note2; + break; + case LogicSequence::Avg: + stepNote = int((note1+note2)/2); + break; + case LogicSequence::NRandomInput: { + int rnd = rng.nextRange(2); + if (rnd == 0) { + stepNote = note1; + } else { + stepNote = note2; + } + } + break; + case LogicSequence::NRandomLogic: { + int rndMode = rng.nextRange(6); + switch (rndMode) { + case 0: + stepNote = note1; + break; + case 1: + stepNote = note2; + break; + case 2: + stepNote = std::min(note1, note2); + break; + case 3: + stepNote = std::max(note1, note2); + break; + case 4: + stepNote = note1 + note2; + break; + case 5: + stepNote = int((note1+note2)/2); + break; + case 6: + int rnd = rng.nextRange(2); + if (rnd == 0) { + stepNote = note1; + } else { + stepNote = note2; + } + break; + } + } + break; + + default: + break; + } + + int note = stepNote + evalTransposition(scale, octave, transpose); + int probability = clamp(step.noteVariationProbability() + probabilityBias, -1, LogicSequence::NoteVariationProbability::Max); + if (useVariation && int(rng.nextRange(LogicSequence::NoteVariationProbability::Range)) <= probability) { + int offset = step.noteVariationRange() == 0 ? 0 : rng.nextRange(std::abs(step.noteVariationRange()) + 1); + if (step.noteVariationRange() < 0) { + offset = -offset; + } + note = LogicSequence::Note::clamp(note + offset); + } + return scale.noteToVolts(note) + (scale.isChromatic() ? rootNote : 0) * (1.f / 12.f); + return 0.f; +} + +void LogicTrackEngine::reset() { + _freeRelativeTick = 0; + _sequenceState.reset(); + _currentStep = -1; + _prevCondition = false; + _activity = false; + _gateOutput = false; + //_cvOutput = 0.f; + //_cvOutputTarget = 0.f; + _slideActive = false; + _gateQueue.clear(); + _cvQueue.clear(); + _recordHistory.clear(); + + changePattern(); +} + +void LogicTrackEngine::restart() { + _freeRelativeTick = 0; + _sequenceState.reset(); + _currentStep = -1; +} + +TrackEngine::TickResult LogicTrackEngine::tick(uint32_t tick) { + ASSERT(_sequence != nullptr, "invalid sequence"); + const auto &sequence = *_sequence; + const auto *linkData = _linkedTrackEngine ? _linkedTrackEngine->linkData() : nullptr; + + + if (_logicTrack.inputTrack1() != -1) { + if (_input1TrackEngine == nullptr) { + auto *ne = &_engine.trackEngine(_logicTrack.inputTrack1()).as(); + _input1TrackEngine = ne; + } + } else { + _input1TrackEngine = nullptr; + } + + + if (_logicTrack.inputTrack2() != -1) { + if (_input2TrackEngine == nullptr) { + auto *ne = &_engine.trackEngine(_logicTrack.inputTrack2()).as(); + _input2TrackEngine = ne; + } + } else { + _input2TrackEngine = nullptr; + } + + if (linkData) { + _linkData = *linkData; + _sequenceState = *linkData->sequenceState; + + if (linkData->relativeTick % linkData->divisor == 0) { + recordStep(tick, linkData->divisor); + triggerStep(tick, linkData->divisor); + } + } else { + uint32_t divisor = sequence.divisor() * (CONFIG_PPQN / CONFIG_SEQUENCE_PPQN); + uint32_t resetDivisor = sequence.resetMeasure() * _engine.measureDivisor(); + uint32_t relativeTick = resetDivisor == 0 ? tick : tick % resetDivisor; + + if (int(_model.project().stepsToStop()) != 0 && int(relativeTick / divisor) == int(_model.project().stepsToStop())) { + _engine.clockStop(); + } + + // handle reset measure + if (relativeTick == 0) { + reset(); + _currentStageRepeat = 1; + } + const auto &sequence = *_sequence; + + // advance sequence + switch (_logicTrack.playMode()) { + case Types::PlayMode::Aligned: + if (relativeTick % divisor == 0) { + int abstoluteStep = int(relativeTick / divisor); + _sequenceState.advanceAligned(abstoluteStep, sequence.runMode(), sequence.firstStep(), sequence.lastStep(), rng); + + triggerStep(tick, divisor); + const auto &step = sequence.step(_sequenceState.step()); + if (step.gateOffset()<0) { + _sequenceState.calculateNextStepAligned( + (relativeTick + divisor) / divisor, + sequence.runMode(), + sequence.firstStep(), + sequence.lastStep(), + rng + ); + + triggerStep(tick + divisor, divisor, true); + } + } + break; + case Types::PlayMode::Free: + relativeTick = _freeRelativeTick; + if (++_freeRelativeTick >= divisor) { + _freeRelativeTick = 0; + } + if (relativeTick == 0) { + + if (_currentStageRepeat == 1) { + _sequenceState.advanceFree(sequence.runMode(), sequence.firstStep(), sequence.lastStep(), rng); + _sequenceState.calculateNextStepFree( + sequence.runMode(), sequence.firstStep(), sequence.lastStep(), rng); + } + + const auto &step = sequence.step(_sequenceState.step()); + bool isLastStageStep = ((int) (step.stageRepeats()+1) - (int) _currentStageRepeat) <= 0; + + if (step.gateOffset() >= 0) { + triggerStep(tick, divisor); + } + + if (!isLastStageStep && step.gateOffset() < 0) { + triggerStep(tick + divisor, divisor, false); + } + + if (isLastStageStep + && sequence.step(_sequenceState.nextStep()).gateOffset() < 0) { + triggerStep(tick + divisor, divisor, true); + } + + if (isLastStageStep) { + _currentStageRepeat = 1; + } else { + _currentStageRepeat++; + } + + } + break; + case Types::PlayMode::Last: + break; + } + + _linkData.divisor = divisor; + _linkData.relativeTick = relativeTick; + _linkData.sequenceState = &_sequenceState; + } + + auto &midiOutputEngine = _engine.midiOutputEngine(); + + TickResult result = TickResult::NoUpdate; + + while (!_gateQueue.empty() && tick >= _gateQueue.front().tick) { + if (!_monitorOverrideActive) { + result |= TickResult::GateUpdate; + _activity = _gateQueue.front().gate; + _gateOutput = (!mute() || fill()) && _activity; + midiOutputEngine.sendGate(_track.trackIndex(), _gateOutput); + } + _gateQueue.pop(); + + } + + while (!_cvQueue.empty() && tick >= _cvQueue.front().tick) { + if (!mute() || _logicTrack.cvUpdateMode() == LogicTrack::CvUpdateMode::Always) { + if (!_monitorOverrideActive) { + result |= TickResult::CvUpdate; + _cvOutputTarget = _cvQueue.front().cv; + _slideActive = _cvQueue.front().slide; + midiOutputEngine.sendCv(_track.trackIndex(), _cvOutputTarget); + midiOutputEngine.sendSlide(_track.trackIndex(), _slideActive); + } + } + _cvQueue.pop(); + } + + return result; +} + +void LogicTrackEngine::update(float dt) { + bool running = _engine.state().running(); + + const auto &sequence = *_sequence; + const auto &scale = sequence.selectedScale(_model.project().scale()); + int rootNote = sequence.selectedRootNote(_model.project().rootNote()); + int octave = _logicTrack.octave(); + int transpose = _logicTrack.transpose(); + + // enable/disable step recording mode + /*if (recording && _model.project().recordMode() == Types::RecordMode::StepRecord) { + if (!_stepRecorder.enabled()) { + _stepRecorder.start(sequence); + } + } else { + if (_stepRecorder.enabled()) { + _stepRecorder.stop(); + } + }*/ + + // helper to send gate/cv from monitoring to midi output engine + auto sendToMidiOutputEngine = [this] (bool gate, float cv = 0.f) { + auto &midiOutputEngine = _engine.midiOutputEngine(); + midiOutputEngine.sendGate(_track.trackIndex(), gate); + if (gate) { + midiOutputEngine.sendCv(_track.trackIndex(), cv); + midiOutputEngine.sendSlide(_track.trackIndex(), false); + } + }; + + // set monitor override + auto setOverride = [&] (float cv) { + _cvOutputTarget = cv; + _activity = _gateOutput = true; + _monitorOverrideActive = true; + // pass through to midi engine + sendToMidiOutputEngine(true, cv); + }; + + // clear monitor override + auto clearOverride = [&] () { + if (_monitorOverrideActive) { + _activity = _gateOutput = false; + _monitorOverrideActive = false; + sendToMidiOutputEngine(false); + } + }; + + // check for step monitoring + bool stepMonitoring = (!running && _monitorStepIndex >= 0); + + // check for live monitoring + auto monitorMode = _model.project().monitorMode(); + bool liveMonitoring = + (monitorMode == Types::MonitorMode::Always) || + (monitorMode == Types::MonitorMode::Stopped && !running); + + if (stepMonitoring) { + const auto &step = sequence.step(_monitorStepIndex); + setOverride(evalStepNote(step, 0, scale, rootNote, octave, transpose, false, 0, 0)); + } else if (liveMonitoring && _recordHistory.isNoteActive()) { + int note = evalTransposition(scale, octave, transpose); + setOverride(scale.noteToVolts(note) + (scale.isChromatic() ? rootNote : 0) * (1.f / 12.f)); + } else { + clearOverride(); + } + + if (_slideActive && _logicTrack.slideTime() > 0) { + _cvOutput = Slide::applySlide(_cvOutput, _cvOutputTarget, _logicTrack.slideTime(), dt); + } else { + _cvOutput = _cvOutputTarget; + } +} + +void LogicTrackEngine::changePattern() { + _sequence = &_logicTrack.sequence(pattern()); + _fillSequence = &_logicTrack.sequence(std::min(pattern() + 1, CONFIG_PATTERN_COUNT - 1)); +} + +void LogicTrackEngine::monitorMidi(uint32_t tick, const MidiMessage &message) { + _recordHistory.write(tick, message); + + if (_engine.recording() && _model.project().recordMode() == Types::RecordMode::StepRecord) { + // _stepRecorder.process(message, *_sequence, [this] (int midiNote) { return noteFromMidiNote(midiNote); }); + } +} + +void LogicTrackEngine::clearMidiMonitoring() { + _recordHistory.clear(); +} + +void LogicTrackEngine::setMonitorStep(int index) { + _monitorStepIndex = (index >= 0 && index < CONFIG_STEP_COUNT) ? index : -1; + + // in step record mode, select step to start recording recording from + if (_engine.recording() && _model.project().recordMode() == Types::RecordMode::StepRecord && + index >= _sequence->firstStep() && index <= _sequence->lastStep()) { + _stepRecorder.setStepIndex(index); + } +} +void LogicTrackEngine::triggerStep(uint32_t tick, uint32_t divisor, bool forNextStep) { + int octave = _logicTrack.octave(); + int transpose = _logicTrack.transpose(); + int rotate = _logicTrack.rotate(); + bool fillStep = fill() && (rng.nextRange(100) < uint32_t(fillAmount())); + bool useFillGates = fillStep && _logicTrack.fillMode() == LogicTrack::FillMode::Gates; + bool useFillSequence = fillStep && _logicTrack.fillMode() == LogicTrack::FillMode::NextPattern; + bool useFillCondition = fillStep && _logicTrack.fillMode() == LogicTrack::FillMode::Condition; + + const auto &sequence = *_sequence; + const auto &evalSequence = useFillSequence ? *_fillSequence : *_sequence; + + // TODO do we need to encounter rotate? + _currentStep = SequenceUtils::rotateStep(_sequenceState.step(), sequence.firstStep(), sequence.lastStep(), rotate); + + int stepIndex; + + if (forNextStep) { + stepIndex = _sequenceState.nextStep(); + } else { + stepIndex = _currentStep; + } + + if (stepIndex < 0) return; + + const auto &step = evalSequence.step(stepIndex); + + int gateOffset = ((int) divisor * step.gateOffset()) / (LogicSequence::GateOffset::Max + 1); + uint32_t stepTick = (int) tick + gateOffset; + + bool stepGate = evalStepGate(step, _logicTrack.gateProbabilityBias()); + + if (_logicTrack.inputTrack1() == -1 || _logicTrack.inputTrack2() == -1) { + return; + } + + const auto ¬eTrack1 = _model.project().track(_logicTrack.inputTrack1()).noteTrack(); + const auto &inputSequence1 = noteTrack1.sequence(_model.project().selectedPatternIndex()); + + const int currentStep1 = _input1TrackEngine->currentStep(); + int stepIndex1 = currentStep1 != -1 ? (currentStep1 - _currentStep) + stepIndex : stepIndex; + int idx1 = SequenceUtils::rotateStep(stepIndex1, inputSequence1.firstStep(), inputSequence1.lastStep(), noteTrack1.rotate()); + + const auto &inputStep1 = inputSequence1.step(idx1); + + const auto ¬eTrack2 = _model.project().track(_logicTrack.inputTrack2()).noteTrack(); + const auto &inputSequence2 = noteTrack2.sequence(_model.project().selectedPatternIndex()); + const int currentStep2 = _input2TrackEngine->currentStep(); + int stepIndex2 = currentStep2 != -1 ? (currentStep2 - _currentStep) + stepIndex : stepIndex; + int idx2 = SequenceUtils::rotateStep(stepIndex2, inputSequence2.firstStep(), inputSequence2.lastStep(), noteTrack2.rotate()); + + const auto &inputStep2 = inputSequence2.step(idx2); + + switch (step.gateLogic()) { + case LogicSequence::GateLogicMode::One: + stepGate = step.gate() && inputStep1.gate(); + break; + case LogicSequence::GateLogicMode::Two: + stepGate = step.gate() && inputStep2.gate(); + break; + case LogicSequence::GateLogicMode::And: + stepGate = step.gate() && (inputStep1.gate() & inputStep2.gate()); + break; + case LogicSequence::GateLogicMode::Or: + stepGate = step.gate() && (inputStep1.gate() | inputStep2.gate()); + break; + case LogicSequence::GateLogicMode::Xor: + stepGate = step.gate() && inputStep1.gate() xor inputStep2.gate(); + break; + case LogicSequence::GateLogicMode::Nand: + stepGate = step.gate() && !(inputStep1.gate() & inputStep2.gate()); + break; + case LogicSequence::GateLogicMode::RandomInput: { + int rnd = rng.nextRange(2); + if (rnd == 0) { + stepGate = step.gate() && inputStep1.gate(); + } else { + stepGate = step.gate() && inputStep2.gate(); + } + } + break; + case LogicSequence::GateLogicMode::RandomLogic: { + int rndMode = rng.nextRange(6); + switch (rndMode) { + case 0: + stepGate = step.gate() && inputStep1.gate(); + break; + case 1: + stepGate = step.gate() && inputStep2.gate(); + break; + case 2: + stepGate = step.gate() && (inputStep1.gate() & inputStep2.gate()); + break; + case 3: + stepGate = step.gate() && (inputStep1.gate() | inputStep2.gate()); + break; + case 4: + stepGate = step.gate() && inputStep1.gate() xor inputStep2.gate(); + break; + case 5: + stepGate = step.gate() && !(inputStep1.gate() & inputStep2.gate()); + break; + case 6: + int rnd = rng.nextRange(2); + if (rnd == 0) { + stepGate = step.gate() && inputStep1.gate(); + } else { + stepGate = step.gate() && inputStep2.gate(); + } + break; + + } + } + break; + default: + break; + } + + + stepGate = stepGate || useFillGates; + if (stepGate) { + stepGate = evalStepCondition(step, _sequenceState.iteration(), useFillCondition, _prevCondition); + } + + switch (step.stageRepeatMode()) { + case Types::StageRepeatMode::Each: + break; + case Types::StageRepeatMode::First: + stepGate = stepGate && _currentStageRepeat == 1; + break; + case Types::StageRepeatMode::Last: + stepGate = stepGate && _currentStageRepeat == step.stageRepeats()+1; + break; + case Types::StageRepeatMode::Middle: + stepGate = stepGate && _currentStageRepeat == (step.stageRepeats()+1)/2; + break; + case Types::StageRepeatMode::Odd: + stepGate = stepGate && _currentStageRepeat % 2 != 0; + break; + case Types::StageRepeatMode::Even: + stepGate = stepGate && _currentStageRepeat % 2 == 0; + break; + case Types::StageRepeatMode::Triplets: + stepGate = stepGate && (_currentStageRepeat - 1) % 3 == 0; + break; + case Types::StageRepeatMode::Random: + int rndMode = rng.nextRange(6); + switch (rndMode) { + case 0: + break; + case 1: + stepGate = stepGate && _currentStageRepeat == 1; + break; + case 2: + stepGate = stepGate && _currentStageRepeat == step.stageRepeats()+1; + break; + case 3: + stepGate = stepGate && _currentStageRepeat % ((step.stageRepeats()+1)/2)+1 == 0; + break; + case 4: + stepGate = stepGate && _currentStageRepeat % 2 != 0; + break; + case 5: + stepGate = stepGate && _currentStageRepeat % 2 == 0; + break; + case 6: + stepGate = stepGate && (_currentStageRepeat - 1) % 3 == 0; + break; + + } + break; + } + + if (stepGate) { + uint32_t stepLength = (divisor * evalStepLength(step, _logicTrack.lengthBias())) / LogicSequence::Length::Range; + int stepRetrigger = evalStepRetrigger(step, _logicTrack.retriggerProbabilityBias()); + if (stepRetrigger > 1) { + uint32_t retriggerLength = divisor / stepRetrigger; + uint32_t retriggerOffset = 0; + while (stepRetrigger-- > 0 && retriggerOffset <= stepLength) { + _gateQueue.pushReplace({ Groove::applySwing(stepTick + retriggerOffset, swing()), true }); + _gateQueue.pushReplace({ Groove::applySwing(stepTick + retriggerOffset + retriggerLength / 2, swing()), false }); + retriggerOffset += retriggerLength; + } + } else { + _gateQueue.pushReplace({ Groove::applySwing(stepTick, swing()), true }); + _gateQueue.pushReplace({ Groove::applySwing(stepTick + stepLength, swing()), false }); + } + } + + if (stepGate || _logicTrack.cvUpdateMode() == LogicTrack::CvUpdateMode::Always) { + const auto &scale = evalSequence.selectedScale(_model.project().scale()); + int rootNote = evalSequence.selectedRootNote(_model.project().rootNote()); + + if (_logicTrack.inputTrack1() == -1 || _logicTrack.inputTrack2() == -1) { + return; + } + _cvQueue.push({ Groove::applySwing(stepTick, swing()), evalStepNote(step, _logicTrack.noteProbabilityBias(), scale, rootNote, octave, transpose, inputSequence1.step(stepIndex1).note(), inputSequence2.step(stepIndex2).note()), step.slide() }); + } +} + +void LogicTrackEngine::triggerStep(uint32_t tick, uint32_t divisor) { + triggerStep(tick, divisor, false); +} + +void LogicTrackEngine::recordStep(uint32_t tick, uint32_t divisor) { + +} diff --git a/src/apps/sequencer/engine/LogicTrackEngine.h b/src/apps/sequencer/engine/LogicTrackEngine.h new file mode 100644 index 00000000..7472cddf --- /dev/null +++ b/src/apps/sequencer/engine/LogicTrackEngine.h @@ -0,0 +1,137 @@ +#pragma once + +#include "NoteTrackEngine.h" +#include "TrackEngine.h" +#include "SequenceState.h" +#include "SortedQueue.h" +#include "Groove.h" +#include "RecordHistory.h" +#include "model/LogicSequence.h" +#include "StepRecorder.h" + +class LogicTrackEngine : public TrackEngine { +public: + LogicTrackEngine(Engine &engine, Model &model, Track &track, const TrackEngine *linkedTrackEngine) : + TrackEngine(engine, model, track, linkedTrackEngine), + _logicTrack(track.logicTrack()) + { + reset(); + } + + virtual Track::TrackMode trackMode() const override { return Track::TrackMode::Logic; } + + virtual void reset() override; + virtual void restart() override; + virtual TickResult tick(uint32_t tick) override; + virtual void update(float dt) override; + + virtual void changePattern() override; + + virtual void monitorMidi(uint32_t tick, const MidiMessage &message) override; + virtual void clearMidiMonitoring() override; + + virtual const TrackLinkData *linkData() const override { return &_linkData; } + + virtual bool activity() const override { return _activity; } + virtual bool gateOutput(int index) const override { return _gateOutput; } + virtual float cvOutput(int index) const override { return _cvOutput; } + virtual float sequenceProgress() const override { + return _currentStep < 0 ? 0.f : float(_currentStep - _sequence->firstStep()) / (_sequence->lastStep() - _sequence->firstStep()); + } + + const LogicSequence &sequence() const { return *_sequence; } + bool isActiveSequence(const LogicSequence &sequence) const { return &sequence == _sequence; } + + int currentStep() const { return _currentStep; } + int currentRecordStep() const { return _stepRecorder.stepIndex(); } + + void setMonitorStep(int index); + + Types::PlayMode playMode() const { return _logicTrack.playMode(); } + + SequenceState sequenceState() { + return _sequenceState; + } + + const NoteTrackEngine &input1TrackEngine() const { + return *_input1TrackEngine; + } + + const NoteTrackEngine &input2TrackEngine() const { + return *_input2TrackEngine; + } + + void setInput1TrackEngine(NoteTrackEngine *ne) { + _input1TrackEngine = ne; + } + + void setInput2TrackEngine(NoteTrackEngine *ne) { + _input2TrackEngine = ne; + } + + +private: + void triggerStep(uint32_t tick, uint32_t divisor, bool nextStep); + void triggerStep(uint32_t tick, uint32_t divisor); + void recordStep(uint32_t tick, uint32_t divisor); + int noteFromMidiNote(uint8_t midiNote) const; + + bool fill() const { + return (_logicTrack.fillMuted() || !TrackEngine::mute()) ? TrackEngine::fill() : false; + } + + LogicTrack &_logicTrack; + + TrackLinkData _linkData; + + LogicSequence *_sequence; + const LogicSequence *_fillSequence; + + NoteTrackEngine *_input1TrackEngine = nullptr; + NoteTrackEngine *_input2TrackEngine = nullptr; + + uint32_t _freeRelativeTick; + SequenceState _sequenceState; + int _currentStep; + bool _prevCondition; + + int _monitorStepIndex = -1; + + RecordHistory _recordHistory; + bool _monitorOverrideActive = false; + StepRecorder _stepRecorder; + + bool _activity; + bool _gateOutput; + float _cvOutput; + float _cvOutputTarget; + bool _slideActive; + unsigned int _currentStageRepeat; + + struct Gate { + uint32_t tick; + bool gate; + }; + + struct GateCompare { + bool operator()(const Gate &a, const Gate &b) { + return a.tick < b.tick; + } + }; + + SortedQueue _gateQueue; + + struct Cv { + uint32_t tick; + float cv; + bool slide; + }; + + struct CvCompare { + bool operator()(const Cv &a, const Cv &b) { + return a.tick < b.tick; + } + }; + + SortedQueue _cvQueue; +}; diff --git a/src/apps/sequencer/engine/MidiCvTrackEngine.h b/src/apps/sequencer/engine/MidiCvTrackEngine.h index 8b6f30d9..7c16e428 100644 --- a/src/apps/sequencer/engine/MidiCvTrackEngine.h +++ b/src/apps/sequencer/engine/MidiCvTrackEngine.h @@ -7,7 +7,7 @@ class MidiCvTrackEngine : public TrackEngine { public: - MidiCvTrackEngine(Engine &engine, const Model &model, Track &track, const TrackEngine *linkedTrackEngine) : + MidiCvTrackEngine(Engine &engine, Model &model, Track &track, const TrackEngine *linkedTrackEngine) : TrackEngine(engine, model, track, linkedTrackEngine), _midiCvTrack(track.midiCvTrack()), _arpeggiatorEngine(_midiCvTrack.arpeggiator()) diff --git a/src/apps/sequencer/engine/NoteTrackEngine.cpp b/src/apps/sequencer/engine/NoteTrackEngine.cpp index 8de68f90..7b3836c9 100644 --- a/src/apps/sequencer/engine/NoteTrackEngine.cpp +++ b/src/apps/sequencer/engine/NoteTrackEngine.cpp @@ -155,7 +155,7 @@ TrackEngine::TickResult NoteTrackEngine::tick(uint32_t tick) { reset(); _currentStageRepeat = 1; } - const auto &sequence = *_sequence; + auto &sequence = *_sequence; // advance sequence switch (_noteTrack.playMode()) { @@ -169,14 +169,18 @@ TrackEngine::TickResult NoteTrackEngine::tick(uint32_t tick) { } triggerStep(tick, divisor); - _sequenceState.calculateNextStepAligned( - (relativeTick + divisor) / divisor, - sequence.runMode(), - sequence.firstStep(), - sequence.lastStep(), - rng - ); - triggerStep(tick + divisor, divisor, true); + const auto &step = sequence.step(_sequenceState.step()); + if (step.gateOffset()<0) { + _sequenceState.calculateNextStepAligned( + (relativeTick + divisor) / divisor, + sequence.runMode(), + sequence.firstStep(), + sequence.lastStep(), + rng + ); + + triggerStep(tick + divisor, divisor, true); + } } break; case Types::PlayMode::Free: @@ -188,13 +192,26 @@ TrackEngine::TickResult NoteTrackEngine::tick(uint32_t tick) { if (_currentStageRepeat == 1) { _sequenceState.advanceFree(sequence.runMode(), sequence.firstStep(), sequence.lastStep(), rng); + _sequenceState.calculateNextStepFree( + sequence.runMode(), sequence.firstStep(), sequence.lastStep(), rng); } recordStep(tick, divisor); const auto &step = sequence.step(_sequenceState.step()); bool isLastStageStep = ((int) (step.stageRepeats()+1) - (int) _currentStageRepeat) <= 0; - triggerStep(tick+divisor, divisor); + if (step.gateOffset() >= 0) { + triggerStep(tick, divisor); + } + + if (!isLastStageStep && step.gateOffset() < 0) { + triggerStep(tick + divisor, divisor, false); + } + + if (isLastStageStep + && sequence.step(_sequenceState.nextStep()).gateOffset() < 0) { + triggerStep(tick + divisor, divisor, true); + } if (isLastStageStep) { _currentStageRepeat = 1; @@ -329,6 +346,11 @@ void NoteTrackEngine::monitorMidi(uint32_t tick, const MidiMessage &message) { if (_engine.recording() && _model.project().recordMode() == Types::RecordMode::StepRecord) { _stepRecorder.process(message, *_sequence, [this] (int midiNote) { return noteFromMidiNote(midiNote); }); + if (Routing::isRouted(Routing::Target::CurrentRecordStep, _model.project().selectedTrackIndex())) { + _sequence->setCurrentRecordStep(_stepRecorder.stepIndex(), true); + } else { + _sequence->setCurrentRecordStep(_stepRecorder.stepIndex(), false); + } } } @@ -380,28 +402,27 @@ void NoteTrackEngine::triggerStep(uint32_t tick, uint32_t divisor, bool forNextS stepGate = evalStepCondition(step, _sequenceState.iteration(), useFillCondition, _prevCondition); } switch (step.stageRepeatMode()) { - case NoteSequence::StageRepeatMode::Each: + case Types::StageRepeatMode::Each: break; - case NoteSequence::StageRepeatMode::First: + case Types::StageRepeatMode::First: stepGate = stepGate && _currentStageRepeat == 1; break; - case NoteSequence::StageRepeatMode::Last: + case Types::StageRepeatMode::Last: stepGate = stepGate && _currentStageRepeat == step.stageRepeats()+1; break; - case NoteSequence::StageRepeatMode::Middle: + case Types::StageRepeatMode::Middle: stepGate = stepGate && _currentStageRepeat == (step.stageRepeats()+1)/2; break; - case NoteSequence::StageRepeatMode::Odd: + case Types::StageRepeatMode::Odd: stepGate = stepGate && _currentStageRepeat % 2 != 0; break; - case NoteSequence::StageRepeatMode::Even: + case Types::StageRepeatMode::Even: stepGate = stepGate && _currentStageRepeat % 2 == 0; break; - case NoteSequence::StageRepeatMode::Triplets: + case Types::StageRepeatMode::Triplets: stepGate = stepGate && (_currentStageRepeat - 1) % 3 == 0; break; - case NoteSequence::StageRepeatMode::Random: - srand((unsigned int)time(NULL)); + case Types::StageRepeatMode::Random: int rndMode = rng.nextRange(6); switch (rndMode) { case 0: diff --git a/src/apps/sequencer/engine/NoteTrackEngine.h b/src/apps/sequencer/engine/NoteTrackEngine.h index 27c572df..b82c9799 100644 --- a/src/apps/sequencer/engine/NoteTrackEngine.h +++ b/src/apps/sequencer/engine/NoteTrackEngine.h @@ -10,7 +10,7 @@ class NoteTrackEngine : public TrackEngine { public: - NoteTrackEngine(Engine &engine, const Model &model, Track &track, const TrackEngine *linkedTrackEngine) : + NoteTrackEngine(Engine &engine, Model &model, Track &track, const TrackEngine *linkedTrackEngine) : TrackEngine(engine, model, track, linkedTrackEngine), _noteTrack(track.noteTrack()) { @@ -43,11 +43,19 @@ class NoteTrackEngine : public TrackEngine { int currentStep() const { return _currentStep; } int currentRecordStep() const { return _stepRecorder.stepIndex(); } + void setCurrentRecordStep(int value) { + _stepRecorder.setStepIndex(value); + } void setMonitorStep(int index); Types::PlayMode playMode() const { return _noteTrack.playMode(); } + SequenceState sequenceState() { + return _sequenceState; + } + + private: void triggerStep(uint32_t tick, uint32_t divisor, bool nextStep); void triggerStep(uint32_t tick, uint32_t divisor); diff --git a/src/apps/sequencer/engine/SequenceState.h b/src/apps/sequencer/engine/SequenceState.h index 6647aabc..2baafdda 100644 --- a/src/apps/sequencer/engine/SequenceState.h +++ b/src/apps/sequencer/engine/SequenceState.h @@ -33,4 +33,6 @@ class SequenceState { int8_t _direction; uint32_t _iteration; uint32_t _nextIteration; + + bool _forNetStep = false; }; diff --git a/src/apps/sequencer/engine/StochasticEngine.cpp b/src/apps/sequencer/engine/StochasticEngine.cpp index c5eb6cf3..dd6a146e 100644 --- a/src/apps/sequencer/engine/StochasticEngine.cpp +++ b/src/apps/sequencer/engine/StochasticEngine.cpp @@ -365,7 +365,13 @@ void StochasticEngine::triggerStep(uint32_t tick, uint32_t divisor, bool forNext int octave = _stochasticTrack.octave(); int transpose = _stochasticTrack.transpose(); + bool fillStep = fill() && (rng.nextRange(100) < uint32_t(fillAmount())); + bool useFillGates = fillStep && _stochasticTrack.fillMode() == StochasticTrack::FillMode::Gates; + bool useFillSequence = fillStep && _stochasticTrack.fillMode() == StochasticTrack::FillMode::NextPattern; + bool useFillCondition = fillStep && _stochasticTrack.fillMode() == StochasticTrack::FillMode::Condition; + auto &sequence = *_sequence; + const auto &evalSequence = useFillSequence ? *_fillSequence : *_sequence; int stepIndex; @@ -407,7 +413,7 @@ void StochasticEngine::triggerStep(uint32_t tick, uint32_t divisor, bool forNext // fill in memory step when sequence is running or when the in memory loop is not full filled if (!sequence.useLoop() || int(_lockedSteps.size()) < sequence.bufferLoopLength()) { - if (_skips != 0 && _index > 0) { + if (_skips != 0 && _index > 0 && !useFillGates) { --_skips; if (int(_lockedSteps.size()) < sequence.bufferLoopLength()) { _lockedSteps.insert(_lockedSteps.end(), StochasticLoopStep(-1, false, step, 0, 0, 0)); @@ -435,15 +441,15 @@ void StochasticEngine::triggerStep(uint32_t tick, uint32_t divisor, bool forNext std::sort (std::begin(probability), std::end(probability), sortTaskByProbRev); stepIndex = getNextWeightedPitch(probability, probability.size()); - step = sequence.step(stepIndex); + step = evalSequence.step(stepIndex); _currentStep = stepIndex; int gateOffset = ((int) divisor * step.gateOffset()) / (StochasticSequence::GateOffset::Max + 1); stepTick = (int) tick + gateOffset; - stepGate = evalStepGate(step, _stochasticTrack.gateProbabilityBias());; + stepGate = evalStepGate(step, _stochasticTrack.gateProbabilityBias()) || useFillGates; if (stepGate) { - stepGate = evalStepCondition(step, _sequenceState.iteration(), false, _prevCondition); + stepGate = evalStepCondition(step, _sequenceState.iteration(), useFillCondition, _prevCondition); } const auto &scale = sequence.selectedScale(_model.project().scale()); int rootNote = sequence.selectedRootNote(_model.project().rootNote()); diff --git a/src/apps/sequencer/engine/StochasticEngine.h b/src/apps/sequencer/engine/StochasticEngine.h index 14d5b42e..f391a9be 100644 --- a/src/apps/sequencer/engine/StochasticEngine.h +++ b/src/apps/sequencer/engine/StochasticEngine.h @@ -81,7 +81,7 @@ class StochasticLoopStep { class StochasticEngine : public TrackEngine { public: - StochasticEngine(Engine &engine, const Model &model, Track &track, const TrackEngine *linkedTrackEngine) : + StochasticEngine(Engine &engine, Model &model, Track &track, const TrackEngine *linkedTrackEngine) : TrackEngine(engine, model, track, linkedTrackEngine), _stochasticTrack(track.stochasticTrack()) { @@ -138,7 +138,7 @@ class StochasticEngine : public TrackEngine { int noteFromMidiNote(uint8_t midiNote) const; bool fill() const { - return false; + return (_stochasticTrack.fillMuted() || !TrackEngine::mute()) ? TrackEngine::fill() : false; } std::vector slicing(std::vector &arr, int X, int Y) diff --git a/src/apps/sequencer/engine/TrackEngine.h b/src/apps/sequencer/engine/TrackEngine.h index 88c63ab6..90810a34 100644 --- a/src/apps/sequencer/engine/TrackEngine.h +++ b/src/apps/sequencer/engine/TrackEngine.h @@ -36,7 +36,7 @@ class TrackEngine { GateUpdate = (1<<1), }; - TrackEngine(Engine &engine, const Model &model, Track &track, const TrackEngine *linkedTrackEngine) : + TrackEngine(Engine &engine, Model &model, Track &track, const TrackEngine *linkedTrackEngine) : _engine(engine), _model(model), _track(track), @@ -101,7 +101,7 @@ class TrackEngine { protected: Engine &_engine; - const Model &_model; + Model &_model; Track &_track; const PlayState::TrackState &_trackState; const TrackEngine *_linkedTrackEngine; diff --git a/src/apps/sequencer/engine/generators/SequenceBuilder.h b/src/apps/sequencer/engine/generators/SequenceBuilder.h index 239d602a..05cc82cd 100644 --- a/src/apps/sequencer/engine/generators/SequenceBuilder.h +++ b/src/apps/sequencer/engine/generators/SequenceBuilder.h @@ -3,6 +3,7 @@ #include "model/NoteSequence.h" #include "model/CurveSequence.h" #include "model/StochasticSequence.h" +#include "model/LogicSequence.h" class SequenceBuilder { public: @@ -95,3 +96,4 @@ class SequenceBuilderImpl : public SequenceBuilder { typedef SequenceBuilderImpl NoteSequenceBuilder; typedef SequenceBuilderImpl CurveSequenceBuilder; typedef SequenceBuilderImpl StochasticSequenceBuilder; +typedef SequenceBuilderImpl LogicSequenceBuilder; diff --git a/src/apps/sequencer/model/ClipBoard.cpp b/src/apps/sequencer/model/ClipBoard.cpp index 0c8fa617..8f55d110 100644 --- a/src/apps/sequencer/model/ClipBoard.cpp +++ b/src/apps/sequencer/model/ClipBoard.cpp @@ -63,6 +63,18 @@ void ClipBoard::copyStochasticSequenceSteps(const StochasticSequence &sequence, stochasticSequenceSteps.selected = selectedSteps; } +void ClipBoard::copyLogicSequence(const LogicSequence &sequence) { + _type = Type::LogicSequence; + _container.as() = sequence; +} + +void ClipBoard::copyLogicSequenceSteps(const LogicSequence &sequence, const SelectedSteps &selectedSteps) { + _type = Type::LogicSequenceSteps; + auto &logicSequenceSteps = _container.as(); + logicSequenceSteps.sequence = sequence; + logicSequenceSteps.selected = selectedSteps; +} + void ClipBoard::copyPattern(int patternIndex) { _type = Type::Pattern; auto &pattern = _container.as(); @@ -76,6 +88,12 @@ void ClipBoard::copyPattern(int patternIndex) { case Track::TrackMode::Curve: pattern.sequences[trackIndex].data.curve = track.curveTrack().sequence(patternIndex); break; + case Track::TrackMode::Stochastic: + pattern.sequences[trackIndex].data.stochastic = track.stochasticTrack().sequence(patternIndex); + break; + case Track::TrackMode::Logic: + pattern.sequences[trackIndex].data.logic = track.logicTrack().sequence(patternIndex); + break; default: break; } @@ -138,6 +156,20 @@ void ClipBoard::pasteStochasticSequenceSteps(StochasticSequence &sequence, const } } +void ClipBoard::pasteLogicSequence(LogicSequence &sequence) const { + if (canPasteLogicSequence()) { + Model::WriteLock lock; + sequence = _container.as(); + } +} + +void ClipBoard::pasteLogicSequenceSteps(LogicSequence &sequence, const SelectedSteps &selectedSteps) const { + if (canPasteLogicSequenceSteps()) { + const auto &logicSequenceSteps = _container.as(); + ModelUtils::copySteps(logicSequenceSteps.sequence.steps(), logicSequenceSteps.selected, sequence.steps(), selectedSteps); + } +} + void ClipBoard::pastePattern(int patternIndex) const { if (canPastePattern()) { Model::WriteLock lock; @@ -152,6 +184,12 @@ void ClipBoard::pastePattern(int patternIndex) const { case Track::TrackMode::Curve: track.curveTrack().sequence(patternIndex) = pattern.sequences[trackIndex].data.curve; break; + case Track::TrackMode::Stochastic: + track.stochasticTrack().sequence(patternIndex) = pattern.sequences[trackIndex].data.stochastic; + break; + case Track::TrackMode::Logic: + track.logicTrack().sequence(patternIndex) = pattern.sequences[trackIndex].data.logic; + break; default: break; } @@ -194,6 +232,14 @@ bool ClipBoard::canPasteStochasticSequenceSteps() const { return _type == Type::StochasticSequenceSteps; } +bool ClipBoard::canPasteLogicSequence() const { + return _type == Type::LogicSequence; +} + +bool ClipBoard::canPasteLogicSequenceSteps() const { + return _type == Type::LogicSequenceSteps; +} + bool ClipBoard::canPastePattern() const { return _type == Type::Pattern; } diff --git a/src/apps/sequencer/model/ClipBoard.h b/src/apps/sequencer/model/ClipBoard.h index cde646d5..eac71747 100644 --- a/src/apps/sequencer/model/ClipBoard.h +++ b/src/apps/sequencer/model/ClipBoard.h @@ -2,6 +2,7 @@ #include "Config.h" +#include "LogicSequence.h" #include "StochasticSequence.h" #include "Track.h" #include "NoteSequence.h" @@ -29,6 +30,8 @@ class ClipBoard { void copyCurveSequenceSteps(const CurveSequence &curveSequence, const SelectedSteps &selectedSteps); void copyStochasticSequence(const StochasticSequence ¬eSequence); void copyStochasticSequenceSteps(const StochasticSequence ¬eSequence, const SelectedSteps &selectedSteps); + void copyLogicSequence(const LogicSequence ¬eSequence); + void copyLogicSequenceSteps(const LogicSequence ¬eSequence, const SelectedSteps &selectedSteps); void copyPattern(int patternIndex); void copyUserScale(const UserScale &userScale); @@ -39,6 +42,8 @@ class ClipBoard { void pasteCurveSequenceSteps(CurveSequence &curveSequence, const SelectedSteps &selectedSteps) const; void pasteStochasticSequence(StochasticSequence ¬eSequence) const; void pasteStochasticSequenceSteps(StochasticSequence ¬eSequence, const SelectedSteps &selectedSteps) const; + void pasteLogicSequence(LogicSequence ¬eSequence) const; + void pasteLogicSequenceSteps(LogicSequence ¬eSequence, const SelectedSteps &selectedSteps) const; void pastePattern(int patternIndex) const; void pasteUserScale(UserScale &userScale) const; @@ -49,6 +54,8 @@ class ClipBoard { bool canPasteCurveSequenceSteps() const; bool canPasteStochasticSequence() const; bool canPasteStochasticSequenceSteps() const; + bool canPasteLogicSequence() const; + bool canPasteLogicSequenceSteps() const; bool canPastePattern() const; bool canPasteUserScale() const; @@ -62,6 +69,8 @@ class ClipBoard { CurveSequenceSteps, StochasticSequence, StochasticSequenceSteps, + LogicSequence, + LogicSequenceSteps, Pattern, UserScale, }; @@ -81,17 +90,24 @@ class ClipBoard { SelectedSteps selected; }; + struct LogicSequenceSteps { + LogicSequence sequence; + SelectedSteps selected; + }; + struct Pattern { struct { Track::TrackMode trackMode; union { NoteSequence note; CurveSequence curve; + StochasticSequence stochastic; + LogicSequence logic; } data; } sequences[CONFIG_TRACK_COUNT]; }; Project &_project; Type _type = Type::None; - Container _container; + Container _container; }; diff --git a/src/apps/sequencer/model/ClockSetup.cpp b/src/apps/sequencer/model/ClockSetup.cpp index 1553e41e..590854a4 100644 --- a/src/apps/sequencer/model/ClockSetup.cpp +++ b/src/apps/sequencer/model/ClockSetup.cpp @@ -15,6 +15,7 @@ void ClockSetup::clear() { _usbRx = false; _usbTx = false; _dirty = true; + _filterNote = false; } void ClockSetup::write(VersionedSerializedWriter &writer) const { @@ -30,6 +31,7 @@ void ClockSetup::write(VersionedSerializedWriter &writer) const { writer.write(_midiTx); writer.write(_usbRx); writer.write(_usbTx); + writer.write(_filterNote); } void ClockSetup::read(VersionedSerializedReader &reader) { @@ -45,4 +47,5 @@ void ClockSetup::read(VersionedSerializedReader &reader) { reader.read(_midiTx); reader.read(_usbRx); reader.read(_usbTx); + reader.read(_filterNote, ProjectVersion::Version37); } diff --git a/src/apps/sequencer/model/ClockSetup.h b/src/apps/sequencer/model/ClockSetup.h index ec2cb774..db2deb59 100644 --- a/src/apps/sequencer/model/ClockSetup.h +++ b/src/apps/sequencer/model/ClockSetup.h @@ -301,6 +301,23 @@ class ClockSetup { ModelUtils::printYesNo(str, usbTx()); } + // filterNote + + bool filterNote() const { return _filterNote; } + void setFilterNote(bool filterNote) { + if (filterNote != _filterNote) { + _filterNote = filterNote; + } + } + + void editFilterNote(int value, bool shift) { + setFilterNote(value > 0); + } + + void printFilterNote(StringBuilder &str) const { + ModelUtils::printYesNo(str, filterNote()); + } + //---------------------------------------- // Methods //---------------------------------------- @@ -327,4 +344,5 @@ class ClockSetup { bool _usbRx; bool _usbTx; bool _dirty; + bool _filterNote; }; diff --git a/src/apps/sequencer/model/Curve.cpp b/src/apps/sequencer/model/Curve.cpp index a2985180..faba8f16 100644 --- a/src/apps/sequencer/model/Curve.cpp +++ b/src/apps/sequencer/model/Curve.cpp @@ -41,11 +41,13 @@ static const float TwoPi = 2.f * Pi; {29, Curve::Bell}, {30, Curve::StepDown}, {31, Curve::StepUp}, - {32, Curve::ExpUp2x}, {33, Curve::ExpDown2x}, + {32, Curve::ExpUp2x}, + {35, Curve::ExpDown3x}, {34, Curve::ExpUp3x}, - {35, Curve::ExpUp4x}, - {36, Curve::ExpDown4x} + {37, Curve::ExpDown4x}, + {36, Curve::ExpUp4x}, + {38, Curve::Low}, }; static const std::map REV_SHAPE_MAP = { @@ -81,11 +83,13 @@ static const float TwoPi = 2.f * Pi; {29, Curve::RevBell}, {30, Curve::StepDown}, {31, Curve::StepUp}, - {32, Curve::ExpUp2x}, - {33, Curve::ExpDown2x}, - {34, Curve::ExpUp3x}, - {35, Curve::ExpUp4x}, - {36, Curve::ExpDown4x} + {32, Curve::ExpDown2x}, + {33, Curve::ExpUp2x}, + {34, Curve::ExpDown3x}, + {35, Curve::ExpUp3x}, + {36, Curve::ExpDown4x}, + {37, Curve::ExpUp4x}, + {38, Curve::Low}, }; static float low(float x) { @@ -240,6 +244,10 @@ static float doubleSmoothDownHalf(float x) { return x < 0.5f ? smoothDown(std::fmod(x * 2.f, 1.f)) : x < 1.f ? smoothDown(std::fmod(x * 2.f, 1.f)) : 0.f; } +static float trigger(float x) { + return x < 0.1f ? 1.f : x < 0.3f ? expDown(std::fmod(x * 2.f, 1.f)) : 0.f; +} + static Curve::Function functions[] = { &low, &high, @@ -278,7 +286,8 @@ static Curve::Function functions[] = { &expDown3x, &expUp3x, &expDown4x, - &expUp4x + &expUp4x, + &trigger }; Curve::Function Curve::function(Type type) { diff --git a/src/apps/sequencer/model/Curve.h b/src/apps/sequencer/model/Curve.h index fe027b6e..bf2364b0 100644 --- a/src/apps/sequencer/model/Curve.h +++ b/src/apps/sequencer/model/Curve.h @@ -38,12 +38,13 @@ class Curve { RevBell, StepUp, StepDown, - ExpDown2x, ExpUp2x, - ExpDown3x, + ExpDown2x, ExpUp3x, - ExpDown4x, + ExpDown3x, ExpUp4x, + ExpDown4x, + Trigger, Last, }; diff --git a/src/apps/sequencer/model/CurveTrack.cpp b/src/apps/sequencer/model/CurveTrack.cpp index fd056b29..a073a893 100644 --- a/src/apps/sequencer/model/CurveTrack.cpp +++ b/src/apps/sequencer/model/CurveTrack.cpp @@ -1,5 +1,6 @@ #include "CurveTrack.h" #include "ProjectVersion.h" +#include "Routing.h" void CurveTrack::writeRouted(Routing::Target target, int intValue, float floatValue) { switch (target) { @@ -18,6 +19,12 @@ void CurveTrack::writeRouted(Routing::Target target, int intValue, float floatVa case Routing::Target::GateProbabilityBias: setGateProbabilityBias(intValue, true); break; + case Routing::Target::CurveMin: + setMin(floatValue, true); + break; + case Routing::Target::CurveMax: + setMax(floatValue, true); + break; default: break; } @@ -33,6 +40,8 @@ void CurveTrack::clear() { setShapeProbabilityBias(0); setGateProbabilityBias(0); setCurveCvInput(Types::CurveCvInput::Off); + setMin(0); + setMax(CurveSequence::Max::max()); for (auto &sequence : _sequences) { sequence.clear(); @@ -51,6 +60,8 @@ void CurveTrack::write(VersionedSerializedWriter &writer) const { writer.write(_shapeProbabilityBias.base); writer.write(_gateProbabilityBias.base); writer.write(_curveCvInput); + writer.write(_min); + writer.write(_max); writeArray(writer, _sequences); } @@ -65,5 +76,7 @@ void CurveTrack::read(VersionedSerializedReader &reader) { reader.read(_shapeProbabilityBias.base, ProjectVersion::Version15); reader.read(_gateProbabilityBias.base, ProjectVersion::Version15); reader.read(_curveCvInput, ProjectVersion::Version36); + reader.read(_min, ProjectVersion::Version37); + reader.read(_max, ProjectVersion::Version37); readArray(reader, _sequences); } diff --git a/src/apps/sequencer/model/CurveTrack.h b/src/apps/sequencer/model/CurveTrack.h index fadbd8d2..1fa89ebb 100644 --- a/src/apps/sequencer/model/CurveTrack.h +++ b/src/apps/sequencer/model/CurveTrack.h @@ -9,6 +9,7 @@ #include "Routing.h" #include "FileDefs.h" #include "core/utils/StringUtils.h" +#include class CurveTrack : public BaseTrack, public BaseTrackPatternFollow { public: @@ -213,6 +214,44 @@ class CurveTrack : public BaseTrack, public BaseTrackPatternFollow { str(Types::curveCvInput(_curveCvInput)); } + // min + + float min() const { return _min.get(isRouted(Routing::Target::CurveMin)); } + void setMin(float min, bool routed = false) { + _min.set(CurveSequence::Min::clamp(min), routed); + _max.set(std::max(max(), this->min()), routed); + } + + void editMin(float value, bool shift) { + if (!isRouted(Routing::Target::CurveMin)) { + setMin(min() + value); + } + } + + void printMin(StringBuilder &str) const { + printRouted(str, Routing::Target::CurveMin); + str("%+.1f%%", (100*min())/CurveSequence::Min::Max); + } + + // max + + float max() const { return _max.get(isRouted(Routing::Target::CurveMax)); } + void setMax(float max, bool routed = false) { + _max.set(CurveSequence::Max::clamp(max), routed); + _min.set(std::min(min(), this->max()), routed); + } + + void editMax(float value, bool shift) { + if (!isRouted(Routing::Target::CurveMax)) { + setMax(max() + value); + } + } + + void printMax(StringBuilder &str) const { + printRouted(str, Routing::Target::CurveMax); + str("%+.1f%%", (100*max()/CurveSequence::Max::Max)); + } + // sequences const CurveSequenceArray &sequences() const { return _sequences; } @@ -262,7 +301,10 @@ class CurveTrack : public BaseTrack, public BaseTrackPatternFollow { Routable _shapeProbabilityBias; Routable _gateProbabilityBias; - Types::CurveCvInput _curveCvInput; + Routable _min; + Routable _max; + + Types::CurveCvInput _curveCvInput; CurveSequenceArray _sequences; diff --git a/src/apps/sequencer/model/FileDefs.h b/src/apps/sequencer/model/FileDefs.h index b6fc8738..255b2af5 100644 --- a/src/apps/sequencer/model/FileDefs.h +++ b/src/apps/sequencer/model/FileDefs.h @@ -11,6 +11,7 @@ enum class FileType : uint8_t { UserScale = 1, NoteSequence= 2, CurveSequence=3, + LogicSequence=4, Settings = 255 }; diff --git a/src/apps/sequencer/model/FileManager.cpp b/src/apps/sequencer/model/FileManager.cpp index af9e63e5..4c9bd1ff 100644 --- a/src/apps/sequencer/model/FileManager.cpp +++ b/src/apps/sequencer/model/FileManager.cpp @@ -32,7 +32,8 @@ FileTypeInfo fileTypeInfos[] = { { "PROJECTS", "PRO" }, { "SCALES", "SCA" }, {"SEQS", "NSQ"}, - {"SEQS", "CSQ"} + {"SEQS", "CSQ"}, + {"SEQS", "LSQ"} }; static void slotPath(StringBuilder &str, FileType type, int slot) { @@ -133,6 +134,18 @@ fs::Error FileManager::readCurveSequence(CurveSequence &curveSequence, int slot) }); } +fs::Error FileManager::writeLogicSequence(const LogicSequence ¬eSequence, int slot) { + return writeFile(FileType::LogicSequence, slot, [&] (const char *path) { + return writeLogicSequence(noteSequence, path); + }); +} + +fs::Error FileManager::readLogicSequence(LogicSequence ¬eSequence, int slot) { + return readFile(FileType::LogicSequence, slot, [&] (const char *path) { + return readLogicSequence(noteSequence, path); + }); +} + fs::Error FileManager::writeProject(const Project &project, const char *path) { fs::FileWriter fileWriter(path); if (fileWriter.error() != fs::OK) { @@ -305,6 +318,49 @@ fs::Error FileManager::readCurveSequence(CurveSequence &curveSequence, const cha return error; } +fs::Error FileManager::writeLogicSequence(const LogicSequence ¬eSequence, const char *path) { + fs::FileWriter fileWriter(path); + if (fileWriter.error() != fs::OK) { + return fileWriter.error(); + } + + FileHeader header(FileType::LogicSequence, 0, noteSequence.name()); + fileWriter.write(&header, sizeof(header)); + + VersionedSerializedWriter writer( + [&fileWriter] (const void *data, size_t len) { fileWriter.write(data, len); }, + ProjectVersion::Latest + ); + + noteSequence.write(writer); + + return fileWriter.finish(); +} + +fs::Error FileManager::readLogicSequence(LogicSequence ¬eSequence, const char *path) { + fs::FileReader fileReader(path); + if (fileReader.error() != fs::OK) { + return fileReader.error(); + } + + FileHeader header; + fileReader.read(&header, sizeof(header)); + + VersionedSerializedReader reader( + [&fileReader] (void *data, size_t len) { fileReader.read(data, len); }, + ProjectVersion::Latest + ); + + bool success = noteSequence.read(reader); + + auto error = fileReader.finish(); + if (error == fs::OK && !success) { + error = fs::INVALID_CHECKSUM; + } + + return error; +} + fs::Error FileManager::writeSettings(const Settings &settings, const char *path) { fs::FileWriter fileWriter(path); if (fileWriter.error() != fs::OK) { diff --git a/src/apps/sequencer/model/FileManager.h b/src/apps/sequencer/model/FileManager.h index d7956623..09abca5e 100644 --- a/src/apps/sequencer/model/FileManager.h +++ b/src/apps/sequencer/model/FileManager.h @@ -33,6 +33,8 @@ class FileManager { static fs::Error readNoteSequence(NoteSequence ¬eSequence, int slot); static fs::Error writeCurveSequence(const CurveSequence &curveSequence, int slot); static fs::Error readCurveSequence(CurveSequence &curveSequence, int slot); + static fs::Error writeLogicSequence(const LogicSequence &logicSequence, int slot); + static fs::Error readLogicSequence(LogicSequence &logicSequence, int slot); static fs::Error writeProject(const Project &project, const char *path); static fs::Error readProject(Project &project, const char *path); @@ -44,6 +46,8 @@ class FileManager { static fs::Error readNoteSequence(NoteSequence ¬eSequence, const char *path); static fs::Error writeCurveSequence(const CurveSequence &curveSequence, const char *path); static fs::Error readCurveSequence(CurveSequence &curveSequence, const char *path); + static fs::Error writeLogicSequence(const LogicSequence &logicSequence, const char *path); + static fs::Error readLogicSequence(LogicSequence &logicSequence, const char *path); static fs::Error writeSettings(const Settings &settings, const char *path); static fs::Error readSettings(Settings &settings, const char *path); diff --git a/src/apps/sequencer/model/LogicSequence.cpp b/src/apps/sequencer/model/LogicSequence.cpp new file mode 100644 index 00000000..768dbc40 --- /dev/null +++ b/src/apps/sequencer/model/LogicSequence.cpp @@ -0,0 +1,350 @@ +#include "LogicSequence.h" +#include "ProjectVersion.h" + +#include "ModelUtils.h" + +Types::LayerRange LogicSequence::layerRange(Layer layer) { + #define CASE(_layer_) \ + case Layer::_layer_: \ + return { _layer_::Min, _layer_::Max }; + + switch (layer) { + case Layer::Gate: + return { 0, 1 }; + case Layer::Slide: + return { 0, 1 }; + CASE(GateOffset) + CASE(GateProbability) + CASE(Retrigger) + CASE(RetriggerProbability) + CASE(Length) + CASE(LengthVariationRange) + CASE(LengthVariationProbability) + CASE(GateLogic) + CASE(NoteLogic) + CASE(NoteVariationRange) + CASE(NoteVariationProbability) + CASE(Condition) + CASE(StageRepeats) + CASE(StageRepeatsMode) + case Layer::Last: + break; + } + + #undef CASE + + return { 0, 0 }; +} + +int LogicSequence::layerDefaultValue(Layer layer) +{ + LogicSequence::Step step; + + switch (layer) { + case Layer::Gate: + return step.gate(); + case Layer::GateLogic: + return step.gateLogic(); + case Layer::GateProbability: + return step.gateProbability(); + case Layer::GateOffset: + return step.gateOffset(); + case Layer::Slide: + return step.slide(); + case Layer::Retrigger: + return step.retrigger(); + case Layer::RetriggerProbability: + return step.retriggerProbability(); + case Layer::Length: + return step.length(); + case Layer::LengthVariationRange: + return step.lengthVariationRange(); + case Layer::LengthVariationProbability: + return step.lengthVariationProbability(); + case Layer::NoteLogic: + return step.noteLogic(); + case Layer::NoteVariationRange: + return step.noteVariationRange(); + case Layer::NoteVariationProbability: + return step.noteVariationProbability(); + case Layer::Condition: + return int(step.condition()); + case Layer::StageRepeats: + return step.stageRepeats(); + case Layer::StageRepeatsMode: + return step.stageRepeatMode(); + case Layer::Last: + break; + } + + return 0; +} + +int LogicSequence::Step::layerValue(Layer layer) const { + switch (layer) { + case Layer::Gate: + return gate() ? 1 : 0; + case Layer::GateLogic: + return gateLogic(); + case Layer::Slide: + return slide() ? 1 : 0; + case Layer::GateProbability: + return gateProbability(); + case Layer::GateOffset: + return gateOffset(); + case Layer::Retrigger: + return retrigger(); + case Layer::RetriggerProbability: + return retriggerProbability(); + case Layer::Length: + return length(); + case Layer::LengthVariationRange: + return lengthVariationRange(); + case Layer::LengthVariationProbability: + return lengthVariationProbability(); + case Layer::NoteLogic: + return noteLogic(); + case Layer::NoteVariationRange: + return noteVariationRange(); + case Layer::NoteVariationProbability: + return noteVariationProbability(); + case Layer::Condition: + return int(condition()); + case Layer::StageRepeats: + return stageRepeats(); + case Layer::StageRepeatsMode: + return stageRepeatMode(); + case Layer::Last: + break; + } + + return 0; +} + +void LogicSequence::Step::setLayerValue(Layer layer, int value) { + switch (layer) { + case Layer::Gate: + setGate(value); + break; + case Layer::Slide: + setSlide(value); + break; + case Layer::GateProbability: + setGateProbability(value); + break; + case Layer::GateOffset: + setGateOffset(value); + break; + case Layer::Retrigger: + setRetrigger(value); + break; + case Layer::RetriggerProbability: + setRetriggerProbability(value); + break; + case Layer::Length: + setLength(value); + break; + case Layer::LengthVariationRange: + setLengthVariationRange(value); + break; + case Layer::LengthVariationProbability: + setLengthVariationProbability(value); + break; + case Layer::GateLogic: + setGateLogic(static_cast(value)); + break; + case Layer::NoteLogic: + setNoteLogic(static_cast(value)); + break; + case Layer::NoteVariationRange: + setNoteVariationRange(value); + break; + case Layer::NoteVariationProbability: + setNoteVariationProbability(value); + break; + case Layer::Condition: + setCondition(Types::Condition(value)); + break; + case Layer::StageRepeats: + setStageRepeats(value); + break; + case Layer::StageRepeatsMode: + setStageRepeatsMode(static_cast(value)); + break; + case Layer::Last: + break; + } +} + +void LogicSequence::Step::clear() { + _data0.raw = 0; + _data1.raw = 1; + setGate(false); + setGateProbability(GateProbability::Max); + setGateOffset(0); + setGateLogic(GateLogicMode::One); + setSlide(false); + setRetrigger(0); + setRetriggerProbability(RetriggerProbability::Max); + setLength(Length::Max / 2); + setLengthVariationRange(0); + setLengthVariationProbability(LengthVariationProbability::Max); + setNoteLogic(NoteLogicMode::NOne); + setNoteVariationRange(0); + setNoteVariationProbability(NoteVariationProbability::Max); + setCondition(Types::Condition::Off); + setStageRepeats(0); + setStageRepeatsMode(Types::StageRepeatMode::Each); +} + +void LogicSequence::Step::write(VersionedSerializedWriter &writer) const { + writer.write(_data0.raw); + writer.write(_data1.raw); +} + +void LogicSequence::Step::read(VersionedSerializedReader &reader) { + reader.read(_data0.raw, ProjectVersion::Version37); + reader.read(_data1.raw, ProjectVersion::Version37); +} + +void LogicSequence::writeRouted(Routing::Target target, int intValue, float floatValue) { + switch (target) { + case Routing::Target::Scale: + //_model._selectedScale[0] = intValue; + setScale(intValue, true); + break; + case Routing::Target::RootNote: + setRootNote(intValue, true); + break; + case Routing::Target::Divisor: + setDivisor(intValue, true); + break; + case Routing::Target::RunMode: + setRunMode(Types::RunMode(intValue), true); + break; + case Routing::Target::FirstStep: + setFirstStep(intValue, true); + break; + case Routing::Target::LastStep: + setLastStep(intValue, true); + break; + default: + break; + } +} + +void LogicSequence::clear() { + setName("INIT"); + setScale(-1); + setRootNote(-1); + setDivisor(12); + setResetMeasure(0); + setRunMode(Types::RunMode::Forward); + setFirstStep(0); + setLastStep(15); + + clearSteps(); +} + +void LogicSequence::clearSteps() { + for (auto &step : _steps) { + step.clear(); + } +} + +void LogicSequence::clearStepsSelected(const std::bitset &selected) { + if (selected.any()) { + for (size_t i = 0; i < CONFIG_STEP_COUNT; ++i) { + if (selected[i]) { + _steps[i].clear(); + } + } + } else { + clearSteps(); + } +} + +bool LogicSequence::isEdited() const { + auto clearStep = Step(); + for (const auto &step : _steps) { + if (step != clearStep) { + return true; + } + } + return false; +} + +void LogicSequence::setGates(std::initializer_list gates) { + size_t step = 0; + for (auto gate : gates) { + if (step < _steps.size()) { + _steps[step++].setGate(gate); + } + } +} + +void LogicSequence::setNotes(std::initializer_list notes) { + size_t step = 0; + for (auto note : notes) { + if (step < _steps.size()) { + _steps[step++].setNote(note); + } + } +} + +void LogicSequence::shiftSteps(const std::bitset &selected, int direction) { + if (selected.any()) { + ModelUtils::shiftSteps(_steps, selected, firstStep(), lastStep(), direction); + } else { + ModelUtils::shiftSteps(_steps, firstStep(), lastStep(), direction); + } +} + +void LogicSequence::duplicateSteps() { + ModelUtils::duplicateSteps(_steps, firstStep(), lastStep()); + setLastStep(lastStep() + (lastStep() - firstStep() + 1)); +} + +void LogicSequence::write(VersionedSerializedWriter &writer) const { + writer.write(_scale.base); + writer.write(_rootNote.base); + writer.write(_divisor.base); + writer.write(_resetMeasure); + writer.write(_runMode.base); + writer.write(_firstStep.base); + writer.write(_lastStep.base); + + writeArray(writer, _steps); + writer.write(_name, NameLength + 1); + writer.write(_slot); + writer.writeHash(); +} + +bool LogicSequence::read(VersionedSerializedReader &reader) { + reader.read(_scale.base); + reader.read(_rootNote.base); + if (reader.dataVersion() < ProjectVersion::Version10) { + reader.readAs(_divisor.base); + } else { + reader.read(_divisor.base); + } + reader.read(_resetMeasure); + reader.read(_runMode.base); + reader.read(_firstStep.base); + reader.read(_lastStep.base); + + readArray(reader, _steps); + + if (reader.dataVersion() >= ProjectVersion::Version35) { + reader.read(_name, NameLength + 1, ProjectVersion::Version35); + reader.read(_slot); + bool success = reader.checkHash(); + if (!success) { + clear(); + } + + return success; + } else { + return true; + } +} diff --git a/src/apps/sequencer/model/LogicSequence.h b/src/apps/sequencer/model/LogicSequence.h new file mode 100644 index 00000000..dfcae1c7 --- /dev/null +++ b/src/apps/sequencer/model/LogicSequence.h @@ -0,0 +1,623 @@ +#pragma once + +#include "Config.h" +#include "Bitfield.h" +#include "Serialize.h" +#include "ModelUtils.h" +#include "Types.h" +#include "Scale.h" +#include "Routing.h" +#include "FileDefs.h" + +#include "core/math/Math.h" +#include "core/utils/StringBuilder.h" +#include "core/utils/StringUtils.h" + +#include +#include +#include +#include +#include +#include + +class LogicSequence { +public: + //---------------------------------------- + // Types + //---------------------------------------- + + typedef UnsignedValue<4> GateProbability; + typedef SignedValue<4> GateOffset; + typedef UnsignedValue<3> Retrigger; + typedef UnsignedValue<4> RetriggerProbability; + typedef UnsignedValue<4> Length; + typedef SignedValue<4> LengthVariationRange; + typedef UnsignedValue<4> LengthVariationProbability; + typedef SignedValue<3> GateLogic; + typedef SignedValue<7> Note; + typedef SignedValue<3> NoteLogic; + typedef SignedValue<7> NoteVariationRange; + typedef UnsignedValue<4> NoteVariationProbability; + typedef UnsignedValue<7> Condition; + typedef UnsignedValue<3> StageRepeats; + typedef UnsignedValue<3> StageRepeatsMode; + + static_assert(int(Types::Condition::Last) <= Condition::Max + 1, "Condition enum does not fit"); + + enum class Layer { + Gate, + GateLogic, + GateProbability, + GateOffset, + Retrigger, + RetriggerProbability, + StageRepeats, + StageRepeatsMode, + Length, + LengthVariationRange, + LengthVariationProbability, + NoteLogic, + NoteVariationRange, + NoteVariationProbability, + Slide, + Condition, + Last + }; + + static const char *layerName(Layer layer) { + switch (layer) { + case Layer::Gate: return "GATE"; + case Layer::GateLogic: return "GATE LOGIC"; + case Layer::GateProbability: return "GATE PROB"; + case Layer::GateOffset: return "GATE OFFSET"; + + case Layer::Retrigger: return "RETRIG"; + case Layer::RetriggerProbability: return "RETRIG PROB"; + + case Layer::Length: return "LENGTH"; + case Layer::LengthVariationRange: return "LENGTH RANGE"; + case Layer::LengthVariationProbability: return "LENGTH PROB"; + + case Layer::NoteLogic: return "NOTE LOGIC"; + case Layer::NoteVariationProbability: return "NOTE PROB"; + case Layer::NoteVariationRange: return "NOTE RANGE"; + case Layer::Slide: return "SLIDE"; + + case Layer::Condition: return "CONDITION"; + + case Layer::StageRepeats: return "REPEAT"; + case Layer::StageRepeatsMode: return "REPEAT MODE"; + case Layer::Last: break; + } + return nullptr; + } + + static Types::LayerRange layerRange(Layer layer); + static int layerDefaultValue(Layer layer); + + + enum GateLogicMode { + One, + Two, + And, + Or, + Xor, + Nand, + RandomInput, + RandomLogic + }; + + enum NoteLogicMode { + NOne, + NTwo, + Min, + Max, + Sum, + Avg, + NRandomInput, + NRandomLogic + }; + + static constexpr size_t NameLength = FileHeader::NameLength; + + + class Step { + + public: + //---------------------------------------- + // Properties + //---------------------------------------- + + // stage + void setStageRepeats(int repeats) { + _data1.stageRepeats = StageRepeats::clamp(repeats); + } + unsigned int stageRepeats() const { return _data1.stageRepeats; } + + void setStageRepeatsMode(Types::StageRepeatMode mode) { + _data1.stageRepeatMode = mode; + } + + Types::StageRepeatMode stageRepeatMode() const { + int value = _data1.stageRepeatMode; + return static_cast(value); + } + + // gate + + bool gate() const { return _data0.gate ? true : false; } + void setGate(bool gate) { _data0.gate = gate; } + void toggleGate() { setGate(!gate()); } + + // gateProbability + + int gateProbability() const { return _data1.gateProbability; } + void setGateProbability(int gateProbability) { + _data1.gateProbability = GateProbability::clamp(gateProbability); + } + + // gateOffset + + int gateOffset() const { return GateOffset::Min + (int) _data1.gateOffset; } + void setGateOffset(int gateOffset) { + _data1.gateOffset = GateOffset::clamp(gateOffset) - GateOffset::Min; + } + + // gateLogic + + GateLogicMode gateLogic() const { + int value = _data0.gateLogic; + return static_cast(value); + } + void setGateLogic(GateLogicMode gateLogic) { + _data0.gateLogic = gateLogic; + } + + // noteLogic + NoteLogicMode noteLogic() const { + int value = _data0.noteLogic; + return static_cast(value); + } + void setNoteLogic(NoteLogicMode noteLogic) { + _data0.noteLogic = noteLogic; + } + + + // slide + + bool slide() const { return _data0.slide ? true : false; } + void setSlide(bool slide) { + _data0.slide = slide; + } + void toggleSlide() { + setSlide(!slide()); + } + + // retrigger + + int retrigger() const { return _data1.retrigger; } + void setRetrigger(int retrigger) { + _data1.retrigger = Retrigger::clamp(retrigger); + } + + // retriggerProbability + + int retriggerProbability() const { return _data1.retriggerProbability; } + void setRetriggerProbability(int retriggerProbability) { + _data1.retriggerProbability = RetriggerProbability::clamp(retriggerProbability); + } + + // length + + int length() const { return _data0.length; } + void setLength(int length) { + _data0.length = Length::clamp(length); + } + + // lengthVariationRange + + int lengthVariationRange() const { return LengthVariationRange::Min + _data0.lengthVariationRange; } + void setLengthVariationRange(int lengthVariationRange) { + _data0.lengthVariationRange = LengthVariationRange::clamp(lengthVariationRange) - LengthVariationRange::Min; + } + + // lengthVariationProbability + + int lengthVariationProbability() const { return _data0.lengthVariationProbability; } + void setLengthVariationProbability(int lengthVariationProbability) { + _data0.lengthVariationProbability = LengthVariationProbability::clamp(lengthVariationProbability); + } + + // note + + int note() const { return 0; } + void setNote(int note) { + // + } + + // noteVariationRange + + int noteVariationRange() const { return NoteVariationRange::Min + _data0.noteVariationRange; } + void setNoteVariationRange(int noteVariationRange) { + _data0.noteVariationRange = NoteVariationRange::clamp(noteVariationRange) - NoteVariationRange::Min; + } + + // noteVariationProbability + + int noteVariationProbability() const { return _data0.noteVariationProbability; } + void setNoteVariationProbability(int noteVariationProbability) { + _data0.noteVariationProbability = NoteVariationProbability::clamp(noteVariationProbability); + } + + // condition + + Types::Condition condition() const { return Types::Condition(int(_data1.condition)); } + void setCondition(Types::Condition condition) { + _data1.condition = int(ModelUtils::clampedEnum(condition)); + } + + int layerValue(Layer layer) const; + void setLayerValue(Layer layer, int value); + + //---------------------------------------- + // Methods + //---------------------------------------- + + Step() { clear(); } + + void clear(); + + void write(VersionedSerializedWriter &writer) const; + void read(VersionedSerializedReader &reader); + + bool operator==(const Step &other) const { + return _data0.raw == other._data0.raw && _data1.raw == other._data1.raw; + } + + bool operator!=(const Step &other) const { + return !(*this == other); + } + + private: + union { + uint32_t raw; + BitField gate; + BitField slide; + BitField length; + BitField lengthVariationRange; + BitField lengthVariationProbability; + BitField gateLogic; + BitField noteVariationRange; + BitField noteVariationProbability; + BitField noteLogic; + + } _data0; + union { + uint32_t raw; + BitField retrigger; + BitField gateProbability; + BitField retriggerProbability; + BitField gateOffset; + BitField condition; + BitField stageRepeats; + BitField stageRepeatMode; + } _data1; + + }; + + typedef std::array StepArray; + + + + //---------------------------------------- + // Properties + //---------------------------------------- + + // slot + + int slot() const { return _slot; } + void setSlot(int slot) { + _slot = slot; + } + bool slotAssigned() const { + return _slot != uint8_t(-1); + } + + // name + + const char *name() const { return _name; } + void setName(const char *name) { + StringUtils::copy(_name, name, sizeof(_name)); + } + + // trackIndex + + int trackIndex() const { return _trackIndex; } + + // scale + + int scale() const { return _scale.get(isRouted(Routing::Target::Scale)); } + void setScale(int s, bool routed = false, int defaultScale = 0) { + + auto &pScale = selectedScale(defaultScale); + + _scale.set(clamp(s, -1, Scale::Count - 1), routed); + + auto &aScale = selectedScale(s); + + if (pScale == aScale) { + return; + } + + if (s != -1 && aScale.isChromatic() && pScale.isChromatic() > 0) { + for (int i = 0; i < 64; ++i) { + + auto pStep = _steps[i]; + + int rN = pScale.noteIndex(pStep.note(), selectedRootNote(0)); + if (rN > 0) { + if (aScale.isNotePresent(rN)) { + int pNoteIndex = aScale.getNoteIndex(rN); + step(i).setNote(pNoteIndex); + } else { + // search nearest note + while (!aScale.isNotePresent(rN)) { + rN--; + } + int pNoteIndex = aScale.getNoteIndex(rN); + step(i).setNote(pNoteIndex); + } + } + + + } + } + + } + + int indexedScale() const { return scale() + 1; } + void setIndexedScale(int index) { + setScale(index - 1); + } + + void editScale(int value, bool shift, int defaultScale = 0) { + if (!isRouted(Routing::Target::Scale)) { + setScale(value, false, defaultScale); + } + } + + void printScale(StringBuilder &str) const { + printRouted(str, Routing::Target::Scale); + str(scale() < 0 ? "Default" : Scale::name(scale())); + } + + const Scale &selectedScale(int defaultScale) const { + return Scale::get(scale() < 0 ? defaultScale : scale()); + } + + // rootNote + + int rootNote() const { return _rootNote.get(isRouted(Routing::Target::RootNote)); } + void setRootNote(int rootNote, bool routed = false) { + _rootNote.set(clamp(rootNote, -1, 11), routed); + } + + int indexedRootNote() const { return rootNote() + 1; } + void setIndexedRootNote(int index) { + setRootNote(index - 1); + } + + void editRootNote(int value, bool shift) { + if (!isRouted(Routing::Target::RootNote)) { + setRootNote(rootNote() + value); + } + } + + void printRootNote(StringBuilder &str) const { + printRouted(str, Routing::Target::RootNote); + if (rootNote() < 0) { + str("Default"); + } else { + Types::printNote(str, rootNote()); + } + } + + int selectedRootNote(int defaultRootNote) const { + return rootNote() < 0 ? defaultRootNote : rootNote(); + } + + // divisor + + int divisor() const { return _divisor.get(isRouted(Routing::Target::Divisor)); } + void setDivisor(int divisor, bool routed = false) { + _divisor.set(ModelUtils::clampDivisor(divisor), routed); + } + + int indexedDivisor() const { return ModelUtils::divisorToIndex(divisor()); } + void setIndexedDivisor(int index) { + int divisor = ModelUtils::indexToDivisor(index); + if (divisor > 0) { + setDivisor(divisor); + } + } + + void editDivisor(int value, bool shift) { + if (!isRouted(Routing::Target::Divisor)) { + setDivisor(ModelUtils::adjustedByDivisor(divisor(), value, shift)); + } + } + + void printDivisor(StringBuilder &str) const { + printRouted(str, Routing::Target::Divisor); + ModelUtils::printDivisor(str, divisor()); + } + + // resetMeasure + + int resetMeasure() const { return _resetMeasure; } + void setResetMeasure(int resetMeasure) { + _resetMeasure = clamp(resetMeasure, 0, 128); + } + + void editResetMeasure(int value, bool shift) { + setResetMeasure(ModelUtils::adjustedByPowerOfTwo(resetMeasure(), value, shift)); + } + + void printResetMeasure(StringBuilder &str) const { + if (resetMeasure() == 0) { + str("off"); + } else { + str("%d %s", resetMeasure(), resetMeasure() > 1 ? "bars" : "bar"); + } + } + + // runMode + + Types::RunMode runMode() const { return _runMode.get(isRouted(Routing::Target::RunMode)); } + void setRunMode(Types::RunMode runMode, bool routed = false) { + _runMode.set(ModelUtils::clampedEnum(runMode), routed); + } + + void editRunMode(int value, bool shift) { + if (!isRouted(Routing::Target::RunMode)) { + setRunMode(ModelUtils::adjustedEnum(runMode(), value)); + } + } + + void printRunMode(StringBuilder &str) const { + printRouted(str, Routing::Target::RunMode); + str(Types::runModeName(runMode())); + } + + // firstStep + + int firstStep() const { + return _firstStep.get(isRouted(Routing::Target::FirstStep)); + } + + void setFirstStep(int firstStep, bool routed = false) { + _firstStep.set(clamp(firstStep, 0, lastStep()), routed); + } + + void editFirstStep(int value, bool shift) { + if (shift) { + offsetFirstAndLastStep(value); + } else if (!isRouted(Routing::Target::FirstStep)) { + setFirstStep(firstStep() + value); + } + } + + void printFirstStep(StringBuilder &str) const { + printRouted(str, Routing::Target::FirstStep); + str("%d", firstStep() + 1); + } + + // lastStep + + int lastStep() const { + // make sure last step is always >= first step even if stored value is invalid (due to routing changes) + return std::max(firstStep(), int(_lastStep.get(isRouted(Routing::Target::LastStep)))); + } + + void setLastStep(int lastStep, bool routed = false) { + _lastStep.set(clamp(lastStep, firstStep(), CONFIG_STEP_COUNT - 1), routed); + } + + void editLastStep(int value, bool shift) { + if (shift) { + offsetFirstAndLastStep(value); + } else if (!isRouted(Routing::Target::LastStep)) { + setLastStep(lastStep() + value); + } + } + + void printLastStep(StringBuilder &str) const { + printRouted(str, Routing::Target::LastStep); + str("%d", lastStep() + 1); + } + + // steps + + const StepArray &steps() const { return _steps; } + StepArray &steps() { return _steps; } + + const Step &step(int index) const { return _steps[index]; } + Step &step(int index) { return _steps[index]; } + + //---------------------------------------- + // Routing + //---------------------------------------- + + inline bool isRouted(Routing::Target target) const { return Routing::isRouted(target, _trackIndex); } + inline void printRouted(StringBuilder &str, Routing::Target target) const { Routing::printRouted(str, target, _trackIndex); } + void writeRouted(Routing::Target target, int intValue, float floatValue); + + //---------------------------------------- + // Methods + //---------------------------------------- + + LogicSequence() { clear(); } + + void clear(); + void clearSteps(); + void clearStepsSelected(const std::bitset &selected); + + bool isEdited() const; + + void setGates(std::initializer_list gates); + void setNotes(std::initializer_list notes); + + void shiftSteps(const std::bitset &selected, int direction); + + void duplicateSteps(); + + void write(VersionedSerializedWriter &writer) const; + bool read(VersionedSerializedReader &reader); + + int trackIndex() { + return _trackIndex; + } + + int section() { return _section; } + const int section() const { return _section; } + + void setSecion(int section) { + _section = section; + } + +private: + void setTrackIndex(int trackIndex) { _trackIndex = trackIndex; } + + void offsetFirstAndLastStep(int value) { + value = clamp(value, -firstStep(), CONFIG_STEP_COUNT - 1 - lastStep()); + if (value > 0) { + editLastStep(value, false); + editFirstStep(value, false); + } else { + editFirstStep(value, false); + editLastStep(value, false); + } + } + + uint8_t _slot = uint8_t(-1); + char _name[NameLength + 1]; + int8_t _trackIndex = -1; + Routable _scale; + + int _previousScale; + + Routable _rootNote; + Routable _divisor; + uint8_t _resetMeasure; + Routable _runMode; + Routable _firstStep; + Routable _lastStep; + + StepArray _steps; + + uint8_t _edited; + + int _section = 0; + + friend class LogicTrack; +}; diff --git a/src/apps/sequencer/model/LogicTrack.cpp b/src/apps/sequencer/model/LogicTrack.cpp new file mode 100644 index 00000000..52001cb5 --- /dev/null +++ b/src/apps/sequencer/model/LogicTrack.cpp @@ -0,0 +1,108 @@ +#include "LogicTrack.h" +#include "ProjectVersion.h" +#include +#include "core/utils/StringBuilder.h" + + +void LogicTrack::writeRouted(Routing::Target target, int intValue, float floatValue) { + switch (target) { + case Routing::Target::SlideTime: + setSlideTime(intValue, true); + break; + case Routing::Target::Octave: + setOctave(intValue, true); + break; + case Routing::Target::Transpose: + setTranspose(intValue, true); + break; + case Routing::Target::Rotate: + setRotate(intValue, true); + break; + case Routing::Target::GateProbabilityBias: + setGateProbabilityBias(intValue, true); + break; + case Routing::Target::RetriggerProbabilityBias: + setRetriggerProbabilityBias(intValue, true); + break; + case Routing::Target::LengthBias: + setLengthBias(intValue, true); + break; + case Routing::Target::NoteProbabilityBias: + setNoteProbabilityBias(intValue, true); + break; + default: + break; + } +} + +void LogicTrack::clear() { + setPlayMode(Types::PlayMode::Aligned); + setFillMode(FillMode::Gates); + setFillMuted(true); + setCvUpdateMode(CvUpdateMode::Gate); + setSlideTime(50); + setOctave(0); + setTranspose(0); + setRotate(0); + setGateProbabilityBias(0); + setRetriggerProbabilityBias(0); + setLengthBias(0); + setNoteProbabilityBias(0); + setDetailedView(false); + + for (auto &sequence : _sequences) { + sequence.clear(); + } +} + + +void LogicTrack::write(VersionedSerializedWriter &writer) const { + writer.write(_name, NameLength + 1); + writer.write(_playMode); + writer.write(_fillMode); + writer.write(_fillMuted); + writer.write(_cvUpdateMode); + writer.write(_slideTime.base); + writer.write(_octave.base); + writer.write(_transpose.base); + writer.write(_rotate.base); + writer.write(_gateProbabilityBias.base); + writer.write(_retriggerProbabilityBias.base); + writer.write(_lengthBias.base); + writer.write(_noteProbabilityBias.base); + writer.write(_inputTrack1); + writer.write(_inputTrack2); + writer.write(_detailedView); + writeArray(writer, _sequences); +} + +void LogicTrack::read(VersionedSerializedReader &reader) { + reader.backupHash(); + + reader.read(_name, NameLength + 1, ProjectVersion::Version33); + + reader.read(_playMode); + reader.read(_fillMode); + reader.read(_fillMuted, ProjectVersion::Version26); + reader.read(_cvUpdateMode, ProjectVersion::Version4); + reader.read(_slideTime.base); + reader.read(_octave.base); + reader.read(_transpose.base); + reader.read(_rotate.base); + reader.read(_gateProbabilityBias.base); + reader.read(_retriggerProbabilityBias.base); + reader.read(_lengthBias.base); + reader.read(_noteProbabilityBias.base); + + reader.read(_inputTrack1, ProjectVersion::Version37); + reader.read(_inputTrack2, ProjectVersion::Version37); + reader.read(_detailedView, ProjectVersion::Version37); + + // There is a bug in previous firmware versions where writing the properties + // of a note track did not update the hash value. + if (reader.dataVersion() < ProjectVersion::Version23) { + reader.restoreHash(); + } + + readArray(reader, _sequences); +} diff --git a/src/apps/sequencer/model/LogicTrack.h b/src/apps/sequencer/model/LogicTrack.h new file mode 100644 index 00000000..a864d92c --- /dev/null +++ b/src/apps/sequencer/model/LogicTrack.h @@ -0,0 +1,383 @@ +#pragma once + +#include "BaseTrackPatternFollow.h" +#include "Config.h" +#include "Types.h" +#include "LogicSequence.h" +#include "Serialize.h" +#include "Routing.h" +#include "FileDefs.h" +#include "core/utils/StringUtils.h" +#include "BaseTrack.h" +#include + + +class LogicTrack : public BaseTrack, public BaseTrackPatternFollow { +public: + //---------------------------------------- + // Types + //---------------------------------------- + + typedef std::array LogicSequenceArray; + + // FillMode + + enum class FillMode : uint8_t { + None, + Gates, + NextPattern, + Condition, + Last + }; + + static const char *fillModeName(FillMode fillMode) { + switch (fillMode) { + case FillMode::None: return "None"; + case FillMode::Gates: return "Gates"; + case FillMode::NextPattern: return "Next Pattern"; + case FillMode::Condition: return "Condition"; + case FillMode::Last: break; + } + return nullptr; + } + + // CvUpdateMode + + enum class CvUpdateMode : uint8_t { + Gate, + Always, + Last + }; + + static const char *cvUpdateModeName(CvUpdateMode mode) { + switch (mode) { + case CvUpdateMode::Gate: return "Gate"; + case CvUpdateMode::Always: return "Always"; + case CvUpdateMode::Last: break; + } + return nullptr; + } + + //---------------------------------------- + // Properties + //---------------------------------------- + + // playMode + + Types::PlayMode playMode() const { return _playMode; } + void setPlayMode(Types::PlayMode playMode) { + _playMode = ModelUtils::clampedEnum(playMode); + } + + void editPlayMode(int value, bool shift) { + setPlayMode(ModelUtils::adjustedEnum(playMode(), value)); + } + + void printPlayMode(StringBuilder &str) const { + str(Types::playModeName(playMode())); + } + + // fillMode + + FillMode fillMode() const { return _fillMode; } + void setFillMode(FillMode fillMode) { + _fillMode = ModelUtils::clampedEnum(fillMode); + } + + void editFillMode(int value, bool shift) { + setFillMode(ModelUtils::adjustedEnum(fillMode(), value)); + } + + void printFillMode(StringBuilder &str) const { + str(fillModeName(fillMode())); + } + + // fillMuted + + bool fillMuted() const { return _fillMuted; } + void setFillMuted(bool fillMuted) { + _fillMuted = fillMuted; + } + + void editFillMuted(int value, bool shift) { + setFillMuted(value > 0); + } + + void printFillMuted(StringBuilder &str) const { + ModelUtils::printYesNo(str, fillMuted()); + } + + // cvUpdateMode + + CvUpdateMode cvUpdateMode() const { return _cvUpdateMode; } + void setCvUpdateMode(CvUpdateMode cvUpdateMode) { + _cvUpdateMode = ModelUtils::clampedEnum(cvUpdateMode); + } + + void editCvUpdateMode(int value, bool shift) { + setCvUpdateMode(ModelUtils::adjustedEnum(cvUpdateMode(), value)); + } + + void printCvUpdateMode(StringBuilder &str) const { + str(cvUpdateModeName(cvUpdateMode())); + } + + // slideTime + + int slideTime() const { return _slideTime.get(isRouted(Routing::Target::SlideTime)); } + void setSlideTime(int slideTime, bool routed = false) { + _slideTime.set(clamp(slideTime, 0, 100), routed); + } + + void editSlideTime(int value, bool shift) { + if (!isRouted(Routing::Target::SlideTime)) { + setSlideTime(ModelUtils::adjustedByStep(slideTime(), value, 5, !shift)); + } + } + + void printSlideTime(StringBuilder &str) const { + printRouted(str, Routing::Target::SlideTime); + str("%d%%", slideTime()); + } + + // octave + + int octave() const { return _octave.get(isRouted(Routing::Target::Octave)); } + void setOctave(int octave, bool routed = false) { + _octave.set(clamp(octave, -10, 10), routed); + } + + void editOctave(int value, bool shift) { + if (!isRouted(Routing::Target::Octave)) { + setOctave(octave() + value); + } + } + + void printOctave(StringBuilder &str) const { + printRouted(str, Routing::Target::Octave); + str("%+d", octave()); + } + + // transpose + + int transpose() const { return _transpose.get(isRouted(Routing::Target::Transpose)); } + void setTranspose(int transpose, bool routed = false) { + _transpose.set(clamp(transpose, -100, 100), routed); + } + + void editTranspose(int value, bool shift) { + if (!isRouted(Routing::Target::Transpose)) { + setTranspose(transpose() + value); + } + } + + void printTranspose(StringBuilder &str) const { + printRouted(str, Routing::Target::Transpose); + str("%+d", transpose()); + } + + // rotate + + int rotate() const { return _rotate.get(isRouted(Routing::Target::Rotate)); } + void setRotate(int rotate, bool routed = false) { + _rotate.set(clamp(rotate, -64, 64), routed); + } + + void editRotate(int value, bool shift) { + if (!isRouted(Routing::Target::Rotate)) { + setRotate(rotate() + value); + } + } + + void printRotate(StringBuilder &str) const { + printRouted(str, Routing::Target::Rotate); + str("%+d", rotate()); + } + + // gateProbabilityBias + + int gateProbabilityBias() const { return _gateProbabilityBias.get(isRouted(Routing::Target::GateProbabilityBias)); } + void setGateProbabilityBias(int gateProbabilityBias, bool routed = false) { + _gateProbabilityBias.set(clamp(gateProbabilityBias, -LogicSequence::GateProbability::Range, LogicSequence::GateProbability::Range), routed); + } + + void editGateProbabilityBias(int value, bool shift) { + if (!isRouted(Routing::Target::GateProbabilityBias)) { + setGateProbabilityBias(gateProbabilityBias() + value); + } + } + + void printGateProbabilityBias(StringBuilder &str) const { + printRouted(str, Routing::Target::GateProbabilityBias); + str("%+.1f%%", gateProbabilityBias() * 12.5f); + } + + // retriggerProbabilityBias + + int retriggerProbabilityBias() const { return _retriggerProbabilityBias.get(isRouted(Routing::Target::RetriggerProbabilityBias)); } + void setRetriggerProbabilityBias(int retriggerProbabilityBias, bool routed = false) { + _retriggerProbabilityBias.set(clamp(retriggerProbabilityBias, -LogicSequence::RetriggerProbability::Range, LogicSequence::RetriggerProbability::Range), routed); + } + + void editRetriggerProbabilityBias(int value, bool shift) { + if (!isRouted(Routing::Target::RetriggerProbabilityBias)) { + setRetriggerProbabilityBias(retriggerProbabilityBias() + value); + } + } + + void printRetriggerProbabilityBias(StringBuilder &str) const { + printRouted(str, Routing::Target::RetriggerProbabilityBias); + str("%+.1f%%", retriggerProbabilityBias() * 12.5f); + } + + // lengthBias + + int lengthBias() const { return _lengthBias.get(isRouted(Routing::Target::LengthBias)); } + void setLengthBias(int lengthBias, bool routed = false) { + _lengthBias.set(clamp(lengthBias, -LogicSequence::Length::Range, LogicSequence::Length::Range), routed); + } + + void editLengthBias(int value, bool shift) { + if (!isRouted(Routing::Target::LengthBias)) { + setLengthBias(lengthBias() + value); + } + } + + void printLengthBias(StringBuilder &str) const { + printRouted(str, Routing::Target::LengthBias); + str("%+.1f%%", lengthBias() * 12.5f); + } + + // noteProbabilityBias + + int noteProbabilityBias() const { return _noteProbabilityBias.get(isRouted(Routing::Target::NoteProbabilityBias)); } + void setNoteProbabilityBias(int noteProbabilityBias, bool routed = false) { + _noteProbabilityBias.set(clamp(noteProbabilityBias, -LogicSequence::NoteVariationProbability::Range, LogicSequence::NoteVariationProbability::Range), routed); + } + + void editNoteProbabilityBias(int value, bool shift) { + if (!isRouted(Routing::Target::NoteProbabilityBias)) { + setNoteProbabilityBias(noteProbabilityBias() + value); + } + } + + void printNoteProbabilityBias(StringBuilder &str) const { + printRouted(str, Routing::Target::NoteProbabilityBias); + str("%+.1f%%", noteProbabilityBias() * 12.5f); + } + + // sequences + + const LogicSequenceArray &sequences() const { return _sequences; } + LogicSequenceArray &sequences() { return _sequences; } + + const LogicSequence &sequence(int index) const { return _sequences[index]; } + LogicSequence &sequence(int index) { return _sequences[index]; } + + void setSequence(int index, LogicSequence seq) { + _sequences[index] = seq; + } + + //---------------------------------------- + // Routing + //---------------------------------------- + + inline bool isRouted(Routing::Target target) const { return Routing::isRouted(target, _trackIndex); } + inline void printRouted(StringBuilder &str, Routing::Target target) const { Routing::printRouted(str, target, _trackIndex); } + void writeRouted(Routing::Target target, int intValue, float floatValue); + + const int inputTrack1() const { + return _inputTrack1; + } + + void setInputTrack1(int trackIndex) { + _inputTrack1 = trackIndex; + } + + void printInputTrack1(StringBuilder &str) const { + if (inputTrack1()==-1) { + str("-"); + } else { + str("%d", inputTrack1()+1); + } + } + + + const int inputTrack2() const { + return _inputTrack2; + } + + void setInputTrack2(int trackIndex) { + _inputTrack2 = trackIndex; + } + + void printInputTrack2(StringBuilder &str) const { + if (inputTrack2()==-1) { + str("-"); + } else { + str("%d", inputTrack2()+1); + } + } + + // detailed view + bool detailedView() const { return _detailedView; } + void setDetailedView(bool value) { + if (value != _detailedView) { + _detailedView = value; + } + } + + void editDetailedView(int value, bool shift) { + setDetailedView(value > 0); + } + + void toggleDetailedView() { + setDetailedView(!detailedView()); + } + + void printDetailedView(StringBuilder &str) const { + ModelUtils::printYesNo(str, detailedView()); + } + + //---------------------------------------- + // Methods + //---------------------------------------- + + LogicTrack() { clear(); } + + void clear(); + + void write(VersionedSerializedWriter &writer) const; + void read(VersionedSerializedReader &reader); + +private: + void setTrackIndex(int trackIndex) { + _trackIndex = trackIndex; + for (auto &sequence : _sequences) { + sequence.setTrackIndex(trackIndex); + } + } + + int8_t _trackIndex = -1; + Types::PlayMode _playMode; + FillMode _fillMode; + bool _fillMuted; + CvUpdateMode _cvUpdateMode; + Routable _slideTime; + Routable _octave; + Routable _transpose; + Routable _rotate; + Routable _gateProbabilityBias; + Routable _retriggerProbabilityBias; + Routable _lengthBias; + Routable _noteProbabilityBias; + + int8_t _inputTrack1 = -1; + int8_t _inputTrack2 = -1; + + bool _detailedView = false; + + LogicSequenceArray _sequences; + + friend class Track; +}; diff --git a/src/apps/sequencer/model/ModelUtils.h b/src/apps/sequencer/model/ModelUtils.h index 75cf799a..9fcef803 100644 --- a/src/apps/sequencer/model/ModelUtils.h +++ b/src/apps/sequencer/model/ModelUtils.h @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include namespace ModelUtils { @@ -68,28 +70,61 @@ static void shiftSteps(std::array &steps, int first, int last, int dire template static void shiftSteps(std::array &steps, const std::bitset &selected, int first, int last, int direction) { - uint8_t indices[N]; - int count = 0; - for (size_t i = 0; i < N; ++i) { - if (selected[i]) indices[count++] = i; - } - if (direction == 1) { - for (int i = last - 1; i >= first; --i) { - int index = indices[i]+1; - if (index == last) { - index = 0; + // [first...last-1] : size=last-first + // direction L = -1 ; R = 1 + + int distance = last - first; + int starting_point = -1; // from where we'll swap. -1 means everything is selected + + if (direction == -1) { + // Find a starting point (the closest step to the left that we don't shift) + + for(int idx=first; idx=0;idx--) { + if (!selected[idx]) { + starting_point = idx; + break;; } - std::swap(steps[indices[i]], steps[index]); + } + + if (starting_point == - 1) { + shiftSteps(steps, first, last, direction); + return; + } + + // scan and swap the whole range, from right to left + for(int i=distance-1; i>=0; i--) { + int idx = (i + starting_point + last) % last; + int next_idx = ((idx + 1) + last) % last; + + if (selected[idx]) + std::swap(steps[next_idx], steps[idx]); } } + + return; } template diff --git a/src/apps/sequencer/model/NoteSequence.cpp b/src/apps/sequencer/model/NoteSequence.cpp index 3c074a6f..392588a0 100644 --- a/src/apps/sequencer/model/NoteSequence.cpp +++ b/src/apps/sequencer/model/NoteSequence.cpp @@ -2,6 +2,11 @@ #include "ProjectVersion.h" #include "ModelUtils.h" +#include "Types.h" +#include "Routing.h" + +#include "os/os.h" +#include Types::LayerRange NoteSequence::layerRange(Layer layer) { #define CASE(_layer_) \ @@ -170,7 +175,7 @@ void NoteSequence::Step::setLayerValue(Layer layer, int value) { setStageRepeats(value); break; case Layer::StageRepeatsMode: - setStageRepeatsMode(static_cast(value)); + setStageRepeatsMode(static_cast(value)); break; case Layer::Last: break; @@ -195,7 +200,7 @@ void NoteSequence::Step::clear() { setNoteVariationProbability(NoteVariationProbability::Max); setCondition(Types::Condition::Off); setStageRepeats(0); - setStageRepeatsMode(StageRepeatMode::Each); + setStageRepeatsMode(Types::StageRepeatMode::Each); } void NoteSequence::Step::write(VersionedSerializedWriter &writer) const { @@ -258,6 +263,25 @@ void NoteSequence::writeRouted(Routing::Target target, int intValue, float float case Routing::Target::LastStep: setLastStep(intValue, true); break; + case Routing::Target::CurrentRecordStep: + if (_gate) { + if (floatValue < 2.f) { + // gate off + _gate = 0; + _lastGateOff = os::ticks(); + } + } else { + if (floatValue > 3.f) { + if (os::ticks() - _lastGateOff >= GateOnDelay) { + // gate on + _gate = 1; + setCurrentRecordStep(currentRecordStep()+1,true); + } + } else { + _lastGateOff = os::ticks(); + } + } + break; default: break; } @@ -272,6 +296,7 @@ void NoteSequence::clear() { setRunMode(Types::RunMode::Forward); setFirstStep(0); setLastStep(15); + setCurrentRecordStep(0); clearSteps(); } @@ -324,9 +349,9 @@ void NoteSequence::setNotes(std::initializer_list notes) { void NoteSequence::shiftSteps(const std::bitset &selected, int direction) { if (selected.any()) { - ModelUtils::shiftSteps(_steps, selected, firstStep(), lastStep(), direction); + ModelUtils::shiftSteps(_steps, selected, firstStep(), lastStep()+1, direction); } else { - ModelUtils::shiftSteps(_steps, firstStep(), lastStep(), direction); + ModelUtils::shiftSteps(_steps, firstStep(), lastStep()+1, direction); } } diff --git a/src/apps/sequencer/model/NoteSequence.h b/src/apps/sequencer/model/NoteSequence.h index aea55edd..dc70e7a3 100644 --- a/src/apps/sequencer/model/NoteSequence.h +++ b/src/apps/sequencer/model/NoteSequence.h @@ -8,6 +8,7 @@ #include "Scale.h" #include "Routing.h" #include "FileDefs.h" +#include "os/os.h" #include "core/math/Math.h" #include "core/utils/StringBuilder.h" @@ -88,18 +89,6 @@ class NoteSequence { static Types::LayerRange layerRange(Layer layer); static int layerDefaultValue(Layer layer); - enum StageRepeatMode { - Each, - First, - Middle, - Last, - Odd, - Even, - Triplets, - Random, - - }; - static constexpr size_t NameLength = FileHeader::NameLength; @@ -116,13 +105,13 @@ class NoteSequence { } unsigned int stageRepeats() const { return _data1.stageRepeats; } - void setStageRepeatsMode(StageRepeatMode mode) { + void setStageRepeatsMode(Types::StageRepeatMode mode) { _data1.stageRepeatMode = mode; } - StageRepeatMode stageRepeatMode() const { + Types::StageRepeatMode stageRepeatMode() const { int value = _data1.stageRepeatMode; - return static_cast(value); + return static_cast(value); } // gate @@ -503,6 +492,25 @@ class NoteSequence { str("%d", lastStep() + 1); } + int currentRecordStep() const { + return _currentRecordStep.get(isRouted(Routing::Target::CurrentRecordStep)); + } + + void setCurrentRecordStep(int step, bool routed = false) { + _currentRecordStep.set(clamp(step, firstStep(), CONFIG_STEP_COUNT - 1), routed); + } + + void editCurrentRecordStep(int value, bool shift) { + if (!isRouted(Routing::Target::CurrentRecordStep)) { + setCurrentRecordStep(currentRecordStep()+value); + } + } + + void printCurrentRecordStep(StringBuilder &str) const { + printRouted(str, Routing::Target::CurrentRecordStep); + str("%d", currentRecordStep() + 1); + } + // steps const StepArray &steps() const { return _steps; } @@ -580,11 +588,16 @@ class NoteSequence { Routable _firstStep; Routable _lastStep; + Routable _currentRecordStep; + StepArray _steps; uint8_t _edited; int _section = 0; + uint32_t _lastGateOff; + uint8_t _gate; + static constexpr uint32_t GateOnDelay = os::time::ms(5); friend class NoteTrack; }; diff --git a/src/apps/sequencer/model/NoteTrack.cpp b/src/apps/sequencer/model/NoteTrack.cpp index d6089361..61f1da0b 100644 --- a/src/apps/sequencer/model/NoteTrack.cpp +++ b/src/apps/sequencer/model/NoteTrack.cpp @@ -69,6 +69,8 @@ void NoteTrack::write(VersionedSerializedWriter &writer) const { writer.write(_retriggerProbabilityBias.base); writer.write(_lengthBias.base); writer.write(_noteProbabilityBias.base); + writer.write(_logicTrack); + writer.write(_logicTrackInput); writeArray(writer, _sequences); } @@ -89,6 +91,8 @@ void NoteTrack::read(VersionedSerializedReader &reader) { reader.read(_retriggerProbabilityBias.base); reader.read(_lengthBias.base); reader.read(_noteProbabilityBias.base); + reader.read(_logicTrack, ProjectVersion::Version37); + reader.read(_logicTrackInput, ProjectVersion::Version37); // There is a bug in previous firmware versions where writing the properties // of a note track did not update the hash value. diff --git a/src/apps/sequencer/model/NoteTrack.h b/src/apps/sequencer/model/NoteTrack.h index f5b11991..0552da69 100644 --- a/src/apps/sequencer/model/NoteTrack.h +++ b/src/apps/sequencer/model/NoteTrack.h @@ -9,6 +9,7 @@ #include "FileDefs.h" #include "core/utils/StringUtils.h" #include "BaseTrack.h" +#include class NoteTrack : public BaseTrack, public BaseTrackPatternFollow { @@ -61,6 +62,8 @@ class NoteTrack : public BaseTrack, public BaseTrackPatternFollow { // Properties //---------------------------------------- + const int trackIndex() const { return _trackIndex;} + // playMode Types::PlayMode playMode() const { return _playMode; } @@ -277,6 +280,40 @@ class NoteTrack : public BaseTrack, public BaseTrackPatternFollow { _sequences[index] = seq; } + const int logicTrack() const { return _logicTrack; } + void setLogicTrack(int logicTrack) { + _logicTrack = clamp(logicTrack, -1, 7); + } + + void printLogicTrack(StringBuilder &str) const { + if (logicTrack()==-1) { + str("Off"); + } else { + str("%d", logicTrack()+1); + } + } + + void editLogicTrack(int value, bool shift) { + setLogicTrack(logicTrack()+ value); + } + + const int logicTrackInput() const { return _logicTrackInput; } + void setLogicTrackInput(int logicTrackInput) { + _logicTrackInput = clamp(logicTrackInput, -1, 1); + } + + void printLogicTrackInput(StringBuilder &str) const { + if (logicTrackInput()==-1) { + str("-"); + } else { + str("%d", logicTrackInput()+1); + } + } + + void editLogicTrackInput(int value, bool shift) { + setLogicTrackInput(logicTrackInput()+ value); + } + //---------------------------------------- // Routing //---------------------------------------- @@ -318,6 +355,9 @@ class NoteTrack : public BaseTrack, public BaseTrackPatternFollow { Routable _lengthBias; Routable _noteProbabilityBias; + int8_t _logicTrack = -1; + int8_t _logicTrackInput = -1; + NoteSequenceArray _sequences; friend class Track; diff --git a/src/apps/sequencer/model/Project.h b/src/apps/sequencer/model/Project.h index e6b73b16..e4638e1b 100644 --- a/src/apps/sequencer/model/Project.h +++ b/src/apps/sequencer/model/Project.h @@ -445,6 +445,9 @@ class Project { case Track::TrackMode::Stochastic: StringUtils::copy(_selectedTrackName, selectedTrack().stochasticTrack().name(), sizeof(_selectedTrackName)); break; + case Track::TrackMode::Logic: + StringUtils::copy(_selectedTrackName, selectedTrack().logicTrack().name(), sizeof(_selectedTrackName)); + break; case Track::TrackMode::Last: break; } @@ -479,10 +482,16 @@ class Project { NoteSequence::Layer selectedNoteSequenceLayer() const { return _selectedNoteSequenceLayer; } void setSelectedNoteSequenceLayer(NoteSequence::Layer layer) { _selectedNoteSequenceLayer = layer; } + // selectedStochasticSequenceLayer StochasticSequence::Layer selectedStochasticSequenceLayer() const { return _selectedStochasticSequenceLayer; } void setSelectedStochasticSequenceLayer(StochasticSequence::Layer layer) { _selectedStochasticSequenceLayer = layer; } + // selectedLogicSequenceLayer + + LogicSequence::Layer selectedLogicSequenceLayer() const { return _selectedLogicSequenceLayer; } + void setSelectedLogicSequenceLayer(LogicSequence::Layer layer) { _selectedLogicSequenceLayer = layer; } + // selectedCurveSequenceLayer CurveSequence::Layer selectedCurveSequenceLayer() const { return _selectedCurveSequenceLayer; } @@ -521,16 +530,30 @@ class Project { const CurveSequence &selectedCurveSequence() const { return curveSequence(_selectedTrackIndex, selectedPatternIndex()); } CurveSequence &selectedCurveSequence() { return curveSequence(_selectedTrackIndex, selectedPatternIndex()); } - // curveSequence + // stochasticSequence const StochasticSequence &stochasticSequence(int trackIndex, int patternIndex) const { return _tracks[trackIndex].stochasticTrack().sequence(patternIndex); } StochasticSequence &stochasticSequence(int trackIndex, int patternIndex) { return _tracks[trackIndex].stochasticTrack().sequence(patternIndex); } - // selectedCurveSequence + // selectedStochasticSequence const StochasticSequence &selectedStochasticSequence() const { return stochasticSequence(_selectedTrackIndex, selectedPatternIndex()); } StochasticSequence &selectedStochasticSequence() { return stochasticSequence(_selectedTrackIndex, selectedPatternIndex()); } + // logicSequence + + const LogicSequence &logicSequence(int trackIndex, int patternIndex) const { return _tracks[trackIndex].logicTrack().sequence(patternIndex); } + LogicSequence &logicSequence(int trackIndex, int patternIndex) { return _tracks[trackIndex].logicTrack().sequence(patternIndex); } + + // selectedLogicSequence + + const LogicSequence &selectedLogicSequence() const { return logicSequence(_selectedTrackIndex, selectedPatternIndex()); } + LogicSequence &selectedLogicSequence() { return logicSequence(_selectedTrackIndex, selectedPatternIndex()); } + + void setselectedLogicSequence(LogicSequence seq) { + _tracks[_selectedTrackIndex].logicTrack().setSequence(selectedPatternIndex(), seq); + } + //---------------------------------------- // Routing //---------------------------------------- @@ -605,6 +628,7 @@ class Project { NoteSequence::Layer _selectedNoteSequenceLayer = NoteSequence::Layer(0); CurveSequence::Layer _selectedCurveSequenceLayer = CurveSequence::Layer(0); StochasticSequence::Layer _selectedStochasticSequenceLayer = StochasticSequence::Layer(10); + LogicSequence::Layer _selectedLogicSequenceLayer = LogicSequence::Layer(0); Observable _observable; }; diff --git a/src/apps/sequencer/model/ProjectVersion.h b/src/apps/sequencer/model/ProjectVersion.h index 83700fac..4b8b126c 100644 --- a/src/apps/sequencer/model/ProjectVersion.h +++ b/src/apps/sequencer/model/ProjectVersion.h @@ -105,6 +105,9 @@ enum ProjectVersion { // change note length form 3 to 4 bits Version36 = 36, + // add logic track + Version37 = 37, + // automatically derive latest version Last, diff --git a/src/apps/sequencer/model/Routing.cpp b/src/apps/sequencer/model/Routing.cpp index cd780031..6dae25cb 100644 --- a/src/apps/sequencer/model/Routing.cpp +++ b/src/apps/sequencer/model/Routing.cpp @@ -224,6 +224,15 @@ void Routing::writeTarget(Target target, uint8_t tracks, float normalized) { } } break; + case Track::TrackMode::Logic: + if (isTrackTarget(target)) { + track.logicTrack().writeRouted(target, intValue, floatValue); + } else { + for (int patternIndex = 0; patternIndex < CONFIG_PATTERN_COUNT; ++patternIndex) { + track.logicTrack().sequence(patternIndex).writeRouted(target, intValue, floatValue); + } + } + break; case Track::TrackMode::Last: break; } @@ -309,6 +318,8 @@ static const TargetInfo targetInfos[int(Routing::Target::Last)] = { [int(Routing::Target::LengthBias)] = { -8, 8, -8, 8, 8 }, [int(Routing::Target::NoteProbabilityBias)] = { -8, 8, -8, 8, 8 }, [int(Routing::Target::ShapeProbabilityBias)] = { -8, 8, -8, 8, 8 }, + [int(Routing::Target::CurveMin)] = { 0, 255, 0, 255, 1 }, + [int(Routing::Target::CurveMax)] = { 0, 255, 0, 255, 1 }, // Sequence targets [int(Routing::Target::FirstStep)] = { 0, 63, 0, 63, 16 }, [int(Routing::Target::LastStep)] = { 0, 63, 0, 63, 16 }, @@ -316,6 +327,8 @@ static const TargetInfo targetInfos[int(Routing::Target::Last)] = { [int(Routing::Target::Divisor)] = { 1, 768, 6, 24, 1 }, [int(Routing::Target::Scale)] = { 0, 23, 0, 23, 1 }, [int(Routing::Target::RootNote)] = { 0, 11, 0, 11, 1 }, + [int(Routing::Target::CurrentRecordStep)] = { 0, 63, 0, 63, 16 }, + [int(Routing::Target::Reseed)] = { 0, 1, 0, 1, 1 }, [int(Routing::Target::RestProbability2)] = { -8, 8, -8, 8, 8 }, [int(Routing::Target::RestProbability4)] = { -8, 8, -8, 8, 8 }, diff --git a/src/apps/sequencer/model/Routing.h b/src/apps/sequencer/model/Routing.h index 56133b13..fa46f37d 100644 --- a/src/apps/sequencer/model/Routing.h +++ b/src/apps/sequencer/model/Routing.h @@ -62,7 +62,9 @@ class Routing { LengthBias, NoteProbabilityBias, ShapeProbabilityBias, - TrackLast = ShapeProbabilityBias, + CurveMin, + CurveMax, + TrackLast = CurveMax, // Sequence targets SequenceFirst, @@ -72,6 +74,7 @@ class Routing { Divisor, Scale, RootNote, + CurrentRecordStep, Reseed, RestProbability2, RestProbability4, @@ -114,6 +117,8 @@ class Routing { case Target::LengthBias: return "Length Bias"; case Target::NoteProbabilityBias: return "Note P. Bias"; case Target::ShapeProbabilityBias: return "Shape P. Bias"; + case Target::CurveMin: return "Curve Min"; + case Target::CurveMax: return "Curve Max"; case Target::FirstStep: return "First Step"; case Target::LastStep: return "Last Step"; @@ -121,6 +126,8 @@ class Routing { case Target::Divisor: return "Divisor"; case Target::Scale: return "Scale"; case Target::RootNote: return "Root Note"; + case Target::CurrentRecordStep: return "REC Step"; + case Target::Reseed: return "Reseed"; case Target::RestProbability2: return "Rest Prob. 2"; case Target::RestProbability4: return "Rest Prob. 4"; @@ -182,7 +189,10 @@ class Routing { case Target::RestProbability2: return 34; case Target::RestProbability4: return 35; case Target::RestProbability8: return 36; - case Target::LengthModifier: return 37; + case Target::LengthModifier: return 37; + case Target::CurrentRecordStep: return 38; + case Target::CurveMin: return 39; + case Target::CurveMax: return 40; case Target::Last: break; } diff --git a/src/apps/sequencer/model/StochasticSequence.cpp b/src/apps/sequencer/model/StochasticSequence.cpp index 45f1a612..62237ceb 100644 --- a/src/apps/sequencer/model/StochasticSequence.cpp +++ b/src/apps/sequencer/model/StochasticSequence.cpp @@ -360,6 +360,8 @@ void StochasticSequence::write(VersionedSerializedWriter &writer) const { writer.write(_lengthModifier); writer.write(_lowOctaveRange); writer.write(_highOctaveRange); + writer.write(_sequenceFirstStep); + writer.write(_sequenceLastStep); writeArray(writer, _steps); } @@ -382,6 +384,8 @@ void StochasticSequence::read(VersionedSerializedReader &reader) { reader.read(_lengthModifier, ProjectVersion::Version36); reader.read(_lowOctaveRange, ProjectVersion::Version36); reader.read(_highOctaveRange, ProjectVersion::Version36); + reader.read(_sequenceFirstStep, ProjectVersion::Version37); + reader.read(_sequenceLastStep, ProjectVersion::Version37); diff --git a/src/apps/sequencer/model/StochasticTrack.cpp b/src/apps/sequencer/model/StochasticTrack.cpp index 5a8b6589..6031a9ed 100644 --- a/src/apps/sequencer/model/StochasticTrack.cpp +++ b/src/apps/sequencer/model/StochasticTrack.cpp @@ -25,10 +25,17 @@ void StochasticTrack::writeRouted(Routing::Target target, int intValue, float fl void StochasticTrack::clear() { setPlayMode(Types::PlayMode::Aligned); + setFillMode(FillMode::Gates); + setFillMuted(true); + setCvUpdateMode(CvUpdateMode::Gate); setSlideTime(50); setOctave(0); setTranspose(0); setRotate(0); + setGateProbabilityBias(0); + setRetriggerProbabilityBias(0); + setLengthBias(0); + setNoteProbabilityBias(0); for (auto &sequence : _sequences) { sequence.clear(); @@ -38,6 +45,9 @@ void StochasticTrack::clear() { void StochasticTrack::write(VersionedSerializedWriter &writer) const { writer.write(_name, NameLength + 1); writer.write(_playMode); + writer.write(_fillMode); + writer.write(_fillMuted); + writer.write(_cvUpdateMode); writer.write(_slideTime.base); writer.write(_octave.base); writer.write(_transpose.base); @@ -51,6 +61,9 @@ void StochasticTrack::read(VersionedSerializedReader &reader) { reader.read(_name, NameLength + 1, ProjectVersion::Version33); reader.read(_playMode); + reader.read(_fillMode); + reader.read(_fillMuted, ProjectVersion::Version26); + reader.read(_cvUpdateMode, ProjectVersion::Version4); reader.read(_slideTime.base); reader.read(_octave.base); reader.read(_transpose.base); diff --git a/src/apps/sequencer/model/Track.cpp b/src/apps/sequencer/model/Track.cpp index 3ce1a93c..a0b7d822 100644 --- a/src/apps/sequencer/model/Track.cpp +++ b/src/apps/sequencer/model/Track.cpp @@ -19,6 +19,9 @@ void Track::clearPattern(int patternIndex) { case TrackMode::Stochastic: _track.stochastic->sequence(patternIndex).clear(); break; + case TrackMode::Logic: + _track.logic->sequence(patternIndex).clear(); + break; case TrackMode::MidiCv: break; case TrackMode::Last: @@ -37,6 +40,9 @@ void Track::copyPattern(int src, int dst) { case TrackMode::Stochastic: _track.stochastic->sequence(dst) = _track.stochastic->sequence(src); break; + case TrackMode::Logic: + _track.logic->sequence(dst) = _track.logic->sequence(src); + break; case TrackMode::MidiCv: break; case TrackMode::Last: @@ -57,6 +63,7 @@ void Track::gateOutputName(int index, StringBuilder &str) const { case TrackMode::Note: case TrackMode::Curve: case TrackMode::Stochastic: + case TrackMode::Logic: str("Gate"); break; case TrackMode::MidiCv: @@ -72,6 +79,7 @@ void Track::cvOutputName(int index, StringBuilder &str) const { case TrackMode::Note: case TrackMode::Curve: case TrackMode::Stochastic: + case TrackMode::Logic: str("CV"); break; case TrackMode::MidiCv: @@ -99,6 +107,9 @@ void Track::write(VersionedSerializedWriter &writer) const { case TrackMode::Stochastic: _track.stochastic->write(writer); break; + case TrackMode::Logic: + _track.logic->write(writer); + break; case TrackMode::Last: break; } @@ -123,6 +134,9 @@ void Track::read(VersionedSerializedReader &reader) { case TrackMode::Stochastic: _track.stochastic->read(reader); break; + case TrackMode::Logic: + _track.logic->read(reader); + break; case TrackMode::Last: break; } @@ -133,6 +147,7 @@ void Track::initContainer() { _track.curve = nullptr; _track.midiCv = nullptr; _track.stochastic = nullptr; + _track.logic = nullptr; switch (_trackMode) { case TrackMode::Note: @@ -146,6 +161,10 @@ void Track::initContainer() { break; case TrackMode::Stochastic: _track.stochastic = _container.create(); + break; + case TrackMode::Logic: + _track.logic = _container.create(); + break; case TrackMode::Last: break; } @@ -159,18 +178,27 @@ void Track::setTrackIndex(int trackIndex) { } void Track::setContainerTrackIndex(int trackIndex) { + FixedStringBuilder<16> str("TRACK %d", trackIndex+1); switch (_trackMode) { case TrackMode::Note: _track.note->setTrackIndex(trackIndex); + _track.note->setName(str); break; case TrackMode::Curve: _track.curve->setTrackIndex(trackIndex); + _track.curve->setName(str); break; case TrackMode::MidiCv: _track.midiCv->setTrackIndex(trackIndex); + _track.midiCv->setName(str); break; case TrackMode::Stochastic: _track.stochastic->setTrackIndex(trackIndex); + _track.stochastic->setName(str); + break; + case TrackMode::Logic: + _track.logic->setTrackIndex(trackIndex); + _track.logic->setName(str); break; case TrackMode::Last: break; diff --git a/src/apps/sequencer/model/Track.h b/src/apps/sequencer/model/Track.h index 40287127..872a2c87 100644 --- a/src/apps/sequencer/model/Track.h +++ b/src/apps/sequencer/model/Track.h @@ -8,6 +8,7 @@ #include "CurveTrack.h" #include "MidiCvTrack.h" #include "StochasticTrack.h" +#include "LogicTrack.h" #include "core/Debug.h" #include "core/math/Math.h" @@ -37,6 +38,7 @@ class Track { Curve, MidiCv, Stochastic, + Logic, Last, Default = Note }; @@ -47,6 +49,7 @@ class Track { case TrackMode::Curve: return "Curve"; case TrackMode::MidiCv: return "MIDI/CV"; case TrackMode::Stochastic: return "Stochastic"; + case TrackMode::Logic: return "Logic"; case TrackMode::Last: break; } return nullptr; @@ -58,6 +61,7 @@ class Track { case TrackMode::Curve: return 1; case TrackMode::MidiCv: return 2; case TrackMode::Stochastic: return 3; + case TrackMode::Logic: return 4; case TrackMode::Last: break; } return 0; @@ -123,6 +127,9 @@ class Track { const StochasticTrack &stochasticTrack() const { SANITIZE_TRACK_MODE(_trackMode, TrackMode::Stochastic); return *_track.stochastic; } StochasticTrack &stochasticTrack() { SANITIZE_TRACK_MODE(_trackMode, TrackMode::Stochastic); return *_track.stochastic; } + const LogicTrack &logicTrack() const { SANITIZE_TRACK_MODE(_trackMode, TrackMode::Logic); return *_track.logic; } + LogicTrack &logicTrack() { SANITIZE_TRACK_MODE(_trackMode, TrackMode::Logic); return *_track.logic; } + //---------------------------------------- // Methods //---------------------------------------- @@ -170,12 +177,13 @@ class Track { TrackMode _trackMode; int8_t _linkTrack; - Container _container; + Container _container; union { NoteTrack *note; CurveTrack *curve; MidiCvTrack *midiCv; StochasticTrack *stochastic; + LogicTrack *logic; } _track; friend class Project; diff --git a/src/apps/sequencer/model/Types.h b/src/apps/sequencer/model/Types.h index 2a3866a1..fa90f877 100644 --- a/src/apps/sequencer/model/Types.h +++ b/src/apps/sequencer/model/Types.h @@ -65,6 +65,18 @@ class Types { Last }; + enum StageRepeatMode { + Each, + First, + Middle, + Last, + Odd, + Even, + Triplets, + Random, + + }; + static const char *cvGateInputName(CvGateInput cvGateInput) { switch (cvGateInput) { case CvGateInput::Off: return "Off"; diff --git a/src/apps/sequencer/python/project.cpp b/src/apps/sequencer/python/project.cpp index ea53e5a6..4f149a3d 100644 --- a/src/apps/sequencer/python/project.cpp +++ b/src/apps/sequencer/python/project.cpp @@ -257,6 +257,7 @@ void register_project(py::module &m) { .def_property_readonly("curveTrack", [] (Track &track) { return &track.curveTrack(); }) .def_property_readonly("midiCvTrack", [] (Track &track) { return &track.midiCvTrack(); }) .def_property_readonly("stochasticTrack", [] (Track &track) { return &track.stochasticTrack(); }) + .def_property_readonly("logicTrack", [] (Track &track) { return &track.logicTrack(); }) .def("clear", &Track::clear) .def("clearPattern", &Track::clearPattern, "patternIndex"_a) .def("copyPattern", &Track::copyPattern, "srcIndex"_a, "dstIndex"_a) @@ -267,6 +268,7 @@ void register_project(py::module &m) { .value("Curve", Track::TrackMode::Curve) .value("MidiCv", Track::TrackMode::MidiCv) .value("Stochastic", Track::TrackMode::Stochastic) + .value("Logic", Track::TrackMode::Logic) .export_values() ; @@ -352,6 +354,47 @@ void register_project(py::module &m) { .export_values() ; + // ------------------------------------------------------------------------ + // Logic Track + // ------------------------------------------------------------------------ + + py::class_ logicTrack(m, "LogicTrack"); + logicTrack + .def_property("playMode", &LogicTrack::playMode, &LogicTrack::setPlayMode) + .def_property("fillMode", &LogicTrack::fillMode, &LogicTrack::setFillMode) + .def_property("cvUpdateMode", &LogicTrack::cvUpdateMode, &LogicTrack::setCvUpdateMode) + .def_property("slideTime", &LogicTrack::slideTime, &LogicTrack::setSlideTime) + .def_property("octave", &LogicTrack::octave, &LogicTrack::setOctave) + .def_property("transpose", &LogicTrack::transpose, &LogicTrack::setTranspose) + .def_property("rotate", &LogicTrack::rotate, &LogicTrack::setRotate) + .def_property("gateProbabilityBias", &LogicTrack::gateProbabilityBias, &LogicTrack::setGateProbabilityBias) + .def_property("retriggerProbabilityBias", &LogicTrack::retriggerProbabilityBias, &LogicTrack::setRetriggerProbabilityBias) + .def_property("lengthBias", &LogicTrack::lengthBias, &LogicTrack::setLengthBias) + .def_property("noteProbabilityBias", &LogicTrack::noteProbabilityBias, &LogicTrack::setNoteProbabilityBias) + .def_property_readonly("sequences", [] (LogicTrack &LogicTrack) { + py::list result; + for (int i = 0; i < CONFIG_PATTERN_COUNT; ++i) { + result.append(&LogicTrack.sequence(i)); + } + return result; + }) + .def("clear", &LogicTrack::clear) + ; + + py::enum_(logicTrack, "FillMode") + .value("None", LogicTrack::FillMode::None) + .value("Gates", LogicTrack::FillMode::Gates) + .value("NextPattern", LogicTrack::FillMode::NextPattern) + .value("Condition", LogicTrack::FillMode::Condition) + .export_values() + ; + + py::enum_(logicTrack, "CvUpdateMode") + .value("Gate", LogicTrack::CvUpdateMode::Gate) + .value("Always", LogicTrack::CvUpdateMode::Always) + .export_values() + ; + // ------------------------------------------------------------------------ // CurveTrack // ------------------------------------------------------------------------ diff --git a/src/apps/sequencer/ui/StepSelection.h b/src/apps/sequencer/ui/StepSelection.h index 8cbd81c2..7a1a42de 100644 --- a/src/apps/sequencer/ui/StepSelection.h +++ b/src/apps/sequencer/ui/StepSelection.h @@ -109,21 +109,28 @@ class StepSelection { _first = 0; } - void shiftLeft() { - rotateL(_selected, 1); + void shiftLeft(int lastStep = N) { + rotateL(_selected, lastStep); } - void shiftRight() { - rotateR(_selected, 1); - + void shiftRight(int lastStep = N) { + rotateR(_selected, lastStep); } - inline void rotateR(std::bitset& b, unsigned m) { - b = b << m | b >> (N-m); + inline void rotateR(std::bitset &x, int r) { + const std::bitset m = (1u << r) - 1; // the r low bits set + + x = (x & ~m) | // return the high bits unchanged + ((x << 1) & m) | // left shift & mask + ((x >> (r - 1)) & std::bitset(1u)); // right shift & 1 } - inline void rotateL(std::bitset& b, unsigned m) { - b = b >> m | b << (N-m); + inline void rotateL(std::bitset &x, int r) { + const std::bitset m = (1u << r) - 1; // the r low bits set + + x = (x & ~m) | // return the high bits unchanged + ((x & m) >> 1) | // mask & right shift + ((x & std::bitset(1u)) << (r - 1)); // & 1 and left shift } void selectEqualSteps(int stepIndex) { @@ -220,4 +227,4 @@ class StepSelection { int _first = -1; int8_t _lastPressedIndex; std::function _stepCompare; -}; +}; \ No newline at end of file diff --git a/src/apps/sequencer/ui/controllers/launchpad/LaunchpadController.cpp b/src/apps/sequencer/ui/controllers/launchpad/LaunchpadController.cpp index 142a6919..c00acfd6 100644 --- a/src/apps/sequencer/ui/controllers/launchpad/LaunchpadController.cpp +++ b/src/apps/sequencer/ui/controllers/launchpad/LaunchpadController.cpp @@ -79,6 +79,29 @@ static const LayerMapItem noteSequenceLayerMap[] = { static constexpr int noteSequenceLayerMapSize = sizeof(noteSequenceLayerMap) / sizeof(noteSequenceLayerMap[0]); +static const LayerMapItem logicSequenceLayerMap[] = { + [int(LogicSequence::Layer::Gate)] = { 0, 0 }, + [int(LogicSequence::Layer::GateLogic)] = { 1, 0 }, + [int(LogicSequence::Layer::GateProbability)] = { 2, 0 }, + [int(LogicSequence::Layer::GateOffset)] = { 3, 0 }, + [int(LogicSequence::Layer::Retrigger)] = { 0, 1 }, + [int(LogicSequence::Layer::RetriggerProbability)] = { 1, 1 }, + [int(LogicSequence::Layer::StageRepeats)] = { 2, 1 }, + [int(LogicSequence::Layer::StageRepeatsMode)] = { 3, 1 }, + [int(LogicSequence::Layer::Length)] = { 0, 2 }, + [int(LogicSequence::Layer::LengthVariationRange)] = { 1, 2 }, + [int(LogicSequence::Layer::LengthVariationProbability)] = { 2, 2 }, + [int(LogicSequence::Layer::NoteLogic)] = { 0, 3 }, + [int(LogicSequence::Layer::NoteVariationRange)] = { 1, 3 }, + [int(LogicSequence::Layer::NoteVariationProbability)] = { 2, 3 }, + [int(LogicSequence::Layer::Slide)] = { 3, 3 }, + [int(LogicSequence::Layer::Condition)] = { 0, 4 }, + + +}; + +static constexpr int logicSequenceLayerMapSize = sizeof(logicSequenceLayerMap) / sizeof(logicSequenceLayerMap[0]); + static const LayerMapItem curveSequenceLayerMap[] = { [int(CurveSequence::Layer::Shape)] = { 0, 0 }, [int(CurveSequence::Layer::ShapeVariation)] = { 1, 0 }, @@ -417,8 +440,6 @@ void LaunchpadController::sequenceButton(const Button &button, ButtonAction acti } } else { if (button.isGrid()) { - - if (_noteStyle == 0) { sequenceEditStep(button.row, button.col); } else { @@ -440,8 +461,10 @@ void LaunchpadController::sequenceButton(const Button &button, ButtonAction acti } else { sequenceEditStep(button.row, button.col); } + break; } - + case Track::TrackMode::Logic: + sequenceEditStep(button.row, button.col); break; default: break; @@ -723,6 +746,17 @@ void LaunchpadController::sequenceUpdateNavigation() { break; } + case Track::TrackMode::Logic: { + auto layer = _project.selectedLogicSequenceLayer(); + _sequence.navigation.left = 0; + _sequence.navigation.right = layer == LogicSequence::Layer::Gate || layer == LogicSequence::Layer::Slide ? 0 : 7; + + auto range = LogicSequence::layerRange(_project.selectedLogicSequenceLayer()); + _sequence.navigation.top = range.max / 8; + _sequence.navigation.bottom = (range.min - 7) / 8; + + break; + } default: break; } @@ -758,7 +792,16 @@ void LaunchpadController::sequenceSetLayer(int row, int col) { break; } } - break; + break; + case Track::TrackMode::Logic: + for (int i = 0; i < logicSequenceLayerMapSize; ++i) { + const auto &item = logicSequenceLayerMap[i]; + if (row == item.row && col == item.col) { + _project.setSelectedLogicSequenceLayer(LogicSequence::Layer(i)); + break; + } + } + break; default: break; } @@ -775,6 +818,10 @@ void LaunchpadController::sequenceSetFirstStep(int step) { break; case Track::TrackMode::Stochastic: _project.selectedStochasticSequence().setSequenceFirstStep(step); + break; + case Track::TrackMode::Logic: + _project.selectedLogicSequence().setFirstStep(step); + break; default: break; } @@ -791,6 +838,10 @@ void LaunchpadController::sequenceSetLastStep(int step) { break; case Track::TrackMode::Stochastic: _project.selectedStochasticSequence().setSequenceLastStep(step); + break; + case Track::TrackMode::Logic: + _project.selectedLogicSequence().setLastStep(step); + break; default: break; } @@ -807,6 +858,9 @@ void LaunchpadController::sequenceSetRunMode(int mode) { case Track::TrackMode::Stochastic: _project.selectedStochasticSequence().setRunMode(Types::RunMode(mode)); break; + case Track::TrackMode::Logic: + _project.selectedLogicSequence().setRunMode(Types::RunMode(mode)); + break; default: break; } @@ -854,6 +908,9 @@ void LaunchpadController::sequenceSetFollowMode(int col) { case Track::TrackMode::Curve: _project.selectedTrack().curveTrack().setPatternFollow(Types::PatternFollow(col)); break; + case Track::TrackMode::Logic: + _project.selectedTrack().logicTrack().setPatternFollow(Types::PatternFollow(col)); + break; default: break; } @@ -864,6 +921,9 @@ void LaunchpadController::sequenceToggleStep(int row, int col) { case Track::TrackMode::Note: sequenceToggleNoteStep(row, col); break; + case Track::TrackMode::Logic: + sequenceToggleLogicStep(row, col); + break; default: break; } @@ -885,6 +945,22 @@ void LaunchpadController::sequenceToggleNoteStep(int row, int col) { } } +void LaunchpadController::sequenceToggleLogicStep(int row, int col) { + auto &sequence = _project.selectedLogicSequence(); + auto layer = _project.selectedLogicSequenceLayer(); + + int linearIndex = col + _sequence.navigation.col * 8; + + switch (layer) { + case LogicSequence::Layer::Gate: + case LogicSequence::Layer::Slide: + break; + default: + sequence.step(linearIndex).toggleGate(); + break; + } +} + void LaunchpadController::sequenceEditStep(int row, int col) { switch (_project.selectedTrack().trackMode()) { case Track::TrackMode::Note: @@ -893,9 +969,12 @@ void LaunchpadController::sequenceEditStep(int row, int col) { case Track::TrackMode::Curve: sequenceEditCurveStep(row, col); break; - case Track::Track::TrackMode::Stochastic: + case Track::TrackMode::Stochastic: sequenceEditStochasticStep(row, col); break; + case Track::TrackMode::Logic: + sequenceEditLogicStep(row, col); + break; default: break; } @@ -969,6 +1048,27 @@ void LaunchpadController::sequenceEditStochasticStep(int row, int col) { } } +void LaunchpadController::sequenceEditLogicStep(int row, int col) { + auto &sequence = _project.selectedLogicSequence(); + auto layer = _project.selectedLogicSequenceLayer(); + + int gridIndex = row * 8 + col; + int linearIndex = col + _sequence.navigation.col * 8; + int value = (7 - row) + _sequence.navigation.row * 8; + + switch (layer) { + case LogicSequence::Layer::Gate: + sequence.step(gridIndex).toggleGate(); + break; + case LogicSequence::Layer::Slide: + sequence.step(gridIndex).toggleSlide(); + break; + default: + sequence.step(linearIndex).setLayerValue(layer, value); + break; + } +} + void LaunchpadController::sequenceDrawLayer() { switch (_project.selectedTrack().trackMode()) { @@ -1002,6 +1102,18 @@ void LaunchpadController::sequenceDrawLayer() { setGridLed(item.row, item.col, selected ? colorYellow() : colorGreen()); } break; + case Track::TrackMode::Logic: + for (int i = 0; i < logicSequenceLayerMapSize; ++i) { + const auto &item = logicSequenceLayerMap[i]; + bool selected = i == int(_project.selectedLogicSequenceLayer()); + + auto playMode = _engine.selectedTrackEngine().as().playMode(); + if (playMode == Types::PlayMode::Aligned && (i == 6 || i == 7)) { + continue; + } + setGridLed(item.row, item.col, selected ? colorYellow() : colorGreen()); + } + break; default: break; } @@ -1024,6 +1136,11 @@ void LaunchpadController::sequenceDrawStepRange(int highlight) { drawRange(sequence.sequenceFirstStep(), sequence.sequenceLastStep(), highlight == 0 ? sequence.sequenceFirstStep() : sequence.sequenceLastStep()); break; } + case Track::TrackMode::Logic: { + const auto &sequence = _project.selectedLogicSequence(); + drawRange(sequence.firstStep(), sequence.lastStep(), highlight == 0 ? sequence.firstStep() : sequence.lastStep()); + break; + } default: break; } @@ -1052,6 +1169,10 @@ void LaunchpadController::sequenceDrawRunMode() { drawEnum(_project.selectedStochasticSequence().runMode()); break; } + case Track::TrackMode::Logic: { + drawEnum(_project.selectedLogicSequence().runMode()); + break; + } default: break; } @@ -1067,6 +1188,10 @@ void LaunchpadController::sequenceDrawFollowMode() { drawEnum(_project.selectedTrack().curveTrack().patternFollow()); break; } + case Track::TrackMode::Logic: { + drawEnum(_project.selectedTrack().logicTrack().patternFollow()); + break; + } default: break; } @@ -1083,6 +1208,9 @@ void LaunchpadController::sequenceDrawSequence() { case Track::TrackMode::Stochastic: sequenceDrawStochasticSequence(); break; + case Track::TrackMode::Logic: + sequenceDrawLogicSequence(); + break; default: break; } @@ -1116,6 +1244,31 @@ void LaunchpadController::sequenceDrawNoteSequence() { } } +void LaunchpadController::sequenceDrawLogicSequence() { + const auto &trackEngine = _engine.selectedTrackEngine().as(); + + auto sequence = std::ref(_project.selectedLogicSequence()); + if (_project.playState().songState().playing()) { + auto trackIndex = _project.selectedTrackIndex() ; + sequence = std::ref(_project.selectedTrack().logicTrack().sequence(_project.playState().trackState(trackIndex).pattern())); + } + auto layer = _project.selectedLogicSequenceLayer(); + int currentStep = trackEngine.isActiveSequence(sequence) ? trackEngine.currentStep() : -1; + + switch (layer) { + case LogicSequence::Layer::Gate: + case LogicSequence::Layer::Slide: + drawLogicSequenceBits(sequence, layer, currentStep); + break; + case LogicSequence::Layer::Condition: + drawLogicSequenceDots(sequence, layer, currentStep); + break; + default: + drawLogicSequenceBars(sequence, layer, currentStep); + break; + } +} + void LaunchpadController::sequenceDrawStochasticSequence() { const auto &trackEngine = _engine.selectedTrackEngine().as(); @@ -1223,6 +1376,12 @@ void LaunchpadController::patternDraw() { if (track.stochasticTrack().sequence(patternIndex).isEdited()) { setGridLed(row, trackIndex, colorRed(2)); } + break; + case Track::TrackMode::Logic: + if (track.logicTrack().sequence(patternIndex).isEdited()) { + setGridLed(row, trackIndex, colorRed(2)); + } + break; default: break; } @@ -1319,8 +1478,13 @@ void LaunchpadController::performerEnter() { case Track::TrackMode::Stochastic: { _startingFirstStep[i] = _project.track(i).stochasticTrack().sequence(_project.selectedPatternIndex()).sequenceFirstStep(); _startingLastStep[i] = _project.track(i).stochasticTrack().sequence(_project.selectedPatternIndex()).sequenceLastStep(); + } + break; + case Track::TrackMode::Logic: { + _startingFirstStep[i] = _project.track(i).logicTrack().sequence(_project.selectedPatternIndex()).firstStep(); + _startingLastStep[i] = _project.track(i).logicTrack().sequence(_project.selectedPatternIndex()).lastStep(); + } break; - } default: break; } @@ -1419,6 +1583,24 @@ void LaunchpadController::performerDraw() { setGridLed(row, col, color); } break; + case Track::TrackMode::Logic: { + const auto &trackEngine = _engine.trackEngine(row).as(); + if (_performFollowMode || track.logicTrack().patternFollow() == Types::PatternFollow::DispAndLP || track.logicTrack().patternFollow() == Types::PatternFollow::LaunchPad) { + int stepOffset = (std::max(0, trackEngine.currentStep()) / 8) * 8; + stepIndex = stepOffset + col; + } + auto sequence = track.logicTrack().sequence(_project.selectedPatternIndex()); + currentStep = trackEngine.currentStep(); + Color color = colorOff(); + if (sequence.step(stepIndex).gate()) { + color = colorGreen(2); + } + if (currentStep == stepIndex) { + color = colorRed(); + } + setGridLed(row, col, color); + } + break; default: break; @@ -1445,6 +1627,11 @@ void LaunchpadController::performerDraw() { currentStep = engine.currentStep(); } break; + case Track::TrackMode::Logic: { + auto engine = _engine.selectedTrackEngine().as(); + currentStep = engine.currentStep(); + } + break; default: break; @@ -1533,6 +1720,9 @@ void LaunchpadController::performerButton(const Button &button, ButtonAction act } else if (_project.track(i).trackMode() == Track::TrackMode::Stochastic) { _project.track(i).stochasticTrack().sequence(_project.selectedPatternIndex()).setSequenceFirstStep(fs); _project.track(i).stochasticTrack().sequence(_project.selectedPatternIndex()).setSequenceLastStep(ls); + } else if (_project.track(i).trackMode() == Track::TrackMode::Logic) { + _project.track(i).logicTrack().sequence(_project.selectedPatternIndex()).setFirstStep(fs); + _project.track(i).logicTrack().sequence(_project.selectedPatternIndex()).setLastStep(ls); } } } else if (_performSelectedLayer == 1) { @@ -1553,13 +1743,20 @@ void LaunchpadController::performerButton(const Button &button, ButtonAction act if (stepIndex<12) { track.stochasticTrack().sequence(_project.selectedPatternIndex()).step(stepIndex).toggleGate(); } + break; } + case Track::TrackMode::Logic: { + const auto &trackEngine = _engine.trackEngine(button.row).as(); + if (_performFollowMode) { + int stepOffset = (std::max(0, trackEngine.currentStep()) / 8) * 8; + stepIndex = stepOffset + button.col; + } + track.logicTrack().sequence(_project.selectedPatternIndex()).step(stepIndex).toggleGate(); + } + break; default: break; - } - - } } else if (button.isScene()) { _project.setSelectedTrackIndex(button.scene()); @@ -1584,8 +1781,10 @@ void LaunchpadController::performerButton(const Button &button, ButtonAction act } else if (_project.track(i).trackMode() == Track::TrackMode::Stochastic) { _project.track(i).stochasticTrack().sequence(_project.selectedPatternIndex()).setSequenceFirstStep(_startingFirstStep[i]); _project.track(i).stochasticTrack().sequence(_project.selectedPatternIndex()).setSequenceLastStep(_startingLastStep[i]); + } else if (_project.track(i).trackMode() == Track::TrackMode::Logic) { + _project.track(i).logicTrack().sequence(_project.selectedPatternIndex()).setFirstStep(_startingFirstStep[i]); + _project.track(i).logicTrack().sequence(_project.selectedPatternIndex()).setLastStep(_startingLastStep[i]); } - } } } @@ -2215,6 +2414,55 @@ void LaunchpadController::drawStochasticSequenceNotes(const StochasticSequence & setGridLed(6,2, !sequence.useLoop() && !sequence.isEmpty() && sequence.reseed() == 1 ? colorYellow(): colorYellow(1)); } +void LaunchpadController::drawLogicSequenceBits(const LogicSequence &sequence, LogicSequence::Layer layer, int currentStep) { + const auto &trackEngine = _engine.selectedTrackEngine().as(); + for (int row = 0; row < 8; ++row) { + for (int col = 0; col < 8; ++col) { + int stepIndex = row * 8 + col; + const auto &step = sequence.step(stepIndex); + + Color color = colorOff(); + if (step.gate()) { + color = colorYellow(); + } + if (step.layerValue(layer) != 0) { + color = colorGreen(2); + } + if (stepIndex == currentStep) { + if (trackEngine.gateOutput(stepIndex)) { + color = colorYellow(); + } else { + color = colorRed(); + } + } + + setGridLed(row, col, color); + } + } +} + +void LaunchpadController::drawLogicSequenceBars(const LogicSequence &sequence, LogicSequence::Layer layer, int currentStep) { + for (int col = 0; col < 8; ++col) { + int stepIndex = col + _sequence.navigation.col * 8; + int lastStep = sequence.lastStep(); + followModeAction(currentStep, lastStep); + const auto &step = sequence.step(stepIndex); + drawBar(col, step.layerValue(layer), step.gate(), stepIndex == currentStep); + } +} + +void LaunchpadController::drawLogicSequenceDots(const LogicSequence &sequence, LogicSequence::Layer layer, int currentStep) { + int ofs = _sequence.navigation.row * 8; + for (int col = 0; col < 8; ++col) { + int stepIndex = col + _sequence.navigation.col * 8; + int lastStep = sequence.lastStep(); + followModeAction(currentStep, lastStep); + const auto &step = sequence.step(stepIndex); + int value = step.layerValue(layer); + setGridLed((7 - value) + ofs, col, stepColor(true, stepIndex == currentStep)); + } +} + void LaunchpadController::followModeAction(int currentStep, int lastStep) { if (_engine.state().running()) { bool followMode = false; @@ -2237,6 +2485,13 @@ void LaunchpadController::followModeAction(int currentStep, int lastStep) { } break; } + case Track::TrackMode::Logic: { + auto mode = _project.selectedTrack().logicTrack().patternFollow(); + if (mode == Types::PatternFollow::LaunchPad || mode == Types::PatternFollow::DispAndLP) { + followMode = true; + } + break; + } default: break; } diff --git a/src/apps/sequencer/ui/controllers/launchpad/LaunchpadController.h b/src/apps/sequencer/ui/controllers/launchpad/LaunchpadController.h index 2d1c7d32..0965a50d 100644 --- a/src/apps/sequencer/ui/controllers/launchpad/LaunchpadController.h +++ b/src/apps/sequencer/ui/controllers/launchpad/LaunchpadController.h @@ -106,10 +106,12 @@ class LaunchpadController : public Controller { void sequenceSetFollowMode(int col); void sequenceToggleStep(int row, int col); void sequenceToggleNoteStep(int row, int col); + void sequenceToggleLogicStep(int row, int col); void sequenceEditStep(int row, int col); void sequenceEditNoteStep(int row, int col); void sequenceEditCurveStep(int row, int col); void sequenceEditStochasticStep(int row, int col); + void sequenceEditLogicStep(int row, int col); void sequenceDrawLayer(); @@ -121,6 +123,7 @@ class LaunchpadController : public Controller { void sequenceDrawNoteSequence(); void sequenceDrawCurveSequence(); void sequenceDrawStochasticSequence(); + void sequenceDrawLogicSequence(); void manageCircuitKeyboard(const Button &button); void manageStochasticCircuitKeyboard(const Button &button); @@ -168,6 +171,11 @@ class LaunchpadController : public Controller { void drawStochasticSequenceNotes(const StochasticSequence &sequence, StochasticSequence::Layer layer, int currentStep); void drawStochasticSequenceDots(const StochasticSequence &sequence, StochasticSequence::Layer layer, int currentStep); + void drawLogicSequenceBits(const LogicSequence &sequence, LogicSequence::Layer layer, int currentStep); + void drawLogicSequenceBars(const LogicSequence &sequence, LogicSequence::Layer layer, int currentStep); + void drawLogicSequenceNotes(const LogicSequence &sequence, LogicSequence::Layer layer, int currentStep); + void drawLogicSequenceDots(const LogicSequence &sequence, LogicSequence::Layer layer, int currentStep); + void drawBar(int row, int amount) { for (int i = 0; i < 8; ++i) { diff --git a/src/apps/sequencer/ui/model/ClockSetupListModel.h b/src/apps/sequencer/ui/model/ClockSetupListModel.h index ca9c4f84..54c9cab4 100644 --- a/src/apps/sequencer/ui/model/ClockSetupListModel.h +++ b/src/apps/sequencer/ui/model/ClockSetupListModel.h @@ -48,6 +48,7 @@ class ClockSetupListModel : public ListModel { MidiTx, UsbRx, UsbTx, + FilterNote, Last }; @@ -65,6 +66,7 @@ class ClockSetupListModel : public ListModel { case MidiTx: return "MIDI TX"; case UsbRx: return "USB RX"; case UsbTx: return "USB TX"; + case FilterNote: return "Filter Note"; case Last: break; } return nullptr; @@ -112,6 +114,9 @@ class ClockSetupListModel : public ListModel { case UsbTx: _clockSetup.printUsbTx(str); break; + case FilterNote: + _clockSetup.printFilterNote(str); + break; case Last: break; } @@ -155,6 +160,9 @@ class ClockSetupListModel : public ListModel { case UsbTx: _clockSetup.editUsbTx(value, shift); break; + case FilterNote: + _clockSetup.editFilterNote(value, shift); + break; case Last: break; } diff --git a/src/apps/sequencer/ui/model/CurveTrackListModel.h b/src/apps/sequencer/ui/model/CurveTrackListModel.h index 5031ebfe..34ed78c7 100644 --- a/src/apps/sequencer/ui/model/CurveTrackListModel.h +++ b/src/apps/sequencer/ui/model/CurveTrackListModel.h @@ -64,6 +64,8 @@ class CurveTrackListModel : public RoutableListModel { GateProbabilityBias, PatternFollow, CurveCvInput, + Min, + Max, Last }; @@ -80,6 +82,8 @@ class CurveTrackListModel : public RoutableListModel { case GateProbabilityBias: return "Gate P. Bias"; case PatternFollow: return "Pattern Follow"; case CurveCvInput: return "Curve CV Input"; + case Min: return "Min"; + case Max: return "Max"; case Last: break; } return nullptr; @@ -124,6 +128,12 @@ class CurveTrackListModel : public RoutableListModel { case CurveCvInput: _track->printCurveCvInput(str); break; + case Min: + _track->printMin(str); + break; + case Max: + _track->printMax(str); + break; case Last: break; } @@ -163,6 +173,12 @@ class CurveTrackListModel : public RoutableListModel { case CurveCvInput: _track->editCurveCvInput(value, shift); break; + case Min: + _track->editMin(value, shift); + break; + case Max: + _track->editMax(value, shift); + break; case Last: break; } diff --git a/src/apps/sequencer/ui/model/LogicSequenceListModel.h b/src/apps/sequencer/ui/model/LogicSequenceListModel.h new file mode 100644 index 00000000..bf010849 --- /dev/null +++ b/src/apps/sequencer/ui/model/LogicSequenceListModel.h @@ -0,0 +1,278 @@ +#pragma once + +#include "Config.h" + +#include "RoutableListModel.h" + +#include "model/LogicSequence.h" +#include "model/Scale.h" +#include + +class LogicSequenceListModel : public RoutableListModel { +public: + enum Item { + Name, + FirstStep, + LastStep, + RunMode, + Divisor, + ResetMeasure, + Scale, + RootNote, + Last + }; + + LogicSequenceListModel() + { + _scales[0] = -1; + for (int i = 1; i < 23; ++i) { + _scales[i] = i-1; + } + + for (int i = 0; i < 8; ++i) { + _selectedScale[i] = 0; + } + } + + void setSequence(LogicSequence *sequence) { + _sequence = sequence; + if (sequence != nullptr) { + int trackIndex = _sequence->trackIndex(); + _selectedScale[trackIndex] = sequence->scale()+1; + } + } + + virtual int rows() const override { + return _sequence ? Last : 0; + } + + virtual int columns() const override { + return 2; + } + + virtual void cell(int row, int column, StringBuilder &str) const override { + if (column == 0) { + formatName(Item(row), str); + } else if (column == 1) { + formatValue(Item(row), str); + } + } + + virtual void edit(int row, int column, int value, bool shift) override { + if (column == 1) { + editValue(Item(row), value, shift); + } + } + + virtual int indexedCount(int row) const override { + return indexedCountValue(Item(row)); + } + + virtual int indexed(int row) const override { + return indexedValue(Item(row)); + } + + virtual void setIndexed(int row, int index) override { + if (index >= 0 && index < indexedCount(row)) { + setIndexedValue(Item(row), index); + } + } + + virtual Routing::Target routingTarget(int row) const override { + switch (Item(row)) { + case Divisor: + return Routing::Target::Divisor; + case FirstStep: + return Routing::Target::FirstStep; + case LastStep: + return Routing::Target::LastStep; + case RunMode: + return Routing::Target::RunMode; + case Scale: + return Routing::Target::Scale; + case RootNote: + return Routing::Target::RootNote; + default: + return Routing::Target::None; + } + } + + void setSelectedScale(int defaultScale, bool force = false) override { + if (_editScale || force) { + _sequence->editScale(_scales[_selectedScale[_sequence->trackIndex()]], false, defaultScale); + } + _editScale = !_editScale; + } + +private: + static const char *itemName(Item item) { + switch (item) { + case Name: return "Name"; + case FirstStep: return "First Step"; + case LastStep: return "Last Step"; + case RunMode: return "Run Mode"; + case Divisor: return "Divisor"; + case ResetMeasure: return "Reset Measure"; + case Scale: return "Scale"; + case RootNote: return "Root Note"; + case Last: break; + } + return nullptr; + } + + void formatName(Item item, StringBuilder &str) const { + str(itemName(item)); + } + + void formatValue(Item item, StringBuilder &str) const { + switch (item) { + case Name: + str(_sequence->name()); + break; + case FirstStep: + _sequence->printFirstStep(str); + break; + case LastStep: + _sequence->printLastStep(str); + break; + case RunMode: + _sequence->printRunMode(str); + break; + case Divisor: + _sequence->printDivisor(str); + break; + case ResetMeasure: + _sequence->printResetMeasure(str); + break; + case Scale: { + int trackIndex = _sequence->trackIndex(); + bool isRouted = Routing::isRouted(Routing::Target::Scale, trackIndex); + if (isRouted) { + _sequence->printScale(str); + } else { + auto name = _scales[_selectedScale[trackIndex]] < 0 ? "Default" : Scale::name(_scales[_selectedScale[trackIndex]]); + str(name); + } + } + break; + case RootNote: + _sequence->printRootNote(str); + break; + case Last: + break; + } + } + + void editValue(Item item, int value, bool shift) { + switch (item) { + case Name: + break; + case FirstStep: + _sequence->editFirstStep(value, shift); + break; + case LastStep: + _sequence->editLastStep(value, shift); + break; + case RunMode: + _sequence->editRunMode(value, shift); + break; + case Divisor: + _sequence->editDivisor(value, shift); + break; + case ResetMeasure: + _sequence->editResetMeasure(value, shift); + break; + case Scale: { + int trackIndex = _sequence->trackIndex(); + bool isRouted = Routing::isRouted(Routing::Target::Scale, trackIndex); + if (!isRouted) { + int trackIndex = _sequence->trackIndex(); + _selectedScale[trackIndex] = clamp(_selectedScale[trackIndex] + value, 0, 23); + } + } + break; + case RootNote: + _sequence->editRootNote(value, shift); + break; + case Last: + break; + } + } + + int indexedCountValue(Item item) const { + switch (item) { + case Name: + break; + case FirstStep: + case LastStep: + return 16; + case RunMode: + return int(Types::RunMode::Last); + case Divisor: + case ResetMeasure: + return 16; + case Scale: + return Scale::Count + 1; + case RootNote: + return 12 + 1; + case Last: + break; + } + return -1; + } + + int indexedValue(Item item) const { + switch (item) { + case Name: + break; + case FirstStep: + return _sequence->firstStep(); + case LastStep: + return _sequence->lastStep(); + case RunMode: + return int(_sequence->runMode()); + case Divisor: + return _sequence->indexedDivisor(); + case ResetMeasure: + return _sequence->resetMeasure(); + case Scale: + return _sequence->indexedScale(); + case RootNote: + return _sequence->indexedRootNote(); + case Last: + break; + } + return -1; + } + + void setIndexedValue(Item item, int index) { + switch (item) { + case Name: + break; + case FirstStep: + return _sequence->setFirstStep(index); + case LastStep: + return _sequence->setLastStep(index); + case RunMode: + return _sequence->setRunMode(Types::RunMode(index)); + case Divisor: + return _sequence->setIndexedDivisor(index); + case ResetMeasure: + return _sequence->setResetMeasure(index); + case Scale: + return _sequence->setIndexedScale(index); + case RootNote: + return _sequence->setIndexedRootNote(index); + case Last: + break; + } + } + + LogicSequence *_sequence; + private: + std::array _scales; + std::array _selectedScale; + bool _editScale = false; + + std::array _logicTracks; +}; diff --git a/src/apps/sequencer/ui/model/LogicTrackListModel.h b/src/apps/sequencer/ui/model/LogicTrackListModel.h new file mode 100644 index 00000000..65866dff --- /dev/null +++ b/src/apps/sequencer/ui/model/LogicTrackListModel.h @@ -0,0 +1,227 @@ +#pragma once + +#include "Config.h" + +#include "RoutableListModel.h" + +#include "model/LogicTrack.h" +#include + +class LogicTrackListModel : public RoutableListModel { +public: + void setTrack(LogicTrack &track) { + _track = &track; + } + + virtual int rows() const override { + return Last; + } + + virtual int columns() const override { + return 2; + } + + virtual void cell(int row, int column, StringBuilder &str) const override { + if (column == 0) { + formatName(Item(row), str); + } else if (column == 1) { + formatValue(Item(row), str); + } + } + + virtual void edit(int row, int column, int value, bool shift) override { + if (column == 1) { + editValue(Item(row), value, shift); + } + } + + virtual Routing::Target routingTarget(int row) const override { + switch (Item(row)) { + case SlideTime: + return Routing::Target::SlideTime; + case Octave: + return Routing::Target::Octave; + case Transpose: + return Routing::Target::Transpose; + case Rotate: + return Routing::Target::Rotate; + case GateProbabilityBias: + return Routing::Target::GateProbabilityBias; + case RetriggerProbabilityBias: + return Routing::Target::RetriggerProbabilityBias; + case LengthBias: + return Routing::Target::LengthBias; + case NoteProbabilityBias: + return Routing::Target::NoteProbabilityBias; + default: + return Routing::Target::None; + } + } + +private: + enum Item { + TrackName, + PlayMode, + FillMode, + FillMuted, + CvUpdateMode, + SlideTime, + Octave, + Transpose, + Rotate, + GateProbabilityBias, + RetriggerProbabilityBias, + LengthBias, + NoteProbabilityBias, + PatternFollow, + InputTrack1, + InputTrack2, + DetailedView, + Last + }; + + static const char *itemName(Item item) { + switch (item) { + case TrackName: return "Name"; + case PlayMode: return "Play Mode"; + case FillMode: return "Fill Mode"; + case FillMuted: return "Fill Muted"; + case CvUpdateMode: return "CV Update Mode"; + case SlideTime: return "Slide Time"; + case Octave: return "Octave"; + case Transpose: return "Transpose"; + case Rotate: return "Rotate"; + case GateProbabilityBias: return "Gate P. Bias"; + case RetriggerProbabilityBias: return "Retrig P. Bias"; + case LengthBias: return "Length Bias"; + case NoteProbabilityBias: return "Note P. Bias"; + case PatternFollow: return "Pattern Follow"; + case InputTrack1: return "Input Trk 1"; + case InputTrack2: return "Input Trk 2"; + case DetailedView: return "Detail View"; + case Last: break; + } + return nullptr; + } + + void formatName(Item item, StringBuilder &str) const { + str(itemName(item)); + } + + void formatValue(Item item, StringBuilder &str) const { + switch (item) { + case TrackName: + str(_track->name()); + break; + case PlayMode: + _track->printPlayMode(str); + break; + case FillMode: + _track->printFillMode(str); + break; + case FillMuted: + _track->printFillMuted(str); + break; + case CvUpdateMode: + _track->printCvUpdateMode(str); + break; + case SlideTime: + _track->printSlideTime(str); + break; + case Octave: + _track->printOctave(str); + break; + case Transpose: + _track->printTranspose(str); + break; + case Rotate: + _track->printRotate(str); + break; + case GateProbabilityBias: + _track->printGateProbabilityBias(str); + break; + case RetriggerProbabilityBias: + _track->printRetriggerProbabilityBias(str); + break; + case LengthBias: + _track->printLengthBias(str); + break; + case NoteProbabilityBias: + _track->printNoteProbabilityBias(str); + break; + case PatternFollow: + _track->printPatternFollow(str); + break; + case InputTrack1: + _track->printInputTrack1(str); + break; + case InputTrack2: + _track->printInputTrack2(str); + break; + case DetailedView: + _track->printDetailedView(str); + break; + case Last: + break; + } + } + + void editValue(Item item, int value, bool shift) { + switch (item) { + + case TrackName: + break; + case PlayMode: + _track->editPlayMode(value, shift); + break; + case FillMode: + _track->editFillMode(value, shift); + break; + case FillMuted: + _track->editFillMuted(value, shift); + break; + case CvUpdateMode: + _track->editCvUpdateMode(value, shift); + break; + case SlideTime: + _track->editSlideTime(value, shift); + break; + case Octave: + _track->editOctave(value, shift); + break; + case Transpose: + _track->editTranspose(value, shift); + break; + case Rotate: + _track->editRotate(value, shift); + break; + case GateProbabilityBias: + _track->editGateProbabilityBias(value, shift); + break; + case RetriggerProbabilityBias: + _track->editRetriggerProbabilityBias(value, shift); + break; + case LengthBias: + _track->editLengthBias(value, shift); + break; + case NoteProbabilityBias: + _track->editNoteProbabilityBias(value, shift); + break; + case PatternFollow: + _track->editPatternFollow(value, shift); + break; + case InputTrack1: + case InputTrack2: + break; + case DetailedView: + _track->editDetailedView(value, shift); + break; + case Last: + break; + } + } + + virtual void setSelectedScale(int defaultScale, bool force = false) override {}; + + LogicTrack *_track; +}; diff --git a/src/apps/sequencer/ui/model/NoteTrackListModel.h b/src/apps/sequencer/ui/model/NoteTrackListModel.h index 7eb5ad1d..8bc6cdce 100644 --- a/src/apps/sequencer/ui/model/NoteTrackListModel.h +++ b/src/apps/sequencer/ui/model/NoteTrackListModel.h @@ -5,13 +5,25 @@ #include "RoutableListModel.h" #include "model/NoteTrack.h" +#include class NoteTrackListModel : public RoutableListModel { public: + + NoteTrackListModel() { + for (int i = 0; i< 8; ++i) { + _selectedTrack[i] = -1; + } + } + void setTrack(NoteTrack &track) { _track = &track; } + void setAvailableLogicTracks(std::vector availableLogicTracks) { + _availableLogicTracks = availableLogicTracks; + } + virtual int rows() const override { return Last; } @@ -73,6 +85,8 @@ class NoteTrackListModel : public RoutableListModel { LengthBias, NoteProbabilityBias, PatternFollow, + LogicTrack, + LogicTrackInput, Last }; @@ -92,6 +106,8 @@ class NoteTrackListModel : public RoutableListModel { case LengthBias: return "Length Bias"; case NoteProbabilityBias: return "Note P. Bias"; case PatternFollow: return "Pattern Follow"; + case LogicTrack: return "Logic Track"; + case LogicTrackInput: return "Logic Track In"; case Last: break; } return nullptr; @@ -145,6 +161,12 @@ class NoteTrackListModel : public RoutableListModel { case PatternFollow: _track->printPatternFollow(str); break; + case LogicTrack: + _track->printLogicTrack(str); + break; + case LogicTrackInput: + _track->printLogicTrackInput(str); + break; case Last: break; } @@ -194,6 +216,56 @@ class NoteTrackListModel : public RoutableListModel { case PatternFollow: _track->editPatternFollow(value, shift); break; + case LogicTrack: { + + if (_availableLogicTracks.size() == 0) { + break; + } + + if (_track->logicTrackInput() != -1) { + break; + } + if (value == -1 && _selectedTrack[_track->trackIndex()] == -1) { + break; + } + + if (value == -1 && _selectedTrack[_track->trackIndex()] == _availableLogicTracks.front()) { + _track->setLogicTrack(-1); + _selectedTrack[_track->trackIndex()] = -1; + break; + } + + if (value == 1 && _selectedTrack[_track->trackIndex()] == _availableLogicTracks.back()) { + break; + } + + if (value == 1) { + for (int i = 0; i < 8; ++i ) { + if (std::find(_availableLogicTracks.begin(), _availableLogicTracks.end(), i) != _availableLogicTracks.end() && i != _selectedTrack[_track->trackIndex()]) { + _track->setLogicTrack(i); + _selectedTrack[_track->trackIndex()] = i; + break; + } + } + } else { + for (int i = 7; i > 0; --i ) { + if (std::find(_availableLogicTracks.begin(), _availableLogicTracks.end(), i) != _availableLogicTracks.end() && i != _selectedTrack[_track->trackIndex()]) { + _track->setLogicTrack(i); + _selectedTrack[_track->trackIndex()] = i; + break; + } + } + } + + + } + break; + case LogicTrackInput: + if (_track->logicTrack()==-1) { + return; + } + _track->editLogicTrackInput(value, shift); + break; case Last: break; } @@ -202,4 +274,7 @@ class NoteTrackListModel : public RoutableListModel { virtual void setSelectedScale(int defaultScale, bool force = false) override {}; NoteTrack *_track; + + std::vector _availableLogicTracks; + int _selectedTrack[8]; }; diff --git a/src/apps/sequencer/ui/model/StochasticTrackListModel.h b/src/apps/sequencer/ui/model/StochasticTrackListModel.h index 8c153765..6c46d91d 100644 --- a/src/apps/sequencer/ui/model/StochasticTrackListModel.h +++ b/src/apps/sequencer/ui/model/StochasticTrackListModel.h @@ -61,8 +61,8 @@ class StochasticTrackListModel : public RoutableListModel { enum Item { TrackName, PlayMode, - //FillMode, - //FillMuted, + FillMode, + FillMuted, CvUpdateMode, SlideTime, Octave, @@ -79,8 +79,8 @@ class StochasticTrackListModel : public RoutableListModel { switch (item) { case TrackName: return "Name"; case PlayMode: return "Play Mode"; - //case FillMode: return "Fill Mode"; - //case FillMuted: return "Fill Muted"; + case FillMode: return "Fill Mode"; + case FillMuted: return "Fill Muted"; case CvUpdateMode: return "CV Update Mode"; case SlideTime: return "Slide Time"; case Octave: return "Octave"; @@ -107,12 +107,12 @@ class StochasticTrackListModel : public RoutableListModel { case PlayMode: _track->printPlayMode(str); break; - /*case FillMode: + case FillMode: _track->printFillMode(str); break; case FillMuted: _track->printFillMuted(str); - break;*/ + break; case CvUpdateMode: _track->printCvUpdateMode(str); break; @@ -153,12 +153,12 @@ class StochasticTrackListModel : public RoutableListModel { case PlayMode: //_track->editPlayMode(value, shift); break; - /*case FillMode: + case FillMode: _track->editFillMode(value, shift); break; case FillMuted: _track->editFillMuted(value, shift); - break;*/ + break; case CvUpdateMode: _track->editCvUpdateMode(value, shift); break; diff --git a/src/apps/sequencer/ui/model/TrackModeListModel.h b/src/apps/sequencer/ui/model/TrackModeListModel.h index 757da63b..05c4ae9f 100644 --- a/src/apps/sequencer/ui/model/TrackModeListModel.h +++ b/src/apps/sequencer/ui/model/TrackModeListModel.h @@ -39,7 +39,30 @@ class TrackModeListModel : public ListModel { newTrackModes[i] = _trackModes[i]; } for (int i = 0; i < CONFIG_TRACK_COUNT; ++i) { + int logicTrack = -1; if (newTrackModes[i] != project.track(i).trackMode()) { + if (project.track(i).trackMode() == Track::TrackMode::Logic) { + // reset related logic track inputs + logicTrack = i; + auto in1 = project.track(i).logicTrack().inputTrack1(); + if (in1 != -1) { + project.track(in1).noteTrack().setLogicTrack(-1); + project.track(in1).noteTrack().setLogicTrackInput(-1); + } + auto in2 = project.track(i).logicTrack().inputTrack2(); + if (in2 != -1 ) { + project.track(in2).noteTrack().setLogicTrack(-1); + project.track(in2).noteTrack().setLogicTrackInput(-1); + } + for (int j = 0; j < CONFIG_TRACK_COUNT; ++j) { + if (project.track(j).trackMode() == Track::TrackMode::Note && logicTrack != -1) { + if (project.track(j).noteTrack().logicTrack() == logicTrack) { + project.track(j).noteTrack().setLogicTrack(-1); + } + } + } + logicTrack = -1; + } project.setTrackMode(i, newTrackModes[i]); } } @@ -64,6 +87,9 @@ class TrackModeListModel : public ListModel { virtual void edit(int row, int column, int value, bool shift) override { if (column == 1) { _trackModes[row] = ModelUtils::adjustedEnum(_trackModes[row], value); + if (row < 2 && _trackModes[row] == Track::TrackMode::Logic) { + _trackModes[row] = Track::TrackMode::Stochastic; + } } } diff --git a/src/apps/sequencer/ui/pages/LogicSequenceEditPage.cpp b/src/apps/sequencer/ui/pages/LogicSequenceEditPage.cpp new file mode 100644 index 00000000..fd8cb34d --- /dev/null +++ b/src/apps/sequencer/ui/pages/LogicSequenceEditPage.cpp @@ -0,0 +1,1210 @@ +#include "LogicSequenceEditPage.h" + +#include "LayoutPage.h" +#include "Pages.h" + +#include "model/LogicSequence.h" +#include "ui/LedPainter.h" +#include "ui/painters/SequencePainter.h" +#include "ui/painters/WindowPainter.h" +#include "engine/SequenceUtils.h" + +#include "model/Scale.h" + +#include "os/os.h" + +#include "core/utils/StringBuilder.h" +#include +#include +#include + + +enum class ContextAction { + Init, + Copy, + Paste, + Duplicate, Generate, + Last +}; + +static const ContextMenuModel::Item contextMenuItems[] = { + { "INIT" }, + { "COPY" }, + { "PASTE" }, + { "DUPL" }, + { "GEN" }, +}; + +enum class Function { + Gate = 0, + Retrigger = 1, + Length = 2, + Note = 3, + Condition = 4, +}; + +static const char *functionNames[] = { "GATE", "RETRIG", "LENGTH", "NOTE", "COND" }; + + +static const LogicSequenceListModel::Item quickEditItems[8] = { + LogicSequenceListModel::Item::FirstStep, + LogicSequenceListModel::Item::LastStep, + LogicSequenceListModel::Item::RunMode, + LogicSequenceListModel::Item::Divisor, + LogicSequenceListModel::Item::ResetMeasure, + LogicSequenceListModel::Item::Scale, + LogicSequenceListModel::Item::RootNote, + LogicSequenceListModel::Item::Last +}; + +LogicSequenceEditPage::LogicSequenceEditPage(PageManager &manager, PageContext &context) : + BasePage(manager, context) +{ + _stepSelection.setStepCompare([this] (int a, int b) { + auto layer = _project.selectedLogicSequenceLayer(); + const auto &sequence = _project.selectedLogicSequence(); + return sequence.step(a).layerValue(layer) == sequence.step(b).layerValue(layer); + }); +} + +void LogicSequenceEditPage::enter() { + updateMonitorStep(); + + _inMemorySequence = _project.selectedLogicSequence(); + auto &trackEngine = _engine.selectedTrackEngine().as(); + auto *ne1 = &trackEngine.input1TrackEngine(); + auto &track = _project.selectedTrack().logicTrack(); + + if (ne1==nullptr && track.inputTrack1()!=-1) { + auto *ne = &_engine.trackEngine(track.inputTrack1()).as(); + trackEngine.setInput1TrackEngine(ne); + } + auto *ne2 = &trackEngine.input2TrackEngine(); + if (ne2==nullptr && track.inputTrack2()!=-1) { + auto &track = _project.selectedTrack().logicTrack(); + auto *ne = &_engine.trackEngine(track.inputTrack2()).as(); + trackEngine.setInput2TrackEngine(ne); + } + + _showDetail = false; +} + +void LogicSequenceEditPage::exit() { + _engine.selectedTrackEngine().as().setMonitorStep(-1); +} + +void LogicSequenceEditPage::draw(Canvas &canvas) { + WindowPainter::clear(canvas); + + auto &track = _project.selectedTrack().logicTrack(); + + /* Prepare flags shown before mode name (top right header) */ + const auto pattern_follow = track.patternFollow(); + const char* pf_repr = Types::patternFollowShortRepresentation(pattern_follow); + + WindowPainter::drawHeader(canvas, _model, _engine, "STEPS", pf_repr); + + WindowPainter::drawActiveFunction(canvas, LogicSequence::layerName(layer())); + WindowPainter::drawFooter(canvas, functionNames, pageKeyState(), activeFunctionKey()); + + const auto &trackEngine = _engine.selectedTrackEngine().as(); + + auto &sequence = _project.selectedLogicSequence(); + int currentStep = trackEngine.isActiveSequence(sequence) ? trackEngine.currentStep() : -1; + + const int stepWidth = Width / StepCount; + const int stepOffset = this->stepOffset(); + + const int loopY = 16; + + // Track Pattern Section on the UI + if (track.isPatternFollowDisplayOn() && _engine.state().running()) { + bool section_change = bool((currentStep) % StepCount == 0); // StepCount is relative to screen + int section_no = int((currentStep) / StepCount); + if (section_change && section_no != sequence.section()) { + sequence.setSecion(section_no); + } + } + + // draw loop points + canvas.setBlendMode(BlendMode::Set); + canvas.setColor(Color::Bright); + SequencePainter::drawLoopStart(canvas, (sequence.firstStep() - stepOffset) * stepWidth + 1, loopY, stepWidth - 2); + SequencePainter::drawLoopEnd(canvas, (sequence.lastStep() - stepOffset) * stepWidth + 1, loopY, stepWidth - 2); + + for (int i = 0; i < StepCount; ++i) { + int stepIndex = stepOffset + i; + const auto &step = sequence.step(stepIndex); + + int x = i * stepWidth; + int y = 20; + + // loop + if (stepIndex > sequence.firstStep() && stepIndex <= sequence.lastStep()) { + canvas.setColor(Color::Bright); + canvas.point(x, loopY); + } + + // step index + { + canvas.setColor(_stepSelection[stepIndex] ? Color::Bright : Color::Medium); + FixedStringBuilder<8> str("%d", stepIndex + 1); + canvas.drawText(x + (stepWidth - canvas.textWidth(str) + 1) / 2, y - 2, str); + } + + // step gate + canvas.setColor(stepIndex == currentStep ? Color::Bright : Color::Medium); + canvas.drawRect(x + 2, y + 2, stepWidth - 4, stepWidth - 4); + if (step.gate()) { + canvas.setColor(_context.model.settings().userSettings().get(SettingDimSequence)->getValue() ? Color::Low : Color::Bright); + if (stepIndex == currentStep) { + if (trackEngine.gateOutput(currentStep)) { + canvas.fillRect(x + 6, y + 6, stepWidth - 12, stepWidth - 12); + canvas.setColor(Color::Medium); + canvas.hline(x + 7, y + 5, 2); + canvas.hline(x + 7, y + 10, 2); + canvas.vline(x + 5, y + 7, 2); + canvas.vline(x + 10, y + 7, 2); + } else { + canvas.fillRect(x + 4, y + 4, stepWidth - 8, stepWidth - 8); + } + } else { + canvas.fillRect(x + 4, y + 4, stepWidth - 8, stepWidth - 8); + } + } else { + if (track.detailedView()) { + if (track.inputTrack1() != -1) { + const int currentStep1 = trackEngine.input1TrackEngine().currentStep(); + int stepIndex1 = currentStep1 != -1 ? (currentStep1 - currentStep) + stepIndex : stepIndex; + const auto ¬eTrack = _project.track(track.inputTrack1()).noteTrack(); + const auto &inputSeq1 = noteTrack.sequence(_project.selectedPatternIndex()); + int idx = SequenceUtils::rotateStep(stepIndex1, inputSeq1.firstStep(), inputSeq1.lastStep(), noteTrack.rotate()); + if (inputSeq1.step(idx).gate()) { + canvas.fillRect(x + 6, y + 6, 4, 4); + } + } + if (track.inputTrack2() != -1) { + const int currentStep2 = trackEngine.input2TrackEngine().currentStep(); + int stepIndex2 = currentStep2 != -1 ? (currentStep2 - currentStep) + stepIndex : stepIndex; + const auto ¬eTrack = _project.track(track.inputTrack2()).noteTrack(); + const auto &inputSeq2 = noteTrack.sequence(_project.selectedPatternIndex()); + int idx = SequenceUtils::rotateStep(stepIndex2, inputSeq2.firstStep(), inputSeq2.lastStep(), noteTrack.rotate()); + if (inputSeq2.step(idx).gate()) { + canvas.hline(x + 4, y + 4, 8); + canvas.hline(x + 4, y + 11, 8); + canvas.vline(x + 4, y + 4, 8); + canvas.vline(x + 11, y + 4, 7); + } + } + } + + } + + switch (layer()) { + case Layer::Gate: + break; + case Layer::GateLogic: + SequencePainter::drawGateLogicMode( + canvas, + x + 2, y + 18, stepWidth - 4, 6, + step.gateLogic() + ); + break; + case Layer::GateProbability: + SequencePainter::drawProbability( + canvas, + x + 2, y + 18, stepWidth - 4, 2, + step.gateProbability() + 1, LogicSequence::GateProbability::Range + ); + break; + case Layer::GateOffset: + SequencePainter::drawOffset( + canvas, + x + 2, y + 18, stepWidth - 4, 2, + step.gateOffset(), LogicSequence::GateOffset::Min - 1, LogicSequence::GateOffset::Max + 1 + ); + break; + case Layer::Retrigger: + SequencePainter::drawRetrigger( + canvas, + x, y + 18, stepWidth, 2, + step.retrigger() + 1, NoteSequence::Retrigger::Range + ); + break; + case Layer::RetriggerProbability: + SequencePainter::drawProbability( + canvas, + x + 2, y + 18, stepWidth - 4, 2, + step.retriggerProbability() + 1, NoteSequence::RetriggerProbability::Range + ); + break; + case Layer::Length: + SequencePainter::drawLength( + canvas, + x + 2, y + 18, stepWidth - 4, 6, + step.length() + 1, LogicSequence::Length::Range + ); + break; + case Layer::LengthVariationRange: + SequencePainter::drawLengthRange( + canvas, + x + 2, y + 18, stepWidth - 4, 6, + step.length() + 1, step.lengthVariationRange(), LogicSequence::Length::Range + ); + break; + case Layer::LengthVariationProbability: + SequencePainter::drawProbability( + canvas, + x + 2, y + 18, stepWidth - 4, 2, + step.lengthVariationProbability() + 1, LogicSequence::LengthVariationProbability::Range + ); + break; + case Layer::NoteLogic: { + SequencePainter::drawNoteLogicMode( + canvas, + x + 2, y + 18, stepWidth - 4, 6, + step.noteLogic() + ); + break; + } + case Layer::NoteVariationRange: { + canvas.setColor(Color::Bright); + FixedStringBuilder<8> str("%d", step.noteVariationRange()); + canvas.drawText(x + (stepWidth - canvas.textWidth(str) + 1) / 2, y + 20, str); + break; + } + case Layer::NoteVariationProbability: + SequencePainter::drawProbability( + canvas, + x + 2, y + 18, stepWidth - 4, 2, + step.noteVariationProbability() + 1, LogicSequence::NoteVariationProbability::Range + ); + break; + + case Layer::Slide: + SequencePainter::drawSlide( + canvas, + x + 4, y + 18, stepWidth - 8, 4, + step.slide() + ); + break; + case Layer::Condition: { + canvas.setColor(Color::Bright); + FixedStringBuilder<8> str; + Types::printCondition(str, step.condition(), Types::ConditionFormat::Short1); + canvas.drawText(x + (stepWidth - canvas.textWidth(str) + 1) / 2, y + 20, str); + str.reset(); + Types::printCondition(str, step.condition(), Types::ConditionFormat::Short2); + canvas.drawText(x + (stepWidth - canvas.textWidth(str) + 1) / 2, y + 27, str); + break; + } + case Layer::StageRepeats: { + canvas.setColor(Bright); + FixedStringBuilder<8> str("x%d", step.stageRepeats()+1); + canvas.drawText(x + (stepWidth - canvas.textWidth(str) + 1) / 2, y + 20, str); + break; + } + case Layer::StageRepeatsMode: { + SequencePainter::drawStageRepeatMode( + canvas, + x + 2, y + 18, stepWidth - 4, 6, + step.stageRepeatMode() + ); + break; + } + case Layer::Last: + break; + } + } + + // handle detail display + + if (_showDetail) { + if (layer() == Layer::Gate || layer() == Layer::Slide || _stepSelection.none()) { + _showDetail = false; + } + if (_stepSelection.isPersisted() && os::ticks() > _showDetailTicks + os::time::ms(500)) { + _showDetail = false; + } + } + + if (_showDetail) { + drawDetail(canvas, sequence.step(_stepSelection.first())); + } + + + +} + +void LogicSequenceEditPage::updateLeds(Leds &leds) { + const auto &trackEngine = _engine.selectedTrackEngine().as(); + auto &sequence = _project.selectedLogicSequence(); + int currentStep = trackEngine.isActiveSequence(sequence) ? trackEngine.currentStep() : -1; + + for (int i = 0; i < 16; ++i) { + int stepIndex = stepOffset() + i; + bool red = (stepIndex == currentStep) || _stepSelection[stepIndex]; + bool green = (stepIndex != currentStep) && (sequence.step(stepIndex).gate() || _stepSelection[stepIndex]); + leds.set(MatrixMap::fromStep(i), red, green); + } + + LedPainter::drawSelectedSequenceSection(leds, sequence.section()); + + // show quick edit keys + if (globalKeyState()[Key::Page] && !globalKeyState()[Key::Shift]) { + for (int i = 0; i < 8; ++i) { + int index = MatrixMap::fromStep(i + 8); + leds.unmask(index); + leds.set(index, false, quickEditItems[i] != LogicSequenceListModel::Item::Last); + leds.mask(index); + } + int index = MatrixMap::fromStep(15); + leds.unmask(index); + leds.set(index, false, true); + leds.mask(index); + index = MatrixMap::fromStep(4); + leds.unmask(index); + leds.set(index, false, true); + leds.mask(index); + } +} + +void LogicSequenceEditPage::keyDown(KeyEvent &event) { + _stepSelection.keyDown(event, stepOffset()); + updateMonitorStep(); +} + +void LogicSequenceEditPage::keyUp(KeyEvent &event) { + _stepSelection.keyUp(event, stepOffset()); + updateMonitorStep(); +} + +void LogicSequenceEditPage::keyPress(KeyPressEvent &event) { + const auto &key = event.key(); + auto &sequence = _project.selectedLogicSequence(); + auto &track = _project.selectedTrack().logicTrack(); + + if (key.isContextMenu()) { + contextShow(); + event.consume(); + return; + } + if (key.pageModifier() && event.count() == 2) { + contextShow(true); + event.consume(); + return; + } + + if (key.isQuickEdit()) { + if (key.is(Key::Step15)) { + bool lpConnected = _engine.isLaunchpadConnected(); + + track.togglePatternFollowDisplay(lpConnected); + } else { + _inMemorySequence = _project.selectedLogicSequence(); + quickEdit(key.quickEdit()); + } + event.consume(); + return; + } + + if (key.pageModifier() && key.is(Key::Step6)) { + // undo function + _project.setselectedLogicSequence(_inMemorySequence); + event.consume(); + return; + } + + if (key.pageModifier() && key.is(Key::Step4)) { + track.toggleDetailedView(); + event.consume(); + return; + } + + if (key.pageModifier()) { + return; + } + + + if (key.isFunction()) { + int v = 0; + switch (key.code()) { + case Key::F0: + v=1; + break; + case Key::F1: + v=2; + break; + case Key::F2: + v=3; + break; + case Key::F3: + v=4; + break; + case Key::F4: + v=5; + break; + } + for (int i=0; i<16; ++i) { + if (key.state(i)) { + const auto &scale = sequence.selectedScale(_project.scale()); + int stepIndex = 0; + if (i>=8) { + stepIndex = i -8; + } else { + stepIndex = i+8; + } + sequence.step(stepIndex).setNote(scale.notesPerOctave()*v); + event.consume(); + return; + + } + } + + } + _stepSelection.keyPress(event, stepOffset()); + updateMonitorStep(); + + if (!key.shiftModifier() && key.isStep()) { + int stepIndex = stepOffset() + key.step(); + switch (layer()) { + case Layer::Gate: + _inMemorySequence = _project.selectedLogicSequence(); + sequence.step(stepIndex).toggleGate(); + event.consume(); + break; + default: + break; + } + } + + KeyPressEvent keyPressEvent =_keyPressEventTracker.process(key); + + if (!key.shiftModifier() && key.isStep() && keyPressEvent.count() == 2) { + int stepIndex = stepOffset() + key.step(); + if (layer() != Layer::Gate) { + _inMemorySequence = _project.selectedLogicSequence(); + sequence.step(stepIndex).toggleGate(); + event.consume(); + } + } + + if (key.isFunction()) { + if(key.shiftModifier() && key.function() == 2 && _stepSelection.any()) { + _inMemorySequence = _project.selectedLogicSequence(); + tieNotes(); + event.consume(); + return; + } + switchLayer(key.function(), key.shiftModifier()); + event.consume(); + } + + if (key.isEncoder()) { + track.setPatternFollowDisplay(false); + _inMemorySequence = _project.selectedLogicSequence(); + if (!_showDetail && _stepSelection.any() && allSelectedStepsActive()) { + setSelectedStepsGate(false); + } else { + setSelectedStepsGate(true); + } + event.consume(); + } + + + if (key.isLeft()) { + if (key.shiftModifier()) { + _inMemorySequence = _project.selectedLogicSequence(); + sequence.shiftSteps(_stepSelection.selected(), -1); + _stepSelection.shiftLeft(); + } else { + track.setPatternFollowDisplay(false); + sequence.setSecion(std::max(0, sequence.section() - 1)); + } + event.consume(); + } + if (key.isRight()) { + if (key.shiftModifier()) { + _inMemorySequence = _project.selectedLogicSequence(); + sequence.shiftSteps(_stepSelection.selected(), 1); + _stepSelection.shiftRight(); + } else { + track.setPatternFollowDisplay(false); + sequence.setSecion(std::min(3, sequence.section() + 1)); + } + event.consume(); + } +} + +void LogicSequenceEditPage::encoder(EncoderEvent &event) { + auto &sequence = _project.selectedLogicSequence(); + const auto &scale = sequence.selectedScale(_project.scale()); + + if (!_stepSelection.any()) + { + switch (layer()) + { + case Layer::Gate: + setLayer(event.value() > 0 ? Layer::GateLogic : Layer::GateProbability); + break; + case Layer::GateLogic: + setLayer(event.value() > 0 ? Layer::GateOffset : Layer::Gate); + break; + case Layer::GateOffset: + setLayer(event.value() > 0 ? Layer::GateProbability : Layer::GateLogic); + break; + case Layer::GateProbability: + setLayer(event.value() > 0 ? Layer::Gate : Layer::GateOffset); + break; + case Layer::Retrigger: + setLayer(event.value() > 0 ? Layer::RetriggerProbability : Layer::StageRepeatsMode); + break; + case Layer::RetriggerProbability: + setLayer(event.value() > 0 ? Layer::StageRepeats : Layer::Retrigger); + break; + case Layer::StageRepeats: + setLayer(event.value() > 0 ? Layer::StageRepeatsMode : Layer::RetriggerProbability); + break; + case Layer::StageRepeatsMode: + setLayer(event.value() > 0 ? Layer::Retrigger : Layer::StageRepeats); + break; + case Layer::Length: + setLayer(event.value() > 0 ? Layer::LengthVariationRange : Layer::LengthVariationProbability); + break; + case Layer::LengthVariationRange: + setLayer(event.value() > 0 ? Layer::LengthVariationProbability : Layer::Length); + break; + case Layer::LengthVariationProbability: + setLayer(event.value() > 0 ? Layer::Length : Layer::LengthVariationRange); + break; + case Layer::NoteLogic: + setLayer(event.value() > 0 ? Layer::NoteVariationRange : Layer::Slide); + break; + case Layer::NoteVariationRange: + setLayer(event.value() > 0 ? Layer::NoteVariationProbability : Layer::NoteLogic); + break; + case Layer::NoteVariationProbability: + setLayer(event.value() > 0 ? Layer::Slide : Layer::NoteVariationRange); + break; + case Layer::Slide: + setLayer(event.value() > 0 ? Layer::NoteLogic : Layer::NoteVariationProbability); + break; + default: + break; + } + return; + } + else + { + _showDetail = true; + _showDetailTicks = os::ticks(); + } + + for (size_t stepIndex = 0; stepIndex < sequence.steps().size(); ++stepIndex) { + if (_stepSelection[stepIndex]) { + auto &step = sequence.step(stepIndex); + bool shift = globalKeyState()[Key::Shift]; + switch (layer()) { + case Layer::Gate: + step.setGate(event.value() > 0); + break; + case Layer::GateLogic: + step.setGateLogic(static_cast(step.gateLogic() + event.value())); + break; + case Layer::GateProbability: + step.setGateProbability(step.gateProbability() + event.value()); + break; + case Layer::GateOffset: + step.setGateOffset(step.gateOffset() + event.value()); + break; + case Layer::Retrigger: + step.setRetrigger(step.retrigger() + event.value()); + break; + case Layer::RetriggerProbability: + step.setRetriggerProbability(step.retriggerProbability() + event.value()); + break; + case Layer::Length: + step.setLength(step.length() + event.value()); + break; + case Layer::LengthVariationRange: + step.setLengthVariationRange(step.lengthVariationRange() + event.value()); + break; + case Layer::LengthVariationProbability: + step.setLengthVariationProbability(step.lengthVariationProbability() + event.value()); + break; + case Layer::NoteLogic: + step.setNoteLogic(static_cast(step.noteLogic() + event.value())); + updateMonitorStep(); + break; + case Layer::NoteVariationRange: + step.setNoteVariationRange(step.noteVariationRange() + event.value() * ((shift && scale.isChromatic()) ? scale.notesPerOctave() : 1)); + break; + case Layer::NoteVariationProbability: + step.setNoteVariationProbability(step.noteVariationProbability() + event.value()); + break; + case Layer::Slide: + step.setSlide(event.value() > 0); + break; + case Layer::Condition: + step.setCondition(ModelUtils::adjustedEnum(step.condition(), event.value())); + break; + case Layer::StageRepeats: + step.setStageRepeats(step.stageRepeats() + event.value()); + break; + case Layer::StageRepeatsMode: + step.setStageRepeatsMode( + static_cast( + step.stageRepeatMode() + event.value() + ) + ); + break; + case Layer::Last: + break; + } + } + } + + event.consume(); +} + +void LogicSequenceEditPage::midi(MidiEvent &event) { + if (!_engine.recording() && layer() == Layer::NoteLogic && _stepSelection.any()) { + auto &trackEngine = _engine.selectedTrackEngine().as(); + auto &sequence = _project.selectedLogicSequence(); + const auto &scale = sequence.selectedScale(_project.scale()); + const auto &message = event.message(); + + if (message.isNoteOn()) { + float volts = (message.note() - 60) * (1.f / 12.f); + int note = scale.noteFromVolts(volts); + + for (size_t stepIndex = 0; stepIndex < sequence.steps().size(); ++stepIndex) { + if (_stepSelection[stepIndex]) { + auto &step = sequence.step(stepIndex); + step.setNote(note); + step.setGate(true); + } + } + + trackEngine.setMonitorStep(_stepSelection.first()); + updateMonitorStep(); + } + } +} + +void LogicSequenceEditPage::switchLayer(int functionKey, bool shift) { + + auto engine = _engine.selectedTrackEngine().as(); + + if (shift) { + switch (Function(functionKey)) { + case Function::Gate: + setLayer(Layer::Gate); + break; + case Function::Retrigger: + if (engine.playMode() == Types::PlayMode::Free) { + setLayer(Layer::StageRepeats); + } + break; + case Function::Length: + if (engine.playMode() == Types::PlayMode::Free) { + setLayer(Layer::StageRepeatsMode); + } + break; + case Function::Note: + setLayer(Layer::NoteLogic); + break; + case Function::Condition: + setLayer(Layer::Condition); + break; + } + return; + } + + switch (Function(functionKey)) { + case Function::Gate: + switch (layer()) { + case Layer::Gate: + setLayer(Layer::GateLogic); + break; + case Layer::GateLogic: + setLayer(Layer::GateOffset); + break; + case Layer::GateOffset: + setLayer(Layer::GateProbability); + break; + default: + setLayer(Layer::Gate); + break; + } + break; + case Function::Retrigger: + switch (layer()) { + case Layer::Retrigger: + setLayer(Layer::RetriggerProbability); + break; + case Layer::RetriggerProbability: + if (engine.playMode() == Types::PlayMode::Free) { + setLayer(Layer::StageRepeats); + break; + } + + case Layer::StageRepeats: + if (engine.playMode() == Types::PlayMode::Free) { + setLayer(Layer::StageRepeatsMode); + break; + } + + default: + setLayer(Layer::Retrigger); + break; + } + break; + case Function::Length: + switch (layer()) { + case Layer::Length: + setLayer(Layer::LengthVariationRange); + break; + case Layer::LengthVariationRange: + setLayer(Layer::LengthVariationProbability); + break; + default: + setLayer(Layer::Length); + break; + } + break; + case Function::Note: + switch (layer()) { + case Layer::NoteLogic: + setLayer(Layer::NoteVariationRange); + break; + case Layer::NoteVariationRange: + setLayer(Layer::NoteVariationProbability); + break; + case Layer::NoteVariationProbability: + setLayer(Layer::Slide); + break; + default: + setLayer(Layer::NoteLogic); + break; + } + break; + case Function::Condition: + setLayer(Layer::Condition); + break; + } +} + +int LogicSequenceEditPage::activeFunctionKey() { + switch (layer()) { + case Layer::Gate: + case Layer::GateProbability: + case Layer::GateOffset: + case Layer::GateLogic: + return 0; + case Layer::Retrigger: + case Layer::RetriggerProbability: + case Layer::StageRepeats: + case Layer::StageRepeatsMode: + return 1; + case Layer::Length: + case Layer::LengthVariationRange: + case Layer::LengthVariationProbability: + return 2; + case Layer::NoteLogic: + case Layer::NoteVariationRange: + case Layer::NoteVariationProbability: + case Layer::Slide: + return 3; + case Layer::Condition: + return 4; + case Layer::Last: + break; + } + + return -1; +} + +void LogicSequenceEditPage::updateMonitorStep() { + auto &trackEngine = _engine.selectedTrackEngine().as(); + + // TODO should we monitor an all layers not just note? + if (layer() == Layer::NoteLogic && !_stepSelection.isPersisted() && _stepSelection.any()) { + trackEngine.setMonitorStep(_stepSelection.first()); + } else { + trackEngine.setMonitorStep(-1); + } +} + +void LogicSequenceEditPage::drawDetail(Canvas &canvas, const LogicSequence::Step &step) { + + FixedStringBuilder<16> str; + + WindowPainter::drawFrame(canvas, 64, 16, 128, 32); + + canvas.setBlendMode(BlendMode::Set); + canvas.setColor(Color::Bright); + canvas.vline(64 + 32, 16, 32); + + canvas.setFont(Font::Small); + str("%d", _stepSelection.first() + 1); + if (_stepSelection.count() > 1) { + str("*"); + } + canvas.drawTextCentered(64, 16, 32, 32, str); + + canvas.setFont(Font::Tiny); + + switch (layer()) { + case Layer::Gate: + case Layer::Slide: + break; + + case Layer::GateLogic: + str.reset(); + switch (step.gateLogic()) { + case LogicSequence::GateLogicMode::One: + str("INPUT 1"); + break; + case LogicSequence::GateLogicMode::Two: + str("INPUT 2"); + break; + case LogicSequence::GateLogicMode::And: + str("AND"); + break; + case LogicSequence::GateLogicMode::Or: + str("OR"); + break; + case LogicSequence::GateLogicMode::Xor: + str("XOR"); + break; + case LogicSequence::GateLogicMode::Nand: + str("NAND"); + break; + + case LogicSequence::GateLogicMode::RandomInput: + str("RND INPUT"); + break; + case LogicSequence::GateLogicMode::RandomLogic: + str("RND LOGIC"); + break; + default: + break; + } + canvas.setFont(Font::Small); + canvas.drawTextCentered(64 + 64, 32 - 4, 32, 8, str); + break; + case Layer::GateProbability: + SequencePainter::drawProbability( + canvas, + 64 + 32 + 8, 32 - 4, 64 - 16, 8, + step.gateProbability(), LogicSequence::GateProbability::Range-1 + ); + str.reset(); + str("%.1f%%", 100.f * (step.gateProbability()) / (LogicSequence::GateProbability::Range-1)); + canvas.setColor(Color::Bright); + canvas.drawTextCentered(64 + 32 + 64, 32 - 4, 32, 8, str); + break; + case Layer::GateOffset: + SequencePainter::drawOffset( + canvas, + 64 + 32 + 8, 32 - 4, 64 - 16, 8, + step.gateOffset(), LogicSequence::GateOffset::Min - 1, LogicSequence::GateOffset::Max + 1 + ); + str.reset(); + str("%.1f%%", 100.f * step.gateOffset() / float(LogicSequence::GateOffset::Max + 1)); + canvas.setColor(Color::Bright); + canvas.drawTextCentered(64 + 32 + 64, 32 - 4, 32, 8, str); + break; + case Layer::Retrigger: + SequencePainter::drawRetrigger( + canvas, + 64+ 32 + 8, 32 - 4, 64 - 16, 8, + step.retrigger() + 1, NoteSequence::Retrigger::Range + ); + str.reset(); + str("%d", step.retrigger() + 1); + canvas.setColor(Color::Bright); + canvas.drawTextCentered(64 + 32 + 64, 32 - 4, 32, 8, str); + break; + case Layer::RetriggerProbability: + SequencePainter::drawProbability( + canvas, + 64 + 32 + 8, 32 - 4, 64 - 16, 8, + step.retriggerProbability(), NoteSequence::RetriggerProbability::Range-1 + ); + str.reset(); + str("%.1f%%", 100.f * (step.retriggerProbability()) / (NoteSequence::RetriggerProbability::Range-1)); + canvas.setColor(Color::Bright); + canvas.drawTextCentered(64 + 32 + 64, 32 - 4, 32, 8, str); + break; + case Layer::Length: + SequencePainter::drawLength( + canvas, + 64 + 32 + 8, 32 - 4, 64 - 16, 8, + step.length() + 1, LogicSequence::Length::Range + ); + str.reset(); + str("%.1f%%", 100.f * (step.length() + 1.f) / LogicSequence::Length::Range); + canvas.setColor(Color::Bright); + canvas.drawTextCentered(64 + 32 + 64, 32 - 4, 32, 8, str); + break; + case Layer::LengthVariationRange: + SequencePainter::drawLengthRange( + canvas, + 64 + 32 + 8, 32 - 4, 64 - 16, 8, + step.length() + 1, step.lengthVariationRange(), LogicSequence::Length::Range + ); + str.reset(); + str("%.1f%%", 100.f * (step.lengthVariationRange()) / LogicSequence::Length::Range); + canvas.setColor(Color::Bright); + canvas.drawTextCentered(64 + 32 + 64, 32 - 4, 32, 8, str); + break; + case Layer::LengthVariationProbability: + SequencePainter::drawProbability( + canvas, + 64 + 32 + 8, 32 - 4, 64 - 16, 8, + step.lengthVariationProbability(), LogicSequence::LengthVariationProbability::Range-1 + ); + str.reset(); + str("%.1f%%", 100.f * (step.lengthVariationProbability()) / (LogicSequence::LengthVariationProbability::Range-1)); + canvas.setColor(Color::Bright); + canvas.drawTextCentered(64 + 32 + 64, 32 - 4, 32, 8, str); + break; + case Layer::NoteLogic: + str.reset(); + switch (step.noteLogic()) { + case LogicSequence::NoteLogicMode::NOne: + str("INPUT 1"); + break; + case LogicSequence::NoteLogicMode::NTwo: + str("INPUT 2"); + break; + case LogicSequence::NoteLogicMode::Min: + str("MIN"); + break; + case LogicSequence::NoteLogicMode::Max: + str("MAX"); + break; + case LogicSequence::NoteLogicMode::Sum: + str("SUM"); + break; + case LogicSequence::NoteLogicMode::Avg: + str("AVG"); + break; + case LogicSequence::NoteLogicMode::NRandomInput: + str("RND INPUT"); + break; + case LogicSequence::NoteLogicMode::NRandomLogic: + str("RND LOGIC"); + break; + default: + break; + } + canvas.setFont(Font::Small); + canvas.drawTextCentered(64 + 64, 32 - 4, 32, 8, str); + break; + + + + break; + case Layer::NoteVariationRange: + str.reset(); + str("%d", step.noteVariationRange()); + canvas.setFont(Font::Small); + canvas.drawTextCentered(64 + 32, 16, 64, 32, str); + break; + case Layer::NoteVariationProbability: + SequencePainter::drawProbability( + canvas, + 64 + 32 + 8, 32 - 4, 64 - 16, 8, + step.noteVariationProbability(), NoteSequence::NoteVariationProbability::Range-1 + ); + str.reset(); + str("%.1f%%", 100.f * (step.noteVariationProbability()) / (NoteSequence::NoteVariationProbability::Range-1)); + canvas.setColor(Color::Bright); + canvas.drawTextCentered(64 + 32 + 64, 32 - 4, 32, 8, str); + break; + + case Layer::Condition: + str.reset(); + Types::printCondition(str, step.condition(), Types::ConditionFormat::Long); + canvas.setFont(Font::Small); + canvas.drawTextCentered(64 + 32, 16, 96, 32, str); + break; + case Layer::StageRepeats: + str.reset(); + str("x%d", step.stageRepeats()+1); + canvas.setFont(Font::Small); + canvas.drawTextCentered(64 + 32, 16, 64, 32, str); + break; + case Layer::StageRepeatsMode: + str.reset(); + switch (step.stageRepeatMode()) { + case Types::Each: + str("EACH"); + break; + case Types::First: + str("FIRST"); + break; + case Types::Middle: + str("MIDDLE"); + break; + case Types::Last: + str("LAST"); + break; + case Types::Odd: + str("ODD"); + break; + case Types::Even: + str("EVEN"); + break; + case Types::Triplets: + str("TRIPLET"); + break; + case Types::Random: + str("RANDOM"); + break; + + default: + break; + } + canvas.setFont(Font::Small); + canvas.drawTextCentered(64 + 32, 16, 64, 32, str); + break; + case Layer::Last: + break; + } +} + +void LogicSequenceEditPage::contextShow(bool doubleClick) { + showContextMenu(ContextMenu( + contextMenuItems, + int(ContextAction::Last), + [&] (int index) { contextAction(index); }, + [&] (int index) { return contextActionEnabled(index); }, doubleClick + )); +} + +void LogicSequenceEditPage::contextAction(int index) { + switch (ContextAction(index)) { + case ContextAction::Init: + initSequence(); + break; + case ContextAction::Copy: + copySequence(); + break; + case ContextAction::Paste: + pasteSequence(); + break; + case ContextAction::Duplicate: + duplicateSequence(); + break; + case ContextAction::Generate: + generateSequence(); + break; + case ContextAction::Last: + break; + } +} + +bool LogicSequenceEditPage::contextActionEnabled(int index) const { + switch (ContextAction(index)) { + case ContextAction::Paste: + return _model.clipBoard().canPasteLogicSequenceSteps(); + default: + return true; + } +} + +void LogicSequenceEditPage::initSequence() { + _project.selectedLogicSequence().clearStepsSelected(_stepSelection.selected()); + showMessage("STEPS INITIALIZED"); +} + +void LogicSequenceEditPage::copySequence() { + _model.clipBoard().copyLogicSequenceSteps(_project.selectedLogicSequence(), _stepSelection.selected()); + showMessage("STEPS COPIED"); +} + +void LogicSequenceEditPage::pasteSequence() { + _model.clipBoard().pasteLogicSequenceSteps(_project.selectedLogicSequence(), _stepSelection.selected()); + showMessage("STEPS PASTED"); +} + +void LogicSequenceEditPage::duplicateSequence() { + _project.selectedLogicSequence().duplicateSteps(); + showMessage("STEPS DUPLICATED"); +} + + +void LogicSequenceEditPage::tieNotes() { + + auto &sequence = _project.selectedLogicSequence(); + + if (_stepSelection.any()) { + int first=-1; + int last=-1; + + for (size_t i = 0; i < sequence.steps().size(); ++i) { + if (_stepSelection[i]) { + if (first == -1 ) { + first = i; + } + last = i; + } + } + + for (int i = first; i <= last; i++) { + sequence.step(i).setGate(true); + if (i != last) { + sequence.step(i).setLength(LogicSequence::Length::Max); + showMessage("NOTES TIED"); + } + sequence.step(i).setNote(sequence.step(first).note()); + } + } +} + +void LogicSequenceEditPage::generateSequence() { + _manager.pages().generatorSelect.show([this] (bool success, Generator::Mode mode) { + if (success) { + auto builder = _builderContainer.create(_project.selectedLogicSequence(), layer()); + + if (_stepSelection.none()) { + _stepSelection.selectAll(); + } + + auto generator = Generator::execute(mode, *builder, _stepSelection.selected()); + if (generator) { + _manager.pages().generator.show(generator, &_stepSelection); + } + } + }); +} + +void LogicSequenceEditPage::quickEdit(int index) { + _listModel.setSequence(&_project.selectedLogicSequence()); + if (quickEditItems[index] != LogicSequenceListModel::Item::Last) { + _manager.pages().quickEdit.show(_listModel, int(quickEditItems[index])); + } +} + +bool LogicSequenceEditPage::allSelectedStepsActive() const { + const auto &sequence = _project.selectedLogicSequence(); + for (size_t stepIndex = 0; stepIndex < _stepSelection.size(); ++stepIndex) { + if (_stepSelection[stepIndex] && !sequence.step(stepIndex).gate()) { + return false; + } + } + return true; +} + +void LogicSequenceEditPage::setSelectedStepsGate(bool gate) { + auto &sequence = _project.selectedLogicSequence(); + for (size_t stepIndex = 0; stepIndex < _stepSelection.size(); ++stepIndex) { + if (_stepSelection[stepIndex]) { + sequence.step(stepIndex).setGate(gate); + } + } +} \ No newline at end of file diff --git a/src/apps/sequencer/ui/pages/LogicSequenceEditPage.h b/src/apps/sequencer/ui/pages/LogicSequenceEditPage.h new file mode 100644 index 00000000..75b2b914 --- /dev/null +++ b/src/apps/sequencer/ui/pages/LogicSequenceEditPage.h @@ -0,0 +1,78 @@ +#pragma once + +#include "BasePage.h" + +#include "ui/StepSelection.h" +#include "ui/model/LogicSequenceListModel.h" + +#include "engine/generators/SequenceBuilder.h" +#include "ui/KeyPressEventTracker.h" + +#include "core/utils/Container.h" + +class LogicSequenceEditPage : public BasePage { +public: + LogicSequenceEditPage(PageManager &manager, PageContext &context); + + virtual void enter() override; + virtual void exit() override; + + virtual void draw(Canvas &canvas) override; + virtual void updateLeds(Leds &leds) override; + + virtual void keyDown(KeyEvent &event) override; + virtual void keyUp(KeyEvent &event) override; + virtual void keyPress(KeyPressEvent &event) override; + virtual void encoder(EncoderEvent &event) override; + virtual void midi(MidiEvent &event) override; + +private: + typedef LogicSequence::Layer Layer; + + static const int StepCount = 16; + + int stepOffset() const { return _project.selectedLogicSequence().section() * StepCount; } + + void switchLayer(int functionKey, bool shift); + int activeFunctionKey(); + + void updateMonitorStep(); + void drawDetail(Canvas &canvas, const LogicSequence::Step &step); + + void contextShow(bool doubleClick = false); + void contextAction(int index); + bool contextActionEnabled(int index) const; + + void initSequence(); + void copySequence(); + void pasteSequence(); + void duplicateSequence(); + void tieNotes(); + void generateSequence(); + + void quickEdit(int index); + + bool allSelectedStepsActive() const; + void setSelectedStepsGate(bool gate); + + void setSectionTracking(bool track); + bool isSectionTracking(); + void toggleSectionTracking(); + + LogicSequence::Layer layer() const { return _project.selectedLogicSequenceLayer(); }; + void setLayer(LogicSequence::Layer layer) { _project.setSelectedLogicSequenceLayer(layer); } + + bool _showDetail; + uint32_t _showDetailTicks; + + KeyPressEventTracker _keyPressEventTracker; + + + LogicSequenceListModel _listModel; + + StepSelection _stepSelection; + + Container _builderContainer; + + LogicSequence _inMemorySequence; +}; diff --git a/src/apps/sequencer/ui/pages/LogicSequencePage.cpp b/src/apps/sequencer/ui/pages/LogicSequencePage.cpp new file mode 100644 index 00000000..ea6a10cf --- /dev/null +++ b/src/apps/sequencer/ui/pages/LogicSequencePage.cpp @@ -0,0 +1,287 @@ +#include "LogicSequencePage.h" + +#include "ListPage.h" +#include "Pages.h" + +#include "ui/LedPainter.h" +#include "ui/painters/WindowPainter.h" + +#include "core/utils/StringBuilder.h" + +enum class ContextAction { + Init, + Copy, + Paste, + Duplicate, + Route, + Last +}; + +enum class SaveContextAction { + Load, + Save, + SaveAs, + Last +}; + +static const ContextMenuModel::Item contextMenuItems[] = { + { "INIT" }, + { "COPY" }, + { "PASTE" }, + { "DUPL" }, + { "ROUTE" }, +}; + +static const ContextMenuModel::Item saveContextMenuItems[] = { + { "LOAD" }, + { "SAVE" }, + { "SAVE AS"}, +}; + + +LogicSequencePage::LogicSequencePage(PageManager &manager, PageContext &context) : + ListPage(manager, context, _listModel) +{} + +void LogicSequencePage::enter() { + _listModel.setSequence(&_project.selectedLogicSequence()); +} + +void LogicSequencePage::exit() { + _listModel.setSequence(nullptr); +} + +void LogicSequencePage::draw(Canvas &canvas) { + WindowPainter::clear(canvas); + WindowPainter::drawHeader(canvas, _model, _engine, "SEQUENCE"); + WindowPainter::drawActiveFunction(canvas, Track::trackModeName(_project.selectedTrack().trackMode())); + WindowPainter::drawFooter(canvas); + + ListPage::draw(canvas); +} + +void LogicSequencePage::updateLeds(Leds &leds) { + ListPage::updateLeds(leds); +} + +void LogicSequencePage::keyPress(KeyPressEvent &event) { + const auto &key = event.key(); + + if (key.shiftModifier() && event.count() == 2) { + saveContextShow(); + event.consume(); + return; + } + + if (key.isContextMenu()) { + contextShow(); + event.consume(); + return; + } + + + if (key.pageModifier() && event.count() == 2) { + contextShow(true); + event.consume(); + return; + } + + if (key.pageModifier()) { + return; + } + + if (key.is(Key::Encoder) && selectedRow() == 0) { + _manager.pages().textInput.show("NAME:", _project.selectedLogicSequence().name(), LogicSequence::NameLength, [this] (bool result, const char *text) { + if (result) { + _project.selectedLogicSequence().setName(text); + } + }); + + return; + } + + if (!event.consumed()) { + ListPage::keyPress(event); + } + if (key.isEncoder()) { + auto row = ListPage::selectedRow(); + if (row == 6) { + _listModel.setSelectedScale(_project.scale()); + } + } +} + +void LogicSequencePage::contextShow(bool doubleClick) { + showContextMenu(ContextMenu( + contextMenuItems, + int(ContextAction::Last), + [&] (int index) { contextAction(index); }, + [&] (int index) { return contextActionEnabled(index); }, + doubleClick + )); +} + +void LogicSequencePage::saveContextShow(bool doubleClick) { + showContextMenu(ContextMenu( + saveContextMenuItems, + int(SaveContextAction::Last), + [&] (int index) { saveContextAction(index); }, + [&] (int index) { return true; }, + doubleClick + )); +} + +void LogicSequencePage::contextAction(int index) { + switch (ContextAction(index)) { + case ContextAction::Init: + initSequence(); + break; + case ContextAction::Copy: + copySequence(); + break; + case ContextAction::Paste: + pasteSequence(); + break; + case ContextAction::Duplicate: + duplicateSequence(); + break; + case ContextAction::Route: + initRoute(); + break; + case ContextAction::Last: + break; + } +} + +void LogicSequencePage::saveContextAction(int index) { + switch (SaveContextAction(index)) { + case SaveContextAction::Load: + loadSequence(); + break; + case SaveContextAction::Save: + saveSequence(); + break; + case SaveContextAction::SaveAs: + saveAsSequence(); + break; + case SaveContextAction::Last: + break; + } +} + +bool LogicSequencePage::contextActionEnabled(int index) const { + switch (ContextAction(index)) { + case ContextAction::Paste: + return _model.clipBoard().canPasteLogicSequence(); + case ContextAction::Route: + return _listModel.routingTarget(selectedRow()) != Routing::Target::None; + default: + return true; + } +} + +void LogicSequencePage::initSequence() { + _project.selectedLogicSequence().clear(); + showMessage("SEQUENCE INITIALIZED"); +} + +void LogicSequencePage::copySequence() { + _model.clipBoard().copyLogicSequence(_project.selectedLogicSequence()); + showMessage("SEQUENCE COPIED"); +} + +void LogicSequencePage::pasteSequence() { + _model.clipBoard().pasteLogicSequence(_project.selectedLogicSequence()); + showMessage("SEQUENCE PASTED"); +} + +void LogicSequencePage::duplicateSequence() { + if (_project.selectedTrack().duplicatePattern(_project.selectedPatternIndex())) { + showMessage("SEQUENCE DUPLICATED"); + } +} + +void LogicSequencePage::initRoute() { + _manager.pages().top.editRoute(_listModel.routingTarget(selectedRow()), _project.selectedTrackIndex()); +} + +void LogicSequencePage::loadSequence() { + _manager.pages().fileSelect.show("LOAD SEQUENCE", FileType::LogicSequence, _project.selectedLogicSequence().slotAssigned() ? _project.selectedLogicSequence().slot() : 0, false, [this] (bool result, int slot) { + if (result) { + _manager.pages().confirmation.show("ARE YOU SURE?", [this, slot] (bool result) { + if (result) { + loadSequenceFromSlot(slot); + } + }); + } + }); +} + +void LogicSequencePage::saveSequence() { + + if (!_project.selectedLogicSequence().slotAssigned() || sizeof(_project.selectedLogicSequence().name())==0) { + saveAsSequence(); + return; + } + + saveSequenceToSlot(_project.selectedLogicSequence().slot()); + + showMessage("SEQUENCE SAVED"); +} + +void LogicSequencePage::saveAsSequence() { + _manager.pages().fileSelect.show("SAVE SEQUENCE", FileType::LogicSequence, _project.selectedLogicSequence().slotAssigned() ? _project.selectedLogicSequence().slot() : 0, true, [this] (bool result, int slot) { + if (result) { + if (FileManager::slotUsed(FileType::LogicSequence, slot)) { + _manager.pages().confirmation.show("ARE YOU SURE?", [this, slot] (bool result) { + if (result) { + saveSequenceToSlot(slot); + } + }); + } else { + saveSequenceToSlot(slot); + } + } + }); +} + +void LogicSequencePage::saveSequenceToSlot(int slot) { + //_engine.suspend(); + _manager.pages().busy.show("SAVING SEQUENCE ..."); + + FileManager::task([this, slot] () { + return FileManager::writeLogicSequence(_project.selectedLogicSequence(), slot); + }, [this] (fs::Error result) { + if (result == fs::OK) { + showMessage("SEQUENCE SAVED"); + } else { + showMessage(FixedStringBuilder<32>("FAILED (%s)", fs::errorToString(result))); + } + // TODO lock ui mutex + _manager.pages().busy.close(); + _engine.resume(); + }); +} + +void LogicSequencePage::loadSequenceFromSlot(int slot) { + //_engine.suspend(); + _manager.pages().busy.show("LOADING SEQUENCE ..."); + + FileManager::task([this, slot] () { + // TODO this is running in file manager thread but model notification affect ui + return FileManager::readLogicSequence(_project.selectedLogicSequence(), slot); + }, [this] (fs::Error result) { + if (result == fs::OK) { + showMessage("SEQUENCE LOADED"); + } else if (result == fs::INVALID_CHECKSUM) { + showMessage("INVALID SEQUENCE FILE"); + } else { + showMessage(FixedStringBuilder<32>("FAILED (%s)", fs::errorToString(result))); + } + // TODO lock ui mutex + _manager.pages().busy.close(); + _engine.resume(); + }); +} + + diff --git a/src/apps/sequencer/ui/pages/LogicSequencePage.h b/src/apps/sequencer/ui/pages/LogicSequencePage.h new file mode 100644 index 00000000..c1a4c6ee --- /dev/null +++ b/src/apps/sequencer/ui/pages/LogicSequencePage.h @@ -0,0 +1,40 @@ +#pragma once + +#include "ListPage.h" + +#include "ui/model/LogicSequenceListModel.h" + +class LogicSequencePage : public ListPage { +public: + LogicSequencePage(PageManager &manager, PageContext &context); + + virtual void enter() override; + virtual void exit() override; + + virtual void draw(Canvas &canvas) override; + virtual void updateLeds(Leds &leds) override; + + virtual void keyPress(KeyPressEvent &event) override; + +private: + void contextShow(bool doubleClick = false); + void saveContextShow(bool doubleClick = false); + void saveContextAction(bool doubleClick = false); + void contextAction(int index); + void saveContextAction(int index); + bool contextActionEnabled(int index) const; + + void initSequence(); + void copySequence(); + void pasteSequence(); + void duplicateSequence(); + void initRoute(); + + void loadSequence(); + void saveSequence(); + void saveAsSequence(); + void saveSequenceToSlot(int slot); + void loadSequenceFromSlot(int slot); + + LogicSequenceListModel _listModel; +}; diff --git a/src/apps/sequencer/ui/pages/NoteSequenceEditPage.cpp b/src/apps/sequencer/ui/pages/NoteSequenceEditPage.cpp index df7b2370..cc5629ab 100644 --- a/src/apps/sequencer/ui/pages/NoteSequenceEditPage.cpp +++ b/src/apps/sequencer/ui/pages/NoteSequenceEditPage.cpp @@ -70,7 +70,17 @@ void NoteSequenceEditPage::enter() { _inMemorySequence = _project.selectedNoteSequence(); _showDetail = false; -} + + if (_project.selectedTrack().noteTrack().playMode() == Types::PlayMode::Aligned) { + if (_project.selectedNoteSequenceLayer() == NoteSequence::Layer::StageRepeats || _project.selectedNoteSequenceLayer() == NoteSequence::Layer::StageRepeatsMode ) { + _project.setSelectedNoteSequenceLayer(NoteSequence::Layer::Retrigger); + } + } else { + if (_project.selectedNoteSequenceLayer() == NoteSequence::Layer::Retrigger) { + _project.setSelectedNoteSequenceLayer(NoteSequence::Layer::StageRepeats); + } + } + } void NoteSequenceEditPage::exit() { _engine.selectedTrackEngine().as().setMonitorStep(-1); @@ -90,11 +100,14 @@ void NoteSequenceEditPage::draw(Canvas &canvas) { WindowPainter::drawActiveFunction(canvas, NoteSequence::layerName(layer())); WindowPainter::drawFooter(canvas, functionNames, pageKeyState(), activeFunctionKey()); - const auto &trackEngine = _engine.selectedTrackEngine().as(); + auto &trackEngine = _engine.selectedTrackEngine().as(); auto &sequence = _project.selectedNoteSequence(); const auto &scale = sequence.selectedScale(_project.scale()); int currentStep = trackEngine.isActiveSequence(sequence) ? trackEngine.currentStep() : -1; + if (trackEngine.currentRecordStep()!=-1) { + trackEngine.setCurrentRecordStep(sequence.currentRecordStep()); + } int currentRecordStep = trackEngine.isActiveSequence(sequence) ? trackEngine.currentRecordStep() : -1; const int stepWidth = Width / StepCount; @@ -351,6 +364,8 @@ void NoteSequenceEditPage::keyPress(KeyPressEvent &event) { auto &sequence = _project.selectedNoteSequence(); auto &track = _project.selectedTrack().noteTrack(); + auto &trackEngine = _engine.selectedTrackEngine().as(); + if (key.isContextMenu()) { contextShow(); event.consume(); @@ -475,20 +490,37 @@ void NoteSequenceEditPage::keyPress(KeyPressEvent &event) { if (key.isLeft()) { if (key.shiftModifier()) { - _inMemorySequence = _project.selectedNoteSequence(); - sequence.shiftSteps(_stepSelection.selected(), -1); - _stepSelection.shiftLeft(); + if (trackEngine.currentRecordStep()!=-1) { + if (Routing::isRouted(Routing::Target::CurrentRecordStep, _model.project().selectedTrackIndex())) { + sequence.setCurrentRecordStep(sequence.currentRecordStep()-1, true); + } else { + sequence.setCurrentRecordStep(sequence.currentRecordStep()-1, false); + } + } else { + _inMemorySequence = _project.selectedNoteSequence(); + sequence.shiftSteps(_stepSelection.selected(), -1); + _stepSelection.shiftLeft(sequence.lastStep()+1); + } } else { - track.setPatternFollowDisplay(false); + track.setPatternFollowDisplay(false); sequence.setSecion(std::max(0, sequence.section() - 1)); } event.consume(); } if (key.isRight()) { if (key.shiftModifier()) { - _inMemorySequence = _project.selectedNoteSequence(); - sequence.shiftSteps(_stepSelection.selected(), 1); - _stepSelection.shiftRight(); + if (trackEngine.currentRecordStep()!=-1) { + if (Routing::isRouted(Routing::Target::CurrentRecordStep, _model.project().selectedTrackIndex())) { + sequence.setCurrentRecordStep(sequence.currentRecordStep()+1, true); + } else { + sequence.setCurrentRecordStep(sequence.currentRecordStep()+1, false); + } + + } else { + _inMemorySequence = _project.selectedNoteSequence(); + sequence.shiftSteps(_stepSelection.selected(), 1); + _stepSelection.shiftRight(sequence.lastStep()+1); + } } else { track.setPatternFollowDisplay(false); sequence.setSecion(std::min(3, sequence.section() + 1)); @@ -614,7 +646,7 @@ void NoteSequenceEditPage::encoder(EncoderEvent &event) { break; case Layer::StageRepeatsMode: step.setStageRepeatsMode( - static_cast( + static_cast( step.stageRepeatMode() + event.value() ) ); @@ -630,6 +662,9 @@ void NoteSequenceEditPage::encoder(EncoderEvent &event) { void NoteSequenceEditPage::midi(MidiEvent &event) { if (!_engine.recording() && layer() == Layer::Note && _stepSelection.any()) { + if (_project.clockSetup().filterNote()) { + return; + } auto &trackEngine = _engine.selectedTrackEngine().as(); auto &sequence = _project.selectedNoteSequence(); const auto &scale = sequence.selectedScale(_project.scale()); @@ -712,8 +747,14 @@ void NoteSequenceEditPage::switchLayer(int functionKey, bool shift) { setLayer(Layer::StageRepeatsMode); break; } - + case Layer::StageRepeatsMode: + setLayer(Layer::Retrigger); + break; default: + if (engine.playMode() == Types::PlayMode::Free) { + setLayer(Layer::StageRepeats); + break; + } setLayer(Layer::Retrigger); break; } @@ -939,28 +980,28 @@ void NoteSequenceEditPage::drawDetail(Canvas &canvas, const NoteSequence::Step & case Layer::StageRepeatsMode: str.reset(); switch (step.stageRepeatMode()) { - case NoteSequence::Each: + case Types::Each: str("EACH"); break; - case NoteSequence::First: + case Types::First: str("FIRST"); break; - case NoteSequence::Middle: + case Types::Middle: str("MIDDLE"); break; - case NoteSequence::Last: + case Types::Last: str("LAST"); break; - case NoteSequence::Odd: + case Types::Odd: str("ODD"); break; - case NoteSequence::Even: + case Types::Even: str("EVEN"); break; - case NoteSequence::Triplets: + case Types::Triplets: str("TRIPLET"); break; - case NoteSequence::Random: + case Types::Random: str("RANDOM"); break; @@ -1060,7 +1101,6 @@ void NoteSequenceEditPage::tieNotes() { showMessage("NOTES TIED"); } sequence.step(i).setNote(sequence.step(first).note()); - std::cerr << _stepSelection[i]; } } } diff --git a/src/apps/sequencer/ui/pages/NoteSequencePage.cpp b/src/apps/sequencer/ui/pages/NoteSequencePage.cpp index af9a58d5..6de137eb 100644 --- a/src/apps/sequencer/ui/pages/NoteSequencePage.cpp +++ b/src/apps/sequencer/ui/pages/NoteSequencePage.cpp @@ -100,6 +100,13 @@ void NoteSequencePage::keyPress(KeyPressEvent &event) { return; } + // We lock the playback parameters when logic track is enabled + /*if (key.is(Key::Encoder) && selectedRow() > 0 && selectedRow() < 6) { + if (_project.selectedTrack().noteTrack().logicTrack() != -1) { + return; + } + }*/ + if (!event.consumed()) { ListPage::keyPress(event); } diff --git a/src/apps/sequencer/ui/pages/OverviewPage.cpp b/src/apps/sequencer/ui/pages/OverviewPage.cpp index 34bbdff3..b3d894ca 100644 --- a/src/apps/sequencer/ui/pages/OverviewPage.cpp +++ b/src/apps/sequencer/ui/pages/OverviewPage.cpp @@ -1,17 +1,62 @@ #include "OverviewPage.h" +#include "TopPage.h" #include "model/NoteTrack.h" #include "ui/painters/WindowPainter.h" #include "ui/LedPainter.h" #include "ui/painters/SequencePainter.h" +#include "Pages.h" + +static const NoteSequenceListModel::Item noteQuickEditItems[8] = { + NoteSequenceListModel::Item::FirstStep, + NoteSequenceListModel::Item::LastStep, + NoteSequenceListModel::Item::RunMode, + NoteSequenceListModel::Item::Divisor, + NoteSequenceListModel::Item::ResetMeasure, + NoteSequenceListModel::Item::Scale, + NoteSequenceListModel::Item::RootNote, + NoteSequenceListModel::Item::Last +}; + +static const CurveSequenceListModel::Item curveQuickEditItems[8] = { + CurveSequenceListModel::Item::FirstStep, + CurveSequenceListModel::Item::LastStep, + CurveSequenceListModel::Item::RunMode, + CurveSequenceListModel::Item::Divisor, + CurveSequenceListModel::Item::ResetMeasure, + CurveSequenceListModel::Item::Range, + CurveSequenceListModel::Item::Last, + CurveSequenceListModel::Item::Last +}; + +static const LogicSequenceListModel::Item logicQuickEditItems[8] = { + LogicSequenceListModel::Item::FirstStep, + LogicSequenceListModel::Item::LastStep, + LogicSequenceListModel::Item::RunMode, + LogicSequenceListModel::Item::Divisor, + LogicSequenceListModel::Item::ResetMeasure, + LogicSequenceListModel::Item::Scale, + LogicSequenceListModel::Item::RootNote, + LogicSequenceListModel::Item::Last +}; + +static const StochasticSequenceListModel::Item stochasticQuickEditItems[8] = { + StochasticSequenceListModel::Item::SequenceFirstStep, + StochasticSequenceListModel::Item::SequenceLastStep, + StochasticSequenceListModel::Item::RunMode, + StochasticSequenceListModel::Item::Divisor, + StochasticSequenceListModel::Item::ResetMeasure, + StochasticSequenceListModel::Item::Scale, + StochasticSequenceListModel::Item::RootNote, + StochasticSequenceListModel::Item::Last +}; static void drawNoteTrack(Canvas &canvas, int trackIndex, const NoteTrackEngine &trackEngine, NoteSequence &sequence, bool running, bool patternFollow) { canvas.setBlendMode(BlendMode::Set); int stepOffset = 16*sequence.section(); if (patternFollow) { - stepOffset = (std::max(0, trackEngine.currentStep()) / 16) * 16*sequence.section(); int section_no = int((trackEngine.currentStep()) / 16); sequence.setSecion(section_no); } @@ -21,7 +66,7 @@ static void drawNoteTrack(Canvas &canvas, int trackIndex, const NoteTrackEngine int stepIndex = stepOffset + i; const auto &step = sequence.step(stepIndex); - int x = 68 + i * 8; + int x = 76 + i * 8; if (trackEngine.currentStep() == stepIndex) { canvas.setColor(step.gate() ? Color::Bright : Color::MediumBright); @@ -30,11 +75,38 @@ static void drawNoteTrack(Canvas &canvas, int trackIndex, const NoteTrackEngine canvas.setColor(step.gate() ? Color::Medium : Color::Low); canvas.fillRect(x + 1, y + 1, 6, 6); } + } +} + +static void drawLogicTrack(Canvas &canvas, int trackIndex, const LogicTrackEngine &trackEngine, LogicSequence &sequence, bool running, bool patternFollow) { + canvas.setBlendMode(BlendMode::Set); - // if (trackEngine.currentStep() == stepIndex) { - // canvas.setColor(Color::Bright); - // canvas.drawRect(x + 1, y + 1, 6, 6); - // } + int stepOffset = 16*sequence.section(); + if (patternFollow) { + int section_no = int((trackEngine.currentStep()) / 16); + sequence.setSecion(section_no); + } + int y = trackIndex * 8; + + for (int i = 0; i < 16; ++i) { + int stepIndex = stepOffset + i; + const auto &step = sequence.step(stepIndex); + + int x = 76 + i * 8; + + if (trackEngine.currentStep() == stepIndex) { + canvas.setColor(step.gate() ? Color::Bright : Color::MediumBright); + + if (trackEngine.gateOutput(stepIndex)) { + canvas.fillRect(x + 3, y + 3, 3, 3); + canvas.setColor(Color::Medium); + } else { + canvas.fillRect(x + 1, y + 1, 6, 6); + } + } else { + canvas.setColor(step.gate() ? Color::Medium : Color::Low); + canvas.fillRect(x + 1, y + 1, 6, 6); + } } } @@ -60,25 +132,33 @@ static void drawCurve(Canvas &canvas, int x, int y, int w, int h, float &lastY, lastY = fy0; } -static void drawStochasticTrack(Canvas &canvas, int trackIndex, const StochasticEngine &trackEngine, const StochasticSequence &sequence) { +static void drawStochasticTrack(Canvas &canvas, int trackIndex, const StochasticEngine &trackEngine, const StochasticSequence &sequence, const Scale &scale) { + canvas.setBlendMode(BlendMode::Set); int stepOffset = (std::max(0, trackEngine.currentStep()) / 12) * 12; int y = trackIndex * 8; for (int i = 0; i < 12; ++i) { + int stepIndex = stepOffset + i; const auto &step = sequence.step(stepIndex); - int x = 16 + (68 + i * 8); + int x = 16 + (76+ i * 8); if (trackEngine.currentStep() == stepIndex) { canvas.setColor(step.gate() ? Color::Bright : Color::MediumBright); canvas.fillRect(x + 1, y + 1, 6, 6); + } else { canvas.setColor(step.gate() ? Color::Medium : Color::Low); canvas.fillRect(x + 1, y + 1, 6, 6); } + if (step.gate() && scale.isNotePresent(step.note())) { + canvas.setBlendMode(BlendMode::Sub); + canvas.fillRect(x + 3, y + 3, 3, 3); + canvas.setBlendMode(BlendMode::Set); + } } } @@ -89,7 +169,6 @@ static void drawCurveTrack(Canvas &canvas, int trackIndex, const CurveTrackEngin int stepOffset = 16*sequence.section(); if (patternFollow) { - stepOffset = (std::max(0, trackEngine.currentStep()) / 16) * 16*sequence.section(); int section_no = int((trackEngine.currentStep()) / 16); sequence.setSecion(section_no); } @@ -104,13 +183,13 @@ static void drawCurveTrack(Canvas &canvas, int trackIndex, const CurveTrackEngin float max = step.maxNormalized(); const auto function = Curve::function(Curve::Type(std::min(Curve::Last - 1, step.shape()))); - int x = 68 + i * 8; + int x = 76 + i * 8; drawCurve(canvas, x, y + 1, 8, 6, lastY, function, min, max); } if (trackEngine.currentStep() >= 0) { - int x = 64 + ((trackEngine.currentStep() - stepOffset) + trackEngine.currentStepFraction()) * 8; + int x = 76 + ((trackEngine.currentStep() - stepOffset) + trackEngine.currentStepFraction()) * 8; canvas.setBlendMode(BlendMode::Set); canvas.setColor(Color::Bright); canvas.vline(x, y + 1, 7); @@ -132,6 +211,8 @@ void OverviewPage::exit() { _engine.selectedTrackEngine().as().setMonitorStep(-1); } else if (_project.selectedTrack().trackMode()==Track::TrackMode::Stochastic) { _engine.selectedTrackEngine().as().setMonitorStep(-1); + } else if (_project.selectedTrack().trackMode()==Track::TrackMode::Logic) { + _engine.selectedTrackEngine().as().setMonitorStep(-1); } } @@ -142,10 +223,10 @@ void OverviewPage::draw(Canvas &canvas) { canvas.setBlendMode(BlendMode::Set); canvas.setColor(Color::Medium); - canvas.vline(68 - 3, 0, 68); - canvas.vline(68 - 2, 0, 68); - canvas.vline(196 + 1, 0, 68); - canvas.vline(196 + 2, 0, 68); + canvas.vline(76 - 3, 0, 68); + canvas.vline(76 - 2, 0, 68); + canvas.vline(204 + 1, 0, 68); + canvas.vline(204 + 2, 0, 68); for (int trackIndex = 0; trackIndex < 8; trackIndex++) { auto &track = _project.track(trackIndex); @@ -159,46 +240,61 @@ void OverviewPage::draw(Canvas &canvas) { // track number / pattern number canvas.setColor(trackState.mute() ? Color::Medium : Color::Bright); + switch (track.trackMode()) { - case Track::TrackMode::Note: - canvas.drawText(2, y, track.noteTrack().name()); + case Track::TrackMode::Note: { + FixedStringBuilder<16> str("%s%s", _project.selectedTrackIndex() == trackIndex ? "+" : "", track.noteTrack().name()); + canvas.drawText(2, y, str); + } break; - case Track::TrackMode::Curve: - canvas.drawText(2, y, track.curveTrack().name()); + case Track::TrackMode::Curve: { + FixedStringBuilder<16> str("%s%s", _project.selectedTrackIndex() == trackIndex ? "+" : "", track.curveTrack().name()); + canvas.drawText(2, y, str); + } break; - case Track::TrackMode::MidiCv: - canvas.drawText(2, y, track.midiCvTrack().name()); + case Track::TrackMode::MidiCv: { + FixedStringBuilder<16> str("%s%s", _project.selectedTrackIndex() == trackIndex ? "+" : "", track.midiCvTrack().name()); + canvas.drawText(2, y, str); + } + break; + case Track::TrackMode::Stochastic: { + FixedStringBuilder<16> str("%s%s", _project.selectedTrackIndex() == trackIndex ? "+" : "", track.stochasticTrack().name()); + canvas.drawText(2, y, str); + } break; - case Track::TrackMode::Stochastic: - canvas.drawText(2, y, track.stochasticTrack().name()); + case Track::TrackMode::Logic: { + FixedStringBuilder<16> str("%s%s", _project.selectedTrackIndex() == trackIndex ? "+" : "", track.logicTrack().name()); + canvas.drawText(2, y, str); + } break; default: break; } - std::string s = std::to_string(trackState.pattern() + 1); - char const *pchar = s.c_str(); - char const p[] = {'P'}; - - canvas.fillRect(46 - 1, y - 5, canvas.textWidth(p)+canvas.textWidth(pchar) + 1, 7); + if (trackState.pattern()>9) { + canvas.fillRect(56 - 1, y - 5, 16,7); + } else { + canvas.fillRect(56 - 1, y - 5, 12,7); + } + canvas.setBlendMode(BlendMode::Sub); - canvas.drawText(46, y, FixedStringBuilder<8>("P%d", trackState.pattern() + 1)); + canvas.drawText(56, y, FixedStringBuilder<8>("P%d", trackState.pattern() + 1)); canvas.setBlendMode(BlendMode::Set); bool gate = _engine.gateOutput() & (1 << trackIndex); canvas.setColor(gate ? Color::Bright : Color::Medium); - canvas.fillRect(256 - 48 + 1, trackIndex * 8 + 1, 6, 6); + canvas.fillRect(256 - 40 + 1, trackIndex * 8 + 1, 6, 6); // cv output canvas.setColor(Color::Bright); - canvas.drawText(256 - 32, y, FixedStringBuilder<8>("%.2fV", _engine.cvOutput().channel(trackIndex))); + canvas.drawText(256 - 28, y, FixedStringBuilder<8>("%.2fV", _engine.cvOutput().channel(trackIndex))); switch (track.trackMode()) { case Track::TrackMode::Note: { bool patterFolow = false; if (track.noteTrack().patternFollow()==Types::PatternFollow::Display || track.noteTrack().patternFollow()==Types::PatternFollow::DispAndLP) { patterFolow = true; - canvas.drawText(256 - 54, y, FixedStringBuilder<8>("F")); + canvas.drawText(256 - 46, y, FixedStringBuilder<8>("F")); } drawNoteTrack(canvas, trackIndex, trackEngine.as(), track.noteTrack().sequence(trackState.pattern()), _engine.state().running(), patterFolow); } @@ -207,13 +303,29 @@ void OverviewPage::draw(Canvas &canvas) { bool patterFolow = false; if (track.curveTrack().patternFollow()==Types::PatternFollow::Display || track.curveTrack().patternFollow()==Types::PatternFollow::DispAndLP) { patterFolow = true; - canvas.drawText(256 - 54, y, FixedStringBuilder<8>("F")); + canvas.drawText(256 - 46, y, FixedStringBuilder<8>("F")); } drawCurveTrack(canvas, trackIndex, trackEngine.as(), track.curveTrack().sequence(trackState.pattern()), _engine.state().running(), patterFolow); } break; - case Track::TrackMode::Stochastic: - drawStochasticTrack(canvas, trackIndex, trackEngine.as(), track.stochasticTrack().sequence(trackState.pattern())); + case Track::TrackMode::Stochastic: { + const auto &sequence = track.stochasticTrack().sequence(trackState.pattern()); + const auto &scale = sequence.selectedScale(_project.scale()); + + if (sequence.useLoop()) { + canvas.drawText(256 - 46, y, FixedStringBuilder<8>("L")); + } + drawStochasticTrack(canvas, trackIndex, trackEngine.as(), sequence, scale); + } + break; + case Track::TrackMode::Logic: { + bool patterFolow = false; + if (track.logicTrack().patternFollow()==Types::PatternFollow::Display || track.logicTrack().patternFollow()==Types::PatternFollow::DispAndLP) { + patterFolow = true; + canvas.drawText(256 - 46, y, FixedStringBuilder<8>("F")); + } + drawLogicTrack(canvas, trackIndex, trackEngine.as(), track.logicTrack().sequence(trackState.pattern()), _engine.state().running(), patterFolow); + } break; case Track::TrackMode::MidiCv: break; @@ -242,9 +354,15 @@ void OverviewPage::draw(Canvas &canvas) { } break; case Track::TrackMode::Curve: { - auto &sequence = _project.selectedCurveSequence(); - drawCurveDetail(canvas, sequence.step(_stepSelection.first())); - } + auto &sequence = _project.selectedCurveSequence(); + drawCurveDetail(canvas, sequence.step(_stepSelection.first())); + } + break; + case Track::TrackMode::Logic: { + auto &sequence = _project.selectedLogicSequence(); + drawLogicDetail(canvas, sequence.step(_stepSelection.first())); + } + break; default: break; } @@ -302,11 +420,55 @@ void OverviewPage::updateLeds(Leds &leds) { LedPainter::drawSelectedSequenceSection(leds, sequence.section()); LedPainter::drawSelectedSequenceSection(leds, sequence.section()); + } + break; + case Track::TrackMode::Logic: { + const auto &trackEngine = _engine.selectedTrackEngine().as(); + auto &sequence = _project.selectedLogicSequence(); + int currentStep = trackEngine.isActiveSequence(sequence) ? trackEngine.currentStep() : -1; + + for (int i = 0; i < 16; ++i) { + int stepIndex = stepOffset() + i; + bool red = (stepIndex == currentStep) || _stepSelection[stepIndex]; + bool green = (stepIndex != currentStep) && (sequence.step(stepIndex).gate() || _stepSelection[stepIndex]); + leds.set(MatrixMap::fromStep(i), red, green); + } + + LedPainter::drawSelectedSequenceSection(leds, sequence.section()); + } break; default: break; } + + if (globalKeyState()[Key::Page] && !globalKeyState()[Key::Shift]) { + for (int i = 0; i < 8; ++i) { + int index = MatrixMap::fromStep(i + 8); + leds.unmask(index); + switch (_project.selectedTrack().trackMode()) { + case Track::TrackMode::Note: + leds.set(index, false, noteQuickEditItems[i] != NoteSequenceListModel::Item::Last); + break; + case Track::TrackMode::Curve: + leds.set(index, false, curveQuickEditItems[i] != CurveSequenceListModel::Item::Last); + break; + case Track::TrackMode::Stochastic: + leds.set(index, false, stochasticQuickEditItems[i] != StochasticSequenceListModel::Item::Last); + break; + case Track::TrackMode::Logic: + leds.set(index, false, logicQuickEditItems[i] != LogicSequenceListModel::Item::Last); + break; + default: + break; + } + leds.mask(index); + } + int index = MatrixMap::fromStep(15); + leds.unmask(index); + leds.set(index, false, true); + leds.mask(index); + } } void OverviewPage::keyDown(KeyEvent &event) { @@ -333,6 +495,8 @@ void OverviewPage::keyPress(KeyPressEvent &event) { if (key.is(Key::Step15)) { bool lpConnected = _engine.isLaunchpadConnected(); track.togglePatternFollowDisplay(lpConnected); + } else { + quickEdit(key.quickEdit()); } } break; @@ -341,12 +505,30 @@ void OverviewPage::keyPress(KeyPressEvent &event) { if (key.is(Key::Step15)) { bool lpConnected = _engine.isLaunchpadConnected(); track.togglePatternFollowDisplay(lpConnected); + } else { + quickEdit(key.quickEdit()); } } break; + case Track::TrackMode::Logic: { + auto &track = _project.selectedTrack().logicTrack(); + if (key.is(Key::Step15)) { + bool lpConnected = _engine.isLaunchpadConnected(); + track.togglePatternFollowDisplay(lpConnected); + } else { + quickEdit(key.quickEdit()); + } + } + case Track::TrackMode::Stochastic: { + quickEdit(key.quickEdit()); + + } + break; default: break; - } + } + event.consume(); + return; } if (key.pageModifier()) { @@ -378,8 +560,40 @@ void OverviewPage::keyPress(KeyPressEvent &event) { default: break; } + event.consume(); + return; } + if (key.isEncoder() && _project.selectedTrack().trackMode() == Track::TrackMode::Logic) { + switch (_project.selectedLogicSequenceLayer()) { + case LogicSequence::Layer::NoteLogic: + showMessage("GATE LOGIC"); + _project.setSelectedLogicSequenceLayer(LogicSequence::Layer::GateLogic); + break; + + default: + showMessage("NOTE LOGIC"); + _project.setSelectedLogicSequenceLayer(LogicSequence::Layer::NoteLogic); + } + event.consume(); + return; + } + + if (key.isEncoder() && _project.selectedTrack().trackMode() == Track::TrackMode::Stochastic) { + auto loop = _project.selectedStochasticSequence().useLoop(); + _project.selectedStochasticSequence().setUseLoop(!loop); + event.consume(); + return;; + + } + + + if (key.isTrack() && event.count() == 2) { + _manager.pages().top.setMode(TopPage::Mode::SequenceEdit); + event.consume(); + return; + } + if (key.isStep() && event.count() == 2) { switch (track.trackMode()) { case Track::TrackMode::Note: { @@ -397,6 +611,13 @@ void OverviewPage::keyPress(KeyPressEvent &event) { event.consume(); } break; + case Track::TrackMode::Logic: { + auto &sequence = _project.selectedLogicSequence(); + int stepIndex = stepOffset() + key.step(); + sequence.step(stepIndex).toggleGate(); + event.consume(); + } + break; case Track::TrackMode::Curve: { int stepIndex = stepOffset() + key.step(); auto &sequence = _project.selectedCurveSequence(); @@ -452,13 +673,14 @@ void OverviewPage::keyPress(KeyPressEvent &event) { str("||||"); break; } - - showMessage(str); - + showMessage(str); + break; } default: break; } + event.consume(); + return; } if (key.isLeft()) { @@ -475,6 +697,12 @@ void OverviewPage::keyPress(KeyPressEvent &event) { track.curveTrack().setPatternFollowDisplay(false); break; } + case Track::TrackMode::Logic: { + auto &sequence = _project.selectedLogicSequence(); + sequence.setSecion(std::max(0, sequence.section() - 1)); + track.logicTrack().setPatternFollowDisplay(false); + break; + } default: break; } @@ -495,6 +723,12 @@ void OverviewPage::keyPress(KeyPressEvent &event) { track.curveTrack().setPatternFollowDisplay(false); break; } + case Track::TrackMode::Logic: { + auto &sequence = _project.selectedLogicSequence(); + sequence.setSecion(std::max(0, sequence.section() + 1)); + track.logicTrack().setPatternFollowDisplay(false); + break; + } default: break; } @@ -503,11 +737,21 @@ void OverviewPage::keyPress(KeyPressEvent &event) { } } +static int wrap(int value, int const lowerBound, int const upperBound) +{ + int rangeSize = upperBound - lowerBound + 1; + if (value < lowerBound) + value += rangeSize * ((lowerBound - value) / rangeSize + 1); + return lowerBound + (value - lowerBound) % rangeSize; +} + void OverviewPage::encoder(EncoderEvent &event) { auto &track = _project.selectedTrack(); if (!_stepSelection.any()) { + const auto val = wrap(_project.selectedTrackIndex()+event.value(), 0, 7); + _project.setSelectedTrackIndex(val); return; } @@ -530,15 +774,15 @@ void OverviewPage::encoder(EncoderEvent &event) { } break; case Track::TrackMode::Stochastic: { - auto &sequence = _project.selectedStochasticSequence(); + auto &sequence = _project.selectedStochasticSequence(); for (size_t stepIndex = 0; stepIndex < sequence.steps().size(); ++stepIndex) { if (_stepSelection[stepIndex]) { auto &step = sequence.step(stepIndex); step.setNoteVariationProbability(step.noteVariationProbability() + event.value()); } } - } - break; + } + break; case Track::TrackMode::Curve: { auto &sequence = _project.selectedCurveSequence(); for (size_t stepIndex = 0; stepIndex < sequence.steps().size(); ++stepIndex) { @@ -563,7 +807,28 @@ void OverviewPage::encoder(EncoderEvent &event) { } } } - } + } + break; + case Track::TrackMode::Logic: { + auto &sequence = _project.selectedLogicSequence(); + for (size_t stepIndex = 0; stepIndex < sequence.steps().size(); ++stepIndex) { + if (_stepSelection[stepIndex]) { + auto &step = sequence.step(stepIndex); + + switch (_project.selectedLogicSequenceLayer()) { + case LogicSequence::Layer::GateLogic: + step.setGateLogic(static_cast(step.gateLogic() + event.value())); + break; + case LogicSequence::Layer::NoteLogic: + step.setNoteLogic(static_cast(step.noteLogic() + event.value())); + break; + default: + break; + } + } + } + } + break; default: break; } @@ -742,11 +1007,178 @@ void OverviewPage::drawStochasticDetail(Canvas &canvas, const StochasticSequence canvas.setFont(Font::Tiny); } +void OverviewPage::drawLogicDetail(Canvas &canvas, const LogicSequence::Step &step) { + FixedStringBuilder<16> str; + + WindowPainter::drawFrame(canvas, 64, 16, 128, 32); + + canvas.setBlendMode(BlendMode::Set); + canvas.setColor(Color::Bright); + canvas.vline(64 + 32, 16, 32); + + canvas.setFont(Font::Small); + str("%d", _stepSelection.first() + 1); + if (_stepSelection.count() > 1) { + str("*"); + } + canvas.drawTextCentered(64, 16, 32, 32, str); + + canvas.setFont(Font::Tiny); + + str.reset(); + switch (_project.selectedLogicSequenceLayer()) { + case LogicSequence::Layer::GateLogic: { + switch (step.gateLogic()) { + case LogicSequence::GateLogicMode::One: + str("INPUT 1"); + break; + case LogicSequence::GateLogicMode::Two: + str("INPUT 2"); + break; + case LogicSequence::GateLogicMode::And: + str("AND"); + break; + case LogicSequence::GateLogicMode::Or: + str("OR"); + break; + case LogicSequence::GateLogicMode::Xor: + str("XOR"); + break; + case LogicSequence::GateLogicMode::Nand: + str("NAND"); + break; + case LogicSequence::GateLogicMode::RandomInput: + str("RND INPUT"); + break; + case LogicSequence::GateLogicMode::RandomLogic: + str("RND LOGIC"); + break; + default: + break; + } + canvas.setFont(Font::Small); + canvas.drawTextCentered(64 + 64, 32 - 4, 32, 8, str); + } + break; + case LogicSequence::Layer::NoteLogic: { + switch (step.noteLogic()) { + case LogicSequence::NoteLogicMode::NOne: + str("INPUT 1"); + break; + case LogicSequence::NoteLogicMode::NTwo: + str("INPUT 2"); + break; + case LogicSequence::NoteLogicMode::Min: + str("MIN"); + break; + case LogicSequence::NoteLogicMode::Max: + str("MAX"); + break; + case LogicSequence::NoteLogicMode::Sum: + str("SUM"); + break; + case LogicSequence::NoteLogicMode::Avg: + str("AVG"); + break; + case LogicSequence::NoteLogicMode::NRandomInput: + str("RND INPUT"); + break; + case LogicSequence::NoteLogicMode::NRandomLogic: + str("RND LOGIC"); + break; + default: + break; + } + canvas.setFont(Font::Small); + canvas.drawTextCentered(64 + 64, 32 - 4, 32, 8, str); + } + break; + default: + break; + } + + canvas.setFont(Font::Tiny); + +} + void OverviewPage::updateMonitorStep() { - auto &trackEngine = _engine.selectedTrackEngine().as(); - // TODO should we monitor an all layers not just note? - if (_stepSelection.any()) { - trackEngine.setMonitorStep(_stepSelection.first()); + switch (_project.selectedTrack().trackMode()) { + case Track::TrackMode::Note: { + auto &trackEngine = _engine.selectedTrackEngine().as(); + // TODO should we monitor an all layers not just note? + if (_stepSelection.any()) { + trackEngine.setMonitorStep(_stepSelection.first()); + } + } + break; + case Track::TrackMode::Curve: { + auto &trackEngine = _engine.selectedTrackEngine().as(); + if ( _stepSelection.any()) { + trackEngine.setMonitorStep(_stepSelection.first()); + trackEngine.setMonitorStepLevel(_project.selectedCurveSequenceLayer() == CurveSequence::Layer::Min ? CurveTrackEngine::MonitorLevel::Min : CurveTrackEngine::MonitorLevel::Max); + } + } + break; + case Track::TrackMode::Stochastic: { + auto &trackEngine = _engine.selectedTrackEngine().as(); + // TODO should we monitor an all layers not just note? + if (_stepSelection.any()) { + trackEngine.setMonitorStep(_stepSelection.first()); + } + } + break; + case Track::TrackMode::Logic: { + auto &trackEngine = _engine.selectedTrackEngine().as(); + // TODO should we monitor an all layers not just note? + if (_stepSelection.any()) { + trackEngine.setMonitorStep(_stepSelection.first()); + } + } + break; + default: + break; + } + +} + +void OverviewPage::quickEdit(int index) { + switch (_project.selectedTrack().trackMode()) { + case Track::TrackMode::Note: { + _noteListModel.setSequence(&_project.selectedNoteSequence()); + if (noteQuickEditItems[index] != NoteSequenceListModel::Item::Last) { + _manager.pages().quickEdit.show(_noteListModel, int(noteQuickEditItems[index])); + } + } + break; + case Track::TrackMode::Curve: { + CurveSequenceListModel _listModel; + + _curveListModel.setSequence(&_project.selectedCurveSequence()); + if (curveQuickEditItems[index] != CurveSequenceListModel::Item::Last) { + _manager.pages().quickEdit.show(_curveListModel, int(curveQuickEditItems[index])); + } + } + break; + case Track::TrackMode::Stochastic: { + StochasticSequenceListModel _listModel; + + _stochasticListModel.setSequence(&_project.selectedStochasticSequence()); + if (stochasticQuickEditItems[index] != StochasticSequenceListModel::Item::Last) { + _manager.pages().quickEdit.show(_stochasticListModel, int(stochasticQuickEditItems[index])); + } + } + break; + case Track::TrackMode::Logic: { + LogicSequenceListModel _listModel; + + _logicListModel.setSequence(&_project.selectedLogicSequence()); + if (logicQuickEditItems[index] != LogicSequenceListModel::Item::Last) { + _manager.pages().quickEdit.show(_logicListModel, int(logicQuickEditItems[index])); + } + } + break; + default: + break; } } \ No newline at end of file diff --git a/src/apps/sequencer/ui/pages/OverviewPage.h b/src/apps/sequencer/ui/pages/OverviewPage.h index e6cd82b5..a6f96320 100644 --- a/src/apps/sequencer/ui/pages/OverviewPage.h +++ b/src/apps/sequencer/ui/pages/OverviewPage.h @@ -2,6 +2,10 @@ #include "BasePage.h" #include "ui/StepSelection.h" +#include "ui/model/NoteSequenceListModel.h" +#include "ui/model/CurveSequenceListModel.h" +#include "ui/model/StochasticSequenceListModel.h" +#include "ui/model/LogicSequenceListModel.h" class OverviewPage : public BasePage { public: @@ -23,7 +27,9 @@ class OverviewPage : public BasePage { void drawDetail(Canvas &canvas, const NoteSequence::Step &step); void drawStochasticDetail(Canvas &canvas, const StochasticSequence::Step &step); void drawCurveDetail(Canvas &canvas, const CurveSequence::Step &step); + void drawLogicDetail(Canvas &canvas, const LogicSequence::Step &step); void updateMonitorStep(); + void quickEdit(int index); static const int StepCount = 16; @@ -48,4 +54,10 @@ class OverviewPage : public BasePage { StepSelection _stepSelection; bool _showDetail; uint32_t _showDetailTicks; + + NoteSequenceListModel _noteListModel; + CurveSequenceListModel _curveListModel; + StochasticSequenceListModel _stochasticListModel; + LogicSequenceListModel _logicListModel; + }; diff --git a/src/apps/sequencer/ui/pages/Pages.h b/src/apps/sequencer/ui/pages/Pages.h index e95c5aac..561fffea 100644 --- a/src/apps/sequencer/ui/pages/Pages.h +++ b/src/apps/sequencer/ui/pages/Pages.h @@ -26,6 +26,8 @@ #include "StartupPage.h" #include "StochasticSequenceEditPage.h" #include "StochasticSequencePage.h" +#include "LogicSequenceEditPage.h" +#include "LogicSequencePage.h" #include "SystemPage.h" #include "TempoPage.h" #include "TextInputPage.h" @@ -52,6 +54,8 @@ struct Pages { NoteSequenceEditPage noteSequenceEdit; CurveSequenceEditPage curveSequenceEdit; StochasticSequenceEditPage stochasticSequenceEdit; + LogicSequencePage logicSequence; + LogicSequenceEditPage logicSequenceEdit; PatternPage pattern; PerformerPage performer; SongPage song; @@ -95,6 +99,8 @@ struct Pages { noteSequenceEdit(manager, context), curveSequenceEdit(manager, context), stochasticSequenceEdit(manager, context), + logicSequence(manager, context), + logicSequenceEdit(manager, context), pattern(manager, context), performer(manager, context), song(manager, context), diff --git a/src/apps/sequencer/ui/pages/StochasticSequenceEditPage.cpp b/src/apps/sequencer/ui/pages/StochasticSequenceEditPage.cpp index 663a7ea3..2fd0e75e 100644 --- a/src/apps/sequencer/ui/pages/StochasticSequenceEditPage.cpp +++ b/src/apps/sequencer/ui/pages/StochasticSequenceEditPage.cpp @@ -43,8 +43,8 @@ enum class Function { static const char *functionNames[] = { "GATE", "RETRIG", "LENGTH", "NOTE", "COND" }; static const StochasticSequenceListModel::Item quickEditItems[8] = { - StochasticSequenceListModel::Item::Last, - StochasticSequenceListModel::Item::Last, + StochasticSequenceListModel::Item::SequenceFirstStep, + StochasticSequenceListModel::Item::SequenceLastStep, StochasticSequenceListModel::Item::RunMode, StochasticSequenceListModel::Item::Divisor, StochasticSequenceListModel::Item::ResetMeasure, @@ -149,7 +149,18 @@ void StochasticSequenceEditPage::draw(Canvas &canvas) { } switch (layer()) { - case Layer::Gate: + case Layer::Gate: { + int rootNote = sequence.selectedRootNote(_model.project().rootNote()); + FixedStringBuilder<8> str; + if (step.bypassScale()) { + const Scale &bypassScale = std::ref(Scale::get(0)); + bypassScale.noteName(str, step.note(), rootNote, Scale::Short1); + canvas.drawText(x + (stepWidth - canvas.textWidth(str) + 1) / 2, y + 27, str); + break; + } + scale.noteName(str, step.note(), rootNote, Scale::Short1); + canvas.drawText(x + (stepWidth - canvas.textWidth(str) + 1) / 2, y + 27, str); + } break; case Layer::GateProbability: SequencePainter::drawProbability( @@ -292,17 +303,9 @@ void StochasticSequenceEditPage::draw(Canvas &canvas) { break; } case Layer::StageRepeats: { - canvas.setColor(Bright); - FixedStringBuilder<8> str("x%d", step.stageRepeats()+1); - canvas.drawText(x + (stepWidth - canvas.textWidth(str) + 1) / 2, y + 20, str); break; } case Layer::StageRepeatsMode: { - SequencePainter::drawStageRepeatMode( - canvas, - x + 2, y + 18, stepWidth - 4, 6, - step.stageRepeatMode() - ); break; } case Layer::Last: diff --git a/src/apps/sequencer/ui/pages/TopPage.cpp b/src/apps/sequencer/ui/pages/TopPage.cpp index 70618aa9..fc56c6b0 100644 --- a/src/apps/sequencer/ui/pages/TopPage.cpp +++ b/src/apps/sequencer/ui/pages/TopPage.cpp @@ -231,6 +231,9 @@ void TopPage::setSequencePage() { case Track::TrackMode::Stochastic: setMainPage(pages.stochasticSequence); break; + case Track::TrackMode::Logic: + setMainPage(pages.logicSequence); + break; case Track::TrackMode::Last: break; } @@ -252,6 +255,9 @@ void TopPage::setSequenceEditPage() { case Track::TrackMode::Stochastic: setMainPage(pages.stochasticSequenceEdit); break; + case Track::TrackMode::Logic: + setMainPage(pages.logicSequenceEdit); + break; case Track::TrackMode::Last: break; } diff --git a/src/apps/sequencer/ui/pages/TrackPage.cpp b/src/apps/sequencer/ui/pages/TrackPage.cpp index 74e3495c..4890772d 100644 --- a/src/apps/sequencer/ui/pages/TrackPage.cpp +++ b/src/apps/sequencer/ui/pages/TrackPage.cpp @@ -6,6 +6,8 @@ #include "ui/painters/WindowPainter.h" #include "core/utils/StringBuilder.h" +#include +#include enum class ContextAction { Init, @@ -100,6 +102,13 @@ void TrackPage::keyPress(KeyPressEvent &event) { } }); break; + case Track::TrackMode::Logic: + _manager.pages().textInput.show("NAME:", _logicTrack->name(), LogicTrack::NameLength, [this] (bool result, const char *text) { + if (result) { + _project.selectedTrack().logicTrack().setName(text); + } + }); + break; case Track::TrackMode::Last: break; } @@ -107,6 +116,67 @@ void TrackPage::keyPress(KeyPressEvent &event) { return; } +if (key.is(Key::Encoder) && selectedRow() == 14) { + + if (_project.selectedTrack().trackMode() == Track::TrackMode::Note) { + std::vector availableLogicTracks; + for (int i =0; i<8; ++i) { + if (_project.track(i).trackMode() == Track::TrackMode::Logic && i > _project.selectedTrack().trackIndex()) { + if ((_project.track(i).logicTrack().inputTrack1() == -1 && _project.track(i).logicTrack().inputTrack2() == -1) || + (_project.track(i).logicTrack().inputTrack1() == -1 || _project.track(i).logicTrack().inputTrack2() == -1)) { + availableLogicTracks.insert(availableLogicTracks.end(), i); + } + } + } + _noteTrackListModel.setAvailableLogicTracks(availableLogicTracks); + } +} + +if (key.is(Key::Encoder) && selectedRow() == 15) { + + + if (_project.selectedTrack().trackMode() == Track::TrackMode::Note) { + int logicTrackIndex = _project.selectedTrack().noteTrack().logicTrack(); + if (logicTrackIndex!=-1) { + + const auto logicTrack = _project.track(logicTrackIndex).logicTrack(); + if ((logicTrack.inputTrack1()!=-1 && logicTrack.inputTrack1() != _project.selectedTrackIndex()) + && (logicTrack.inputTrack2()!=-1 && logicTrack.inputTrack2() != _project.selectedTrackIndex())) { + return; + } + + const auto tmpVal = _project.selectedTrack().noteTrack().logicTrackInput(); + + + if (tmpVal == 0 && logicTrack.inputTrack1() != -1 && logicTrack.inputTrack1() != _project.selectedTrack().trackIndex()) { + _project.selectedTrack().noteTrack().setLogicTrackInput(tmpVal+1); + } else if (tmpVal == 0 && logicTrack.inputTrack1() == -1 && logicTrack.inputTrack1() == _project.selectedTrack().trackIndex()) { + _project.selectedTrack().noteTrack().setLogicTrackInput(tmpVal); + } + if (tmpVal == 1 && logicTrack.inputTrack2() != -1 && logicTrack.inputTrack2() != _project.selectedTrack().trackIndex()) { + _project.selectedTrack().noteTrack().setLogicTrackInput(tmpVal-1); + } else if (tmpVal == 1 && logicTrack.inputTrack2() == -1 && logicTrack.inputTrack1() == _project.selectedTrack().trackIndex()) { + _project.selectedTrack().noteTrack().setLogicTrackInput(tmpVal); + } + + + + if (_project.selectedTrack().noteTrack().logicTrackInput() == 0) { + _project.track(logicTrackIndex).logicTrack().setInputTrack1(_project.selectedTrack().trackIndex()); + } else if (_project.selectedTrack().noteTrack().logicTrackInput() == 1) { + _project.track(logicTrackIndex).logicTrack().setInputTrack2(_project.selectedTrack().trackIndex()); + } else { + if (_project.track(logicTrackIndex).logicTrack().inputTrack1() == _project.selectedTrack().trackIndex()) { + _project.track(logicTrackIndex).logicTrack().setInputTrack1(-1); + } + if (_project.track(logicTrackIndex).logicTrack().inputTrack2() == _project.selectedTrack().trackIndex()) { + _project.track(logicTrackIndex).logicTrack().setInputTrack2(-1); + } + } + } + } + +} ListPage::keyPress(event); } @@ -135,6 +205,11 @@ void TrackPage::setTrack(Track &track) { newListModel = &_stochasticTrackListModel; _stochasticTrack = &track.stochasticTrack(); break; + case Track::TrackMode::Logic: + _logicTrackListModel.setTrack(track.logicTrack()); + newListModel = &_logicTrackListModel; + _logicTrack = &track.logicTrack(); + break; case Track::TrackMode::Last: ASSERT(false, "invalid track mode"); break; diff --git a/src/apps/sequencer/ui/pages/TrackPage.h b/src/apps/sequencer/ui/pages/TrackPage.h index 890a6c0b..adb6c4ee 100644 --- a/src/apps/sequencer/ui/pages/TrackPage.h +++ b/src/apps/sequencer/ui/pages/TrackPage.h @@ -6,6 +6,7 @@ #include "ui/model/CurveTrackListModel.h" #include "ui/model/MidiCvTrackListModel.h" #include "ui/model/StochasticTrackListModel.h" +#include "ui/model/LogicTrackListModel.h" class TrackPage : public ListPage { @@ -37,6 +38,7 @@ class TrackPage : public ListPage { CurveTrackListModel _curveTrackListModel; MidiCvTrackListModel _midiCvTrackListModel; StochasticTrackListModel _stochasticTrackListModel; + LogicTrackListModel _logicTrackListModel; Track *_track; @@ -44,4 +46,5 @@ class TrackPage : public ListPage { CurveTrack *_curveTrack; MidiCvTrack *_midiCvTrack; StochasticTrack *_stochasticTrack; + LogicTrack *_logicTrack; }; diff --git a/src/apps/sequencer/ui/painters/SequencePainter.cpp b/src/apps/sequencer/ui/painters/SequencePainter.cpp index 47181954..5586f50a 100644 --- a/src/apps/sequencer/ui/painters/SequencePainter.cpp +++ b/src/apps/sequencer/ui/painters/SequencePainter.cpp @@ -2,6 +2,7 @@ #include "core/gfx/Canvas.h" #include "model/NoteSequence.h" #include "model/StochasticSequence.h" +#include "model/LogicSequence.h" #include void SequencePainter::drawLoopStart(Canvas &canvas, int x, int y, int w) { @@ -112,7 +113,7 @@ void SequencePainter::drawBypassScale(Canvas &canvas, int x, int y, int w, int h } const std::bitset<4> mask = 0x1; -void SequencePainter::drawStageRepeatMode(Canvas &canvas, int x, int y, int w, int h, NoteSequence::StageRepeatMode mode) { +void SequencePainter::drawStageRepeatMode(Canvas &canvas, int x, int y, int w, int h, Types::StageRepeatMode mode) { canvas.setBlendMode(BlendMode::Set); canvas.setColor(Bright); int bottom = y + h - 1; @@ -120,34 +121,34 @@ void SequencePainter::drawStageRepeatMode(Canvas &canvas, int x, int y, int w, i x += (w - 8) / 2; switch (mode) { - case NoteSequence::StageRepeatMode::Each: + case Types::StageRepeatMode::Each: enabled = 0xf; break; - case NoteSequence::StageRepeatMode::First: + case Types::StageRepeatMode::First: enabled = 0x1; break; - case NoteSequence::StageRepeatMode::Middle: + case Types::StageRepeatMode::Middle: enabled = 0x1 << 2; break; - case NoteSequence::StageRepeatMode::Last: + case Types::StageRepeatMode::Last: enabled = 0x8; break; - case NoteSequence::StageRepeatMode::Odd: + case Types::StageRepeatMode::Odd: enabled = 0x5; break; - case NoteSequence::StageRepeatMode::Even: + case Types::StageRepeatMode::Even: enabled = 0x5 << 1; break; - case NoteSequence::StageRepeatMode::Triplets: + case Types::StageRepeatMode::Triplets: enabled = 0x9; break; - case NoteSequence::StageRepeatMode::Random: + case Types::StageRepeatMode::Random: enabled = 0xf; break; } for (int i = 0; i < 4; i++) { - if (mode == NoteSequence::StageRepeatMode::Random) { + if (mode == Types::StageRepeatMode::Random) { canvas.drawText(x-1, y+4, "????"); } else { if (((enabled >> i) & mask) == 1) { @@ -159,50 +160,71 @@ void SequencePainter::drawStageRepeatMode(Canvas &canvas, int x, int y, int w, i } } -void SequencePainter::drawStageRepeatMode(Canvas &canvas, int x, int y, int w, int h, StochasticSequence::StageRepeatMode mode) { +void SequencePainter::drawGateLogicMode(Canvas &canvas, int x, int y, int w, int h, LogicSequence::GateLogicMode mode) { canvas.setBlendMode(BlendMode::Set); canvas.setColor(Bright); - int bottom = y + h - 1; std::bitset<4> enabled; x += (w - 8) / 2; switch (mode) { - case StochasticSequence::StageRepeatMode::Each: - enabled = 0xf; + case LogicSequence::GateLogicMode::One: + canvas.drawTextCentered(x, y+4, 8, -8, "1"); break; - case StochasticSequence::StageRepeatMode::First: - enabled = 0x1; + case LogicSequence::GateLogicMode::Two: + canvas.drawTextCentered(x, y+4, 8, -8, "2"); break; - case StochasticSequence::StageRepeatMode::Middle: - enabled = 0x1 << 2; + case LogicSequence::GateLogicMode::And: + canvas.drawTextCentered(x, y+4, 8, -8, "&"); break; - case StochasticSequence::StageRepeatMode::Last: - enabled = 0x8; + case LogicSequence::GateLogicMode::Or: + canvas.drawTextCentered(x, y+4, 8, -8, "|"); break; - case StochasticSequence::StageRepeatMode::Odd: - enabled = 0x5; + case LogicSequence::GateLogicMode::Xor: + canvas.drawTextCentered(x, y+4, 8, -8, "x|"); break; - case StochasticSequence::StageRepeatMode::Even: - enabled = 0x5 << 1; + case LogicSequence::GateLogicMode::Nand: + canvas.drawTextCentered(x, y+4, 8, -8, "!&"); break; - case StochasticSequence::StageRepeatMode::Triplets: - enabled = 0x9; + case LogicSequence::GateLogicMode::RandomInput: + canvas.drawTextCentered(x, y+4, 8, -8, "1?2"); break; - case StochasticSequence::StageRepeatMode::Random: - enabled = 0xf; + case LogicSequence::GateLogicMode::RandomLogic: + canvas.drawTextCentered(x, y+4, 8, -8, "????"); break; } +} - for (int i = 0; i < 4; i++) { - if (mode == StochasticSequence::StageRepeatMode::Random) { - canvas.drawText(x-1, y+4, "????"); - } else { - if (((enabled >> i) & mask) == 1) { - canvas.vline(x + 2 * i, y, h); - } else { - canvas.hline(x + 2 * i, bottom, 1); - } - } +void SequencePainter::drawNoteLogicMode(Canvas &canvas, int x, int y, int w, int h, LogicSequence::NoteLogicMode mode) { + canvas.setBlendMode(BlendMode::Set); + canvas.setColor(Bright); + std::bitset<4> enabled; + x += (w - 8) / 2; + + switch (mode) { + case LogicSequence::NoteLogicMode::NOne: + canvas.drawTextCentered(x, y+4, 8, -8, "1"); + break; + case LogicSequence::NoteLogicMode::NTwo: + canvas.drawTextCentered(x, y+4, 8, -8, "2"); + break; + case LogicSequence::NoteLogicMode::Min: + canvas.drawTextCentered(x, y+4, 8, -8, "<"); + break; + case LogicSequence::NoteLogicMode::Max: + canvas.drawTextCentered(x, y+4, 8, -8, ">"); + break; + case LogicSequence::NoteLogicMode::Sum: + canvas.drawTextCentered(x, y+4, 8, -8, "+"); + break; + case LogicSequence::NoteLogicMode::Avg: + canvas.drawTextCentered(x, y+4, 8, -8, "~"); + break; + case LogicSequence::NoteLogicMode::NRandomInput: + canvas.drawTextCentered(x, y+4, 8, -8, "1?2"); + break; + case LogicSequence::NoteLogicMode::NRandomLogic: + canvas.drawTextCentered(x, y+4, 8, -8, "????"); + break; } } diff --git a/src/apps/sequencer/ui/painters/SequencePainter.h b/src/apps/sequencer/ui/painters/SequencePainter.h index e95746f8..375d158e 100644 --- a/src/apps/sequencer/ui/painters/SequencePainter.h +++ b/src/apps/sequencer/ui/painters/SequencePainter.h @@ -3,6 +3,7 @@ #include "core/gfx/Canvas.h" #include "model/NoteSequence.h" #include "model/StochasticSequence.h" +#include "model/LogicSequence.h" class SequencePainter { @@ -18,8 +19,10 @@ class SequencePainter { static void drawSlide(Canvas &canvas, int x, int y, int w, int h, bool active); static void drawBypassScale(Canvas &canvas, int x, int y, int w, int h, bool active); - static void drawStageRepeatMode(Canvas &canvas, int x, int y, int w, int h, NoteSequence::StageRepeatMode mode); - static void drawStageRepeatMode(Canvas &canvas, int x, int y, int w, int h, StochasticSequence::StageRepeatMode mode); + static void drawStageRepeatMode(Canvas &canvas, int x, int y, int w, int h, Types::StageRepeatMode mode); + + static void drawGateLogicMode(Canvas &canvas, int x, int y, int w, int h, LogicSequence::GateLogicMode mode); + static void drawNoteLogicMode(Canvas &canvas, int x, int y, int w, int h, LogicSequence::NoteLogicMode mode); static void drawSequenceProgress(Canvas &canvas, int x, int y, int w, int h, float progress);