Deluge Firmware 1.3.0
Build date: 2026.10.29
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 (!timelineCounter) {
149 FREEZE_WITH_ERROR("E369");
150 }
151 return timelineCounter;
152 }
153
154 inline TimelineCounter* getTimelineCounterAllowNull() const { return timelineCounter; }
155
156 inline void setTimelineCounter(TimelineCounter* newTimelineCounter) { timelineCounter = newTimelineCounter; }
157
158protected:
159 TimelineCounter* timelineCounter; // Allowed to be NULL
160};
161
163public:
164 Song* song;
165
166protected:
167 TimelineCounter* timelineCounter; // Allowed to be NULL
168public:
169 inline ModelStackWithTimelineCounter* toWithTimelineCounter() const { return (ModelStackWithTimelineCounter*)this; }
170
171 inline TimelineCounter* getTimelineCounter() const { return toWithTimelineCounter()->getTimelineCounter(); }
172
173 inline TimelineCounter* getTimelineCounterAllowNull() const {
174 return toWithTimelineCounter()->getTimelineCounterAllowNull();
175 }
176
177 inline void setTimelineCounter(TimelineCounter* newTimelineCounter) {
178 toWithTimelineCounter()->setTimelineCounter(newTimelineCounter);
179 }
180
181 inline bool timelineCounterIsSet() const { return toWithTimelineCounter()->timelineCounterIsSet(); }
182
183 ModelStackWithNoteRow* automaticallyAddNoteRowFromId() const;
184 int32_t noteRowId; // Valid and mandatory, iff noteRow is set
185};
186
188public:
189 inline void setNoteRow(NoteRow* newNoteRow, int32_t newNoteRowId) {
190 noteRow = newNoteRow;
191 noteRowId = newNoteRowId;
192 }
193
194 inline NoteRow* getNoteRow() const {
195 if (!noteRow) {
196 FREEZE_WITH_ERROR("E379");
197 }
198 return noteRow;
199 }
200
201 inline NoteRow* getNoteRowAllowNull() const { return noteRow; }
202
203 inline void setNoteRow(NoteRow* newNoteRow) { noteRow = newNoteRow; }
204
206 ParamManager* newParamManager) const;
207 ModelStackWithModControllable* addModControllable(ModControllable* newModControllable) const;
208 ModelStackWithThreeMainThings* addOtherTwoThingsAutomaticallyGivenNoteRow() const;
209
210 int32_t getLoopLength() const;
211 int32_t getRepeatCount() const;
212 int32_t getLastProcessedPos() const;
213 int32_t getLivePos() const;
214 bool isCurrentlyPlayingReversed() const;
215 int32_t getPosAtWhichPlaybackWillCut() const;
216
217protected:
218 NoteRow* noteRow; // Very often will be NULL
219};
220
222public:
223 ModControllable* modControllable;
224 ModelStackWithThreeMainThings* addParamManager(ParamManagerForTimeline* newParamManager) const;
225};
226
228public:
229 ParamManager* paramManager;
230
231 ModelStackWithParamCollection* addParamCollection(ParamCollection* newParamCollection,
232 ParamCollectionSummary* newSummary) const;
233 ModelStackWithParamCollection* addParamCollectionSummary(ParamCollectionSummary* newSummary) const;
234 ModelStackWithParamId* addParamCollectionAndId(ParamCollection* newParamCollection,
235 ParamCollectionSummary* newSummary, int32_t newParamId) const;
236 ModelStackWithAutoParam* addParam(ParamCollection* newParamCollection, ParamCollectionSummary* newSummary,
237 int32_t newParamId, AutoParam* newAutoParam) const;
238 ModelStackWithAutoParam* getUnpatchedAutoParamFromId(int32_t newParamId);
239 ModelStackWithAutoParam* getPatchedAutoParamFromId(int32_t newParamId);
240 ModelStackWithAutoParam* getPatchCableAutoParamFromId(int32_t newParamId);
241
242 inline ModelStackWithSoundFlags* addSoundFlags() const;
243 ModelStackWithAutoParam* getExpressionAutoParamFromID(int32_t newParamId);
244};
245
247public:
248 ParamCollection* paramCollection;
249 ParamCollectionSummary* summary;
250
251 ModelStackWithParamId* addParamId(int32_t newParamId) const;
252 ModelStackWithAutoParam* addAutoParam(int32_t newParamId, AutoParam* newAutoParam) const;
253};
254
256public:
257 int32_t paramId;
258
259 ModelStackWithAutoParam* addAutoParam(AutoParam* newAutoParam) const;
260
262};
263
270
271#define SOUND_FLAG_SOURCE_0_ACTIVE_DISREGARDING_MISSING_SAMPLE 0
272#define SOUND_FLAG_SOURCE_1_ACTIVE_DISREGARDING_MISSING_SAMPLE 1
273#define SOUND_FLAG_SOURCE_0_ACTIVE 2
274#define SOUND_FLAG_SOURCE_1_ACTIVE 3
275#define NUM_SOUND_FLAGS 4
276
277#define FLAG_FALSE 0
278#define FLAG_TRUE 1
279#define FLAG_TBD 2
280#define FLAG_SHOULDNT_BE_NEEDED 3
281
283public:
284 uint8_t soundFlags[NUM_SOUND_FLAGS];
285
286 bool checkSourceEverActiveDisregardingMissingSample(int32_t s);
287 bool checkSourceEverActive(int32_t s);
288};
289
290#define MODEL_STACK_MAX_SIZE sizeof(ModelStackWithAutoParam)
291
292ModelStackWithThreeMainThings* getModelStackFromSoundDrum(void* memory, SoundDrum* soundDrum);
293
294inline ModelStack* setupModelStackWithSong(void* memory, Song* newSong) {
295 ModelStack* modelStack = (ModelStack*)memory;
296 modelStack->song = newSong;
297
298 return modelStack;
299}
300
301inline ModelStackWithTimelineCounter* setupModelStackWithTimelineCounter(void* memory, Song* newSong,
302 TimelineCounter* newTimelineCounter) {
304 modelStack->song = newSong;
305 modelStack->setTimelineCounter(newTimelineCounter);
306
307 return modelStack;
308}
309
310inline ModelStackWithModControllable* setupModelStackWithModControllable(void* memory, Song* newSong,
311 TimelineCounter* newTimelineCounter,
312 ModControllable* newModControllable) {
313
314 return setupModelStackWithSong(memory, newSong)
315 ->addTimelineCounter(newTimelineCounter)
316 ->addNoteRow(0, nullptr)
317 ->addModControllable(newModControllable);
318}
319
321setupModelStackWithThreeMainThingsButNoNoteRow(void* memory, Song* newSong, ModControllable* newModControllable,
322 TimelineCounter* newTimelineCounter, ParamManager* newParamManager) {
323
324 return setupModelStackWithSong(memory, newSong)
325 ->addTimelineCounter(newTimelineCounter)
326 ->addNoteRow(0, nullptr)
327 ->addOtherTwoThings(newModControllable, newParamManager);
328}
329
330inline ModelStackWithThreeMainThings* setupModelStackWithThreeMainThingsIncludingNoteRow(
331 void* memory, Song* newSong, TimelineCounter* newTimelineCounter, int32_t noteRowId, NoteRow* noteRow,
332 ModControllable* newModControllable, ParamManagerForTimeline* newParamManager) {
333
334 return setupModelStackWithSong(memory, newSong)
335 ->addTimelineCounter(newTimelineCounter)
336 ->addNoteRow(noteRowId, noteRow)
337 ->addOtherTwoThings(newModControllable, newParamManager);
338}
339
340inline ModelStackWithTimelineCounter* ModelStack::addTimelineCounter(TimelineCounter* newTimelineCounter) const {
341 ModelStackWithTimelineCounter* toReturn = (ModelStackWithTimelineCounter*)this;
342 toReturn->setTimelineCounter(newTimelineCounter);
343 return toReturn;
344}
345
346inline ModelStackWithNoteRowId* ModelStackWithTimelineCounter::addNoteRowId(int32_t noteRowId) const {
347 ModelStackWithNoteRowId* toReturn = (ModelStackWithNoteRowId*)this;
348 toReturn->noteRowId = noteRowId;
349 return toReturn;
350}
351
352inline ModelStackWithNoteRow* ModelStackWithTimelineCounter::addNoteRow(int32_t noteRowId, NoteRow* noteRow) const {
353 ModelStackWithNoteRow* toReturn = (ModelStackWithNoteRow*)this;
354 toReturn->noteRowId = noteRowId;
355 toReturn->setNoteRow(noteRow);
356 return toReturn;
357}
358
360ModelStackWithTimelineCounter::addModControllableButNoNoteRow(ModControllable* newModControllable) const {
361 return addNoteRow(0, nullptr)->addModControllable(newModControllable);
362}
363
365ModelStackWithTimelineCounter::addOtherTwoThingsButNoNoteRow(ModControllable* newModControllable,
366 ParamManager* newParamManager) const {
367 return addNoteRow(0, nullptr)->addOtherTwoThings(newModControllable, newParamManager);
368}
369
371ModelStackWithNoteRow::addModControllable(ModControllable* newModControllable) const {
372 ModelStackWithModControllable* toReturn = (ModelStackWithModControllable*)this;
373 toReturn->modControllable = newModControllable;
374 return toReturn;
375}
376
381 ParamManager* newParamManager) const {
383 toReturn->modControllable = newModControllable;
384 toReturn->paramManager = newParamManager;
385 return toReturn;
386}
387
389ModelStackWithModControllable::addParamManager(ParamManagerForTimeline* newParamManager) const {
391 toReturn->paramManager = newParamManager;
392 return toReturn;
393}
394
395// Although the ParamCollection is referenced inside the Summary, this is to call when you've already grabbed that
396// pointer out, to avoid the CPU having to go and look at it again.
398ModelStackWithThreeMainThings::addParamCollection(ParamCollection* newParamCollection,
399 ParamCollectionSummary* newSummary) const {
400 ModelStackWithParamCollection* toReturn = (ModelStackWithParamCollection*)this;
401 toReturn->paramCollection = newParamCollection;
402 toReturn->summary = newSummary;
403 return toReturn;
404}
405
406// To call when you haven't already separately grabbed the paramCollection pointer out - for convenience.
408ModelStackWithThreeMainThings::addParamCollectionSummary(ParamCollectionSummary* newSummary) const {
409 ModelStackWithParamCollection* toReturn = (ModelStackWithParamCollection*)this;
410 toReturn->summary = newSummary;
411 toReturn->paramCollection = newSummary->paramCollection;
412 return toReturn;
413}
414
416ModelStackWithThreeMainThings::addParamCollectionAndId(ParamCollection* newParamCollection,
417 ParamCollectionSummary* newSummary, int32_t newParamId) const {
418 ModelStackWithParamId* toReturn = (ModelStackWithParamId*)this;
419 toReturn->paramCollection = newParamCollection;
420 toReturn->summary = newSummary;
421 toReturn->paramId = newParamId;
422 return toReturn;
423}
424
425inline ModelStackWithAutoParam* ModelStackWithThreeMainThings::addParam(ParamCollection* newParamCollection,
426 ParamCollectionSummary* newSummary,
427 int32_t newParamId,
428 AutoParam* newAutoParam) const {
429 ModelStackWithAutoParam* toReturn = (ModelStackWithAutoParam*)this;
430 toReturn->paramCollection = newParamCollection;
431 toReturn->summary = newSummary;
432 toReturn->paramId = newParamId;
433 toReturn->autoParam = newAutoParam;
434 return toReturn;
435}
436
437inline ModelStackWithParamId* ModelStackWithParamCollection::addParamId(int32_t newParamId) const {
438 ModelStackWithParamId* toReturn = (ModelStackWithParamId*)this;
439 toReturn->paramId = newParamId;
440 return toReturn;
441}
442
443inline ModelStackWithAutoParam* ModelStackWithParamCollection::addAutoParam(int32_t newParamId,
444 AutoParam* newAutoParam) const {
445 ModelStackWithAutoParam* toReturn = (ModelStackWithAutoParam*)this;
446 toReturn->paramId = newParamId;
447 toReturn->autoParam = newAutoParam;
448 return toReturn;
449}
450
451inline ModelStackWithAutoParam* ModelStackWithParamId::addAutoParam(AutoParam* newAutoParam) const {
452 ModelStackWithAutoParam* toReturn = (ModelStackWithAutoParam*)this;
453 toReturn->autoParam = newAutoParam;
454 return toReturn;
455}
456
457inline ModelStackWithSoundFlags* ModelStackWithThreeMainThings::addSoundFlags() const {
458 ModelStackWithSoundFlags* toReturn = (ModelStackWithSoundFlags*)this;
459 for (int32_t i = 0; i < NUM_SOUND_FLAGS; i++) {
460 toReturn->soundFlags[i] = FLAG_TBD;
461 }
462 return toReturn;
463}
464
465void copyModelStack(void* newMemory, void const* oldMemory, int32_t size);
466
467/*
468
469char modelStackMemory[MODEL_STACK_MAX_SIZE];
470ModelStackWithTimelineCounter* modelStack = currentSong->setupModelStackWithCurrentClip(modelStackMemory);
471
472*/
473
474/*
475 char modelStackMemory[MODEL_STACK_MAX_SIZE];
476 ModelStack* modelStack = setupModelStackWithSong(modelStackMemory, song);
477 ModelStackWithTimelineCounter* modelStackWithTimelineCounter = modelStack->addTimelineCounter(clip);
478
479
480 char modelStackMemory[MODEL_STACK_MAX_SIZE];
481 ModelStackWithTimelineCounter* modelStack = setupModelStackWithSong(modelStackMemory,
482 currentSong)->addTimelineCounter(clip);
483
484
485 */
Definition auto_param.h:44
Definition mod_controllable.h:40
Definition model_stack.h:264
AutoParam * autoParam
Definition model_stack.h:268
Definition model_stack.h:221
Definition model_stack.h:162
Definition model_stack.h:187
ModelStackWithThreeMainThings * addOtherTwoThings(ModControllable *newModControllable, ParamManager *newParamManager) const
Definition model_stack.h:380
Definition model_stack.h:246
Definition model_stack.h:255
Definition model_stack.h:282
Definition model_stack.h:227
Definition model_stack.h:129
Definition model_stack.h:123
Definition note_row.h:99
Definition param_collection_summary.h:24
Definition param_collection.h:39
Definition param_manager.h:155
Definition param_manager.h:45
Definition song.h:103
Definition sound_drum.h:28
Definition timeline_counter.h:28
uint8_t ParamType
Definition param.h:65
Kind
Definition param.h:42