Mercurial > hg > beaglert
comparison examples/10-Instruments/d-box/render.cpp @ 464:8fcfbfb32aa0 prerelease
Examples reorder with subdirectories. Added header to each project. Moved Doxygen to bottom of render.cpp.
author | Robert Jack <robert.h.jack@gmail.com> |
---|---|
date | Mon, 20 Jun 2016 16:20:38 +0100 |
parents | |
children | 8f8809c77dda |
comparison
equal
deleted
inserted
replaced
463:c47709e8b5c9 | 464:8fcfbfb32aa0 |
---|---|
1 /* | |
2 * render.cpp | |
3 * | |
4 * Created on: May 28, 2014 | |
5 * Author: Victor Zappi | |
6 */ | |
7 | |
8 #include <Bela.h> | |
9 #include <PRU.h> | |
10 | |
11 #include "StatusLED.h" | |
12 #include "config.h" | |
13 #include "OscillatorBank.h" | |
14 #include "FeedbackOscillator.h" | |
15 #include "ADSR.h" | |
16 #include "FIRfilter.h" | |
17 #include <assert.h> | |
18 #include <cmath> | |
19 #include <vector> | |
20 | |
21 #undef DBOX_CAPE_TEST | |
22 | |
23 // Mappings from pin numbers on PCB to actual DAC channels | |
24 // This gives the DAC and ADC connectors the same effective pinout | |
25 // Update June 2016: this is no longer needed in the latest Bela | |
26 // release, but is kept here for convenience: it used to be | |
27 // 6 4 2 0 1 3 5 7 for the DAC pins | |
28 #define DAC_PIN0 0 | |
29 #define DAC_PIN1 1 | |
30 #define DAC_PIN2 2 | |
31 #define DAC_PIN3 3 | |
32 #define DAC_PIN4 4 | |
33 #define DAC_PIN5 5 | |
34 #define DAC_PIN6 6 | |
35 #define DAC_PIN7 7 | |
36 | |
37 #define ADC_PIN0 0 | |
38 #define ADC_PIN1 1 | |
39 #define ADC_PIN2 2 | |
40 #define ADC_PIN3 3 | |
41 #define ADC_PIN4 4 | |
42 #define ADC_PIN5 5 | |
43 #define ADC_PIN6 6 | |
44 #define ADC_PIN7 7 | |
45 | |
46 #define N_OCT 4.0 // maximum number of octaves on sensor 1 | |
47 | |
48 extern vector<OscillatorBank*> gOscBanks; | |
49 extern int gCurrentOscBank; | |
50 extern int gNextOscBank; | |
51 extern PRU *gPRU; | |
52 extern StatusLED gStatusLED; | |
53 extern bool gIsLoading; | |
54 extern bool gAudioIn; | |
55 | |
56 float *gOscillatorBuffer1, *gOscillatorBuffer2; | |
57 float *gOscillatorBufferRead, *gOscillatorBufferWrite; | |
58 int gOscillatorBufferReadPointer = 0; | |
59 int gOscillatorBufferReadCurrentSize = 0; | |
60 int gOscillatorBufferWriteCurrentSize = 0; | |
61 bool gOscillatorNeedsRender = false; | |
62 | |
63 int gMatrixSampleCount = 0; // How many samples have elapsed on the matrix | |
64 | |
65 // Wavetable which changes in response to an oscillator | |
66 float *gDynamicWavetable; | |
67 int gDynamicWavetableLength; | |
68 bool gDynamicWavetableNeedsRender = false; | |
69 | |
70 // These variables handle the hysteresis oscillator used for setting the playback speed | |
71 bool gSpeedHysteresisOscillatorRising = false; | |
72 int gSpeedHysteresisLastTrigger = 0; | |
73 | |
74 // These variables handle the feedback oscillator used for controlling the wavetable | |
75 FeedbackOscillator gFeedbackOscillator; | |
76 float *gFeedbackOscillatorTable; | |
77 int gFeedbackOscillatorTableLength; | |
78 | |
79 // This comes from sensor.cpp where it records the most recent touch location on | |
80 // sensor 0. | |
81 extern float gSensor0LatestTouchPos; | |
82 extern int gSensor0LatestTouchNum; | |
83 float gPitchLatestInput = 0; | |
84 | |
85 extern float gSensor1LatestTouchPos[]; | |
86 //extern float gSensor1LatestTouchSizes[]; | |
87 extern int gSensor1LatestTouchCount; | |
88 extern int gSensor1LatestTouchIndex; | |
89 int gSensor1LastTouchIndex = -1; | |
90 int gSensor1InputDelayCounter = -1; | |
91 int gSensor1InputIndex = 0; | |
92 float gSensor1MatrixTouchPos[5] = {0}; | |
93 | |
94 // FSR value from matrix input | |
95 extern int gLastFSRValue; | |
96 | |
97 // Loop points from matrix input 4 | |
98 const int gLoopPointsInputBufferSize = 256; | |
99 float gLoopPointsInputBuffer[gLoopPointsInputBufferSize]; | |
100 int gLoopPointsInputBufferPointer = 0; | |
101 float gLoopPointMin = 0, gLoopPointMax = 0; | |
102 | |
103 // multiplier to activate or mute audio in | |
104 int audioInStatus = 0; | |
105 | |
106 // xenomai timer | |
107 SRTIME prevChangeNs = 0; | |
108 | |
109 // pitch vars | |
110 float octaveSplitter; | |
111 float semitones[((int)N_OCT*12)+1]; | |
112 float deltaTouch = 0; | |
113 float deltaWeightP = 0.5 / 65536.0; | |
114 float deltaWeightI = 0.0005 / 65536.0; | |
115 | |
116 // filter vars | |
117 ne10_fir_instance_f32_t filter[2]; | |
118 ne10_float32_t *filterIn[2]; | |
119 ne10_float32_t *filterOut[2]; | |
120 ne10_uint32_t blockSize; | |
121 ne10_float32_t *filterState[2]; | |
122 ne10_float32_t prevFiltered[2]; | |
123 int filterGain = 80; | |
124 ADSR PeakBurst[2]; | |
125 float peak[2]; | |
126 float peakThresh = 0.2; | |
127 | |
128 // Tasks for lower-priority calculation | |
129 AuxiliaryTask gMediumPriorityRender, gLowPriorityRender; | |
130 | |
131 | |
132 extern "C" { | |
133 // Function prototype for ARM assembly implementation of oscillator bank | |
134 void oscillator_bank_neon(int numAudioFrames, float *audioOut, | |
135 int activePartialNum, int lookupTableSize, | |
136 float *phases, float *frequencies, float *amplitudes, | |
137 float *freqDerivatives, float *ampDerivatives, | |
138 float *lookupTable); | |
139 | |
140 void wavetable_interpolate_neon(int numSamplesIn, int numSamplesOut, | |
141 float *tableIn, float *tableOut); | |
142 } | |
143 | |
144 void wavetable_interpolate(int numSamplesIn, int numSamplesOut, | |
145 float *tableIn, float *tableOut, | |
146 float *sineTable, float sineMix); | |
147 | |
148 inline float hysteresis_oscillator(float input, float risingThreshold, | |
149 float fallingThreshold, bool *rising); | |
150 | |
151 void render_medium_prio(); | |
152 void render_low_prio(); | |
153 | |
154 #ifdef DBOX_CAPE_TEST | |
155 void render_capetest(int numMatrixFrames, int numAudioFrames, float *audioIn, float *audioOut, | |
156 uint16_t *matrixIn, uint16_t *matrixOut); | |
157 #endif | |
158 | |
159 bool setup(BelaContext *context, void *userData) { | |
160 int oscBankHopSize = *(int *)userData; | |
161 | |
162 if(context->analogChannels != 8) { | |
163 printf("Error: D-Box needs matrix enabled with 8 channels.\n"); | |
164 return false; | |
165 } | |
166 | |
167 // Allocate two buffers for rendering oscillator bank samples | |
168 // One will be used for writing in the background while the other is used for reading | |
169 // on the audio thread. 8-byte alignment needed for the NEON code. | |
170 if(posix_memalign((void **)&gOscillatorBuffer1, 8, oscBankHopSize * context->audioChannels * sizeof(float))) { | |
171 printf("Error allocating render buffers\n"); | |
172 return false; | |
173 } | |
174 if(posix_memalign((void **)&gOscillatorBuffer2, 8, oscBankHopSize * context->audioChannels * sizeof(float))) { | |
175 printf("Error allocating render buffers\n"); | |
176 return false; | |
177 } | |
178 gOscillatorBufferWrite = gOscillatorBuffer1; | |
179 gOscillatorBufferRead = gOscillatorBuffer2; | |
180 | |
181 memset(gOscillatorBuffer1, 0, oscBankHopSize * context->audioChannels * sizeof(float)); | |
182 memset(gOscillatorBuffer2, 0, oscBankHopSize * context->audioChannels * sizeof(float)); | |
183 | |
184 // Initialise the dynamic wavetable used by the oscillator bank | |
185 // It should match the size of the static one already allocated in the OscillatorBank object | |
186 // Don't forget a guard point at the end of the table | |
187 gDynamicWavetableLength = gOscBanks[gCurrentOscBank]->lookupTableSize; | |
188 if(posix_memalign((void **)&gDynamicWavetable, 8, (gDynamicWavetableLength + 1) * sizeof(float))) { | |
189 printf("Error allocating wavetable\n"); | |
190 return false; | |
191 } | |
192 | |
193 gFeedbackOscillator.initialise(8192, 10.0, context->analogSampleRate); | |
194 | |
195 for(int n = 0; n < gDynamicWavetableLength + 1; n++) | |
196 gDynamicWavetable[n] = 0; | |
197 | |
198 // pitch | |
199 float midPos = 0.5; | |
200 octaveSplitter = 1.0 / N_OCT; | |
201 int numOfSemi = 12*N_OCT; | |
202 int middleSemitone = 12*N_OCT/2; | |
203 int lastSemitone = middleSemitone+numOfSemi/2; | |
204 float inc = 1.0 / (N_OCT*12.0); | |
205 int i = -1; | |
206 for(int semi=middleSemitone; semi<=lastSemitone; semi++) | |
207 semitones[semi] = ( midPos + (++i)*inc) + 0.5; | |
208 i = 0; | |
209 for(int semi=middleSemitone-1; semi>=0; semi--) | |
210 semitones[semi] = ( midPos - (++i)*inc) + 0.5; | |
211 | |
212 if(gAudioIn) | |
213 audioInStatus = 1; | |
214 | |
215 // filter | |
216 blockSize = context->audioFrames; | |
217 filterState[0] = (ne10_float32_t *) NE10_MALLOC ((FILTER_TAP_NUM+blockSize-1) * sizeof (ne10_float32_t)); | |
218 filterState[1] = (ne10_float32_t *) NE10_MALLOC ((FILTER_TAP_NUM+blockSize-1) * sizeof (ne10_float32_t)); | |
219 filterIn[0] = (ne10_float32_t *) NE10_MALLOC (blockSize * sizeof (ne10_float32_t)); | |
220 filterIn[1] = (ne10_float32_t *) NE10_MALLOC (blockSize * sizeof (ne10_float32_t)); | |
221 filterOut[0] = (ne10_float32_t *) NE10_MALLOC (blockSize * sizeof (ne10_float32_t)); | |
222 filterOut[1] = (ne10_float32_t *) NE10_MALLOC (blockSize * sizeof (ne10_float32_t)); | |
223 ne10_fir_init_float(&filter[0], FILTER_TAP_NUM, filterTaps, filterState[0], blockSize); | |
224 ne10_fir_init_float(&filter[1], FILTER_TAP_NUM, filterTaps, filterState[1], blockSize); | |
225 | |
226 // peak outputs | |
227 PeakBurst[0].setAttackRate(.00001 * context->analogSampleRate); | |
228 PeakBurst[1].setAttackRate(.00001 * context->analogSampleRate); | |
229 PeakBurst[0].setDecayRate(.5 * context->analogSampleRate); | |
230 PeakBurst[1].setDecayRate(.5 * context->analogSampleRate); | |
231 PeakBurst[0].setSustainLevel(0.0); | |
232 PeakBurst[1].setSustainLevel(0.0); | |
233 | |
234 // Initialise auxiliary tasks | |
235 if((gMediumPriorityRender = Bela_createAuxiliaryTask(&render_medium_prio, BELA_AUDIO_PRIORITY - 10, "dbox-calculation-medium")) == 0) | |
236 return false; | |
237 if((gLowPriorityRender = Bela_createAuxiliaryTask(&render_low_prio, BELA_AUDIO_PRIORITY - 15, "dbox-calculation-low")) == 0) | |
238 return false; | |
239 | |
240 return true; | |
241 } | |
242 | |
243 void render(BelaContext *context, void *userData) | |
244 { | |
245 #ifdef DBOX_CAPE_TEST | |
246 render_capetest(numMatrixFrames, numAudioFrames, audioIn, audioOut, matrixIn, matrixOut); | |
247 #else | |
248 if(gOscBanks[gCurrentOscBank]->state==bank_toreset) | |
249 gOscBanks[gCurrentOscBank]->resetOscillators(); | |
250 | |
251 if(gOscBanks[gCurrentOscBank]->state==bank_playing) | |
252 { | |
253 assert(context->audioChannels == 2); | |
254 | |
255 #ifdef OLD_OSCBANK | |
256 memset(audioOut, 0, numAudioFrames * * sizeof(float)); | |
257 | |
258 /* Render the oscillator bank. The oscillator bank function is written in NEON assembly | |
259 * and it strips out all extra checks, so find out in advance whether we can render a whole | |
260 * block or whether the frame will increment in the middle of this buffer. | |
261 */ | |
262 | |
263 int framesRemaining = numAudioFrames; | |
264 float *audioOutWithOffset = audioOut; | |
265 | |
266 while(framesRemaining > 0) { | |
267 if(gOscBanks[gCurrentOscBank]->hopCounter >= framesRemaining) { | |
268 /* More frames left in this hop than we need this time. Render and finish */ | |
269 oscillator_bank_neon(framesRemaining, audioOutWithOffset, | |
270 gOscBanks[gCurrentOscBank]->actPartNum, gOscBanks[gCurrentOscBank]->lookupTableSize, | |
271 gOscBanks[gCurrentOscBank]->oscillatorPhases, gOscBanks[gCurrentOscBank]->oscillatorNormFrequencies, | |
272 gOscBanks[gCurrentOscBank]->oscillatorAmplitudes, | |
273 gOscBanks[gCurrentOscBank]->oscillatorNormFreqDerivatives, | |
274 gOscBanks[gCurrentOscBank]->oscillatorAmplitudeDerivatives, | |
275 gDynamicWavetable/*gOscBanks[gCurrentOscBank]->lookupTable*/); | |
276 gOscBanks[gCurrentOscBank]->hopCounter -= framesRemaining; | |
277 if(gOscBanks[gCurrentOscBank]->hopCounter <= 0) | |
278 gOscBanks[gCurrentOscBank]->nextHop(); | |
279 framesRemaining = 0; | |
280 } | |
281 else { | |
282 /* More frames to render than are left in this hop. Render and decrement the | |
283 * number of remaining frames; then advance to the next oscillator frame. | |
284 */ | |
285 oscillator_bank_neon(gOscBanks[gCurrentOscBank]->hopCounter, audioOutWithOffset, | |
286 gOscBanks[gCurrentOscBank]->actPartNum, gOscBanks[gCurrentOscBank]->lookupTableSize, | |
287 gOscBanks[gCurrentOscBank]->oscillatorPhases, gOscBanks[gCurrentOscBank]->oscillatorNormFrequencies, | |
288 gOscBanks[gCurrentOscBank]->oscillatorAmplitudes, | |
289 gOscBanks[gCurrentOscBank]->oscillatorNormFreqDerivatives, | |
290 gOscBanks[gCurrentOscBank]->oscillatorAmplitudeDerivatives, | |
291 gDynamicWavetable/*gOscBanks[gCurrentOscBank]->lookupTable*/); | |
292 framesRemaining -= gOscBanks[gCurrentOscBank]->hopCounter; | |
293 audioOutWithOffset += * gOscBanks[gCurrentOscBank]->hopCounter; | |
294 gOscBanks[gCurrentOscBank]->sampleCount += gOscBanks[gCurrentOscBank]->hopCounter; | |
295 gOscBanks[gCurrentOscBank]->nextHop(); | |
296 } | |
297 } | |
298 #else | |
299 for(unsigned int n = 0; n < context->audioFrames; n++) { | |
300 context->audioOut[2*n] = gOscillatorBufferRead[gOscillatorBufferReadPointer++]+context->audioIn[2*n]*audioInStatus; | |
301 context->audioOut[2*n + 1] = gOscillatorBufferRead[gOscillatorBufferReadPointer++]+context->audioIn[2*n+1]*audioInStatus; | |
302 | |
303 filterIn[0][n] = fabs(context->audioIn[2*n]); // rectify for peak detection in 1 | |
304 filterIn[1][n] = fabs(context->audioIn[2*n+1]); // rectify for peak detection in 2 | |
305 | |
306 /* FIXME why doesn't this work? */ | |
307 /* | |
308 if(gOscillatorBufferReadPointer == gOscillatorBufferCurrentSize/2) { | |
309 gOscillatorNeedsRender = true; | |
310 scheduleAuxiliaryTask(gLowPriorityRender); | |
311 } */ | |
312 | |
313 if(gOscillatorBufferReadPointer >= gOscillatorBufferReadCurrentSize) { | |
314 // Finished reading from the buffer: swap to the next buffer | |
315 if(gOscillatorBufferRead == gOscillatorBuffer1) { | |
316 gOscillatorBufferRead = gOscillatorBuffer2; | |
317 gOscillatorBufferWrite = gOscillatorBuffer1; | |
318 } | |
319 else { | |
320 gOscillatorBufferRead = gOscillatorBuffer1; | |
321 gOscillatorBufferWrite = gOscillatorBuffer2; | |
322 } | |
323 | |
324 // New buffer size is whatever finished writing last hop | |
325 gOscillatorBufferReadCurrentSize = gOscillatorBufferWriteCurrentSize; | |
326 gOscillatorBufferReadPointer = 0; | |
327 | |
328 gOscillatorNeedsRender = true; | |
329 Bela_scheduleAuxiliaryTask(gMediumPriorityRender); | |
330 } | |
331 } | |
332 #endif | |
333 } | |
334 else | |
335 { | |
336 for(unsigned int n = 0; n < context->audioFrames; n++) { | |
337 context->audioOut[2*n] = context->audioIn[2*n]*audioInStatus; | |
338 context->audioOut[2*n + 1] = context->audioIn[2*n+1]*audioInStatus; | |
339 | |
340 filterIn[0][n] = fabs(context->audioIn[2*n]); // rectify for peak detection in 1 | |
341 filterIn[1][n] = fabs(context->audioIn[2*n+1]); // rectify for peak detection in 2 | |
342 } | |
343 } | |
344 | |
345 // low pass filter audio in 1 and 2 for peak detection | |
346 ne10_fir_float_neon(&filter[0], filterIn[0], filterOut[0], blockSize); | |
347 ne10_fir_float_neon(&filter[1], filterIn[1], filterOut[1], blockSize); | |
348 | |
349 for(unsigned int n = 0; n < context->analogFrames; n++) { | |
350 | |
351 | |
352 /* Matrix Out 0, In 0 | |
353 * | |
354 * CV loop | |
355 * Controls pitch of sound | |
356 */ | |
357 float touchPosInt = gSensor0LatestTouchPos; | |
358 if(touchPosInt < 0) touchPosInt = 0; | |
359 if(touchPosInt > 1.0) touchPosInt = 1.0; | |
360 context->analogOut[n*8 + DAC_PIN0] = touchPosInt; | |
361 | |
362 gPitchLatestInput = context->analogIn[n*8 + ADC_PIN0]; | |
363 | |
364 | |
365 /* Matrix Out 7 | |
366 * | |
367 * Loop feedback with Matrix In 0 | |
368 * Controls discreet pitch | |
369 */ | |
370 float deltaTarget = 0; | |
371 int semitoneIndex = 0; | |
372 if(gSensor0LatestTouchNum>0) | |
373 { | |
374 // current pitch is gPitchLatestInput, already retrieved | |
375 semitoneIndex = ( gPitchLatestInput * 12 * N_OCT )+0.5; // closest semitone | |
376 deltaTarget = (semitones[semitoneIndex]-gPitchLatestInput); // delta between pitch and target | |
377 deltaTouch += deltaTarget*(deltaWeightI); // update feedback [previous + current] | |
378 } | |
379 else | |
380 deltaTouch = 0; | |
381 | |
382 float nextOut = touchPosInt + deltaTarget*deltaWeightP + deltaTouch; // add feedback to touch -> next out | |
383 if(nextOut < 0) nextOut = 0; // clamp | |
384 if(nextOut > 1.0) nextOut = 1.0; // clamp | |
385 context->analogOut[n*8 + DAC_PIN7] = nextOut; // send next nextOut | |
386 | |
387 | |
388 /* | |
389 * Matrix Out 1, In 1 | |
390 * | |
391 * Hysteresis (comparator) oscillator | |
392 * Controls speed of playback | |
393 */ | |
394 bool wasRising = gSpeedHysteresisOscillatorRising; | |
395 context->analogOut[n*8 + DAC_PIN1] = hysteresis_oscillator(context->analogIn[n*8 + ADC_PIN1], 48000.0/65536.0, | |
396 16000.0/65536.0, &gSpeedHysteresisOscillatorRising); | |
397 | |
398 // Find interval of zero crossing | |
399 if(wasRising && !gSpeedHysteresisOscillatorRising) { | |
400 int interval = gMatrixSampleCount - gSpeedHysteresisLastTrigger; | |
401 | |
402 // Interval since last trigger will be the new hop size; calculate to set speed | |
403 if(interval < 1) | |
404 interval = 1; | |
405 //float speed = (float)gOscBanks[gCurrentOscBank]->getHopSize() / (float)interval; | |
406 float speed = 144.0 / interval; // Normalise to a fixed expected speed | |
407 gOscBanks[gCurrentOscBank]->setSpeed(speed); | |
408 | |
409 gSpeedHysteresisLastTrigger = gMatrixSampleCount; | |
410 } | |
411 | |
412 /* | |
413 * Matrix Out 2, In 2 | |
414 * | |
415 * Feedback (phase shift) oscillator | |
416 * Controls wavetable used for oscillator bank | |
417 */ | |
418 | |
419 int tableLength = gFeedbackOscillator.process(context->analogIn[n*8 + ADC_PIN2], &context->analogOut[n*8 + DAC_PIN2]); | |
420 if(tableLength != 0) { | |
421 gFeedbackOscillatorTableLength = tableLength; | |
422 gFeedbackOscillatorTable = gFeedbackOscillator.wavetable(); | |
423 gDynamicWavetableNeedsRender = true; | |
424 Bela_scheduleAuxiliaryTask(gLowPriorityRender); | |
425 } | |
426 | |
427 /* | |
428 * Matrix Out 3, In 3 | |
429 * | |
430 * CV loop with delay for time alignment | |
431 * Touch positions from sensor 1 | |
432 * Change every 32 samples (ca. 1.5 ms) | |
433 */ | |
434 volatile int touchCount = gSensor1LatestTouchCount; | |
435 if(touchCount == 0) | |
436 context->analogOut[n*8 + DAC_PIN3] = 0; | |
437 else { | |
438 int touchIndex = (gMatrixSampleCount >> 5) % touchCount; | |
439 context->analogOut[n*8 + DAC_PIN3] = gSensor1LatestTouchPos[touchIndex] * 56000.0f / 65536.0f; | |
440 if(touchIndex != gSensor1LastTouchIndex) { | |
441 // Just changed to a new touch output. Reset the counter. | |
442 // It will take 2*matrixFrames samples for this output to come back to the | |
443 // ADC input. But we also want to read near the end of the 32 sample block; | |
444 // let's say 24 samples into it. | |
445 | |
446 // FIXME this won't work for p > 2 | |
447 gSensor1InputDelayCounter = 24 + 2*context->analogFrames; | |
448 gSensor1InputIndex = touchIndex; | |
449 } | |
450 gSensor1LastTouchIndex = touchIndex; | |
451 } | |
452 | |
453 if(gSensor1InputDelayCounter-- >= 0 && touchCount > 0) { | |
454 gSensor1MatrixTouchPos[gSensor1InputIndex] = context->analogIn[n*8 + ADC_PIN3]; | |
455 } | |
456 | |
457 /* Matrix Out 4 | |
458 * | |
459 * Sensor 1 last pos | |
460 */ | |
461 touchPosInt = gSensor1LatestTouchPos[gSensor1LatestTouchIndex]; | |
462 if(touchPosInt < 0) touchPosInt = 0; | |
463 if(touchPosInt > 1.0) touchPosInt = 1.0; | |
464 context->analogOut[n*8 + DAC_PIN4] = touchPosInt; | |
465 | |
466 /* Matrix In 4 | |
467 * | |
468 * Loop points selector | |
469 */ | |
470 gLoopPointsInputBuffer[gLoopPointsInputBufferPointer++] = context->analogIn[n*8 + ADC_PIN4]; | |
471 if(gLoopPointsInputBufferPointer >= gLoopPointsInputBufferSize) { | |
472 // Find min and max values | |
473 float loopMax = 0, loopMin = 1.0; | |
474 for(int i = 0; i < gLoopPointsInputBufferSize; i++) { | |
475 if(gLoopPointsInputBuffer[i] < loopMin) | |
476 loopMin = gLoopPointsInputBuffer[i]; | |
477 if(gLoopPointsInputBuffer[i] > loopMax/* && gLoopPointsInputBuffer[i] != 65535*/) | |
478 loopMax = gLoopPointsInputBuffer[i]; | |
479 } | |
480 | |
481 if(loopMin >= loopMax) | |
482 loopMax = loopMin; | |
483 | |
484 gLoopPointMax = loopMax; | |
485 gLoopPointMin = loopMin; | |
486 gLoopPointsInputBufferPointer = 0; | |
487 } | |
488 | |
489 /* Matrix Out 5 | |
490 * | |
491 * Audio In 1 peak detection and peak burst output | |
492 */ | |
493 | |
494 filterOut[0][n*2+1] *= filterGain; | |
495 float burstOut = PeakBurst[0].getOutput(); | |
496 if( burstOut < 0.1) | |
497 { | |
498 if( (prevFiltered[0]>=peakThresh) && (prevFiltered[0]>=filterOut[0][n*2+1]) ) | |
499 { | |
500 peak[0] = prevFiltered[0]; | |
501 PeakBurst[0].gate(1); | |
502 } | |
503 } | |
504 | |
505 PeakBurst[0].process(1); | |
506 | |
507 float convAudio = burstOut*peak[0]; | |
508 context->analogOut[n*8 + DAC_PIN5] = convAudio; | |
509 prevFiltered[0] = filterOut[0][n*2+1]; | |
510 if(prevFiltered[0]>1) | |
511 prevFiltered[0] = 1; | |
512 | |
513 /* Matrix In 5 | |
514 * | |
515 * Dissonance, via changing frequency motion of partials | |
516 */ | |
517 float amount = (float)context->analogIn[n*8 + ADC_PIN5]; | |
518 gOscBanks[gCurrentOscBank]->freqMovement = 1.0 - amount; | |
519 | |
520 | |
521 | |
522 | |
523 /* Matrix Out 6 | |
524 * | |
525 * Audio In 2 peak detection and peak burst output | |
526 */ | |
527 | |
528 filterOut[1][n*2+1] *= filterGain; | |
529 burstOut = PeakBurst[1].getOutput(); | |
530 if( burstOut < 0.1) | |
531 { | |
532 if( (prevFiltered[1]>=peakThresh) && (prevFiltered[1]>=filterOut[1][n*2+1]) ) | |
533 { | |
534 peak[1] = prevFiltered[1]; | |
535 PeakBurst[1].gate(1); | |
536 } | |
537 } | |
538 | |
539 PeakBurst[1].process(1); | |
540 | |
541 convAudio = burstOut*peak[1]; | |
542 context->analogOut[n*8 + DAC_PIN6] = convAudio; | |
543 prevFiltered[1] = filterOut[1][n*2+1]; | |
544 if(prevFiltered[1]>1) | |
545 prevFiltered[1] = 1; | |
546 | |
547 /* Matrix In 6 | |
548 * | |
549 * Sound selector | |
550 */ | |
551 if(!gIsLoading) { | |
552 // Use hysteresis to avoid jumping back and forth between sounds | |
553 if(gOscBanks.size() > 1) { | |
554 float input = context->analogIn[n*8 + ADC_PIN6]; | |
555 const float hystValue = 16000.0 / 65536.0; | |
556 | |
557 float upHysteresisValue = ((gCurrentOscBank + 1) + hystValue) / gOscBanks.size(); | |
558 float downHysteresisValue = (gCurrentOscBank - hystValue) / gOscBanks.size(); | |
559 | |
560 if(input > upHysteresisValue || input < downHysteresisValue) { | |
561 gNextOscBank = input * gOscBanks.size(); | |
562 if(gNextOscBank < 0) | |
563 gNextOscBank = 0; | |
564 if((unsigned)gNextOscBank >= gOscBanks.size()) | |
565 gNextOscBank = gOscBanks.size() - 1; | |
566 } | |
567 } | |
568 } | |
569 | |
570 /* | |
571 * Matrix In 7 | |
572 * | |
573 * FSR from primary touch sensor | |
574 * Value ranges from 0-1799 | |
575 */ | |
576 gLastFSRValue = context->analogIn[n*8 + ADC_PIN7] * 1799.0; | |
577 //gLastFSRValue = 1799 - context->analogIn[n*8 + ADC_PIN7] * (1799.0 / 65535.0); | |
578 //dbox_printf("%i\n",gLastFSRValue); | |
579 | |
580 gMatrixSampleCount++; | |
581 } | |
582 | |
583 #endif /* DBOX_CAPE_TEST */ | |
584 } | |
585 | |
586 // Medium-priority render function used for audio hop calculations | |
587 void render_medium_prio() | |
588 { | |
589 | |
590 if(gOscillatorNeedsRender) { | |
591 gOscillatorNeedsRender = false; | |
592 | |
593 /* Render one frame into the write buffer */ | |
594 memset(gOscillatorBufferWrite, 0, gOscBanks[gCurrentOscBank]->hopCounter * 2 * sizeof(float)); /* assumes 2 audio channels */ | |
595 | |
596 oscillator_bank_neon(gOscBanks[gCurrentOscBank]->hopCounter, gOscillatorBufferWrite, | |
597 gOscBanks[gCurrentOscBank]->actPartNum, gOscBanks[gCurrentOscBank]->lookupTableSize, | |
598 gOscBanks[gCurrentOscBank]->oscillatorPhases, gOscBanks[gCurrentOscBank]->oscillatorNormFrequencies, | |
599 gOscBanks[gCurrentOscBank]->oscillatorAmplitudes, | |
600 gOscBanks[gCurrentOscBank]->oscillatorNormFreqDerivatives, | |
601 gOscBanks[gCurrentOscBank]->oscillatorAmplitudeDerivatives, | |
602 /*gOscBanks[gCurrentOscBank]->lookupTable*/gDynamicWavetable); | |
603 | |
604 gOscillatorBufferWriteCurrentSize = gOscBanks[gCurrentOscBank]->hopCounter * 2; | |
605 | |
606 /* Update the pitch right before the hop | |
607 * Total CV range +/- N_OCT octaves | |
608 */ | |
609 float pitch = (float)gPitchLatestInput / octaveSplitter - N_OCT/2; | |
610 //gOscBanks[gCurrentOscBank]->pitchMultiplier = powf(2.0f, pitch); | |
611 gOscBanks[gCurrentOscBank]->pitchMultiplier = pow(2.0f, pitch); | |
612 | |
613 #ifdef FIXME_LATER // This doesn't work very well yet | |
614 gOscBanks[gCurrentOscBank]->filterNum = gSensor1LatestTouchCount; | |
615 float freqScaler = gOscBanks[gCurrentOscBank]->getFrequencyScaler(); | |
616 for(int i=0; i < gOscBanks[gCurrentOscBank]->filterNum; i++) | |
617 { | |
618 // touch pos is linear but freqs are log | |
619 gOscBanks[gCurrentOscBank]->filterFreqs[i] = ((expf(gSensor1MatrixTouchPos[i]*4)-1)/(expf(4)-1))*gOscBanks[gCurrentOscBank]->filterMaxF*freqScaler; | |
620 gOscBanks[gCurrentOscBank]->filterQ[i] = gSensor1LatestTouchSizes[i]; | |
621 if(gOscBanks[gCurrentOscBank]->filterFreqs[i]>500*freqScaler) | |
622 gOscBanks[gCurrentOscBank]->filterPadding[i] = 1+100000*( (gOscBanks[gCurrentOscBank]->filterFreqs[i]-500*freqScaler)/(gOscBanks[gCurrentOscBank]->filterMaxF-500)*freqScaler ); | |
623 else | |
624 gOscBanks[gCurrentOscBank]->filterPadding[i] = 1; | |
625 } | |
626 #endif | |
627 | |
628 RTIME ticks = rt_timer_read(); | |
629 SRTIME ns = rt_timer_tsc2ns(ticks); | |
630 SRTIME delta = ns-prevChangeNs; | |
631 | |
632 // switch to next bank cannot be too frequent, to avoid seg fault! [for example sef fault happens when removing both VDD and GND from breadboard] | |
633 if(gNextOscBank != gCurrentOscBank && delta>100000000) { | |
634 | |
635 /*printf("ticks %llu\n", (unsigned long long)ticks); | |
636 printf("ns %llu\n", (unsigned long long)ns); | |
637 printf("prevChangeNs %llu\n", (unsigned long long)prevChangeNs); | |
638 printf("-------------------------->%llud\n", (unsigned long long)(ns-prevChangeNs));*/ | |
639 | |
640 prevChangeNs = ns; | |
641 dbox_printf("Changing to bank %d...\n", gNextOscBank); | |
642 if(gOscBanks[gCurrentOscBank]->state==bank_playing){ | |
643 gOscBanks[gCurrentOscBank]->stop(); | |
644 } | |
645 | |
646 gCurrentOscBank = gNextOscBank; | |
647 gOscBanks[gCurrentOscBank]->hopNumTh = 0; | |
648 } | |
649 else { | |
650 /* Advance to the next oscillator frame */ | |
651 gOscBanks[gCurrentOscBank]->nextHop(); | |
652 } | |
653 } | |
654 } | |
655 | |
656 // Lower-priority render function which performs matrix calculations | |
657 // State should be transferred in via global variables | |
658 void render_low_prio() | |
659 { | |
660 gPRU->setGPIOTestPin(); | |
661 if(gDynamicWavetableNeedsRender) { | |
662 // Find amplitude of wavetable | |
663 float meanAmplitude = 0; | |
664 float sineMix; | |
665 | |
666 for(int i = 0; i < gFeedbackOscillatorTableLength; i++) { | |
667 //meanAmplitude += fabsf(gFeedbackOscillatorTable[i]); | |
668 meanAmplitude += fabs(gFeedbackOscillatorTable[i]); | |
669 } | |
670 meanAmplitude /= (float)gFeedbackOscillatorTableLength; | |
671 | |
672 if(meanAmplitude > 0.35) | |
673 sineMix = 0; | |
674 else | |
675 sineMix = (.35 - meanAmplitude) / .35; | |
676 | |
677 //dbox_printf("amp %f mix %f\n", meanAmplitude, sineMix); | |
678 | |
679 // Copy to main wavetable | |
680 wavetable_interpolate(gFeedbackOscillatorTableLength, gDynamicWavetableLength, | |
681 gFeedbackOscillatorTable, gDynamicWavetable, | |
682 gOscBanks[gCurrentOscBank]->lookupTable, sineMix); | |
683 } | |
684 | |
685 if(gLoopPointMin >= 60000.0/65536.0 && gLoopPointMax >= 60000.0/65536.0) { | |
686 // KLUDGE! | |
687 if(gCurrentOscBank == 0) | |
688 gOscBanks[gCurrentOscBank]->setLoopHops(50, ((float)gOscBanks[gCurrentOscBank]->getLastHop() * 0.6) - 1); | |
689 else | |
690 gOscBanks[gCurrentOscBank]->setLoopHops(5, ((float)gOscBanks[gCurrentOscBank]->getLastHop() * 0.7) - 1); | |
691 } | |
692 else { | |
693 float normLoopPointMin = (float)gLoopPointMin * gOscBanks[gCurrentOscBank]->getLastHop(); | |
694 float normLoopPointMax = (float)gLoopPointMax * gOscBanks[gCurrentOscBank]->getLastHop(); | |
695 | |
696 int intLoopPointMin = normLoopPointMin; | |
697 if(intLoopPointMin < 1) | |
698 intLoopPointMin = 1; | |
699 int intLoopPointMax = normLoopPointMax; | |
700 if(intLoopPointMax <= intLoopPointMin) | |
701 intLoopPointMax = intLoopPointMin + 1; | |
702 if(intLoopPointMax > gOscBanks[gCurrentOscBank]->getLastHop() - 1) | |
703 intLoopPointMax = gOscBanks[gCurrentOscBank]->getLastHop() - 1; | |
704 | |
705 //dbox_printf("Loop points %d-%d / %d-%d\n", gLoopPointMin, gLoopPointMax, intLoopPointMin, intLoopPointMax); | |
706 | |
707 /* WORKS, jsut need to fix the glitch when jumps! | |
708 * *int currentHop = gOscBanks[gCurrentOscBank]->getCurrentHop(); | |
709 if(currentHop < intLoopPointMin -1 ) | |
710 gOscBanks[gCurrentOscBank]->setJumpHop(intLoopPointMin + 1); | |
711 else if(currentHop > intLoopPointMax + 1) | |
712 gOscBanks[gCurrentOscBank]->setJumpHop(intLoopPointMax - 1);*/ | |
713 gOscBanks[gCurrentOscBank]->setLoopHops(intLoopPointMin, intLoopPointMax); | |
714 } | |
715 | |
716 if(gIsLoading) | |
717 gStatusLED.blink(25, 75); // Blink quickly until load finished | |
718 else | |
719 gStatusLED.blink(250 / gOscBanks[gCurrentOscBank]->getSpeed(), 250 / gOscBanks[gCurrentOscBank]->getSpeed()); | |
720 gPRU->clearGPIOTestPin(); | |
721 | |
722 // static int counter = 32; | |
723 // if(--counter == 0) { | |
724 // for(int i = 0; i < gLoopPointsInputBufferSize; i++) { | |
725 // dbox_printf("%d ", gLoopPointsInputBuffer[i]); | |
726 // if(i % 32 == 31) | |
727 // dbox_printf("\n"); | |
728 // } | |
729 // dbox_printf("\n\n"); | |
730 // counter = 32; | |
731 // } | |
732 | |
733 //dbox_printf("min %d max %d\n", gLoopPointMin, gLoopPointMax); | |
734 } | |
735 | |
736 // Clean up at the end of render | |
737 void cleanup(BelaContext *context, void *userData) | |
738 { | |
739 free(gOscillatorBuffer1); | |
740 free(gOscillatorBuffer2); | |
741 free(gDynamicWavetable); | |
742 } | |
743 | |
744 // Interpolate one wavetable into another. The output size | |
745 // does not include the guard point at the end which will be identical | |
746 // to the first point | |
747 void wavetable_interpolate(int numSamplesIn, int numSamplesOut, | |
748 float *tableIn, float *tableOut, | |
749 float *sineTable, float sineMix) | |
750 { | |
751 float fractionalScaler = (float)numSamplesIn / (float)numSamplesOut; | |
752 | |
753 for(int k = 0; k < numSamplesOut; k++) { | |
754 float fractionalIndex = (float) k * fractionalScaler; | |
755 //int sB = (int)floorf(fractionalIndex); | |
756 int sB = (int)floor(fractionalIndex); | |
757 int sA = sB + 1; | |
758 if(sA >= numSamplesIn) | |
759 sA = 0; | |
760 float fraction = fractionalIndex - sB; | |
761 tableOut[k] = fraction * tableIn[sA] + (1.0f - fraction) * tableIn[sB]; | |
762 tableOut[k] = sineMix * sineTable[k] + (1.0 - sineMix) * tableOut[k]; | |
763 } | |
764 | |
765 tableOut[numSamplesOut] = tableOut[0]; | |
766 } | |
767 | |
768 // Create a hysteresis oscillator with a matrix input and output | |
769 inline float hysteresis_oscillator(float input, float risingThreshold, float fallingThreshold, bool *rising) | |
770 { | |
771 float value; | |
772 | |
773 if(*rising) { | |
774 if(input > risingThreshold) { | |
775 *rising = false; | |
776 value = 0; | |
777 } | |
778 else | |
779 value = 1.0; | |
780 } | |
781 else { | |
782 if(input < fallingThreshold) { | |
783 *rising = true; | |
784 value = 1.0; | |
785 } | |
786 else | |
787 value = 0; | |
788 } | |
789 | |
790 return value; | |
791 } | |
792 | |
793 #ifdef DBOX_CAPE_TEST | |
794 // Test the functionality of the D-Box cape by checking each input and output | |
795 // Loopback cable from ADC to DAC needed | |
796 void render_capetest(int numMatrixFrames, int numAudioFrames, float *audioIn, float *audioOut, | |
797 uint16_t *matrixIn, uint16_t *matrixOut) | |
798 { | |
799 static float phase = 0.0; | |
800 static int sampleCounter = 0; | |
801 static int invertChannel = 0; | |
802 | |
803 // Play a sine wave on the audio output | |
804 for(int n = 0; n < numAudioFrames; n++) { | |
805 audioOut[2*n] = audioOut[2*n + 1] = 0.5*sinf(phase); | |
806 phase += 2.0 * M_PI * 440.0 / 44100.0; | |
807 if(phase >= 2.0 * M_PI) | |
808 phase -= 2.0 * M_PI; | |
809 } | |
810 | |
811 for(int n = 0; n < numMatrixFrames; n++) { | |
812 // Change outputs every 512 samples | |
813 if(sampleCounter < 512) { | |
814 for(int k = 0; k < 8; k++) { | |
815 if(k == invertChannel) | |
816 matrixOut[n*8 + k] = 50000; | |
817 else | |
818 matrixOut[n*8 + k] = 0; | |
819 } | |
820 } | |
821 else { | |
822 for(int k = 0; k < 8; k++) { | |
823 if(k == invertChannel) | |
824 matrixOut[n*8 + k] = 0; | |
825 else | |
826 matrixOut[n*8 + k] = 50000; | |
827 } | |
828 } | |
829 | |
830 // Read after 256 samples: input should be low | |
831 if(sampleCounter == 256) { | |
832 for(int k = 0; k < 8; k++) { | |
833 if(k == invertChannel) { | |
834 if(matrixIn[n*8 + k] < 50000) { | |
835 dbox_printf("FAIL channel %d -- output HIGH input %d (inverted)\n", k, matrixIn[n*8 + k]); | |
836 } | |
837 } | |
838 else { | |
839 if(matrixIn[n*8 + k] > 2048) { | |
840 dbox_printf("FAIL channel %d -- output LOW input %d\n", k, matrixIn[n*8 + k]); | |
841 } | |
842 } | |
843 } | |
844 } | |
845 else if(sampleCounter == 768) { | |
846 for(int k = 0; k < 8; k++) { | |
847 if(k == invertChannel) { | |
848 if(matrixIn[n*8 + k] > 2048) { | |
849 dbox_printf("FAIL channel %d -- output LOW input %d (inverted)\n", k, matrixIn[n*8 + k]); | |
850 } | |
851 } | |
852 else { | |
853 if(matrixIn[n*8 + k] < 50000) { | |
854 dbox_printf("FAIL channel %d -- output HIGH input %d\n", k, matrixIn[n*8 + k]); | |
855 } | |
856 } | |
857 } | |
858 } | |
859 | |
860 if(++sampleCounter >= 1024) { | |
861 sampleCounter = 0; | |
862 invertChannel++; | |
863 if(invertChannel >= 8) | |
864 invertChannel = 0; | |
865 } | |
866 } | |
867 } | |
868 #endif | |
869 | |
870 |