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