Deluge Firmware 1.3.0
Build date: 2026.10.14
Loading...
Searching...
No Matches
playback_handler.h
1/*
2 * Copyright © 2014-2023 Synthstrom Audible Limited
3 *
4 * This file is part of The Synthstrom Audible Deluge Firmware.
5 *
6 * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software Foundation,
8 * either version 3 of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 * See the GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along with this program.
15 * If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#pragma once
19
20#include "definitions_cxx.hpp"
21#include "util/d_string.h"
22#include <cstdint>
23
24enum class RecordingMode {
25 OFF,
26 NORMAL,
27 ARRANGEMENT,
28};
29
30constexpr int32_t kNumInputTicksForMovingAverage = 24;
31
32#define PLAYBACK_CLOCK_INTERNAL_ACTIVE 1
33#define PLAYBACK_CLOCK_EXTERNAL_ACTIVE 2
34#define PLAYBACK_SWITCHED_ON 4
35
36#define PLAYBACK_CLOCK_EITHER_ACTIVE (PLAYBACK_CLOCK_INTERNAL_ACTIVE | PLAYBACK_CLOCK_EXTERNAL_ACTIVE)
37
38class Action;
39class Clip;
40class InstrumentClip;
41class Kit;
42class MIDICable;
43class NoteRow;
44class Song;
45
46constexpr uint16_t metronomeValuesBPM[16] = {
47 60, 63, 66, 69, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116,
48};
49
50constexpr uint16_t metronomeValueBoundaries[16] = {
51 1793, 1709, 1633, 1542, 1490, 1413, 1345, 1282, 1225, 1173, 1125, 1081, 1040, 1002, 967, 934,
52};
53
54class PlaybackHandler {
55public:
56 PlaybackHandler();
57 void midiRoutine();
58 void routine();
59
60 void playButtonPressed(int32_t buttonPressLatency);
61 void recordButtonPressed();
62 void setupPlaybackUsingInternalClock(int32_t buttonPressLatencyForTempolessRecord = 0, bool allowCountIn = true,
63 bool restartingPlayback = false, bool restartingPlaybackAtBeginning = false);
64 void setupPlaybackUsingExternalClock(bool switchingFromInternalClock = false, bool fromContinueCommand = false);
65 void setupPlayback(int32_t newPlaybackState, int32_t playFromPos, bool doOneLastAudioRoutineCall = false,
66 bool shouldShiftAccordingToClipInstance = true,
67 int32_t buttonPressLatencyForTempolessRecord = 0);
68 void endPlayback();
69 void inputTick(bool fromTriggerClock = false, uint32_t time = 0);
70 void startMessageReceived();
71 void continueMessageReceived();
72 void stopMessageReceived();
73 void clockMessageReceived(uint32_t time);
74 void tempoEncoderAction(int8_t offset, bool encoderButtonPressed, bool shiftButtonPressed);
75 bool isCurrentlyRecording();
76 void positionPointerReceived(uint8_t data1, uint8_t data2);
77 void doSongSwap(bool preservePlayPosition = false);
78 void forceResetPlayPos(Song* song, bool restartingPlaybackAtBeginning = false);
79 void expectEvent();
80 void setMidiInClockEnabled(bool newValue);
81 int32_t getActualArrangementRecordPos();
82 int32_t getArrangementRecordPosAtLastActionedSwungTick();
83 void slowRoutine();
84 void scheduleSwungTickFromExternalClock();
85 int32_t getNumSwungTicksInSinceLastTimerTick(uint32_t* timeRemainder = nullptr);
86 int32_t getNumSwungTicksInSinceLastActionedSwungTick(uint32_t* timeRemainder = nullptr);
87 int64_t getActualSwungTickCount(uint32_t* timeRemainder = nullptr);
88 int64_t getCurrentInternalTickCount(uint32_t* remainder = nullptr);
89 void scheduleSwungTick();
90 int32_t getInternalTickTime(int64_t internalTickCount);
91 void scheduleTriggerClockOutTick();
92 void scheduleMIDIClockOutTick();
93 void scheduleNextTimerTick(uint32_t doubleSwingInterval);
94
95 // MIDI-clock-out ticks
96 bool midiClockOutTickScheduled;
97 uint32_t timeNextMIDIClockOutTick;
98 int64_t lastMIDIClockOutTickDone;
99 // audioSampleTimer when we last actually sent a MIDI clock out tick. Used to rate-limit emission under external
100 // clock so a backlog of buffered input clocks (e.g. drained after a blocking song load/save) can't burst out.
101 uint32_t timeLastMIDIClockOutTickSent;
102
103 // Playback
104 uint8_t playbackState;
105 bool usingAnalogClockInput; // Value is only valid if usingInternalClock is false
106 RecordingMode recording;
107 bool ignoringMidiClockInput;
108
109 int32_t posToNextContinuePlaybackFrom; // This will then have 1 subtracted from it when actually physically set
110 uint32_t timeLastMIDIStartOrContinueMessageSent;
111
112 // Timer ticks
113 int64_t lastTimerTickActioned; // Not valid while playback being set up
114 int64_t nextTimerTickScheduled; // *Yes* valid while (internal clock) playback being set up. Will be zero during
115 // that time
116 uint64_t timeNextTimerTickBig; // Not valid while playback being set up
117 uint64_t timeLastTimerTickBig; // Not valid while playback being set up
118
119 // Input ticks
120 uint32_t timeLastInputTicks[kNumInputTicksForMovingAverage];
121 uint32_t timePerInputTickMovingAverage; // 0 means that a default will be set the first time it's used
122 uint8_t numInputTickTimesCounted;
123
124 bool tempoMagnitudeMatchingActiveNow;
125 // unsigned long timeFirstInputTick; // First tick received for current tally
126 unsigned long
127 timeVeryFirstInputTick; // Very first tick received in playback. Only used for tempo magnitude matching
128 int64_t lastInputTickReceived;
129 unsigned long targetedTimePerInputTick;
130
131 // Swung ticks
132 bool swungTickScheduled;
133 uint32_t scheduledSwungTickTime;
134 // Now, swung ticks are only "actioned" in the following circumstances:
135 // - A note starts or ends
136 // - Automation event
137 // - A clip loops
138 // - A "launch" event
139 // - Start of playback, including if count-in ends
140 int64_t lastSwungTickActioned; // Will be set to a phony-ish "0" while playback being set up
141 // True between setupPlayback() and the first actionSwungTick(): tick 0 is due but hasn't been actioned yet, so a
142 // swungTicksTilNextEvent of 0 legitimately means "action tick 0" rather than "re-action the current tick".
143 bool firstSwungTickPending;
144
145 // Trigger-clock-out ticks
146 bool triggerClockOutTickScheduled;
147 uint32_t timeNextTriggerClockOutTick;
148 int64_t lastTriggerClockOutTickDone;
149 // audioSampleTimer when we last actually sent an analog/trigger clock out tick. Used to rate-limit emission under
150 // external clock so a backlog of buffered input clocks (e.g. drained after a blocking song load/save) can't burst
151 // out. See timeLastMIDIClockOutTickSent and issue #3587.
152 uint32_t timeLastTriggerClockOutTickSent;
153
154 uint32_t analogOutTicksPPQN; // Relative to the "external world", which also means relative to the displayed tempo
155 uint32_t analogInTicksPPQN;
156 uint32_t timeLastAnalogClockInputRisingEdge;
157 bool analogClockInputAutoStart;
158
159 bool songSwapShouldPreserveTempo;
160
161 // User options
162 bool metronomeOn;
163 bool midiOutClockEnabled;
164 bool midiInClockEnabled;
165 bool tempoMagnitudeMatchingEnabled;
166 uint8_t countInBars;
167
168 int32_t swungTicksTilNextEvent;
169
170 int32_t ticksLeftInCountIn;
171 int32_t currentVisualCountForCountIn;
172
173 int32_t metronomeOffset;
174
175 void setLedStates();
176 void tapTempoAutoSwitchOff();
177 void reassessInputTickScaling();
178 void resyncInternalTicksToInputTicks(Song* song);
179 bool shouldRecordNotesNow();
180 void stopAnyRecording();
181 uint32_t getTimePerInternalTick();
182 uint64_t getTimePerInternalTickBig();
183 float getTimePerInternalTickFloat();
184 uint32_t getTimePerInternalTickInverse(bool getStickyValue = false);
185 void tapTempoButtonPress(bool useNormalTapTempoBehaviour);
186 void doTriggerClockOutTick();
187 void doMIDIClockOutTick();
188 void resyncAnalogOutTicksToInternalTicks();
189 void resyncMIDIClockOutTicksToInternalTicks();
190 void analogClockRisingEdge(uint32_t time);
191 void toggleMetronomeStatus();
192 void commandDisplayTempo();
193 void setMidiOutClockMode(bool newValue);
194 void pitchBendReceived(MIDICable& cable, uint8_t channel, uint8_t data1, uint8_t data2, bool* doingMidiThru);
195 void midiCCReceived(MIDICable& cable, uint8_t channel, uint8_t ccNumber, uint8_t value, bool* doingMidiThru);
196 void programChangeReceived(MIDICable& cable, int32_t channel, int32_t program);
197 void aftertouchReceived(MIDICable& cable, int32_t channel, int32_t value, int32_t noteCode,
198 bool* doingMidiThru); // noteCode -1 means channel-wide
199 void loopCommand(OverDubType overdubNature);
200 void grabTempoFromClip(Clip* clip);
201 int32_t getTimeLeftInCountIn();
202
203 void noteMessageReceived(MIDICable& cable, bool on, int32_t channel, int32_t note, int32_t velocity,
204 bool* doingMidiThru);
205 bool subModeAllowsRecording();
206
207 float calculateBPM(float timePerInternalTick);
208 float getTempoBPMAtArrangementPos(int32_t pos);
209 void getTempoParamsAtArrangementPos(int32_t pos, int32_t* magnitude, int8_t* whichValue);
210 void switchToArrangement();
211 void switchToSession();
212 void finishTempolessRecording(bool startPlaybackAgain, int32_t buttonLatencyForTempolessRecord,
213 bool shouldExitRecordMode = true);
214
215 int32_t arrangementPosToStartAtOnSwitch;
216
217 bool stopOutputRecordingAtLoopEnd;
218
219 void actionTimerTick();
220 void actionTimerTickPart2();
221 void actionSwungTick();
222 void scheduleSwungTickFromInternalClock();
223 bool currentlySendingMIDIOutputClocks();
224
225 inline bool isExternalClockActive() { return (playbackState & PLAYBACK_CLOCK_EXTERNAL_ACTIVE); }
226 inline bool isInternalClockActive() { return (playbackState & PLAYBACK_CLOCK_INTERNAL_ACTIVE); }
227 inline bool isEitherClockActive() { return (playbackState & PLAYBACK_CLOCK_EITHER_ACTIVE); }
228
229 // TEMPO encoder commands
231 void commandEditSwingAmount(int8_t offset);
233 void commandEditSwingInterval(int8_t offset);
234 void commandNudgeClock(int8_t offset);
235 void commandEditClockOutScale(int8_t offset);
236 void commandEditTempoCoarse(int8_t offset);
237 void commandEditTempoFine(int8_t offset);
238 void commandDisplayTempo(int8_t offset);
239 void commandClearTempoAutomation();
240
241 void getTempoStringForOLED(float tempoBPM, StringBuf& buffer);
242
243 void tryLoopCommand(GlobalMIDICommand command);
244
245 float calculateBPMForDisplay();
246
247private:
248 uint32_t timerTicksToOutputTicks(uint32_t timerTicks);
249
250 // These are all only relevant while playing synced.
251 uint32_t timePerInternalTickMovingAverage; // Recalculated every ~24 clocks
252 uint32_t veryCurrentTimePerInternalTickInverse; // Recalculated at every received clock message
253 uint32_t stickyCurrentTimePerInternalTickInverse; // Moving average kinda thing
254 uint32_t lowpassedTimePerInternalTick;
255 uint32_t slowpassedTimePerInternalTick;
256 uint32_t stickyTimePerInternalTick;
257
258 uint16_t tapTempoNumPresses;
259 uint32_t tapTempoFirstPressTime;
260
261 int32_t numOutputClocksWaitingToBeSent;
262 int32_t numInputTicksToSkip;
263 uint32_t skipAnalogClocks;
264 uint32_t skipMidiClocks;
265
266 void resetTimePerInternalTickMovingAverage();
267 void getCurrentTempoParams(int32_t* magnitude, int8_t* whichValue);
268 void getTempoParamsFromTimePer(uint64_t timePer, int32_t* magnitude, int8_t* whichValue);
269 void displayTempoFromParams(int32_t magnitude, int8_t whichValue);
270 void displayTempoBPM(float tempoBPM);
271 void getAnalogOutTicksToInternalTicksRatio(uint32_t* internalTicksPer, uint32_t* analogOutTicksPer);
272 void getMIDIClockOutTicksToInternalTicksRatio(uint32_t* internalTicksPer, uint32_t* midiClockOutTicksPer);
273 void getInternalTicksToInputTicksRatio(uint32_t* inputTicksPer, uint32_t* internalTicksPer);
274 void sendOutPositionViaMIDI(int32_t pos, bool outputClocksWereSwitchedOff = false);
275 // void scheduleNextTimerTick();
276 bool startIgnoringMidiClockInputIfNecessary();
277 uint32_t setTempoFromAudioClipLength(uint64_t loopLengthSamples, Action* action);
278 bool offerNoteToLearnedThings(MIDICable& cable, bool on, int32_t channel, int32_t note);
279 bool tryGlobalMIDICommands(MIDICable& cable, int32_t channel, int32_t note);
280 bool tryGlobalMIDICommandsOff(MIDICable& cable, int32_t channel, int32_t note);
281 void decideOnCurrentPlaybackMode();
282 float getCurrentInternalTickFloatFollowingExternalClock();
283 void scheduleTriggerClockOutTickParamsKnown(uint32_t analogOutTicksPer, uint64_t fractionLastTimerTick,
284 uint64_t fractionNextAnalogOutTick);
285 void scheduleTriggerClockOutTickFromExternalClock();
286 void scheduleMIDIClockOutTickParamsKnown(uint32_t midiClockOutTicksPer, uint64_t fractionLastTimerTick,
287 uint64_t fractionNextMIDIClockOutTick);
288 void scheduleMIDIClockOutTickFromExternalClock();
289};
290
291extern PlaybackHandler playbackHandler;
Definition action.h:79
Definition clip.h:46
Definition instrument_clip.h:48
Definition kit.h:34
A MIDI cable connection. Stores all state specific to a given cable and its contained ports and chann...
Definition midi_device.h:96
Definition note_row.h:99
Definition playback_handler.h:54
void commandDisplaySwingInterval()
Definition playback_handler.cpp:2094
void commandDisplaySwingAmount()
Definition playback_handler.cpp:2115
Definition song.h:103
Definition d_stringbuf.h:16