Deluge Firmware 1.3.0
Build date: 2025.04.16
Loading...
Searching...
No Matches
model_stack.h
1/*
2 * Copyright © 2020-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 "hid/display/display.h"
21#include "modulation/params/param.h"
22#include "modulation/params/param_manager.h"
23
24class Song;
25class ModControllable;
26class TimelineCounter;
28class ParamCollection;
29class AutoParam;
35class NoteRow;
40class ParamManager;
42class SoundDrum;
43
44/* ====================== ModelStacks =====================
45 *
46 * This is a system that helps each function keep track of the “things” (objects) it’s dealing with while it runs.
47 * These “things” often include the Song, the Clip, the NoteRow - that sort of thing.
48 * This was only introduced into the Deluge’s codebase only in 2020 - some functions do not (yet) use it.
49 * Its inclusion has been beneficial to the codebase’s ease-of-modification, as well as code tidiness,
50 * and probably a very slight performance improvement.
51 *
52 * Previously, the Deluge’s functions had to be passed these individual “things” as arguments
53 * - a function might need to be passed a Clip and an AutoParam, say.
54 * However, if I later decided that a function needed additional access - say to the relevant ParamCollection,
55 * this could be tiresome to change, since the function’s caller might not have this,
56 * so its caller would have to pass it through, but that caller might not have it either - etc.
57 * Also, all this passing of arguments can’t be good for the compiled code’s efficiency and RAM / stack / register
58 * usage.
59 *
60 * Another option would be for each “thing”, as stored in memory to include a pointer to its “parent” object.
61 * E.g. each Clip would contain a pointer back to the Song, so that any function dealing with the
62 * Clip could also find the Song. However, this would be unsatisfactory and inefficient because RAM
63 * storage and access would be being used for something which theoretically the code should just be able to “know”.
64 *
65 * Enter my (Rohan’s) own invented solution, “ModelStacks” - a “stack” of the relevant parts of the “model”
66 * (objects representing the makeup of a project on the Deluge) which the currently executing functions are dealing
67 * with. Things can be “pushed and popped” (though the implementation doesn’t quite put it that way) onto and off the
68 * ModelStack as needed. Now all that needs to be passed between functions is the pointer to the ModelStack - no other
69 * memory or pointers need copying (except in special cases), and no additional arguments need to be passed. The
70 * ModelStack typically exists in program stack memory.
71 *
72 * For example, suppose a Song needs to call a function on all Clips. The ModelStack begins by containing just the Song.
73 * Then as each Clip has its function called, that Clip is set on the ModelStack.
74 * And suppose each Clip then needs to call a function on its ParamManager - that’s pushed onto the ModelStack too.
75 * So now, if the ParamManager, or anything else lower-level, needs access to the Song or Clip,
76 * it’s right there on the ModelStack. The code now just “knows” what this stuff is, which I consider to be the
77 * way it “should” be: a human reading / debugging / understanding the code will know what these higher-up objects are,
78 * so why shouldn’t the code also have an intrinsic way to “know”?
79 *
80 * This is additionally beneficial because, suppose we decide at some future point that there needs to be
81 * some new object inserted between Songs and Clips - maybe each Clip now belongs to a ClipGroup. We can
82 * now mandate that the addClip() call is only available on a newly implemented ModelStackWithClipGroup,
83 * for which having a ClipGroup is now a prerequisite. By simply trying to compile the code, the compiler
84 * will generate errors, showing us everywhere that needs to be modified to add a relevant ClipGroup to the ModelStack
85 * - still a bit of a task, but far easier as it will only be functions at higher-up levels that need to add
86 * the ClipGroup, and then we can just take it for granted that it’s there in the ModelStack. The alternative would
87 * be having to modify many functions all the way down the “tree” of the object / model structure, to accept a
88 * ClipGroup as an argument, so that it can be passed down to the next thing / object.
89 *
90 * Another advantage is that error checking can be built into the ModelStack - which may also be easily
91 * switched off for certain builds. For example, there are many instances in the Deluge codebase where
92 * ModelStackWithTimelineCounter::getTimelineCounter() is called - usually to get the Clip
93 * (TimelineCounter is a base class of Clip). We know that the returned TimelineCounter is not allowed to be NULL.
94 * Rather than insert error checking into every instance of such a call to ensure that it wasn’t passed a NULL,
95 * we can instead have getTimelineCounter() itself perform the check for us and generate an error if need be,
96 * all in a single line of code.
97 *
98 * One disadvantage is that some simple function calls on a “leaf” / low-level object such as AutoParam
99 * now require an entire ModelStack to be built up and provided, even if the function only in fact needed
100 * to know about one parent object - e.g. the Clip. However, in practice, I’ve observed very few cases
101 * where ModelStacks get populated unnecessarily - especially as ModelStacks are implemented more widely
102 * throughout the codebase, so most functions already have a relevant ModelStack to pass further down the line.
103 *
104 * Another potential pitfall - suppose a “leaf” / low-level object - say AutoParam - needs to call a function
105 * on its parent ParamCollection. In this sort of case, which is very common too, the ModelStack is passed back
106 * upwards in the “tree hierarchy”. But now, what if this function in ParamCollection now needs to do something
107 * that requires calling a function on each of its AutoParams? If it sets the AutoParam on the ModelStack, then
108 * the original AutoParam - to which execution will eventually be returned - is no longer there on the ModelStack,
109 * which may break things and we might not realise as we write the code. Ideally, I wish there was a solution
110 * where we know that so long as the code compiles, we’re not at risk of overwriting anything on the ModelStack
111 * that might be needed. I couldn’t devise a nice solution to this other than just exercising caution as the programmer.
112 * My memory doesn’t quite serve me here - I experimented with having functions only accept a const ModelStack*
113 * (which is now the case for many of the functions and I can’t quite remember why, sorry!) but this somehow did not
114 * provide a solution for the code to be immune to the pitfall identified above.
115 *
116 * I’m actually not sure how fields like game development deal with similar problems, which they must
117 * encounter as e.g. a “world” might contain many “levels”, which might also contain many “enemies”
118 * - a tree-like structure which the code must have to traverse, like on the Deluge. I tried Googling it,
119 * but couldn’t find anything about a standard approach to this. Perhaps the each-object-stores-a-pointer-to-its-parent
120 * solution, as I mentioned above, is the norm? If you know, I’d be really interested to know!
121 */
122
124public:
125 Song* song;
126 ModelStackWithTimelineCounter* addTimelineCounter(TimelineCounter* newTimelineCounter) const;
127};
128
130public:
131 Song* song;
132
133 ModelStackWithNoteRow* addNoteRow(int32_t noteRowId, NoteRow* noteRow) const;
134 ModelStackWithNoteRowId* addNoteRowId(int32_t noteRowId) const;
135 ModelStackWithThreeMainThings* addNoteRowAndExtraStuff(int32_t noteRowIndex, NoteRow* newNoteRow) const;
136 ModelStackWithThreeMainThings* addOtherTwoThingsButNoNoteRow(ModControllable* newModControllable,
137 ParamManager* newParamManager) const;
138 ModelStackWithModControllable* addModControllableButNoNoteRow(ModControllable* newModControllable) const;
139
140 inline ModelStack*
141 toWithSong() const { // I thiiiink you're supposed to just be real careful about when you call this etc...
142 return (ModelStack*)this;
143 }
144
145 inline bool timelineCounterIsSet() const { return timelineCounter; }
146
147 inline TimelineCounter* getTimelineCounter() const {
148#if ALPHA_OR_BETA_VERSION
149 if (!timelineCounter) {
150 FREEZE_WITH_ERROR("E369");
151 }
152#endif
153 return timelineCounter;
154 }
155
156 inline TimelineCounter* getTimelineCounterAllowNull() const { return timelineCounter; }
157
158 inline void setTimelineCounter(TimelineCounter* newTimelineCounter) { timelineCounter = newTimelineCounter; }
159
160protected:
161 TimelineCounter* timelineCounter; // Allowed to be NULL
162};
163
165public:
166 Song* song;
167
168protected:
169 TimelineCounter* timelineCounter; // Allowed to be NULL
170public:
171 inline ModelStackWithTimelineCounter* toWithTimelineCounter() const { return (ModelStackWithTimelineCounter*)this; }
172
173 inline TimelineCounter* getTimelineCounter() const { return toWithTimelineCounter()->getTimelineCounter(); }
174
175 inline TimelineCounter* getTimelineCounterAllowNull() const {
176 return toWithTimelineCounter()->getTimelineCounterAllowNull();
177 }
178
179 inline void setTimelineCounter(TimelineCounter* newTimelineCounter) {
180 toWithTimelineCounter()->setTimelineCounter(newTimelineCounter);
181 }
182
183 inline bool timelineCounterIsSet() const { return toWithTimelineCounter()->timelineCounterIsSet(); }
184
185 ModelStackWithNoteRow* automaticallyAddNoteRowFromId() const;
186 int32_t noteRowId; // Valid and mandatory, iff noteRow is set
187};
188
190public:
191 inline void setNoteRow(NoteRow* newNoteRow, int32_t newNoteRowId) {
192 noteRow = newNoteRow;
193 noteRowId = newNoteRowId;
194 }
195
196 inline NoteRow* getNoteRow() const {
197#if ALPHA_OR_BETA_VERSION
198 if (!noteRow) {
199 FREEZE_WITH_ERROR("E379");
200 }
201#endif
202 return noteRow;
203 }
204
205 inline NoteRow* getNoteRowAllowNull() const { return noteRow; }
206
207 inline void setNoteRow(NoteRow* newNoteRow) { noteRow = newNoteRow; }
208
210 ParamManager* newParamManager) const;
211 ModelStackWithModControllable* addModControllable(ModControllable* newModControllable) const;
212 ModelStackWithThreeMainThings* addOtherTwoThingsAutomaticallyGivenNoteRow() const;
213
214 int32_t getLoopLength() const;
215 int32_t getRepeatCount() const;
216 int32_t getLastProcessedPos() const;
217 int32_t getLivePos() const;
218 bool isCurrentlyPlayingReversed() const;
219 int32_t getPosAtWhichPlaybackWillCut() const;
220
221protected:
222 NoteRow* noteRow; // Very often will be NULL
223};
224
226public:
227 ModControllable* modControllable;
228 ModelStackWithThreeMainThings* addParamManager(ParamManagerForTimeline* newParamManager) const;
229};
230
232public:
233 ParamManager* paramManager;
234
235 ModelStackWithParamCollection* addParamCollection(ParamCollection* newParamCollection,
236 ParamCollectionSummary* newSummary) const;
237 ModelStackWithParamCollection* addParamCollectionSummary(ParamCollectionSummary* newSummary) const;
238 ModelStackWithParamId* addParamCollectionAndId(ParamCollection* newParamCollection,
239 ParamCollectionSummary* newSummary, int32_t newParamId) const;
240 ModelStackWithAutoParam* addParam(ParamCollection* newParamCollection, ParamCollectionSummary* newSummary,
241 int32_t newParamId, AutoParam* newAutoParam) const;
242 ModelStackWithAutoParam* getUnpatchedAutoParamFromId(int32_t newParamId);
243 ModelStackWithAutoParam* getPatchedAutoParamFromId(int32_t newParamId);
244 ModelStackWithAutoParam* getPatchCableAutoParamFromId(int32_t newParamId);
245
246 inline ModelStackWithSoundFlags* addSoundFlags() const;
247 inline ModelStackWithSoundFlags* addDummySoundFlags() const;
248 ModelStackWithAutoParam* getExpressionAutoParamFromID(int32_t newParamId);
249};
250
252public:
253 ParamCollection* paramCollection;
254 ParamCollectionSummary* summary;
255
256 ModelStackWithParamId* addParamId(int32_t newParamId) const;
257 ModelStackWithAutoParam* addAutoParam(int32_t newParamId, AutoParam* newAutoParam) const;
258};
259
261public:
262 int32_t paramId;
263
264 ModelStackWithAutoParam* addAutoParam(AutoParam* newAutoParam) const;
265
267};
268
275
276#define SOUND_FLAG_SOURCE_0_ACTIVE_DISREGARDING_MISSING_SAMPLE 0
277#define SOUND_FLAG_SOURCE_1_ACTIVE_DISREGARDING_MISSING_SAMPLE 1
278#define SOUND_FLAG_SOURCE_0_ACTIVE 2
279#define SOUND_FLAG_SOURCE_1_ACTIVE 3
280#define NUM_SOUND_FLAGS 4
281
282#define FLAG_FALSE 0
283#define FLAG_TRUE 1
284#define FLAG_TBD 2
285#define FLAG_SHOULDNT_BE_NEEDED 3
286
288public:
289 uint8_t soundFlags[NUM_SOUND_FLAGS];
290
291 bool checkSourceEverActiveDisregardingMissingSample(int32_t s);
292 bool checkSourceEverActive(int32_t s);
293};
294
295#define MODEL_STACK_MAX_SIZE sizeof(ModelStackWithAutoParam)
296
297ModelStackWithThreeMainThings* getModelStackFromSoundDrum(void* memory, SoundDrum* soundDrum);
298
299inline ModelStack* setupModelStackWithSong(void* memory, Song* newSong) {
300 ModelStack* modelStack = (ModelStack*)memory;
301 modelStack->song = newSong;
302
303 return modelStack;
304}
305
306inline ModelStackWithTimelineCounter* setupModelStackWithTimelineCounter(void* memory, Song* newSong,
307 TimelineCounter* newTimelineCounter) {
309 modelStack->song = newSong;
310 modelStack->setTimelineCounter(newTimelineCounter);
311
312 return modelStack;
313}
314
315inline ModelStackWithModControllable* setupModelStackWithModControllable(void* memory, Song* newSong,
316 TimelineCounter* newTimelineCounter,
317 ModControllable* newModControllable) {
318
319 return setupModelStackWithSong(memory, newSong)
320 ->addTimelineCounter(newTimelineCounter)
321 ->addNoteRow(0, nullptr)
322 ->addModControllable(newModControllable);
323}
324
326setupModelStackWithThreeMainThingsButNoNoteRow(void* memory, Song* newSong, ModControllable* newModControllable,
327 TimelineCounter* newTimelineCounter, ParamManager* newParamManager) {
328
329 return setupModelStackWithSong(memory, newSong)
330 ->addTimelineCounter(newTimelineCounter)
331 ->addNoteRow(0, nullptr)
332 ->addOtherTwoThings(newModControllable, newParamManager);
333}
334
335inline ModelStackWithThreeMainThings* setupModelStackWithThreeMainThingsIncludingNoteRow(
336 void* memory, Song* newSong, TimelineCounter* newTimelineCounter, int32_t noteRowId, NoteRow* noteRow,
337 ModControllable* newModControllable, ParamManagerForTimeline* newParamManager) {
338
339 return setupModelStackWithSong(memory, newSong)
340 ->addTimelineCounter(newTimelineCounter)
341 ->addNoteRow(noteRowId, noteRow)
342 ->addOtherTwoThings(newModControllable, newParamManager);
343}
344
345inline ModelStackWithTimelineCounter* ModelStack::addTimelineCounter(TimelineCounter* newTimelineCounter) const {
346 ModelStackWithTimelineCounter* toReturn = (ModelStackWithTimelineCounter*)this;
347 toReturn->setTimelineCounter(newTimelineCounter);
348 return toReturn;
349}
350
351inline ModelStackWithNoteRowId* ModelStackWithTimelineCounter::addNoteRowId(int32_t noteRowId) const {
352 ModelStackWithNoteRowId* toReturn = (ModelStackWithNoteRowId*)this;
353 toReturn->noteRowId = noteRowId;
354 return toReturn;
355}
356
357inline ModelStackWithNoteRow* ModelStackWithTimelineCounter::addNoteRow(int32_t noteRowId, NoteRow* noteRow) const {
358 ModelStackWithNoteRow* toReturn = (ModelStackWithNoteRow*)this;
359 toReturn->noteRowId = noteRowId;
360 toReturn->setNoteRow(noteRow);
361 return toReturn;
362}
363
365ModelStackWithTimelineCounter::addModControllableButNoNoteRow(ModControllable* newModControllable) const {
366 return addNoteRow(0, nullptr)->addModControllable(newModControllable);
367}
368
370ModelStackWithTimelineCounter::addOtherTwoThingsButNoNoteRow(ModControllable* newModControllable,
371 ParamManager* newParamManager) const {
372 return addNoteRow(0, nullptr)->addOtherTwoThings(newModControllable, newParamManager);
373}
374
376ModelStackWithNoteRow::addModControllable(ModControllable* newModControllable) const {
377 ModelStackWithModControllable* toReturn = (ModelStackWithModControllable*)this;
378 toReturn->modControllable = newModControllable;
379 return toReturn;
380}
381
386 ParamManager* newParamManager) const {
388 toReturn->modControllable = newModControllable;
389 toReturn->paramManager = newParamManager;
390 return toReturn;
391}
392
394ModelStackWithModControllable::addParamManager(ParamManagerForTimeline* newParamManager) const {
396 toReturn->paramManager = newParamManager;
397 return toReturn;
398}
399
400// Although the ParamCollection is referenced inside the Summary, this is to call when you've already grabbed that
401// pointer out, to avoid the CPU having to go and look at it again.
403ModelStackWithThreeMainThings::addParamCollection(ParamCollection* newParamCollection,
404 ParamCollectionSummary* newSummary) const {
405 ModelStackWithParamCollection* toReturn = (ModelStackWithParamCollection*)this;
406 toReturn->paramCollection = newParamCollection;
407 toReturn->summary = newSummary;
408 return toReturn;
409}
410
411// To call when you haven't already separately grabbed the paramCollection pointer out - for convenience.
413ModelStackWithThreeMainThings::addParamCollectionSummary(ParamCollectionSummary* newSummary) const {
414 ModelStackWithParamCollection* toReturn = (ModelStackWithParamCollection*)this;
415 toReturn->summary = newSummary;
416 toReturn->paramCollection = newSummary->paramCollection;
417 return toReturn;
418}
419
421ModelStackWithThreeMainThings::addParamCollectionAndId(ParamCollection* newParamCollection,
422 ParamCollectionSummary* newSummary, int32_t newParamId) const {
423 ModelStackWithParamId* toReturn = (ModelStackWithParamId*)this;
424 toReturn->paramCollection = newParamCollection;
425 toReturn->summary = newSummary;
426 toReturn->paramId = newParamId;
427 return toReturn;
428}
429
430inline ModelStackWithAutoParam* ModelStackWithThreeMainThings::addParam(ParamCollection* newParamCollection,
431 ParamCollectionSummary* newSummary,
432 int32_t newParamId,
433 AutoParam* newAutoParam) const {
434 ModelStackWithAutoParam* toReturn = (ModelStackWithAutoParam*)this;
435 toReturn->paramCollection = newParamCollection;
436 toReturn->summary = newSummary;
437 toReturn->paramId = newParamId;
438 toReturn->autoParam = newAutoParam;
439 return toReturn;
440}
441
442inline ModelStackWithParamId* ModelStackWithParamCollection::addParamId(int32_t newParamId) const {
443 ModelStackWithParamId* toReturn = (ModelStackWithParamId*)this;
444 toReturn->paramId = newParamId;
445 return toReturn;
446}
447
448inline ModelStackWithAutoParam* ModelStackWithParamCollection::addAutoParam(int32_t newParamId,
449 AutoParam* newAutoParam) const {
450 ModelStackWithAutoParam* toReturn = (ModelStackWithAutoParam*)this;
451 toReturn->paramId = newParamId;
452 toReturn->autoParam = newAutoParam;
453 return toReturn;
454}
455
456inline ModelStackWithAutoParam* ModelStackWithParamId::addAutoParam(AutoParam* newAutoParam) const {
457 ModelStackWithAutoParam* toReturn = (ModelStackWithAutoParam*)this;
458 toReturn->autoParam = newAutoParam;
459 return toReturn;
460}
461
462inline ModelStackWithSoundFlags* ModelStackWithThreeMainThings::addSoundFlags() const {
463 ModelStackWithSoundFlags* toReturn = (ModelStackWithSoundFlags*)this;
464 for (int32_t i = 0; i < NUM_SOUND_FLAGS; i++) {
465 toReturn->soundFlags[i] = FLAG_TBD;
466 }
467 return toReturn;
468}
469
470inline ModelStackWithSoundFlags* ModelStackWithThreeMainThings::addDummySoundFlags() const {
471 ModelStackWithSoundFlags* toReturn = (ModelStackWithSoundFlags*)this;
472#if ALPHA_OR_BETA_VERSION
473 for (int32_t i = i; i < NUM_SOUND_FLAGS; i++) {
474 toReturn->soundFlags[i] = FLAG_SHOULDNT_BE_NEEDED;
475 }
476#endif
477 return toReturn;
478}
479
480void copyModelStack(void* newMemory, void const* oldMemory, int32_t size);
481
482/*
483
484char modelStackMemory[MODEL_STACK_MAX_SIZE];
485ModelStackWithTimelineCounter* modelStack = currentSong->setupModelStackWithCurrentClip(modelStackMemory);
486
487*/
488
489/*
490 char modelStackMemory[MODEL_STACK_MAX_SIZE];
491 ModelStack* modelStack = setupModelStackWithSong(modelStackMemory, song);
492 ModelStackWithTimelineCounter* modelStackWithTimelineCounter = modelStack->addTimelineCounter(clip);
493
494
495 char modelStackMemory[MODEL_STACK_MAX_SIZE];
496 ModelStackWithTimelineCounter* modelStack = setupModelStackWithSong(modelStackMemory,
497 currentSong)->addTimelineCounter(clip);
498
499
500 */
Definition auto_param.h:44
Definition mod_controllable.h:40
Definition model_stack.h:269
AutoParam * autoParam
Definition model_stack.h:273
Definition model_stack.h:225
Definition model_stack.h:164
Definition model_stack.h:189
ModelStackWithThreeMainThings * addOtherTwoThings(ModControllable *newModControllable, ParamManager *newParamManager) const
Definition model_stack.h:385
Definition model_stack.h:251
Definition model_stack.h:260
Definition model_stack.h:287
Definition model_stack.h:231
Definition model_stack.h:129
Definition model_stack.h:123
Definition note_row.h:98
Definition param_collection_summary.h:24
Definition param_collection.h:39
Definition param_manager.h:174
Definition param_manager.h:45
Definition song.h:104
Definition sound_drum.h:28
Definition timeline_counter.h:28
uint8_t ParamType
Definition param.h:65
Kind
Definition param.h:42