Deluge Firmware 1.3.0
Build date: 2025.04.16
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
100 // Playback
101 uint8_t playbackState;
102 bool usingAnalogClockInput; // Value is only valid if usingInternalClock is false
103 RecordingMode recording;
104 bool ignoringMidiClockInput;
105
106 int32_t posToNextContinuePlaybackFrom; // This will then have 1 subtracted from it when actually physically set
107 uint32_t timeLastMIDIStartOrContinueMessageSent;
108
109 // Timer ticks
110 int64_t lastTimerTickActioned; // Not valid while playback being set up
111 int64_t nextTimerTickScheduled; // *Yes* valid while (internal clock) playback being set up. Will be zero during
112 // that time
113 uint64_t timeNextTimerTickBig; // Not valid while playback being set up
114 uint64_t timeLastTimerTickBig; // Not valid while playback being set up
115
116 // Input ticks
117 uint32_t timeLastInputTicks[kNumInputTicksForMovingAverage];
118 uint32_t timePerInputTickMovingAverage; // 0 means that a default will be set the first time it's used
119 uint8_t numInputTickTimesCounted;
120
121 bool tempoMagnitudeMatchingActiveNow;
122 // unsigned long timeFirstInputTick; // First tick received for current tally
123 unsigned long
124 timeVeryFirstInputTick; // Very first tick received in playback. Only used for tempo magnitude matching
125 int64_t lastInputTickReceived;
126 unsigned long targetedTimePerInputTick;
127
128 // Swung ticks
129 bool swungTickScheduled;
130 uint32_t scheduledSwungTickTime;
131 // Now, swung ticks are only "actioned" in the following circumstances:
132 // - A note starts or ends
133 // - Automation event
134 // - A clip loops
135 // - A "launch" event
136 // - Start of playback, including if count-in ends
137 int64_t lastSwungTickActioned; // Will be set to a phony-ish "0" while playback being set up
138
139 // Trigger-clock-out ticks
140 bool triggerClockOutTickScheduled;
141 uint32_t timeNextTriggerClockOutTick;
142 int64_t lastTriggerClockOutTickDone;
143
144 uint32_t analogOutTicksPPQN; // Relative to the "external world", which also means relative to the displayed tempo
145 uint32_t analogInTicksPPQN;
146 uint32_t timeLastAnalogClockInputRisingEdge;
147 bool analogClockInputAutoStart;
148
149 bool songSwapShouldPreserveTempo;
150
151 // User options
152 bool metronomeOn;
153 bool midiOutClockEnabled;
154 bool midiInClockEnabled;
155 bool tempoMagnitudeMatchingEnabled;
156 uint8_t countInBars;
157
158 int32_t swungTicksTilNextEvent;
159
160 int32_t ticksLeftInCountIn;
161 int32_t currentVisualCountForCountIn;
162
163 int32_t metronomeOffset;
164
165 void setLedStates();
166 void tapTempoAutoSwitchOff();
167 void reassessInputTickScaling();
168 void resyncInternalTicksToInputTicks(Song* song);
169 bool shouldRecordNotesNow();
170 void stopAnyRecording();
171 uint32_t getTimePerInternalTick();
172 uint64_t getTimePerInternalTickBig();
173 float getTimePerInternalTickFloat();
174 uint32_t getTimePerInternalTickInverse(bool getStickyValue = false);
175 void tapTempoButtonPress(bool useNormalTapTempoBehaviour);
176 void doTriggerClockOutTick();
177 void doMIDIClockOutTick();
178 void resyncAnalogOutTicksToInternalTicks();
179 void resyncMIDIClockOutTicksToInternalTicks();
180 void analogClockRisingEdge(uint32_t time);
181 void toggleMetronomeStatus();
182 void commandDisplayTempo();
183 void setMidiOutClockMode(bool newValue);
184 void pitchBendReceived(MIDICable& cable, uint8_t channel, uint8_t data1, uint8_t data2, bool* doingMidiThru);
185 void midiCCReceived(MIDICable& cable, uint8_t channel, uint8_t ccNumber, uint8_t value, bool* doingMidiThru);
186 void programChangeReceived(MIDICable& cable, int32_t channel, int32_t program);
187 void aftertouchReceived(MIDICable& cable, int32_t channel, int32_t value, int32_t noteCode,
188 bool* doingMidiThru); // noteCode -1 means channel-wide
189 void loopCommand(OverDubType overdubNature);
190 void grabTempoFromClip(Clip* clip);
191 int32_t getTimeLeftInCountIn();
192
193 void noteMessageReceived(MIDICable& cable, bool on, int32_t channel, int32_t note, int32_t velocity,
194 bool* doingMidiThru);
195 bool subModeAllowsRecording();
196
197 float calculateBPM(float timePerInternalTick);
198 void switchToArrangement();
199 void switchToSession();
200 void finishTempolessRecording(bool startPlaybackAgain, int32_t buttonLatencyForTempolessRecord,
201 bool shouldExitRecordMode = true);
202
203 int32_t arrangementPosToStartAtOnSwitch;
204
205 bool stopOutputRecordingAtLoopEnd;
206
207 void actionTimerTick();
208 void actionTimerTickPart2();
209 void actionSwungTick();
210 void scheduleSwungTickFromInternalClock();
211 bool currentlySendingMIDIOutputClocks();
212
213 inline bool isExternalClockActive() { return (playbackState & PLAYBACK_CLOCK_EXTERNAL_ACTIVE); }
214 inline bool isInternalClockActive() { return (playbackState & PLAYBACK_CLOCK_INTERNAL_ACTIVE); }
215 inline bool isEitherClockActive() { return (playbackState & PLAYBACK_CLOCK_EITHER_ACTIVE); }
216
217 // TEMPO encoder commands
219 void commandEditSwingAmount(int8_t offset);
221 void commandEditSwingInterval(int8_t offset);
222 void commandNudgeClock(int8_t offset);
223 void commandEditClockOutScale(int8_t offset);
224 void commandEditTempoCoarse(int8_t offset);
225 void commandEditTempoFine(int8_t offset);
226 void commandDisplayTempo(int8_t offset);
227 void commandClearTempoAutomation();
228
229 void getTempoStringForOLED(float tempoBPM, StringBuf& buffer);
230
231 void tryLoopCommand(GlobalMIDICommand command);
232
233 float calculateBPMForDisplay();
234
235private:
236 uint32_t timerTicksToOutputTicks(uint32_t timerTicks);
237
238 // These are all only relevant while playing synced.
239 uint32_t timePerInternalTickMovingAverage; // Recalculated every ~24 clocks
240 uint32_t veryCurrentTimePerInternalTickInverse; // Recalculated at every received clock message
241 uint32_t stickyCurrentTimePerInternalTickInverse; // Moving average kinda thing
242 uint32_t lowpassedTimePerInternalTick;
243 uint32_t slowpassedTimePerInternalTick;
244 uint32_t stickyTimePerInternalTick;
245
246 uint16_t tapTempoNumPresses;
247 uint32_t tapTempoFirstPressTime;
248
249 int32_t numOutputClocksWaitingToBeSent;
250 int32_t numInputTicksToSkip;
251 uint32_t skipAnalogClocks;
252 uint32_t skipMidiClocks;
253
254 void resetTimePerInternalTickMovingAverage();
255 void getCurrentTempoParams(int32_t* magnitude, int8_t* whichValue);
256 void displayTempoFromParams(int32_t magnitude, int8_t whichValue);
257 void displayTempoBPM(float tempoBPM);
258 void getAnalogOutTicksToInternalTicksRatio(uint32_t* internalTicksPer, uint32_t* analogOutTicksPer);
259 void getMIDIClockOutTicksToInternalTicksRatio(uint32_t* internalTicksPer, uint32_t* midiClockOutTicksPer);
260 void getInternalTicksToInputTicksRatio(uint32_t* inputTicksPer, uint32_t* internalTicksPer);
261 void sendOutPositionViaMIDI(int32_t pos, bool outputClocksWereSwitchedOff = false);
262 // void scheduleNextTimerTick();
263 bool startIgnoringMidiClockInputIfNecessary();
264 uint32_t setTempoFromAudioClipLength(uint64_t loopLengthSamples, Action* action);
265 bool offerNoteToLearnedThings(MIDICable& cable, bool on, int32_t channel, int32_t note);
266 bool tryGlobalMIDICommands(MIDICable& cable, int32_t channel, int32_t note);
267 bool tryGlobalMIDICommandsOff(MIDICable& cable, int32_t channel, int32_t note);
268 void decideOnCurrentPlaybackMode();
269 float getCurrentInternalTickFloatFollowingExternalClock();
270 void scheduleTriggerClockOutTickParamsKnown(uint32_t analogOutTicksPer, uint64_t fractionLastTimerTick,
271 uint64_t fractionNextAnalogOutTick);
272 void scheduleMIDIClockOutTickParamsKnown(uint32_t midiClockOutTicksPer, uint64_t fractionLastTimerTick,
273 uint64_t fractionNextMIDIClockOutTick);
274};
275
276extern PlaybackHandler playbackHandler;
Definition action.h:75
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:94
Definition note_row.h:98
Definition playback_handler.h:54
void commandDisplaySwingInterval()
Definition playback_handler.cpp:1949
void commandDisplaySwingAmount()
Definition playback_handler.cpp:1970
Definition song.h:104
Definition d_string.h:106