annotate scripts/hvresources/heavy_render.cpp @ 480:4ff80956c27a prerelease

heavy supports digitals at message rate
author Giulio Moro <giuliomoro@yahoo.it>
date Tue, 21 Jun 2016 05:44:21 +0100
parents 6462d0cc8906
children 4d5edf7ee953
rev   line source
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 }