Deluge Firmware 1.3.0
Build date: 2026.07.15
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
142 // Trigger-clock-out ticks
143 bool triggerClockOutTickScheduled;
144 uint32_t timeNextTriggerClockOutTick;
145 int64_t lastTriggerClockOutTickDone;
146 // audioSampleTimer when we last actually sent an analog/trigger clock out tick. Used to rate-limit emission under
147 // external clock so a backlog of buffered input clocks (e.g. drained after a blocking song load/save) can't burst
148 // out. See timeLastMIDIClockOutTickSent and issue #3587.
149 uint32_t timeLastTriggerClockOutTickSent;
150
151 uint32_t analogOutTicksPPQN; // Relative to the "external world", which also means relative to the displayed tempo
152 uint32_t analogInTicksPPQN;
153 uint32_t timeLastAnalogClockInputRisingEdge;
154 bool analogClockInputAutoStart;
155
156 bool songSwapShouldPreserveTempo;
157
158 // User options
159 bool metronomeOn;
160 bool midiOutClockEnabled;
161 bool midiInClockEnabled;
162 bool tempoMagnitudeMatchingEnabled;
163 uint8_t countInBars;
164
165 int32_t swungTicksTilNextEvent;
166
167 int32_t ticksLeftInCountIn;
168 int32_t currentVisualCountForCountIn;
169
170 int32_t metronomeOffset;
171
172 void setLedStates();
173 void tapTempoAutoSwitchOff();
174 void reassessInputTickScaling();
175 void resyncInternalTicksToInputTicks(Song* song);
176 bool shouldRecordNotesNow();
177 void stopAnyRecording();
178 uint32_t getTimePerInternalTick();
179 uint64_t getTimePerInternalTickBig();
180 float getTimePerInternalTickFloat();
181 uint32_t getTimePerInternalTickInverse(bool getStickyValue = false);
182 void tapTempoButtonPress(bool useNormalTapTempoBehaviour);
183 void doTriggerClockOutTick();
184 void doMIDIClockOutTick();
185 void resyncAnalogOutTicksToInternalTicks();
186 void resyncMIDIClockOutTicksToInternalTicks();
187 void analogClockRisingEdge(uint32_t time);
188 void toggleMetronomeStatus();
189 void commandDisplayTempo();
190 void setMidiOutClockMode(bool newValue);
191 void pitchBendReceived(MIDICable& cable, uint8_t channel, uint8_t data1, uint8_t data2, bool* doingMidiThru);
192 void midiCCReceived(MIDICable& cable, uint8_t channel, uint8_t ccNumber, uint8_t value, bool* doingMidiThru);
193 void programChangeReceived(MIDICable& cable, int32_t channel, int32_t program);
194 void aftertouchReceived(MIDICable& cable, int32_t channel, int32_t value, int32_t noteCode,
195 bool* doingMidiThru); // noteCode -1 means channel-wide
196 void loopCommand(OverDubType overdubNature);
197 void grabTempoFromClip(Clip* clip);
198 int32_t getTimeLeftInCountIn();
199
200 void noteMessageReceived(MIDICable& cable, bool on, int32_t channel, int32_t note, int32_t velocity,
201 bool* doingMidiThru);
202 bool subModeAllowsRecording();
203
204 float calculateBPM(float timePerInternalTick);
205 void switchToArrangement();
206 void switchToSession();
207 void finishTempolessRecording(bool startPlaybackAgain, int32_t buttonLatencyForTempolessRecord,
208 bool shouldExitRecordMode = true);
209
210 int32_t arrangementPosToStartAtOnSwitch;
211
212 bool stopOutputRecordingAtLoopEnd;
213
214 void actionTimerTick();
215 void actionTimerTickPart2();
216 void actionSwungTick();
217 void scheduleSwungTickFromInternalClock();
218 bool currentlySendingMIDIOutputClocks();
219
220 inline bool isExternalClockActive() { return (playbackState & PLAYBACK_CLOCK_EXTERNAL_ACTIVE); }
221 inline bool isInternalClockActive() { return (playbackState & PLAYBACK_CLOCK_INTERNAL_ACTIVE); }
222 inline bool isEitherClockActive() { return (playbackState & PLAYBACK_CLOCK_EITHER_ACTIVE); }
223
224 // TEMPO encoder commands
226 void commandEditSwingAmount(int8_t offset);
228 void commandEditSwingInterval(int8_t offset);
229 void commandNudgeClock(int8_t offset);
230 void commandEditClockOutScale(int8_t offset);
231 void commandEditTempoCoarse(int8_t offset);
232 void commandEditTempoFine(int8_t offset);
233 void commandDisplayTempo(int8_t offset);
234 void commandClearTempoAutomation();
235
236 void getTempoStringForOLED(float tempoBPM, StringBuf& buffer);
237
238 void tryLoopCommand(GlobalMIDICommand command);
239
240 float calculateBPMForDisplay();
241
242private:
243 uint32_t timerTicksToOutputTicks(uint32_t timerTicks);
244
245 // These are all only relevant while playing synced.
246 uint32_t timePerInternalTickMovingAverage; // Recalculated every ~24 clocks
247 uint32_t veryCurrentTimePerInternalTickInverse; // Recalculated at every received clock message
248 uint32_t stickyCurrentTimePerInternalTickInverse; // Moving average kinda thing
249 uint32_t lowpassedTimePerInternalTick;
250 uint32_t slowpassedTimePerInternalTick;
251 uint32_t stickyTimePerInternalTick;
252
253 uint16_t tapTempoNumPresses;
254 uint32_t tapTempoFirstPressTime;
255
256 int32_t numOutputClocksWaitingToBeSent;
257 int32_t numInputTicksToSkip;
258 uint32_t skipAnalogClocks;
259 uint32_t skipMidiClocks;
260
261 void resetTimePerInternalTickMovingAverage();
262 void getCurrentTempoParams(int32_t* magnitude, int8_t* whichValue);
263 void displayTempoFromParams(int32_t magnitude, int8_t whichValue);
264 void displayTempoBPM(float tempoBPM);
265 void getAnalogOutTicksToInternalTicksRatio(uint32_t* internalTicksPer, uint32_t* analogOutTicksPer);
266 void getMIDIClockOutTicksToInternalTicksRatio(uint32_t* internalTicksPer, uint32_t* midiClockOutTicksPer);
267 void getInternalTicksToInputTicksRatio(uint32_t* inputTicksPer, uint32_t* internalTicksPer);
268 void sendOutPositionViaMIDI(int32_t pos, bool outputClocksWereSwitchedOff = false);
269 // void scheduleNextTimerTick();
270 bool startIgnoringMidiClockInputIfNecessary();
271 uint32_t setTempoFromAudioClipLength(uint64_t loopLengthSamples, Action* action);
272 bool offerNoteToLearnedThings(MIDICable& cable, bool on, int32_t channel, int32_t note);
273 bool tryGlobalMIDICommands(MIDICable& cable, int32_t channel, int32_t note);
274 bool tryGlobalMIDICommandsOff(MIDICable& cable, int32_t channel, int32_t note);
275 void decideOnCurrentPlaybackMode();
276 float getCurrentInternalTickFloatFollowingExternalClock();
277 void scheduleTriggerClockOutTickParamsKnown(uint32_t analogOutTicksPer, uint64_t fractionLastTimerTick,
278 uint64_t fractionNextAnalogOutTick);
279 void scheduleTriggerClockOutTickFromExternalClock();
280 void scheduleMIDIClockOutTickParamsKnown(uint32_t midiClockOutTicksPer, uint64_t fractionLastTimerTick,
281 uint64_t fractionNextMIDIClockOutTick);
282 void scheduleMIDIClockOutTickFromExternalClock();
283};
284
285extern 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:2083
void commandDisplaySwingAmount()
Definition playback_handler.cpp:2104
Definition song.h:103
Definition d_stringbuf.h:16