chris@160
|
1 /*
|
chris@160
|
2 * render.cpp
|
chris@160
|
3 *
|
chris@160
|
4 * Template render.cpp file for on-board heavy compiling
|
chris@160
|
5 *
|
chris@160
|
6 * N.B. this is currently *not* compatible with foleyDesigner source files!
|
chris@160
|
7 *
|
chris@160
|
8 * Created on: November 5, 2015
|
chris@160
|
9 *
|
chris@160
|
10 * Christian Heinrichs
|
chris@160
|
11 *
|
chris@160
|
12 */
|
chris@160
|
13
|
giuliomoro@329
|
14 #include <Bela.h>
|
giuliomoro@198
|
15 #include <Midi.h>
|
chris@160
|
16 #include <cmath>
|
chris@160
|
17 #include "Heavy_bbb.h"
|
chris@190
|
18 #include <string.h>
|
chris@190
|
19 #include <stdlib.h>
|
chris@190
|
20 #include <string.h>
|
giuliomoro@480
|
21 #include <DigitalChannelManager.h>
|
giuliomoro@480
|
22
|
chris@160
|
23 /*
|
chris@160
|
24 * HEAVY CONTEXT & BUFFERS
|
chris@160
|
25 */
|
chris@160
|
26
|
chris@160
|
27 Hv_bbb *gHeavyContext;
|
chris@160
|
28 float *gHvInputBuffers = NULL, *gHvOutputBuffers = NULL;
|
giuliomoro@480
|
29 unsigned int gHvInputChannels = 0, gHvOutputChannels = 0;
|
chris@160
|
30
|
chris@160
|
31 float gInverseSampleRate;
|
chris@160
|
32
|
chris@160
|
33 /*
|
chris@160
|
34 * HEAVY FUNCTIONS
|
chris@160
|
35 */
|
chris@160
|
36
|
giuliomoro@480
|
37 // TODO: rename this
|
giuliomoro@480
|
38 #define LIBPD_DIGITAL_OFFSET 11 // digitals are preceded by 2 audio and 8 analogs (even if using a different number of analogs)
|
giuliomoro@480
|
39
|
chris@160
|
40 void printHook(double timestampSecs, const char *printLabel, const char *msgString, void *userData) {
|
giuliomoro@480
|
41 rt_printf("Message from Heavy patch: [@ %.3f] %s: %s\n", timestampSecs, printLabel, msgString);
|
chris@160
|
42 }
|
chris@160
|
43
|
giuliomoro@480
|
44
|
giuliomoro@480
|
45 // digitals
|
giuliomoro@480
|
46 static DigitalChannelManager dcm;
|
giuliomoro@480
|
47
|
giuliomoro@480
|
48 void sendDigitalMessage(bool state, unsigned int delay, void* receiverName){
|
giuliomoro@480
|
49 hv_sendFloatToReceiver(gHeavyContext, hv_stringToHash((char*)receiverName), (float)state);
|
giuliomoro@480
|
50 // rt_printf("%s: %d\n", (char*)receiverName, state);
|
giuliomoro@480
|
51 }
|
giuliomoro@480
|
52
|
giuliomoro@480
|
53 // TODO: turn them into hv hashes and adjust sendDigitalMessage accordingly
|
giuliomoro@480
|
54 char hvDigitalInHashes[16][21]={
|
giuliomoro@480
|
55 {"bela_digitalIn11"},{"bela_digitalIn12"},{"bela_digitalIn13"},{"bela_digitalIn14"},{"bela_digitalIn15"},
|
giuliomoro@480
|
56 {"bela_digitalIn16"},{"bela_digitalIn17"},{"bela_digitalIn18"},{"bela_digitalIn19"},{"bela_digitalIn20"},
|
giuliomoro@480
|
57 {"bela_digitalIn21"},{"bela_digitalIn22"},{"bela_digitalIn23"},{"bela_digitalIn24"},{"bela_digitalIn25"},
|
giuliomoro@480
|
58 {"bela_digitalIn26"}
|
giuliomoro@480
|
59 };
|
giuliomoro@480
|
60
|
chris@160
|
61 static void sendHook(
|
giuliomoro@480
|
62 double timestamp, // in milliseconds
|
giuliomoro@480
|
63 const char *receiverName,
|
giuliomoro@480
|
64 const HvMessage *const m,
|
giuliomoro@480
|
65 void *userData) {
|
chris@160
|
66
|
giuliomoro@480
|
67 // Bela digital
|
giuliomoro@480
|
68
|
giuliomoro@480
|
69 // Bela digital run-time messages
|
chris@160
|
70
|
giuliomoro@480
|
71 // TODO: this first block is almost an exact copy of libpd's code, should we add this to the class?
|
giuliomoro@480
|
72 // let's make this as optimized as possible for built-in digital Out parsing
|
giuliomoro@480
|
73 // the built-in digital receivers are of the form "bela_digitalOutXX" where XX is between 11 and 26
|
giuliomoro@480
|
74 static int prefixLength = 15; // strlen("bela_digitalOut")
|
giuliomoro@480
|
75 if(strncmp(receiverName, "bela_digitalOut", prefixLength)==0){
|
giuliomoro@480
|
76 if(receiverName[prefixLength] != 0){ //the two ifs are used instead of if(strlen(source) >= prefixLength+2)
|
giuliomoro@480
|
77 if(receiverName[prefixLength + 1] != 0){
|
giuliomoro@480
|
78 // quickly convert the suffix to integer, assuming they are numbers, avoiding to call atoi
|
giuliomoro@480
|
79 int receiver = ((receiverName[prefixLength] - 48) * 10);
|
giuliomoro@480
|
80 receiver += (receiverName[prefixLength+1] - 48);
|
giuliomoro@480
|
81 unsigned int channel = receiver - LIBPD_DIGITAL_OFFSET; // go back to the actual Bela digital channel number
|
giuliomoro@480
|
82 bool value = hv_msg_getFloat(m, 0);
|
giuliomoro@480
|
83 if(channel < 16){ //16 is the hardcoded value for the number of digital channels
|
giuliomoro@480
|
84 dcm.setValue(channel, value);
|
giuliomoro@480
|
85 }
|
giuliomoro@480
|
86 }
|
giuliomoro@480
|
87 }
|
giuliomoro@480
|
88 }
|
giuliomoro@480
|
89
|
giuliomoro@480
|
90 // Bela digital initialization messages
|
giuliomoro@480
|
91 if(strcmp(receiverName, "bela_setDigital") == 0){
|
giuliomoro@480
|
92 // Third argument (optional) can be ~ or sig for signal-rate, message-rate otherwise.
|
giuliomoro@480
|
93 // [in 14 ~(
|
giuliomoro@480
|
94 // |
|
giuliomoro@480
|
95 // [s bela_setDigital]
|
giuliomoro@480
|
96 // is signal("sig" or "~") or message("message", default) rate
|
giuliomoro@480
|
97 bool isMessageRate = true; // defaults to message rate
|
giuliomoro@480
|
98 bool direction = 0; // initialize it just to avoid the compiler's warning
|
giuliomoro@480
|
99 bool disable = false;
|
giuliomoro@480
|
100 int numArgs = hv_msg_getNumElements(m);
|
giuliomoro@480
|
101 if(numArgs < 2 || numArgs > 3 || !hv_msg_isSymbol(m, 0) || !hv_msg_isFloat(m, 1))
|
giuliomoro@480
|
102 return;
|
giuliomoro@480
|
103 if(numArgs == 3 && !hv_msg_isSymbol(m,2))
|
giuliomoro@480
|
104 return;
|
giuliomoro@480
|
105 char * symbol = hv_msg_getSymbol(m, 0);
|
giuliomoro@480
|
106
|
giuliomoro@480
|
107 if(strcmp(symbol, "in") == 0){
|
giuliomoro@480
|
108 direction = INPUT;
|
giuliomoro@480
|
109 } else if(strcmp(symbol, "out") == 0){
|
giuliomoro@480
|
110 direction = OUTPUT;
|
giuliomoro@480
|
111 } else if(strcmp(symbol, "disable") == 0){
|
giuliomoro@480
|
112 disable = true;
|
giuliomoro@480
|
113 } else {
|
giuliomoro@480
|
114 return;
|
giuliomoro@480
|
115 }
|
giuliomoro@480
|
116 int channel = hv_msg_getFloat(m, 1) - LIBPD_DIGITAL_OFFSET;
|
giuliomoro@480
|
117 if(disable == true){
|
giuliomoro@480
|
118 dcm.unmanage(channel);
|
giuliomoro@480
|
119 return;
|
giuliomoro@480
|
120 }
|
giuliomoro@480
|
121 if(numArgs >= 3){
|
giuliomoro@480
|
122 char* s = hv_msg_getSymbol(m, 2);
|
giuliomoro@480
|
123 if(strcmp(s, "~") == 0 || strncmp(s, "sig", 3) == 0){
|
giuliomoro@480
|
124 isMessageRate = false;
|
giuliomoro@480
|
125 }
|
giuliomoro@480
|
126 }
|
giuliomoro@480
|
127 dcm.manage(channel, direction, isMessageRate);
|
giuliomoro@480
|
128 }
|
chris@160
|
129 }
|
chris@160
|
130
|
giuliomoro@480
|
131
|
chris@160
|
132 /*
|
chris@166
|
133 * SETUP, RENDER LOOP & CLEANUP
|
chris@160
|
134 */
|
chris@160
|
135
|
giuliomoro@480
|
136
|
giuliomoro@480
|
137
|
giuliomoro@480
|
138 // Midi
|
giuliomoro@198
|
139 Midi midi;
|
giuliomoro@480
|
140 unsigned int hvMidiHashes[7];
|
giuliomoro@480
|
141
|
giuliomoro@480
|
142
|
giuliomoro@329
|
143 bool setup(BelaContext *context, void *userData) {
|
chris@160
|
144
|
giuliomoro@480
|
145 printf("top o setup\n");
|
chris@160
|
146 /* HEAVY */
|
giuliomoro@480
|
147 hvMidiHashes[kmmNoteOn] = hv_stringToHash("__hv_notein");
|
giuliomoro@480
|
148 hvMidiHashes[kmmNoteOff] = hv_stringToHash("noteoff"); // this is handled differently, see the render function
|
giuliomoro@480
|
149 hvMidiHashes[kmmControlChange] = hv_stringToHash("__hv_ctlin");
|
giuliomoro@480
|
150 hvMidiHashes[kmmProgramChange] = hv_stringToHash("pgmin");
|
giuliomoro@480
|
151 hvMidiHashes[kmmPolyphonicKeyPressure] = hv_stringToHash("polytouchin");
|
giuliomoro@480
|
152 hvMidiHashes[kmmChannelPressure] = hv_stringToHash("touchin");
|
giuliomoro@480
|
153 hvMidiHashes[kmmPitchBend] = hv_stringToHash("bendin");
|
chris@160
|
154
|
giuliomoro@480
|
155 printf("after midi o setup\n");
|
chris@160
|
156 gHeavyContext = hv_bbb_new(context->audioSampleRate);
|
chris@160
|
157
|
giuliomoro@480
|
158 printf("aftet new o setup\n");
|
chris@160
|
159 gHvInputChannels = hv_getNumInputChannels(gHeavyContext);
|
chris@160
|
160 gHvOutputChannels = hv_getNumOutputChannels(gHeavyContext);
|
chris@160
|
161
|
chris@160
|
162 rt_printf("Starting Heavy context with %d input channels and %d output channels\n",
|
chris@160
|
163 gHvInputChannels, gHvOutputChannels);
|
chris@160
|
164
|
chris@160
|
165 if(gHvInputChannels != 0) {
|
chris@160
|
166 gHvInputBuffers = (float *)calloc(gHvInputChannels * context->audioFrames,sizeof(float));
|
chris@160
|
167 }
|
chris@160
|
168 if(gHvOutputChannels != 0) {
|
chris@160
|
169 gHvOutputBuffers = (float *)calloc(gHvOutputChannels * context->audioFrames,sizeof(float));
|
chris@160
|
170 }
|
giuliomoro@480
|
171 printf("mid o setup\n");
|
chris@160
|
172
|
chris@160
|
173 gInverseSampleRate = 1.0 / context->audioSampleRate;
|
chris@160
|
174
|
chris@160
|
175 // Set heavy print hook
|
giuliomoro@480
|
176 hv_setPrintHook(gHeavyContext, printHook);
|
chris@160
|
177 // Set heavy send hook
|
chris@160
|
178 hv_setSendHook(gHeavyContext, sendHook);
|
chris@160
|
179
|
giuliomoro@480
|
180 // TODO: change these hardcoded port values and actually change them in the Midi class
|
giuliomoro@198
|
181 midi.readFrom(0);
|
giuliomoro@198
|
182 midi.writeTo(0);
|
giuliomoro@198
|
183 midi.enableParser(true);
|
giuliomoro@480
|
184
|
giuliomoro@480
|
185 // Bela digital
|
giuliomoro@480
|
186 dcm.setCallback(sendDigitalMessage);
|
giuliomoro@480
|
187 if(context->digitalChannels > 0){
|
giuliomoro@480
|
188 for(unsigned int ch = 0; ch < context->digitalChannels; ++ch){
|
giuliomoro@480
|
189 dcm.setCallbackArgument(ch, hvDigitalInHashes[ch]);
|
giuliomoro@480
|
190 }
|
giuliomoro@480
|
191 }
|
giuliomoro@480
|
192 // unlike libpd, no need here to bind the bela_digitalOut.. receivers
|
giuliomoro@480
|
193
|
giuliomoro@480
|
194 printf("end o setup\n");
|
chris@160
|
195 return true;
|
chris@160
|
196 }
|
chris@160
|
197
|
chris@160
|
198
|
giuliomoro@329
|
199 void render(BelaContext *context, void *userData)
|
chris@160
|
200 {
|
giuliomoro@480
|
201 {
|
giuliomoro@480
|
202 int num;
|
giuliomoro@480
|
203 while((num = midi.getParser()->numAvailableMessages()) > 0){
|
giuliomoro@480
|
204 static MidiChannelMessage message;
|
giuliomoro@480
|
205 message = midi.getParser()->getNextChannelMessage();
|
giuliomoro@480
|
206 switch(message.getType()){
|
giuliomoro@480
|
207 case kmmNoteOn: {
|
giuliomoro@480
|
208 // message.prettyPrint();
|
giuliomoro@480
|
209 float noteNumber = message.getDataByte(0);
|
giuliomoro@480
|
210 float velocity = message.getDataByte(1);
|
giuliomoro@480
|
211 float channel = message.getChannel();
|
giuliomoro@480
|
212 // rt_printf("message: noteNumber: %f, velocity: %f, channel: %f\n", noteNumber, velocity, channel);
|
giuliomoro@480
|
213 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmNoteOn], 0, "fff", noteNumber, velocity, channel);
|
giuliomoro@480
|
214 break;
|
giuliomoro@480
|
215 }
|
giuliomoro@480
|
216 case kmmNoteOff:
|
giuliomoro@480
|
217 {
|
giuliomoro@480
|
218 /* PureData does not seem to handle noteoff messages as per the MIDI specs,
|
giuliomoro@480
|
219 * so that the noteoff velocity is ignored. Here we convert them to noteon
|
giuliomoro@480
|
220 * with a velocity of 0.
|
giuliomoro@480
|
221 */
|
giuliomoro@480
|
222 float noteNumber = message.getDataByte(0);
|
giuliomoro@480
|
223 // int velocity = message.getDataByte(1); // would be ignored by Pd
|
giuliomoro@480
|
224 float channel = message.getChannel();
|
giuliomoro@480
|
225 // note we are sending the below to hvHashes[kmmNoteOn] !!
|
giuliomoro@480
|
226 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmNoteOn], 0, "fff", noteNumber, 0, channel);
|
giuliomoro@480
|
227 break;
|
giuliomoro@480
|
228 }
|
giuliomoro@480
|
229 case kmmControlChange: {
|
giuliomoro@480
|
230 int channel = message.getChannel();
|
giuliomoro@480
|
231 int controller = message.getDataByte(0);
|
giuliomoro@480
|
232 int value = message.getDataByte(1);
|
giuliomoro@480
|
233 //TODO: maybe the order of the arguments is wrong here?
|
giuliomoro@480
|
234 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmControlChange], 0, "fff",
|
giuliomoro@480
|
235 value, controller, channel);
|
giuliomoro@480
|
236 break;
|
giuliomoro@480
|
237 }
|
giuliomoro@480
|
238 case kmmProgramChange: {
|
giuliomoro@480
|
239 int channel = message.getChannel();
|
giuliomoro@480
|
240 int program = message.getDataByte(0);
|
giuliomoro@480
|
241 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmProgramChange], 0, "ff",
|
giuliomoro@480
|
242 program, channel);
|
giuliomoro@480
|
243 //TODO: maybe the order of the arguments is wrong here?
|
giuliomoro@480
|
244 break;
|
giuliomoro@480
|
245 }
|
giuliomoro@480
|
246 case kmmPolyphonicKeyPressure: {
|
giuliomoro@480
|
247 int channel = message.getChannel();
|
giuliomoro@480
|
248 int pitch = message.getDataByte(0);
|
giuliomoro@480
|
249 int value = message.getDataByte(1);
|
giuliomoro@480
|
250 //TODO: maybe the order of the arguments is wrong here?
|
giuliomoro@480
|
251 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmPolyphonicKeyPressure], 0, "fff",
|
giuliomoro@480
|
252 channel, pitch, value);
|
giuliomoro@480
|
253 break;
|
giuliomoro@480
|
254 }
|
giuliomoro@480
|
255 case kmmChannelPressure:
|
giuliomoro@480
|
256 {
|
giuliomoro@480
|
257 int channel = message.getChannel();
|
giuliomoro@480
|
258 int value = message.getDataByte(0);
|
giuliomoro@480
|
259 //TODO: maybe the order of the arguments is wrong here?
|
giuliomoro@480
|
260 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmChannelPressure], 0, "ff",
|
giuliomoro@480
|
261 channel, value);
|
giuliomoro@480
|
262 break;
|
giuliomoro@480
|
263 }
|
giuliomoro@480
|
264 case kmmPitchBend:
|
giuliomoro@480
|
265 {
|
giuliomoro@480
|
266 int channel = message.getChannel();
|
giuliomoro@480
|
267 int value = ((message.getDataByte(1) << 7) | message.getDataByte(0)) + 8192;
|
giuliomoro@480
|
268 //TODO: is the value range correct?
|
giuliomoro@480
|
269 //TODO: maybe the order of the arguments is wrong here?
|
giuliomoro@480
|
270 hv_vscheduleMessageForReceiver(gHeavyContext, hvMidiHashes[kmmPitchBend], 0, "ff",
|
giuliomoro@480
|
271 channel, value);
|
giuliomoro@480
|
272 break;
|
giuliomoro@480
|
273 }
|
giuliomoro@480
|
274 case kmmNone:
|
giuliomoro@480
|
275 case kmmAny:
|
giuliomoro@480
|
276 break;
|
giuliomoro@480
|
277 }
|
giuliomoro@480
|
278 }
|
giuliomoro@480
|
279 }
|
chris@160
|
280
|
chris@160
|
281 // De-interleave the data
|
chris@160
|
282 if(gHvInputBuffers != NULL) {
|
giuliomoro@480
|
283 for(unsigned int n = 0; n < context->audioFrames; n++) {
|
giuliomoro@480
|
284 for(unsigned int ch = 0; ch < gHvInputChannels; ch++) {
|
chris@160
|
285 if(ch >= context->audioChannels+context->analogChannels) {
|
chris@160
|
286 // THESE ARE PARAMETER INPUT 'CHANNELS' USED FOR ROUTING
|
chris@160
|
287 // 'sensor' outputs from routing channels of dac~ are passed through here
|
chris@160
|
288 break;
|
chris@160
|
289 } else {
|
chris@160
|
290 // If more than 2 ADC inputs are used in the pd patch, route the analog inputs
|
chris@160
|
291 // i.e. ADC3->analogIn0 etc. (first two are always audio inputs)
|
chris@160
|
292 if(ch >= context->audioChannels) {
|
chris@160
|
293 int m = n/2;
|
chris@160
|
294 float mIn = context->analogIn[m*context->analogChannels + (ch-context->audioChannels)];
|
chris@160
|
295 gHvInputBuffers[ch * context->audioFrames + n] = mIn;
|
chris@160
|
296 } else {
|
chris@160
|
297 gHvInputBuffers[ch * context->audioFrames + n] = context->audioIn[n * context->audioChannels + ch];
|
chris@160
|
298 }
|
chris@160
|
299 }
|
chris@160
|
300 }
|
chris@160
|
301 }
|
chris@160
|
302 }
|
chris@160
|
303
|
giuliomoro@480
|
304 // Bela digital in
|
giuliomoro@480
|
305 // note: in multiple places below we assume that the number of digital frames is same as number of audio
|
giuliomoro@480
|
306 // digital in at message-rate
|
giuliomoro@480
|
307 dcm.processInput(context->digital, context->digitalFrames);
|
giuliomoro@480
|
308
|
giuliomoro@480
|
309 // Bela digital in at signal-rate
|
giuliomoro@480
|
310 // TODO: not really straightforward to implement as Heavy determines the number of channels in use at compile time
|
giuliomoro@480
|
311 // on the basis of the number of adc~ / dac~ of the patch ... Maybe we should always include
|
giuliomoro@480
|
312 // a dummy [adc~ 27] [dac~ 27] to make sure all channels are always allocated and then leave them all unprocessed ?
|
giuliomoro@480
|
313
|
chris@160
|
314 // replacement for bang~ object
|
chris@160
|
315 //hv_vscheduleMessageForReceiver(gHeavyContext, "bbb_bang", 0.0f, "b");
|
chris@160
|
316
|
chris@160
|
317 hv_bbb_process_inline(gHeavyContext, gHvInputBuffers, gHvOutputBuffers, context->audioFrames);
|
chris@160
|
318
|
giuliomoro@480
|
319 // Bela digital out
|
giuliomoro@480
|
320 // digital out at signal-rate
|
giuliomoro@480
|
321 // TODO: see note above.
|
giuliomoro@480
|
322
|
giuliomoro@480
|
323 // digital out at message-rate
|
giuliomoro@480
|
324 dcm.processOutput(context->digital, context->digitalFrames);
|
giuliomoro@480
|
325
|
chris@160
|
326 // Interleave the output data
|
chris@160
|
327 if(gHvOutputBuffers != NULL) {
|
giuliomoro@480
|
328 for(unsigned int n = 0; n < context->audioFrames; n++) {
|
chris@160
|
329
|
giuliomoro@480
|
330 for(unsigned int ch = 0; ch < gHvOutputChannels; ch++) {
|
chris@160
|
331 if(ch >= context->audioChannels+context->analogChannels) {
|
chris@160
|
332 // THESE ARE SENSOR OUTPUT 'CHANNELS' USED FOR ROUTING
|
chris@160
|
333 // they are the content of the 'sensor output' dac~ channels
|
chris@160
|
334 } else {
|
chris@160
|
335 if(ch >= context->audioChannels) {
|
chris@160
|
336 int m = n/2;
|
chris@160
|
337 context->analogOut[m * context->analogFrames + (ch-context->audioChannels)] = constrain(gHvOutputBuffers[ch*context->audioFrames + n],0.0,1.0);
|
chris@160
|
338 } else {
|
chris@160
|
339 context->audioOut[n * context->audioChannels + ch] = gHvOutputBuffers[ch * context->audioFrames + n];
|
chris@160
|
340 }
|
chris@160
|
341 }
|
chris@160
|
342 }
|
chris@160
|
343 }
|
chris@160
|
344 }
|
chris@160
|
345
|
chris@160
|
346 }
|
chris@160
|
347
|
chris@160
|
348
|
giuliomoro@329
|
349 void cleanup(BelaContext *context, void *userData)
|
chris@160
|
350 {
|
chris@160
|
351
|
chris@160
|
352 hv_bbb_free(gHeavyContext);
|
chris@160
|
353 if(gHvInputBuffers != NULL)
|
chris@160
|
354 free(gHvInputBuffers);
|
chris@160
|
355 if(gHvOutputBuffers != NULL)
|
chris@160
|
356 free(gHvOutputBuffers);
|
chris@160
|
357
|
chris@160
|
358 }
|