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