~pbatch/patchwerk

patchwerk/egraph.w -rw-r--r-- 11.6 KiB
9c265356 — paul plan9 additions from Sigrid 3 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
@** Event Graphs.
\mkref{egraph}
Event graphs are a complimentary data structure to
accompany the main patchwerk audio graph. While audio graphs represent
the signal flow of a given patch, event graphs represent discrete events
that happen during the duration of the patch.

@<Top@>+=
@<Event Graph Top@>

@* A Single Event Node. An event graph is composed of interconnected nodes
called event nodes, otherwise known as a |pw_evnode|.

@<Type Declarations@>+=
typedef struct pw_evnode pw_evnode;

@ The |pw_evnode| is centered around a callback which is the event
associated with the node. The callback is a |typedef| called
|pw_evnode_cb|.

@<Header@>+=
typedef pw_evnode * (*pw_evnode_cb)(pw_evnode *, int);

@ The default |pw_evnode_cb| returns the next entry in the linked list.
However, this does not always have to be the case. Other event nodes,
such as ones stored in userdata, also be returned.

@<An Empty Event Callback@>+=
static pw_evnode * empty_event(pw_evnode *evn, int pos)
{
    return pw_evnode_next(evn);
}

@ The |pw_evnode| contains the following data:
\smallskip
{
\par
\begingroup
\leftskip=20pt

\item{$\bullet$} A callback for the actual event.
\item{$\bullet$} An optional callback for cleanup.
\item{$\bullet$} The duration of the event, in ticks.
\item{$\bullet$} A void pointer for any user data.
\item{$\bullet$} A prev and next entry, needed for bidirectional
linked list

\par
\endgroup
}

@<Event Graph Top@>+=
struct pw_evnode {
    pw_evnode_cb event;
    void (*clean)(pw_evnode *);
    int dur;
    void *ud;
    pw_evnode *prev;
    pw_evnode *next;
};

@* Event Node Functions.

@ To make room for future memory allocation options in patchwerk, top level
patchwerk functions |pw_evnode_new| and |pw_evnode_bye| have been created.
These functions take in a |pw_patch| as an argument, and are designed
to have clearer error checking.

@<Header@>+=
int pw_evnode_new(pw_patch *patch, pw_evnode **evn);
int pw_evnode_bye(pw_patch *patch, pw_evnode **evn);

@ These functions use the internal memory allocation routines inside
of the main |pw_patch| instance.

@<Event Graph Top@>+=
int pw_evnode_new(pw_patch *patch, pw_evnode **evn)
{
    pw_evnode *e;
    int rc;
    e = NULL;
    rc = pw_memory_alloc(patch, sizeof(pw_evnode), (void **)&e);
    if (e != NULL) {
        pw_evnode_init(e);
        *evn = e;
    }
    return rc;
}

int pw_evnode_bye(pw_patch *patch, pw_evnode **evn)
{
    return pw_memory_free(patch, (void **)evn);
}

@ Once a |pw_evnode| has been allocated, it can be initialized with
|pw_evnode_init|.
@<Header@>+=
void pw_evnode_init(pw_evnode *evn);

@ @<Event Graph Top@>+=
@<An Empty Event Callback@>@/
@<An Empty Cleanup Callback@>@/
void pw_evnode_init(pw_evnode *evn)
{
    evn->dur = 0;
    evn->ud = NULL;
    evn->event = empty_event;
    evn->clean = empty_clean;
    evn->prev = NULL;
    evn->next = NULL;
}

@ Like |empty_event|, |empty_clean| supplies a default empty callback
for cleanup.
@<An Empty Cleanup Callback@>+=
static void empty_clean(pw_evnode *evn)
{

}

@ The callback inside of node can be called with the function
|pw_evnode_fire|.
@<Header@>+=
pw_evnode * pw_evnode_fire(pw_evnode *evn, int pos);

@ @<Event Graph Top@>+=
pw_evnode * pw_evnode_fire(pw_evnode *evn, int pos)
{
    return evn->event(evn, pos);
}

@ The event callback can be set using the function |pw_evnode_callback|
@<Header@>+=
void pw_evnode_callback(pw_evnode *evn, pw_evnode_cb cb);

@ @<Event Graph Top@>+=
void pw_evnode_callback(pw_evnode *evn, pw_evnode_cb cb)
{
    evn->event = cb;
}

