giuliomoro@230
|
1 /*
|
giuliomoro@230
|
2 * render.cpp
|
giuliomoro@230
|
3 *
|
giuliomoro@230
|
4 * Created on: Oct 24, 2014
|
giuliomoro@230
|
5 * Author: parallels
|
giuliomoro@230
|
6 */
|
giuliomoro@230
|
7
|
giuliomoro@301
|
8 #include <Bela.h>
|
giuliomoro@345
|
9 #include <DigitalToMessage.h>
|
giuliomoro@230
|
10 #include <cmath>
|
giuliomoro@230
|
11 #include <Utilities.h>
|
giuliomoro@230
|
12 #include <I2c_Codec.h>
|
giuliomoro@230
|
13 #include <PRU.h>
|
giuliomoro@230
|
14 #include <stdio.h>
|
giuliomoro@230
|
15 #include "z_libpd.h"
|
giuliomoro@338
|
16 #include "z_queued.h"
|
giuliomoro@342
|
17 #include "s_stuff.h"
|
giuliomoro@230
|
18 #include <UdpServer.h>
|
giuliomoro@324
|
19 #include <Midi.h>
|
giuliomoro@345
|
20 //extern t_sample* sys_soundin;
|
giuliomoro@345
|
21 //extern t_sample* sys_soundout;
|
giuliomoro@345
|
22 // if you are 100% sure of what value was used to compile libpd/puredata, then
|
giuliomoro@345
|
23 // you could #define this instead of getting it at runtime. It has proved to give some 0.3%
|
giuliomoro@343
|
24 // performance boost when it is 8 (thanks to vectorize optimizations I guess).
|
giuliomoro@230
|
25 int gBufLength;
|
giuliomoro@230
|
26
|
giuliomoro@230
|
27 float* gInBuf;
|
giuliomoro@230
|
28 float* gOutBuf;
|
giuliomoro@230
|
29
|
giuliomoro@230
|
30 void pdnoteon(int ch, int pitch, int vel) {
|
giuliomoro@230
|
31 printf("noteon: %d %d %d\n", ch, pitch, vel);
|
giuliomoro@230
|
32 }
|
giuliomoro@230
|
33
|
giuliomoro@301
|
34 void Bela_printHook(const char *recv){
|
giuliomoro@230
|
35 rt_printf("%s", recv);
|
giuliomoro@230
|
36 }
|
giuliomoro@230
|
37
|
giuliomoro@340
|
38 void libpdReadFilesLoop(){
|
giuliomoro@230
|
39 while(!gShouldStop){
|
giuliomoro@337
|
40 // check for modified sockets/file descriptors
|
giuliomoro@337
|
41 // (libpd would normally do this every block WITHIN the audio thread)
|
giuliomoro@337
|
42 // not sure if this is thread-safe at the moment
|
giuliomoro@230
|
43 libpd_sys_microsleep(0);
|
giuliomoro@230
|
44 usleep(1000);
|
giuliomoro@230
|
45 }
|
giuliomoro@230
|
46 }
|
giuliomoro@340
|
47
|
giuliomoro@325
|
48 #define PARSE_MIDI
|
giuliomoro@340
|
49 AuxiliaryTask libpdReadFilesTask;
|
giuliomoro@340
|
50 AuxiliaryTask libpdProcessMessageQueueTask;
|
giuliomoro@340
|
51 AuxiliaryTask libpdProcessMidiQueueTask;
|
giuliomoro@324
|
52 Midi midi;
|
giuliomoro@342
|
53 //UdpServer udpServer;
|
giuliomoro@337
|
54
|
giuliomoro@345
|
55 void sendDigitalMessage(bool state, unsigned int delay, void* receiverName){
|
giuliomoro@345
|
56 libpd_float((char*)receiverName, (float)state);
|
giuliomoro@345
|
57 // rt_printf("%s: %d\n", (char*)receiverName, state);
|
giuliomoro@345
|
58 }
|
giuliomoro@345
|
59 char receiverNames[16][21]={
|
giuliomoro@345
|
60 {"bela_digitalIn11"},{"bela_digitalIn12"},{"bela_digitalIn13"},{"bela_digitalIn14"},{"bela_digitalIn15"},
|
giuliomoro@345
|
61 {"bela_digitalIn16"},{"bela_digitalIn17"},{"bela_digitalIn18"},{"bela_digitalIn19"},{"bela_digitalIn20"},
|
giuliomoro@345
|
62 {"bela_digitalIn21"},{"bela_digitalIn22"},{"bela_digitalIn23"},{"bela_digitalIn24"},{"bela_digitalIn25"},
|
giuliomoro@345
|
63 {"bela_digitalIn26"}
|
giuliomoro@345
|
64 };
|
giuliomoro@345
|
65
|
giuliomoro@345
|
66 static DigitalToMessage** dtm;
|
giuliomoro@345
|
67 static unsigned int analogChannelsInUse;
|
giuliomoro@345
|
68 static unsigned int gLibpdBlockSize;
|
giuliomoro@345
|
69 static unsigned int gChannelsInUse = 26;
|
giuliomoro@345
|
70
|
giuliomoro@301
|
71 bool setup(BelaContext *context, void *userData)
|
giuliomoro@230
|
72 {
|
giuliomoro@345
|
73 analogChannelsInUse = min(context->analogChannels, gChannelsInUse - context->audioChannels - context->digitalChannels);
|
giuliomoro@345
|
74 dtm = new DigitalToMessage* [context->digitalChannels];
|
giuliomoro@345
|
75 if(context->digitalChannels > 0){
|
giuliomoro@345
|
76 for(unsigned int ch = 0; ch < context->digitalChannels; ++ch){
|
giuliomoro@345
|
77 dtm[ch] = new DigitalToMessage;
|
giuliomoro@345
|
78 dtm[ch]->setCallback(sendDigitalMessage, receiverNames[ch]);
|
giuliomoro@348
|
79 pinMode(context, 0, ch, INPUT);
|
giuliomoro@345
|
80 }
|
giuliomoro@345
|
81 }
|
giuliomoro@324
|
82 midi.readFrom(0);
|
giuliomoro@324
|
83 midi.writeTo(0);
|
giuliomoro@325
|
84 #ifdef PARSE_MIDI
|
giuliomoro@324
|
85 midi.enableParser(true);
|
giuliomoro@325
|
86 #else
|
giuliomoro@325
|
87 midi.enableParser(false);
|
giuliomoro@325
|
88 #endif /* PARSE_MIDI */
|
giuliomoro@345
|
89 // gChannelsInUse = min((int)(context->analogChannels+context->audioChannels), (int)gChannelsInUse);
|
giuliomoro@342
|
90 // udpServer.bindToPort(1234);
|
giuliomoro@230
|
91
|
giuliomoro@340
|
92 gLibpdBlockSize = libpd_blocksize();
|
giuliomoro@337
|
93 // check that we are not running with a blocksize smaller than gLibPdBlockSize
|
giuliomoro@230
|
94 // it would still work, but the load would be executed unevenly between calls to render
|
giuliomoro@340
|
95 if(context->audioFrames < gLibpdBlockSize){
|
giuliomoro@340
|
96 fprintf(stderr, "Error: minimum block size must be %d\n", gLibpdBlockSize);
|
giuliomoro@230
|
97 return false;
|
giuliomoro@230
|
98 }
|
giuliomoro@230
|
99 // init pd
|
giuliomoro@338
|
100 libpd_set_queued_printhook(Bela_printHook); // set this before calling libpd_init
|
giuliomoro@338
|
101 libpd_set_queued_noteonhook(pdnoteon);
|
giuliomoro@340
|
102 //TODO: add hooks for other midi events and generate MIDI output appropriately
|
giuliomoro@338
|
103 libpd_queued_init();
|
giuliomoro@340
|
104 //TODO: ideally, we would analyse the ASCII of the patch file and find the in/outs to use
|
giuliomoro@230
|
105 libpd_init_audio(gChannelsInUse, gChannelsInUse, context->audioSampleRate);
|
giuliomoro@230
|
106
|
giuliomoro@230
|
107 libpd_start_message(1); // one entry in list
|
giuliomoro@230
|
108 libpd_add_float(1.0f);
|
giuliomoro@230
|
109 libpd_finish_message("pd", "dsp");
|
giuliomoro@230
|
110
|
giuliomoro@340
|
111 gBufLength = max(gLibpdBlockSize, context->audioFrames);
|
giuliomoro@230
|
112
|
giuliomoro@230
|
113 char file[] = "_main.pd";
|
giuliomoro@230
|
114 char folder[] = "./";
|
giuliomoro@230
|
115 // open patch [; pd open file folder(
|
giuliomoro@230
|
116 libpd_openfile(file, folder);
|
giuliomoro@342
|
117 gInBuf = libpd_get_sys_soundin();
|
giuliomoro@342
|
118 gOutBuf = libpd_get_sys_soundout();
|
giuliomoro@340
|
119 libpdReadFilesTask = Bela_createAuxiliaryTask(libpdReadFilesLoop, 60, "libpdReadFiles");
|
giuliomoro@340
|
120 Bela_scheduleAuxiliaryTask(libpdReadFilesTask);
|
giuliomoro@340
|
121
|
giuliomoro@340
|
122 // Higher priority for the midi queue and lower priority for the message queue. Adjust to taste
|
giuliomoro@340
|
123 libpdProcessMidiQueueTask = Bela_createAuxiliaryTask(libpd_queued_receive_midi_messages, 80, "libpdProcessMidiQueue");
|
giuliomoro@340
|
124 libpdProcessMessageQueueTask = Bela_createAuxiliaryTask(libpd_queued_receive_pd_messages, 70, "libpdProcessMessageQueue");
|
giuliomoro@230
|
125 return true;
|
giuliomoro@230
|
126 }
|
giuliomoro@230
|
127
|
giuliomoro@230
|
128 // render() is called regularly at the highest priority by the audio engine.
|
giuliomoro@230
|
129 // Input and output are given from the audio hardware and the other
|
giuliomoro@230
|
130 // ADCs and DACs (if available). If only audio is available, numMatrixFrames
|
giuliomoro@230
|
131 // will be 0.
|
giuliomoro@345
|
132
|
giuliomoro@301
|
133 void render(BelaContext *context, void *userData)
|
giuliomoro@230
|
134 {
|
giuliomoro@324
|
135 int num;
|
giuliomoro@341
|
136 // the safest thread-safe option to handle MIDI input is to process the MIDI buffer
|
giuliomoro@341
|
137 // from the audio thread.
|
giuliomoro@325
|
138 #ifdef PARSE_MIDI
|
giuliomoro@324
|
139 while((num = midi.getParser()->numAvailableMessages()) > 0){
|
giuliomoro@324
|
140 static MidiChannelMessage message;
|
giuliomoro@324
|
141 message = midi.getParser()->getNextChannelMessage();
|
giuliomoro@341
|
142 //message.prettyPrint(); // use this to print beautified message (channel, data bytes)
|
giuliomoro@324
|
143 switch(message.getType()){
|
giuliomoro@325
|
144 case kmmNoteOn:
|
giuliomoro@325
|
145 {
|
giuliomoro@324
|
146 int noteNumber = message.getDataByte(0);
|
giuliomoro@324
|
147 int velocity = message.getDataByte(1);
|
giuliomoro@324
|
148 int channel = message.getChannel();
|
giuliomoro@324
|
149 libpd_noteon(channel, noteNumber, velocity);
|
giuliomoro@325
|
150 break;
|
giuliomoro@324
|
151 }
|
giuliomoro@325
|
152 case kmmNoteOff:
|
giuliomoro@325
|
153 {
|
giuliomoro@325
|
154 /* PureData does not seem to handle noteoff messages as per the MIDI specs,
|
giuliomoro@325
|
155 * so that the noteoff velocity is ignored. Here we convert them to noteon
|
giuliomoro@325
|
156 * with a velocity of 0.
|
giuliomoro@325
|
157 */
|
giuliomoro@325
|
158 int noteNumber = message.getDataByte(0);
|
giuliomoro@325
|
159 // int velocity = message.getDataByte(1); // would be ignored by Pd
|
giuliomoro@325
|
160 int channel = message.getChannel();
|
giuliomoro@325
|
161 libpd_noteon(channel, noteNumber, 0);
|
giuliomoro@325
|
162 break;
|
giuliomoro@324
|
163 }
|
giuliomoro@325
|
164 case kmmControlChange:
|
giuliomoro@325
|
165 {
|
giuliomoro@325
|
166 int channel = message.getChannel();
|
giuliomoro@325
|
167 int controller = message.getDataByte(0);
|
giuliomoro@325
|
168 int value = message.getDataByte(1);
|
giuliomoro@325
|
169 libpd_controlchange(channel, controller, value);
|
giuliomoro@325
|
170 break;
|
giuliomoro@325
|
171 }
|
giuliomoro@325
|
172 case kmmProgramChange:
|
giuliomoro@325
|
173 {
|
giuliomoro@325
|
174 int channel = message.getChannel();
|
giuliomoro@325
|
175 int program = message.getDataByte(0);
|
giuliomoro@325
|
176 libpd_programchange(channel, program);
|
giuliomoro@325
|
177 break;
|
giuliomoro@325
|
178 }
|
giuliomoro@325
|
179 case kmmPolyphonicKeyPressure:
|
giuliomoro@325
|
180 {
|
giuliomoro@325
|
181 int channel = message.getChannel();
|
giuliomoro@325
|
182 int pitch = message.getDataByte(0);
|
giuliomoro@325
|
183 int value = message.getDataByte(1);
|
giuliomoro@325
|
184 libpd_polyaftertouch(channel, pitch, value);
|
giuliomoro@325
|
185 break;
|
giuliomoro@325
|
186 }
|
giuliomoro@325
|
187 case kmmChannelPressure:
|
giuliomoro@325
|
188 {
|
giuliomoro@325
|
189 int channel = message.getChannel();
|
giuliomoro@325
|
190 int value = message.getDataByte(0);
|
giuliomoro@325
|
191 libpd_aftertouch(channel, value);
|
giuliomoro@325
|
192 break;
|
giuliomoro@325
|
193 }
|
giuliomoro@325
|
194 case kmmPitchBend:
|
giuliomoro@325
|
195 {
|
giuliomoro@325
|
196 int channel = message.getChannel();
|
giuliomoro@325
|
197 int value = (message.getDataByte(1) << 7)| message.getDataByte(0);
|
giuliomoro@325
|
198 libpd_pitchbend(channel, value);
|
giuliomoro@325
|
199 break;
|
giuliomoro@325
|
200 }
|
giuliomoro@337
|
201 case kmmNone:
|
giuliomoro@337
|
202 case kmmAny:
|
giuliomoro@337
|
203 break;
|
giuliomoro@324
|
204 }
|
giuliomoro@324
|
205 }
|
giuliomoro@325
|
206 #else
|
giuliomoro@325
|
207 int input;
|
giuliomoro@325
|
208 while((input = midi.getInput()) >= 0){
|
giuliomoro@325
|
209 libpd_midibyte(0, input);
|
giuliomoro@325
|
210 }
|
giuliomoro@325
|
211 #endif /* PARSE_MIDI */
|
giuliomoro@340
|
212
|
giuliomoro@230
|
213 /*
|
giuliomoro@230
|
214 * NOTE: if you are only using audio (or only analogs) and you are using interleaved buffers
|
giuliomoro@337
|
215 * and the blocksize of Bela is the same as gLibPdBlockSize, then you probably
|
giuliomoro@230
|
216 * do not need the for loops before and after libpd_process_float, so you can save quite some
|
giuliomoro@230
|
217 * memory operations.
|
giuliomoro@230
|
218 */
|
giuliomoro@341
|
219 static unsigned int numberOfPdBlocksToProcess = gBufLength / gLibpdBlockSize;
|
giuliomoro@343
|
220 for(unsigned int tick = 0; tick < numberOfPdBlocksToProcess; ++tick){
|
giuliomoro@345
|
221 unsigned int audioFrameBase = gLibpdBlockSize * tick;
|
giuliomoro@343
|
222 unsigned int j;
|
giuliomoro@343
|
223 unsigned int k;
|
giuliomoro@343
|
224 float* p0;
|
giuliomoro@343
|
225 float* p1;
|
giuliomoro@343
|
226 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
|
giuliomoro@343
|
227 for (k = 0, p1 = p0; k < context->audioChannels; k++, p1 += gLibpdBlockSize) {
|
giuliomoro@345
|
228 *p1 = audioRead(context, audioFrameBase + j, k);
|
giuliomoro@232
|
229 }
|
giuliomoro@343
|
230 }
|
giuliomoro@343
|
231 // then analogs
|
giuliomoro@343
|
232 // this loop resamples by ZOH, as needed, using m
|
giuliomoro@343
|
233 if(context->analogChannels == 8 ){ //hold the value for two frames
|
giuliomoro@343
|
234 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
|
giuliomoro@343
|
235 for (k = 0, p1 = p0 + gLibpdBlockSize * context->audioChannels; k < analogChannelsInUse; k++, p1 += gLibpdBlockSize) {
|
giuliomoro@345
|
236 unsigned int analogFrame = (audioFrameBase + j) / 2;
|
giuliomoro@343
|
237 *p1 = analogRead(context, analogFrame, k);
|
giuliomoro@341
|
238 }
|
giuliomoro@343
|
239 }
|
giuliomoro@343
|
240 } else if(context->analogChannels == 4){ //write every frame
|
giuliomoro@343
|
241 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
|
giuliomoro@343
|
242 for (k = 0, p1 = p0 + gLibpdBlockSize * context->audioChannels; k < analogChannelsInUse; k++, p1 += gLibpdBlockSize) {
|
giuliomoro@345
|
243 unsigned int analogFrame = audioFrameBase + j;
|
giuliomoro@343
|
244 *p1 = analogRead(context, analogFrame, k);
|
giuliomoro@341
|
245 }
|
giuliomoro@343
|
246 }
|
giuliomoro@343
|
247 } else if(context->analogChannels == 2){ //drop every other frame
|
giuliomoro@343
|
248 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
|
giuliomoro@343
|
249 for (k = 0, p1 = p0 + gLibpdBlockSize * context->audioChannels; k < analogChannelsInUse; k++, p1 += gLibpdBlockSize) {
|
giuliomoro@345
|
250 unsigned int analogFrame = (audioFrameBase + j) * 2;
|
giuliomoro@343
|
251 *p1 = analogRead(context, analogFrame, k);
|
giuliomoro@341
|
252 }
|
giuliomoro@232
|
253 }
|
giuliomoro@230
|
254 }
|
giuliomoro@345
|
255
|
giuliomoro@345
|
256 //then digital
|
giuliomoro@348
|
257 //TODO: in multiple places below we assume that the number of digitals is same as number of audio
|
giuliomoro@348
|
258 // digital in at message-rate
|
giuliomoro@345
|
259 for(unsigned int n = 0; n < context->digitalChannels; ++n){
|
giuliomoro@348
|
260 // note that we consider only the first sample of the block
|
giuliomoro@345
|
261 // considering all of them is notably more expensive
|
giuliomoro@345
|
262 // TODO: only process the channels marked as such
|
giuliomoro@345
|
263 dtm[n]->process(n + 16, &context->digital[audioFrameBase], 1);
|
giuliomoro@345
|
264 }
|
giuliomoro@348
|
265 // digital in at signal-rate
|
giuliomoro@348
|
266 for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
|
giuliomoro@348
|
267 for (k = 0, p1 = p0 + gLibpdBlockSize * (context->audioChannels + 8);
|
giuliomoro@348
|
268 k < 16; ++k, p1 += gLibpdBlockSize) {
|
giuliomoro@348
|
269 // note that we only write the last sample of the block
|
giuliomoro@348
|
270 // writing all of them is notably more expensive.
|
giuliomoro@348
|
271
|
giuliomoro@348
|
272 // TODO: only process the channels marked as such
|
giuliomoro@348
|
273 unsigned int digitalFrame = audioFrameBase + j;
|
giuliomoro@348
|
274 *p1 = digitalRead(context, digitalFrame, k);
|
giuliomoro@348
|
275 }
|
giuliomoro@348
|
276 }
|
giuliomoro@345
|
277
|
giuliomoro@342
|
278 libpd_process_sys(); // process the block
|
giuliomoro@345
|
279
|
giuliomoro@348
|
280 //digital out
|
giuliomoro@348
|
281 // digital at message-rate
|
giuliomoro@345
|
282 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) {
|
giuliomoro@345
|
283 unsigned int digitalFrame = (audioFrameBase + j);
|
giuliomoro@345
|
284 for (k = 0, p1 = p0 + gLibpdBlockSize * (context->audioChannels + 8);
|
giuliomoro@345
|
285 k < context->digitalChannels; k++, p1 += gLibpdBlockSize) {
|
giuliomoro@345
|
286 // TODO: only process the channels marked as such
|
giuliomoro@345
|
287 digitalWriteOnce(context, digitalFrame, k, *p1 > 0.5);
|
giuliomoro@345
|
288 }
|
giuliomoro@345
|
289 }
|
giuliomoro@348
|
290
|
giuliomoro@345
|
291 //audio
|
giuliomoro@343
|
292 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j++, p0++) {
|
giuliomoro@343
|
293 for (k = 0, p1 = p0; k < context->audioChannels; k++, p1 += gLibpdBlockSize) {
|
giuliomoro@345
|
294 audioWrite(context, audioFrameBase + j, k, *p1);
|
giuliomoro@341
|
295 }
|
giuliomoro@343
|
296 }
|
giuliomoro@345
|
297 //analog
|
giuliomoro@343
|
298 if(context->analogChannels == 8){
|
giuliomoro@343
|
299 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j += 2, p0 += 2) { //write every two frames
|
giuliomoro@345
|
300 unsigned int analogFrame = (audioFrameBase + j) / 2;
|
giuliomoro@343
|
301 for (k = 0, p1 = p0 + gLibpdBlockSize * context->audioChannels; k < analogChannelsInUse; k++, p1 += gLibpdBlockSize) {
|
giuliomoro@345
|
302 analogWriteOnce(context, analogFrame, k, *p1);
|
giuliomoro@341
|
303 }
|
giuliomoro@343
|
304 }
|
giuliomoro@343
|
305 } else if(context->analogChannels == 4){ //write every frame
|
giuliomoro@343
|
306 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) {
|
giuliomoro@345
|
307 unsigned int analogFrame = (audioFrameBase + j);
|
giuliomoro@343
|
308 for (k = 0, p1 = p0 + gLibpdBlockSize * context->audioChannels; k < analogChannelsInUse; k++, p1 += gLibpdBlockSize) {
|
giuliomoro@345
|
309 analogWriteOnce(context, analogFrame, k, *p1);
|
giuliomoro@341
|
310 }
|
giuliomoro@343
|
311 }
|
giuliomoro@343
|
312 } else if(context->analogChannels == 2){ //write every frame twice
|
giuliomoro@343
|
313 for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j++, p0++) {
|
giuliomoro@343
|
314 for (k = 0, p1 = p0 + gLibpdBlockSize * context->audioChannels; k < analogChannelsInUse; k++, p1 += gLibpdBlockSize) {
|
giuliomoro@345
|
315 int analogFrame = audioFrameBase * 2 + j * 2;
|
giuliomoro@345
|
316 analogWriteOnce(context, analogFrame, k, *p1);
|
giuliomoro@345
|
317 analogWriteOnce(context, analogFrame + 1, k, *p1);
|
giuliomoro@232
|
318 }
|
giuliomoro@232
|
319 }
|
giuliomoro@230
|
320 }
|
giuliomoro@230
|
321 }
|
giuliomoro@340
|
322 Bela_scheduleAuxiliaryTask(libpdProcessMidiQueueTask);
|
giuliomoro@340
|
323 Bela_scheduleAuxiliaryTask(libpdProcessMessageQueueTask);
|
giuliomoro@230
|
324 }
|
giuliomoro@232
|
325
|
giuliomoro@230
|
326 // cleanup() is called once at the end, after the audio has stopped.
|
giuliomoro@230
|
327 // Release any resources that were allocated in setup().
|
giuliomoro@230
|
328
|
giuliomoro@301
|
329 void cleanup(BelaContext *context, void *userData)
|
giuliomoro@230
|
330 {
|
giuliomoro@338
|
331 libpd_queued_release();
|
giuliomoro@345
|
332 delete[] dtm;
|
giuliomoro@230
|
333 }
|