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