@ The clean callback inside of node can be called with the function
|pw_evnode_clean|.
@<Header@>+=
void pw_evnode_clean(pw_evnode *evn);

@ @<Event Graph Top@>+=
void pw_evnode_clean(pw_evnode *evn)
{
    evn->clean(evn);
}

@ The clean callback can be set using the function |pw_evnode_clean_set|
@<Header@>+=
void pw_evnode_clean_set(pw_evnode *evn, void (*cb)(pw_evnode *));

@ @<Event Graph Top@>+=
void pw_evnode_clean_set(pw_evnode *evn, void (*cb)(pw_evnode *))
{
    evn->clean = cb;
}

@ User data can be associated to an event node using setter/getter
functions |pw_evnode_data_set| and |pw_evnode_data_get|, respectively.

@<Header@>+=
void pw_evnode_data_set(pw_evnode *evn, void *ud);
void * pw_evnode_data_get(pw_evnode *evn);

@ User data is contained as a generic |void *| pointer. If data is to be
managed by an instance of an event node, a cleanup function should be
provided via |pw_evnode_clean_set|.

Alternatively, one could indirectly have memory handled by using the
|pw_pointer| and |pw_pointerlist| interfaces attached with a |pw_patch|
or |pw_subpatch|. See the pointer section in

|@<Pointer Top@>| for more information.

@<Event Graph Top@>+=
void pw_evnode_data_set(pw_evnode *evn, void *ud)
{
    evn->ud = ud;
}

void * pw_evnode_data_get(pw_evnode *evn)
{
    return evn->ud;
}

@ The next event node in the list (if it exists) is returned using the
function |pw_evnode_next|.
@<Header@>+=
pw_evnode * pw_evnode_next(pw_evnode *evn);

@ Note that |pw_evnode_next| does not check for null values.
@<Event Graph Top@>+=
pw_evnode * pw_evnode_next(pw_evnode *evn)
{
    return evn->next;
}

@ The function |pw_evnode_dur| sets the duration.

@<Header@>+=
void pw_evnode_dur(pw_evnode *evn, int dur);

@ @<Event Graph Top@>+=
void pw_evnode_dur(pw_evnode *evn, int dur)
{
    evn->dur = dur;
}

@ The function |pw_evnode_dur_get| returns the duration.
@<Header@>+=
int pw_evnode_dur_get(pw_evnode *evn);

@ @<Event Graph Top@>+=
int pw_evnode_dur_get(pw_evnode *evn)
{
    return evn->dur;
}

@ The function |pw_evnode_is_terminal| checks if an event node is a terminal
node.

@<Header@>+=
int pw_evnode_is_terminal(pw_evnode *evn);

@ A node is terminal if the |next| entry is null.

@<Event Graph Top@>+=
int pw_evnode_is_terminal(pw_evnode *evn)
{
    return evn->next == NULL;
}

@ TODO: Words, please.

@<Header@>+=
void pw_evnode_set_next(pw_evnode *evn, pw_evnode *next);

@ TODO: Words, please.

@<Event Graph Top@>+=
void pw_evnode_set_next(pw_evnode *evn, pw_evnode *next)
{
    evn->next = next;
}

@** The Event Walker. An event walker is used to step through the event graph,
starting at any node. Each event walker contains internal state information
about the current location in the graph. Even walkers are designed such that
multiple walkers can occur at the same time on the same graph.

@<Event Graph Top@>+=
@<Event Walker Top@>

@* Event Walker Data. The event walker is contained in a typedef struct
called |pw_evwalker|. The type declaration is contained inside the header file,
so dynamic memory allocation is not required.
@<Type Declarations@>+=
typedef struct {
@<Struct Contents in Event Walker@>@/
} pw_evwalker;

@ The data in the |pw_evwalker| struct contains all the necessary state
information to locally know and keep track of the current position in the graph.

These elements include:

\smallskip
{
\par
\begingroup
\leftskip=20pt
\item{$\bullet$} The current event node.
\item{$\bullet$} A counter, to keep track of duration.
\item{$\bullet$} A boolean flag to indicate if it has reached a terminal node.
\par
\endgroup
}

@<Struct Contents in Eve...@>+=
pw_evnode *node;
int count;
int terminal;

