chris@552: /* chris@552: * render.cpp chris@552: * chris@552: * Created on: Oct 24, 2014 chris@552: * Author: parallels chris@552: */ chris@552: chris@552: #include chris@552: #include chris@552: #include chris@552: #include chris@552: #include chris@552: #include chris@552: #include chris@552: #include chris@552: #include chris@552: #include chris@552: #include chris@552: chris@552: /* chris@552: * MODIFICATION chris@552: * ------------ chris@552: * Global variables for tremolo effect applied to libpd output chris@552: */ chris@552: chris@552: float gTremoloRate = 4.0; chris@552: float gPhase; chris@552: float gInverseSampleRate; chris@552: chris@552: /*********/ chris@552: chris@552: // if you are 100% sure of what value was used to compile libpd/puredata, then chris@552: // you could #define gBufLength instead of getting it at runtime. It has proved to give some 0.3% chris@552: // performance boost when it is 8 (thanks to vectorize optimizations I guess). chris@552: int gBufLength; chris@552: chris@552: float* gInBuf; chris@552: float* gOutBuf; chris@552: chris@552: void pdnoteon(int ch, int pitch, int vel) { chris@552: printf("noteon: %d %d %d\n", ch, pitch, vel); chris@552: } chris@552: chris@552: void Bela_printHook(const char *recv){ chris@552: rt_printf("%s", recv); chris@552: } chris@552: #define PARSE_MIDI chris@552: static Midi midi; chris@552: static DigitalChannelManager dcm; chris@552: chris@552: void sendDigitalMessage(bool state, unsigned int delay, void* receiverName){ chris@552: libpd_float((char*)receiverName, (float)state); chris@552: // rt_printf("%s: %d\n", (char*)receiverName, state); chris@552: } chris@552: chris@552: #define LIBPD_DIGITAL_OFFSET 11 // digitals are preceded by 2 audio and 8 analogs (even if using a different number of analogs) chris@552: chris@552: void Bela_messageHook(const char *source, const char *symbol, int argc, t_atom *argv){ chris@552: if(strcmp(source, "bela_setDigital") == 0){ chris@552: // symbol is the direction, argv[0] is the channel, argv[1] (optional) chris@552: // is signal("sig" or "~") or message("message", default) rate chris@552: bool isMessageRate = true; // defaults to message rate chris@552: bool direction = 0; // initialize it just to avoid the compiler's warning chris@552: bool disable = false; chris@552: if(strcmp(symbol, "in") == 0){ chris@552: direction = INPUT; chris@552: } else if(strcmp(symbol, "out") == 0){ chris@552: direction = OUTPUT; chris@552: } else if(strcmp(symbol, "disable") == 0){ chris@552: disable = true; chris@552: } else { chris@552: return; chris@552: } chris@552: if(argc == 0){ chris@552: return; chris@552: } else if (libpd_is_float(&argv[0]) == false){ chris@552: return; chris@552: } chris@552: int channel = libpd_get_float(&argv[0]) - LIBPD_DIGITAL_OFFSET; chris@552: if(disable == true){ chris@552: dcm.unmanage(channel); chris@552: return; chris@552: } chris@552: if(argc >= 2){ chris@552: t_atom* a = &argv[1]; chris@552: if(libpd_is_symbol(a)){ chris@552: char *s = libpd_get_symbol(a); chris@552: if(strcmp(s, "~") == 0 || strncmp(s, "sig", 3) == 0){ chris@552: isMessageRate = false; chris@552: } chris@552: } chris@552: } chris@552: dcm.manage(channel, direction, isMessageRate); chris@552: } chris@552: } chris@552: chris@552: void Bela_floatHook(const char *source, float value){ chris@552: chris@552: /* chris@552: * MODIFICATION chris@552: * ------------ chris@552: * Parse float sent to receiver 'tremoloRate' and assign it to a global variable chris@552: * N.B. When using libpd receiver names need to be registered (see setup() function below) chris@552: */ chris@552: if(strncmp(source, "tremoloRate", 11) == 0){ chris@552: gTremoloRate = value; chris@552: } chris@552: chris@552: /*********/ chris@552: chris@552: // let's make this as optimized as possible for built-in digital Out parsing chris@552: // the built-in digital receivers are of the form "bela_digitalOutXX" where XX is between 11 and 26 chris@552: static int prefixLength = 15; // strlen("bela_digitalOut") chris@552: if(strncmp(source, "bela_digitalOut", prefixLength)==0){ chris@552: if(source[prefixLength] != 0){ //the two ifs are used instead of if(strlen(source) >= prefixLength+2) chris@552: if(source[prefixLength + 1] != 0){ chris@552: // quickly convert the suffix to integer, assuming they are numbers, avoiding to call atoi chris@552: int receiver = ((source[prefixLength] - 48) * 10); chris@552: receiver += (source[prefixLength+1] - 48); chris@552: unsigned int channel = receiver - 11; // go back to the actual Bela digital channel number chris@552: if(channel < 16){ //16 is the hardcoded value for the number of digital channels chris@552: dcm.setValue(channel, value); chris@552: } chris@552: } chris@552: } chris@552: } chris@552: } chris@552: chris@552: char receiverNames[16][21]={ chris@552: {"bela_digitalIn11"},{"bela_digitalIn12"},{"bela_digitalIn13"},{"bela_digitalIn14"},{"bela_digitalIn15"}, chris@552: {"bela_digitalIn16"},{"bela_digitalIn17"},{"bela_digitalIn18"},{"bela_digitalIn19"},{"bela_digitalIn20"}, chris@552: {"bela_digitalIn21"},{"bela_digitalIn22"},{"bela_digitalIn23"},{"bela_digitalIn24"},{"bela_digitalIn25"}, chris@552: {"bela_digitalIn26"} chris@552: }; chris@552: chris@552: static unsigned int gAnalogChannelsInUse; chris@552: static unsigned int gLibpdBlockSize; chris@552: // 2 audio + (up to)8 analog + (up to) 16 digital + 4 scope outputs chris@552: static const unsigned int gChannelsInUse = 30; chris@552: //static const unsigned int gFirstAudioChannel = 0; chris@552: static const unsigned int gFirstAnalogChannel = 2; chris@552: static const unsigned int gFirstDigitalChannel = 10; chris@552: static const unsigned int gFirstScopeChannel = 26; chris@552: chris@552: Scope scope; chris@552: unsigned int gScopeChannelsInUse = 4; chris@552: float* gScopeOut; chris@552: chris@552: bool setup(BelaContext *context, void *userData) chris@552: { chris@552: chris@552: /* chris@552: * MODIFICATION chris@552: * ------------ chris@552: * Initialise variables for tremolo effect chris@552: */ chris@552: chris@552: gInverseSampleRate = 1.0 / context->audioSampleRate; chris@552: gPhase = 0.0; chris@552: chris@552: /*********/ chris@552: chris@552: scope.setup(gScopeChannelsInUse, context->audioSampleRate); chris@552: gScopeOut = new float[gScopeChannelsInUse]; chris@552: chris@552: // Check first of all if file exists. Will actually open it later. chris@552: char file[] = "_main.pd"; chris@552: char folder[] = "./"; chris@552: unsigned int strSize = strlen(file) + strlen(folder) + 1; chris@552: char* str = (char*)malloc(sizeof(char) * strSize); chris@552: snprintf(str, strSize, "%s%s", folder, file); chris@552: if(access(str, F_OK) == -1 ) { chris@552: printf("Error file %s/%s not found. The %s file should be your main patch.\n", folder, file, file); chris@552: return false; chris@552: } chris@552: if(context->analogInChannels != context->analogOutChannels || chris@552: context->audioInChannels != context->audioOutChannels){ chris@552: printf("This project requires the number of inputs and the number of outputs to be the same\n"); chris@552: return false; chris@552: } chris@552: // analog setup chris@552: gAnalogChannelsInUse = context->analogInChannels; chris@552: chris@552: // digital setup chris@552: dcm.setCallback(sendDigitalMessage); chris@552: if(context->digitalChannels > 0){ chris@552: for(unsigned int ch = 0; ch < context->digitalChannels; ++ch){ chris@552: dcm.setCallbackArgument(ch, receiverNames[ch]); chris@552: } chris@552: } chris@552: chris@552: midi.readFrom(0); chris@552: midi.writeTo(0); chris@552: #ifdef PARSE_MIDI chris@552: midi.enableParser(true); chris@552: #else chris@552: midi.enableParser(false); chris@552: #endif /* PARSE_MIDI */ chris@552: // udpServer.bindToPort(1234); chris@552: chris@552: gLibpdBlockSize = libpd_blocksize(); chris@552: // check that we are not running with a blocksize smaller than gLibPdBlockSize chris@552: // We could still make it work, but the load would be executed unevenly between calls to render chris@552: if(context->audioFrames < gLibpdBlockSize){ chris@552: fprintf(stderr, "Error: minimum block size must be %d\n", gLibpdBlockSize); chris@552: return false; chris@552: } chris@552: // set hooks before calling libpd_init chris@552: libpd_set_printhook(Bela_printHook); chris@552: libpd_set_floathook(Bela_floatHook); chris@552: libpd_set_messagehook(Bela_messageHook); chris@552: libpd_set_noteonhook(pdnoteon); chris@552: //TODO: add hooks for other midi events and generate MIDI output appropriately chris@552: libpd_init(); chris@552: //TODO: ideally, we would analyse the ASCII of the patch file and find out which in/outs to use chris@552: libpd_init_audio(gChannelsInUse, gChannelsInUse, context->audioSampleRate); chris@552: gInBuf = libpd_get_sys_soundin(); chris@552: gOutBuf = libpd_get_sys_soundout(); chris@552: chris@552: libpd_start_message(1); // one entry in list chris@552: libpd_add_float(1.0f); chris@552: libpd_finish_message("pd", "dsp"); chris@552: chris@552: gBufLength = max(gLibpdBlockSize, context->audioFrames); chris@552: chris@552: chris@552: // bind your receivers here chris@552: libpd_bind("bela_digitalOut11"); chris@552: libpd_bind("bela_digitalOut12"); chris@552: libpd_bind("bela_digitalOut13"); chris@552: libpd_bind("bela_digitalOut14"); chris@552: libpd_bind("bela_digitalOut15"); chris@552: libpd_bind("bela_digitalOut16"); chris@552: libpd_bind("bela_digitalOut17"); chris@552: libpd_bind("bela_digitalOut18"); chris@552: libpd_bind("bela_digitalOut19"); chris@552: libpd_bind("bela_digitalOut20"); chris@552: libpd_bind("bela_digitalOut21"); chris@552: libpd_bind("bela_digitalOut22"); chris@552: libpd_bind("bela_digitalOut23"); chris@552: libpd_bind("bela_digitalOut24"); chris@552: libpd_bind("bela_digitalOut25"); chris@552: libpd_bind("bela_digitalOut26"); chris@552: libpd_bind("bela_setDigital"); chris@552: /* chris@552: * MODIFICATION chris@552: * ------------ chris@552: * Bind an additional receiver for the tremoloRate parameter chris@552: */ chris@552: libpd_bind("tremoloRate"); chris@552: /*********/ chris@552: chris@552: // open patch [; pd open file folder( chris@552: void* patch = libpd_openfile(file, folder); chris@552: if(patch == NULL){ chris@552: printf("Error: file %s/%s is corrupted.\n", folder, file); chris@552: return false; chris@552: } chris@552: return true; chris@552: } chris@552: chris@552: // render() is called regularly at the highest priority by the audio engine. chris@552: // Input and output are given from the audio hardware and the other chris@552: // ADCs and DACs (if available). If only audio is available, numMatrixFrames chris@552: // will be 0. chris@552: chris@552: void render(BelaContext *context, void *userData) chris@552: { chris@552: int num; chris@552: // the safest thread-safe option to handle MIDI input is to process the MIDI buffer chris@552: // from the audio thread. chris@552: #ifdef PARSE_MIDI chris@552: while((num = midi.getParser()->numAvailableMessages()) > 0){ chris@552: static MidiChannelMessage message; chris@552: message = midi.getParser()->getNextChannelMessage(); chris@552: //message.prettyPrint(); // use this to print beautified message (channel, data bytes) chris@552: switch(message.getType()){ chris@552: case kmmNoteOn: chris@552: { chris@552: int noteNumber = message.getDataByte(0); chris@552: int velocity = message.getDataByte(1); chris@552: int channel = message.getChannel(); chris@552: libpd_noteon(channel, noteNumber, velocity); chris@552: break; chris@552: } chris@552: case kmmNoteOff: chris@552: { chris@552: /* PureData does not seem to handle noteoff messages as per the MIDI specs, chris@552: * so that the noteoff velocity is ignored. Here we convert them to noteon chris@552: * with a velocity of 0. chris@552: */ chris@552: int noteNumber = message.getDataByte(0); chris@552: // int velocity = message.getDataByte(1); // would be ignored by Pd chris@552: int channel = message.getChannel(); chris@552: libpd_noteon(channel, noteNumber, 0); chris@552: break; chris@552: } chris@552: case kmmControlChange: chris@552: { chris@552: int channel = message.getChannel(); chris@552: int controller = message.getDataByte(0); chris@552: int value = message.getDataByte(1); chris@552: libpd_controlchange(channel, controller, value); chris@552: break; chris@552: } chris@552: case kmmProgramChange: chris@552: { chris@552: int channel = message.getChannel(); chris@552: int program = message.getDataByte(0); chris@552: libpd_programchange(channel, program); chris@552: break; chris@552: } chris@552: case kmmPolyphonicKeyPressure: chris@552: { chris@552: int channel = message.getChannel(); chris@552: int pitch = message.getDataByte(0); chris@552: int value = message.getDataByte(1); chris@552: libpd_polyaftertouch(channel, pitch, value); chris@552: break; chris@552: } chris@552: case kmmChannelPressure: chris@552: { chris@552: int channel = message.getChannel(); chris@552: int value = message.getDataByte(0); chris@552: libpd_aftertouch(channel, value); chris@552: break; chris@552: } chris@552: case kmmPitchBend: chris@552: { chris@552: int channel = message.getChannel(); chris@552: int value = ((message.getDataByte(1) << 7)| message.getDataByte(0)) - 8192; chris@552: libpd_pitchbend(channel, value); chris@552: break; chris@552: } chris@552: case kmmNone: chris@552: case kmmAny: chris@552: break; chris@552: } chris@552: } chris@552: #else chris@552: int input; chris@552: while((input = midi.getInput()) >= 0){ chris@552: libpd_midibyte(0, input); chris@552: } chris@552: #endif /* PARSE_MIDI */ chris@552: chris@552: static unsigned int numberOfPdBlocksToProcess = gBufLength / gLibpdBlockSize; chris@552: chris@552: for(unsigned int tick = 0; tick < numberOfPdBlocksToProcess; ++tick){ chris@552: unsigned int audioFrameBase = gLibpdBlockSize * tick; chris@552: unsigned int j; chris@552: unsigned int k; chris@552: float* p0; chris@552: float* p1; chris@552: for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) { chris@552: for (k = 0, p1 = p0; k < context->audioInChannels; k++, p1 += gLibpdBlockSize) { chris@552: *p1 = audioRead(context, audioFrameBase + j, k); chris@552: } chris@552: } chris@552: // then analogs chris@552: // this loop resamples by ZOH, as needed, using m chris@552: if(context->analogInChannels == 8 ){ //hold the value for two frames chris@552: for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) { chris@552: for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; ++k, p1 += gLibpdBlockSize) { chris@552: unsigned int analogFrame = (audioFrameBase + j) / 2; chris@552: *p1 = analogRead(context, analogFrame, k); chris@552: } chris@552: } chris@552: } else if(context->analogInChannels == 4){ //write every frame chris@552: for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) { chris@552: for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; ++k, p1 += gLibpdBlockSize) { chris@552: unsigned int analogFrame = audioFrameBase + j; chris@552: *p1 = analogRead(context, analogFrame, k); chris@552: } chris@552: } chris@552: } else if(context->analogInChannels == 2){ //drop every other frame chris@552: for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) { chris@552: for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; ++k, p1 += gLibpdBlockSize) { chris@552: unsigned int analogFrame = (audioFrameBase + j) * 2; chris@552: *p1 = analogRead(context, analogFrame, k); chris@552: } chris@552: } chris@552: } chris@552: chris@552: // Bela digital input chris@552: // note: in multiple places below we assume that the number of digitals is same as number of audio chris@552: // digital in at message-rate chris@552: dcm.processInput(&context->digital[audioFrameBase], gLibpdBlockSize); chris@552: chris@552: // digital in at signal-rate chris@552: for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) { chris@552: unsigned int digitalFrame = audioFrameBase + j; chris@552: for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstDigitalChannel; chris@552: k < 16; ++k, p1 += gLibpdBlockSize) { chris@552: if(dcm.isSignalRate(k) && dcm.isInput(k)){ // only process input channels that are handled at signal rate chris@552: *p1 = digitalRead(context, digitalFrame, k); chris@552: } chris@552: } chris@552: } chris@552: chris@552: libpd_process_sys(); // process the block chris@552: chris@552: //digital out chris@552: // digital out at signal-rate chris@552: for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) { chris@552: unsigned int digitalFrame = (audioFrameBase + j); chris@552: for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstDigitalChannel; chris@552: k < context->digitalChannels; k++, p1 += gLibpdBlockSize) { chris@552: if(dcm.isSignalRate(k) && dcm.isOutput(k)){ // only process output channels that are handled at signal rate chris@552: digitalWriteOnce(context, digitalFrame, k, *p1 > 0.5); chris@552: } chris@552: } chris@552: } chris@552: chris@552: // digital out at message-rate chris@552: dcm.processOutput(&context->digital[audioFrameBase], gLibpdBlockSize); chris@552: chris@552: //audio chris@552: for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j++, p0++) { chris@552: chris@552: /* chris@552: * MODIFICATION chris@552: * ------------ chris@552: * Processing for tremolo effect while writing libpd output to Bela output buffer chris@552: */ chris@552: chris@552: // Generate a sinewave with frequency set by gTremoloRate chris@552: // and amplitude from -0.5 to 0.5 chris@552: float lfo = sinf(gPhase) * 0.5; chris@552: // Keep track and wrap the phase of the sinewave chris@552: gPhase += 2.0 * M_PI * gTremoloRate * gInverseSampleRate; chris@552: if(gPhase > 2.0 * M_PI) chris@552: gPhase -= 2.0 * M_PI; chris@552: chris@552: /*********/ chris@552: chris@552: for (k = 0, p1 = p0; k < context->audioOutChannels; k++, p1 += gLibpdBlockSize) { chris@552: audioWrite(context, audioFrameBase + j, k, *p1 * lfo); // MODIFICATION (* lfo) chris@552: } chris@552: } chris@552: chris@552: //scope chris@552: for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) { chris@552: for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstScopeChannel; k < gScopeChannelsInUse; k++, p1 += gLibpdBlockSize) { chris@552: gScopeOut[k] = *p1; chris@552: } chris@552: scope.log(gScopeOut[0], gScopeOut[1], gScopeOut[2], gScopeOut[3]); chris@552: } chris@552: chris@552: chris@552: //analog chris@552: if(context->analogOutChannels == 8){ chris@552: for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j += 2, p0 += 2) { //write every two frames chris@552: unsigned int analogFrame = (audioFrameBase + j) / 2; chris@552: for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; k++, p1 += gLibpdBlockSize) { chris@552: analogWriteOnce(context, analogFrame, k, *p1); chris@552: } chris@552: } chris@552: } else if(context->analogOutChannels == 4){ //write every frame chris@552: for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) { chris@552: unsigned int analogFrame = (audioFrameBase + j); chris@552: for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; k++, p1 += gLibpdBlockSize) { chris@552: analogWriteOnce(context, analogFrame, k, *p1); chris@552: } chris@552: } chris@552: } else if(context->analogOutChannels == 2){ //write every frame twice chris@552: for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j++, p0++) { chris@552: for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; k++, p1 += gLibpdBlockSize) { chris@552: int analogFrame = audioFrameBase * 2 + j * 2; chris@552: analogWriteOnce(context, analogFrame, k, *p1); chris@552: analogWriteOnce(context, analogFrame + 1, k, *p1); chris@552: } chris@552: } chris@552: } chris@552: } chris@552: } chris@552: chris@552: // cleanup() is called once at the end, after the audio has stopped. chris@552: // Release any resources that were allocated in setup(). chris@552: chris@552: void cleanup(BelaContext *context, void *userData) chris@552: { chris@552: delete [] gScopeOut; chris@552: }