annotate examples/10-Instruments/d-box/render.cpp @ 556:ce391098f321 prerelease tip

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