@* Event Walker Functions.
@ The function |pw_evwalker_reset| zeros out the event walker data contained
inside of |pw_evwalker|. This will overwrite any previous data.
@<Header@>+=
void pw_evwalker_reset(pw_evwalker *walker);

@ Note that there are no calls to malloc required, making it safe to call
more than once.
@<Event Walker Top@>+=
void pw_evwalker_reset(pw_evwalker *walker)
{
    walker->node = NULL;
    walker->count = 0;
    walker->terminal = 1; /* set terminal flag on by default */
}

@ A walker can be initialized with a starting event node using the function
|pw_evwalker_init|. This function sets up the walker with a starting event
node. If the starting event node argument is NULL, it initializes an empty
event walker. This function does not handle any memory allocation, so it is
safe to call again for reinitialization.

@<Header@>+=
void pw_evwalker_init(pw_evwalker *walker, pw_evnode *start, int delay);


@ The task of initializing an event walker can be broken down into the
following subtasks:

\smallskip
{
\par
\begingroup
\leftskip=20pt
\item{$\bullet$} Set the initial node to be the starter node.
\item{$\bullet$} Set the counter duration to be initial delay. If the
initial delay is zero, the first node function will be called immediately.
A delay of one will wait one step before firing, a delay of two
steps will wait two steps before firing, etc.
\item{$\bullet$} Set the terminal flag to the offstate.
\item{$\bullet$} If the starting node is NULL, then it is an empty event walker.
The terminal flag is flipped on.
\par
\endgroup
}

@<Event Walker Top@>+=
void pw_evwalker_init(pw_evwalker *walker, pw_evnode *start, int delay)
{
    walker->count = delay;
    walker->node = start;
    walker->terminal = 0;
    if(start == NULL) {
        walker->terminal = 1;
    }
}

@ Once an event walker has been initialized with |pw_evwalker_init|, it can
begin stepping through the event graph with the function |pw_evwalker_walk|.
This will return 0 if it has reached a terminal node. Otherwise, it will
return a 1.

The argument |pos| is for situations where sample accuracy is desired.
This refers to the current sample position in the audio render loop. A value
zero is fine if you want to ignore this.

@<Header@>+=
int pw_evwalker_walk(pw_evwalker *walker, int pos);


@ The Walker Algorithm can be broken down into the following steps:

{
\par
\begingroup
\leftskip=20pt
\item{$\bullet$} First, the walker checks for the terminal flag.
If it has been set, it immediately returns 0. This happens first so that no
null reads occur.

\item{$\bullet$} After the terminal flag check, there is a counter a check.
A counter greater than zero will result in the counter variable decreasing
by one and then exiting.

\item{$\bullet$} When the counter is zero, it indicates that it is time to
jump to the next event node.

\item{$\bullet$} The current event node is checked for being terminal.
If it is, the terminal flag is set. The return code is set to be zero.

\item{$\bullet$} The counter is set to be the duration the of current event
node. This can be thought of as sort of delay until the next node in the graph.

\item{$\bullet$} Finally, the event function of the current procedure is called.
This function replaces the current event node in the walker.

\item{$\bullet$} While nodes are zero and non-terminal,
step through and fire off nodes in the graph. This particular feature allows for
multiple nodes to be fired in a single step.

\item{$\bullet$} An event node that returns NULL is said to be treated the
same way as a terminal event node.

\item{$\bullet$} The end of the function returns a boolean check on if the
terminal flag is set or not.

\item{$\bullet$} When a new non-zero counter event has occured, time has
passed for this note, and thus the counter must be decreased by one when
it breaks out of the loop.

\par
\endgroup
}

@<Event Walker Top@>+=
int pw_evwalker_walk(pw_evwalker *walker, int pos)
{
    if(walker->terminal) return 0;

    if(walker->count > 0) {
        walker->count--;
        return 1;
    } else if(walker->count == 0) {
        while(walker->count == 0) {
            if(pw_evnode_is_terminal(walker->node)) {
                pw_evnode_fire(walker->node, pos);
                walker->terminal = 1;
                return 0;
            } else {
                walker->count = walker->node->dur;
                walker->node = pw_evnode_fire(walker->node, pos);
                if(walker->node == NULL) {
                    walker->terminal = 1;
                    return 0;
                }
            }
        }
        walker->count--;
        return !walker->terminal;
    }

    return 0;
}