robert@464: /* robert@464: * render.cpp robert@464: * robert@464: * Created on: May 28, 2014 robert@464: * Author: Victor Zappi robert@464: */ robert@464: robert@464: #include robert@464: #include robert@464: robert@464: #include "StatusLED.h" robert@464: #include "config.h" robert@464: #include "OscillatorBank.h" robert@464: #include "FeedbackOscillator.h" robert@464: #include "ADSR.h" robert@464: #include "FIRfilter.h" robert@464: #include robert@464: #include robert@464: #include robert@464: robert@464: #undef DBOX_CAPE_TEST robert@464: robert@464: // Mappings from pin numbers on PCB to actual DAC channels robert@464: // This gives the DAC and ADC connectors the same effective pinout robert@464: // Update June 2016: this is no longer needed in the latest Bela robert@464: // release, but is kept here for convenience: it used to be robert@464: // 6 4 2 0 1 3 5 7 for the DAC pins robert@464: #define DAC_PIN0 0 robert@464: #define DAC_PIN1 1 robert@464: #define DAC_PIN2 2 robert@464: #define DAC_PIN3 3 robert@464: #define DAC_PIN4 4 robert@464: #define DAC_PIN5 5 robert@464: #define DAC_PIN6 6 robert@464: #define DAC_PIN7 7 robert@464: robert@464: #define ADC_PIN0 0 robert@464: #define ADC_PIN1 1 robert@464: #define ADC_PIN2 2 robert@464: #define ADC_PIN3 3 robert@464: #define ADC_PIN4 4 robert@464: #define ADC_PIN5 5 robert@464: #define ADC_PIN6 6 robert@464: #define ADC_PIN7 7 robert@464: robert@464: #define N_OCT 4.0 // maximum number of octaves on sensor 1 robert@464: robert@464: extern vector gOscBanks; robert@464: extern int gCurrentOscBank; robert@464: extern int gNextOscBank; robert@464: extern PRU *gPRU; robert@464: extern StatusLED gStatusLED; robert@464: extern bool gIsLoading; robert@464: extern bool gAudioIn; robert@464: robert@464: float *gOscillatorBuffer1, *gOscillatorBuffer2; robert@464: float *gOscillatorBufferRead, *gOscillatorBufferWrite; robert@464: int gOscillatorBufferReadPointer = 0; robert@464: int gOscillatorBufferReadCurrentSize = 0; robert@464: int gOscillatorBufferWriteCurrentSize = 0; robert@464: bool gOscillatorNeedsRender = false; robert@464: robert@464: int gMatrixSampleCount = 0; // How many samples have elapsed on the matrix robert@464: robert@464: // Wavetable which changes in response to an oscillator robert@464: float *gDynamicWavetable; robert@464: int gDynamicWavetableLength; robert@464: bool gDynamicWavetableNeedsRender = false; robert@464: robert@464: // These variables handle the hysteresis oscillator used for setting the playback speed robert@464: bool gSpeedHysteresisOscillatorRising = false; robert@464: int gSpeedHysteresisLastTrigger = 0; robert@464: robert@464: // These variables handle the feedback oscillator used for controlling the wavetable robert@464: FeedbackOscillator gFeedbackOscillator; robert@464: float *gFeedbackOscillatorTable; robert@464: int gFeedbackOscillatorTableLength; robert@464: robert@464: // This comes from sensor.cpp where it records the most recent touch location on robert@464: // sensor 0. robert@464: extern float gSensor0LatestTouchPos; robert@464: extern int gSensor0LatestTouchNum; robert@464: float gPitchLatestInput = 0; robert@464: robert@464: extern float gSensor1LatestTouchPos[]; robert@464: //extern float gSensor1LatestTouchSizes[]; robert@464: extern int gSensor1LatestTouchCount; robert@464: extern int gSensor1LatestTouchIndex; robert@464: int gSensor1LastTouchIndex = -1; robert@464: int gSensor1InputDelayCounter = -1; robert@464: int gSensor1InputIndex = 0; robert@464: float gSensor1MatrixTouchPos[5] = {0}; robert@464: robert@464: // FSR value from matrix input robert@464: extern int gLastFSRValue; robert@464: robert@464: // Loop points from matrix input 4 robert@464: const int gLoopPointsInputBufferSize = 256; robert@464: float gLoopPointsInputBuffer[gLoopPointsInputBufferSize]; robert@464: int gLoopPointsInputBufferPointer = 0; robert@464: float gLoopPointMin = 0, gLoopPointMax = 0; robert@464: robert@464: // multiplier to activate or mute audio in robert@464: int audioInStatus = 0; robert@464: robert@464: // xenomai timer robert@464: SRTIME prevChangeNs = 0; robert@464: robert@464: // pitch vars robert@464: float octaveSplitter; robert@464: float semitones[((int)N_OCT*12)+1]; robert@464: float deltaTouch = 0; robert@464: float deltaWeightP = 0.5 / 65536.0; robert@464: float deltaWeightI = 0.0005 / 65536.0; robert@464: robert@464: // filter vars robert@464: ne10_fir_instance_f32_t filter[2]; robert@464: ne10_float32_t *filterIn[2]; robert@464: ne10_float32_t *filterOut[2]; robert@464: ne10_uint32_t blockSize; robert@464: ne10_float32_t *filterState[2]; robert@464: ne10_float32_t prevFiltered[2]; robert@464: int filterGain = 80; robert@464: ADSR PeakBurst[2]; robert@464: float peak[2]; robert@464: float peakThresh = 0.2; robert@464: robert@464: // Tasks for lower-priority calculation robert@464: AuxiliaryTask gMediumPriorityRender, gLowPriorityRender; robert@464: robert@464: robert@464: extern "C" { robert@464: // Function prototype for ARM assembly implementation of oscillator bank robert@464: void oscillator_bank_neon(int numAudioFrames, float *audioOut, robert@464: int activePartialNum, int lookupTableSize, robert@464: float *phases, float *frequencies, float *amplitudes, robert@464: float *freqDerivatives, float *ampDerivatives, robert@464: float *lookupTable); robert@464: robert@464: void wavetable_interpolate_neon(int numSamplesIn, int numSamplesOut, robert@464: float *tableIn, float *tableOut); robert@464: } robert@464: robert@464: void wavetable_interpolate(int numSamplesIn, int numSamplesOut, robert@464: float *tableIn, float *tableOut, robert@464: float *sineTable, float sineMix); robert@464: robert@464: inline float hysteresis_oscillator(float input, float risingThreshold, robert@464: float fallingThreshold, bool *rising); robert@464: robert@464: void render_medium_prio(); robert@464: void render_low_prio(); robert@464: robert@464: #ifdef DBOX_CAPE_TEST robert@464: void render_capetest(int numMatrixFrames, int numAudioFrames, float *audioIn, float *audioOut, robert@464: uint16_t *matrixIn, uint16_t *matrixOut); robert@464: #endif robert@464: robert@464: bool setup(BelaContext *context, void *userData) { robert@464: int oscBankHopSize = *(int *)userData; robert@464: robert@464: if(context->analogChannels != 8) { robert@464: printf("Error: D-Box needs matrix enabled with 8 channels.\n"); robert@464: return false; robert@464: } robert@464: robert@464: // Allocate two buffers for rendering oscillator bank samples robert@464: // One will be used for writing in the background while the other is used for reading robert@464: // on the audio thread. 8-byte alignment needed for the NEON code. robert@464: if(posix_memalign((void **)&gOscillatorBuffer1, 8, oscBankHopSize * context->audioChannels * sizeof(float))) { robert@464: printf("Error allocating render buffers\n"); robert@464: return false; robert@464: } robert@464: if(posix_memalign((void **)&gOscillatorBuffer2, 8, oscBankHopSize * context->audioChannels * sizeof(float))) { robert@464: printf("Error allocating render buffers\n"); robert@464: return false; robert@464: } robert@464: gOscillatorBufferWrite = gOscillatorBuffer1; robert@464: gOscillatorBufferRead = gOscillatorBuffer2; robert@464: robert@464: memset(gOscillatorBuffer1, 0, oscBankHopSize * context->audioChannels * sizeof(float)); robert@464: memset(gOscillatorBuffer2, 0, oscBankHopSize * context->audioChannels * sizeof(float)); robert@464: robert@464: // Initialise the dynamic wavetable used by the oscillator bank robert@464: // It should match the size of the static one already allocated in the OscillatorBank object robert@464: // Don't forget a guard point at the end of the table robert@464: gDynamicWavetableLength = gOscBanks[gCurrentOscBank]->lookupTableSize; robert@464: if(posix_memalign((void **)&gDynamicWavetable, 8, (gDynamicWavetableLength + 1) * sizeof(float))) { robert@464: printf("Error allocating wavetable\n"); robert@464: return false; robert@464: } robert@464: robert@464: gFeedbackOscillator.initialise(8192, 10.0, context->analogSampleRate); robert@464: robert@464: for(int n = 0; n < gDynamicWavetableLength + 1; n++) robert@464: gDynamicWavetable[n] = 0; robert@464: robert@464: // pitch robert@464: float midPos = 0.5; robert@464: octaveSplitter = 1.0 / N_OCT; robert@464: int numOfSemi = 12*N_OCT; robert@464: int middleSemitone = 12*N_OCT/2; robert@464: int lastSemitone = middleSemitone+numOfSemi/2; robert@464: float inc = 1.0 / (N_OCT*12.0); robert@464: int i = -1; robert@464: for(int semi=middleSemitone; semi<=lastSemitone; semi++) robert@464: semitones[semi] = ( midPos + (++i)*inc) + 0.5; robert@464: i = 0; robert@464: for(int semi=middleSemitone-1; semi>=0; semi--) robert@464: semitones[semi] = ( midPos - (++i)*inc) + 0.5; robert@464: robert@464: if(gAudioIn) robert@464: audioInStatus = 1; robert@464: robert@464: // filter robert@464: blockSize = context->audioFrames; robert@464: filterState[0] = (ne10_float32_t *) NE10_MALLOC ((FILTER_TAP_NUM+blockSize-1) * sizeof (ne10_float32_t)); robert@464: filterState[1] = (ne10_float32_t *) NE10_MALLOC ((FILTER_TAP_NUM+blockSize-1) * sizeof (ne10_float32_t)); robert@464: filterIn[0] = (ne10_float32_t *) NE10_MALLOC (blockSize * sizeof (ne10_float32_t)); robert@464: filterIn[1] = (ne10_float32_t *) NE10_MALLOC (blockSize * sizeof (ne10_float32_t)); robert@464: filterOut[0] = (ne10_float32_t *) NE10_MALLOC (blockSize * sizeof (ne10_float32_t)); robert@464: filterOut[1] = (ne10_float32_t *) NE10_MALLOC (blockSize * sizeof (ne10_float32_t)); robert@464: ne10_fir_init_float(&filter[0], FILTER_TAP_NUM, filterTaps, filterState[0], blockSize); robert@464: ne10_fir_init_float(&filter[1], FILTER_TAP_NUM, filterTaps, filterState[1], blockSize); robert@464: robert@464: // peak outputs robert@464: PeakBurst[0].setAttackRate(.00001 * context->analogSampleRate); robert@464: PeakBurst[1].setAttackRate(.00001 * context->analogSampleRate); robert@464: PeakBurst[0].setDecayRate(.5 * context->analogSampleRate); robert@464: PeakBurst[1].setDecayRate(.5 * context->analogSampleRate); robert@464: PeakBurst[0].setSustainLevel(0.0); robert@464: PeakBurst[1].setSustainLevel(0.0); robert@464: robert@464: // Initialise auxiliary tasks robert@464: if((gMediumPriorityRender = Bela_createAuxiliaryTask(&render_medium_prio, BELA_AUDIO_PRIORITY - 10, "dbox-calculation-medium")) == 0) robert@464: return false; robert@464: if((gLowPriorityRender = Bela_createAuxiliaryTask(&render_low_prio, BELA_AUDIO_PRIORITY - 15, "dbox-calculation-low")) == 0) robert@464: return false; robert@464: robert@464: return true; robert@464: } robert@464: robert@464: void render(BelaContext *context, void *userData) robert@464: { robert@464: #ifdef DBOX_CAPE_TEST robert@464: render_capetest(numMatrixFrames, numAudioFrames, audioIn, audioOut, matrixIn, matrixOut); robert@464: #else robert@464: if(gOscBanks[gCurrentOscBank]->state==bank_toreset) robert@464: gOscBanks[gCurrentOscBank]->resetOscillators(); robert@464: robert@464: if(gOscBanks[gCurrentOscBank]->state==bank_playing) robert@464: { robert@464: assert(context->audioChannels == 2); robert@464: robert@464: #ifdef OLD_OSCBANK robert@464: memset(audioOut, 0, numAudioFrames * * sizeof(float)); robert@464: robert@464: /* Render the oscillator bank. The oscillator bank function is written in NEON assembly robert@464: * and it strips out all extra checks, so find out in advance whether we can render a whole robert@464: * block or whether the frame will increment in the middle of this buffer. robert@464: */ robert@464: robert@464: int framesRemaining = numAudioFrames; robert@464: float *audioOutWithOffset = audioOut; robert@464: robert@464: while(framesRemaining > 0) { robert@464: if(gOscBanks[gCurrentOscBank]->hopCounter >= framesRemaining) { robert@464: /* More frames left in this hop than we need this time. Render and finish */ robert@464: oscillator_bank_neon(framesRemaining, audioOutWithOffset, robert@464: gOscBanks[gCurrentOscBank]->actPartNum, gOscBanks[gCurrentOscBank]->lookupTableSize, robert@464: gOscBanks[gCurrentOscBank]->oscillatorPhases, gOscBanks[gCurrentOscBank]->oscillatorNormFrequencies, robert@464: gOscBanks[gCurrentOscBank]->oscillatorAmplitudes, robert@464: gOscBanks[gCurrentOscBank]->oscillatorNormFreqDerivatives, robert@464: gOscBanks[gCurrentOscBank]->oscillatorAmplitudeDerivatives, robert@464: gDynamicWavetable/*gOscBanks[gCurrentOscBank]->lookupTable*/); robert@464: gOscBanks[gCurrentOscBank]->hopCounter -= framesRemaining; robert@464: if(gOscBanks[gCurrentOscBank]->hopCounter <= 0) robert@464: gOscBanks[gCurrentOscBank]->nextHop(); robert@464: framesRemaining = 0; robert@464: } robert@464: else { robert@464: /* More frames to render than are left in this hop. Render and decrement the robert@464: * number of remaining frames; then advance to the next oscillator frame. robert@464: */ robert@464: oscillator_bank_neon(gOscBanks[gCurrentOscBank]->hopCounter, audioOutWithOffset, robert@464: gOscBanks[gCurrentOscBank]->actPartNum, gOscBanks[gCurrentOscBank]->lookupTableSize, robert@464: gOscBanks[gCurrentOscBank]->oscillatorPhases, gOscBanks[gCurrentOscBank]->oscillatorNormFrequencies, robert@464: gOscBanks[gCurrentOscBank]->oscillatorAmplitudes, robert@464: gOscBanks[gCurrentOscBank]->oscillatorNormFreqDerivatives, robert@464: gOscBanks[gCurrentOscBank]->oscillatorAmplitudeDerivatives, robert@464: gDynamicWavetable/*gOscBanks[gCurrentOscBank]->lookupTable*/); robert@464: framesRemaining -= gOscBanks[gCurrentOscBank]->hopCounter; robert@464: audioOutWithOffset += * gOscBanks[gCurrentOscBank]->hopCounter; robert@464: gOscBanks[gCurrentOscBank]->sampleCount += gOscBanks[gCurrentOscBank]->hopCounter; robert@464: gOscBanks[gCurrentOscBank]->nextHop(); robert@464: } robert@464: } robert@464: #else robert@464: for(unsigned int n = 0; n < context->audioFrames; n++) { robert@464: context->audioOut[2*n] = gOscillatorBufferRead[gOscillatorBufferReadPointer++]+context->audioIn[2*n]*audioInStatus; robert@464: context->audioOut[2*n + 1] = gOscillatorBufferRead[gOscillatorBufferReadPointer++]+context->audioIn[2*n+1]*audioInStatus; robert@464: robert@464: filterIn[0][n] = fabs(context->audioIn[2*n]); // rectify for peak detection in 1 robert@464: filterIn[1][n] = fabs(context->audioIn[2*n+1]); // rectify for peak detection in 2 robert@464: robert@464: /* FIXME why doesn't this work? */ robert@464: /* robert@464: if(gOscillatorBufferReadPointer == gOscillatorBufferCurrentSize/2) { robert@464: gOscillatorNeedsRender = true; robert@464: scheduleAuxiliaryTask(gLowPriorityRender); robert@464: } */ robert@464: robert@464: if(gOscillatorBufferReadPointer >= gOscillatorBufferReadCurrentSize) { robert@464: // Finished reading from the buffer: swap to the next buffer robert@464: if(gOscillatorBufferRead == gOscillatorBuffer1) { robert@464: gOscillatorBufferRead = gOscillatorBuffer2; robert@464: gOscillatorBufferWrite = gOscillatorBuffer1; robert@464: } robert@464: else { robert@464: gOscillatorBufferRead = gOscillatorBuffer1; robert@464: gOscillatorBufferWrite = gOscillatorBuffer2; robert@464: } robert@464: robert@464: // New buffer size is whatever finished writing last hop robert@464: gOscillatorBufferReadCurrentSize = gOscillatorBufferWriteCurrentSize; robert@464: gOscillatorBufferReadPointer = 0; robert@464: robert@464: gOscillatorNeedsRender = true; robert@464: Bela_scheduleAuxiliaryTask(gMediumPriorityRender); robert@464: } robert@464: } robert@464: #endif robert@464: } robert@464: else robert@464: { robert@464: for(unsigned int n = 0; n < context->audioFrames; n++) { robert@464: context->audioOut[2*n] = context->audioIn[2*n]*audioInStatus; robert@464: context->audioOut[2*n + 1] = context->audioIn[2*n+1]*audioInStatus; robert@464: robert@464: filterIn[0][n] = fabs(context->audioIn[2*n]); // rectify for peak detection in 1 robert@464: filterIn[1][n] = fabs(context->audioIn[2*n+1]); // rectify for peak detection in 2 robert@464: } robert@464: } robert@464: robert@464: // low pass filter audio in 1 and 2 for peak detection robert@464: ne10_fir_float_neon(&filter[0], filterIn[0], filterOut[0], blockSize); robert@464: ne10_fir_float_neon(&filter[1], filterIn[1], filterOut[1], blockSize); robert@464: robert@464: for(unsigned int n = 0; n < context->analogFrames; n++) { robert@464: robert@464: robert@464: /* Matrix Out 0, In 0 robert@464: * robert@464: * CV loop robert@464: * Controls pitch of sound robert@464: */ robert@464: float touchPosInt = gSensor0LatestTouchPos; robert@464: if(touchPosInt < 0) touchPosInt = 0; robert@464: if(touchPosInt > 1.0) touchPosInt = 1.0; robert@464: context->analogOut[n*8 + DAC_PIN0] = touchPosInt; robert@464: robert@464: gPitchLatestInput = context->analogIn[n*8 + ADC_PIN0]; robert@464: robert@464: robert@464: /* Matrix Out 7 robert@464: * robert@464: * Loop feedback with Matrix In 0 robert@464: * Controls discreet pitch robert@464: */ robert@464: float deltaTarget = 0; robert@464: int semitoneIndex = 0; robert@464: if(gSensor0LatestTouchNum>0) robert@464: { robert@464: // current pitch is gPitchLatestInput, already retrieved robert@464: semitoneIndex = ( gPitchLatestInput * 12 * N_OCT )+0.5; // closest semitone robert@464: deltaTarget = (semitones[semitoneIndex]-gPitchLatestInput); // delta between pitch and target robert@464: deltaTouch += deltaTarget*(deltaWeightI); // update feedback [previous + current] robert@464: } robert@464: else robert@464: deltaTouch = 0; robert@464: robert@464: float nextOut = touchPosInt + deltaTarget*deltaWeightP + deltaTouch; // add feedback to touch -> next out robert@464: if(nextOut < 0) nextOut = 0; // clamp robert@464: if(nextOut > 1.0) nextOut = 1.0; // clamp robert@464: context->analogOut[n*8 + DAC_PIN7] = nextOut; // send next nextOut robert@464: robert@464: robert@464: /* robert@464: * Matrix Out 1, In 1 robert@464: * robert@464: * Hysteresis (comparator) oscillator robert@464: * Controls speed of playback robert@464: */ robert@464: bool wasRising = gSpeedHysteresisOscillatorRising; robert@464: context->analogOut[n*8 + DAC_PIN1] = hysteresis_oscillator(context->analogIn[n*8 + ADC_PIN1], 48000.0/65536.0, robert@464: 16000.0/65536.0, &gSpeedHysteresisOscillatorRising); robert@464: robert@464: // Find interval of zero crossing robert@464: if(wasRising && !gSpeedHysteresisOscillatorRising) { robert@464: int interval = gMatrixSampleCount - gSpeedHysteresisLastTrigger; robert@464: robert@464: // Interval since last trigger will be the new hop size; calculate to set speed robert@464: if(interval < 1) robert@464: interval = 1; robert@464: //float speed = (float)gOscBanks[gCurrentOscBank]->getHopSize() / (float)interval; robert@464: float speed = 144.0 / interval; // Normalise to a fixed expected speed robert@464: gOscBanks[gCurrentOscBank]->setSpeed(speed); robert@464: robert@464: gSpeedHysteresisLastTrigger = gMatrixSampleCount; robert@464: } robert@464: robert@464: /* robert@464: * Matrix Out 2, In 2 robert@464: * robert@464: * Feedback (phase shift) oscillator robert@464: * Controls wavetable used for oscillator bank robert@464: */ robert@464: robert@464: int tableLength = gFeedbackOscillator.process(context->analogIn[n*8 + ADC_PIN2], &context->analogOut[n*8 + DAC_PIN2]); robert@464: if(tableLength != 0) { robert@464: gFeedbackOscillatorTableLength = tableLength; robert@464: gFeedbackOscillatorTable = gFeedbackOscillator.wavetable(); robert@464: gDynamicWavetableNeedsRender = true; robert@464: Bela_scheduleAuxiliaryTask(gLowPriorityRender); robert@464: } robert@464: robert@464: /* robert@464: * Matrix Out 3, In 3 robert@464: * robert@464: * CV loop with delay for time alignment robert@464: * Touch positions from sensor 1 robert@464: * Change every 32 samples (ca. 1.5 ms) robert@464: */ robert@464: volatile int touchCount = gSensor1LatestTouchCount; robert@464: if(touchCount == 0) robert@464: context->analogOut[n*8 + DAC_PIN3] = 0; robert@464: else { robert@464: int touchIndex = (gMatrixSampleCount >> 5) % touchCount; robert@464: context->analogOut[n*8 + DAC_PIN3] = gSensor1LatestTouchPos[touchIndex] * 56000.0f / 65536.0f; robert@464: if(touchIndex != gSensor1LastTouchIndex) { robert@464: // Just changed to a new touch output. Reset the counter. robert@464: // It will take 2*matrixFrames samples for this output to come back to the robert@464: // ADC input. But we also want to read near the end of the 32 sample block; robert@464: // let's say 24 samples into it. robert@464: robert@464: // FIXME this won't work for p > 2 robert@464: gSensor1InputDelayCounter = 24 + 2*context->analogFrames; robert@464: gSensor1InputIndex = touchIndex; robert@464: } robert@464: gSensor1LastTouchIndex = touchIndex; robert@464: } robert@464: robert@464: if(gSensor1InputDelayCounter-- >= 0 && touchCount > 0) { robert@464: gSensor1MatrixTouchPos[gSensor1InputIndex] = context->analogIn[n*8 + ADC_PIN3]; robert@464: } robert@464: robert@464: /* Matrix Out 4 robert@464: * robert@464: * Sensor 1 last pos robert@464: */ robert@464: touchPosInt = gSensor1LatestTouchPos[gSensor1LatestTouchIndex]; robert@464: if(touchPosInt < 0) touchPosInt = 0; robert@464: if(touchPosInt > 1.0) touchPosInt = 1.0; robert@464: context->analogOut[n*8 + DAC_PIN4] = touchPosInt; robert@464: robert@464: /* Matrix In 4 robert@464: * robert@464: * Loop points selector robert@464: */ robert@464: gLoopPointsInputBuffer[gLoopPointsInputBufferPointer++] = context->analogIn[n*8 + ADC_PIN4]; robert@464: if(gLoopPointsInputBufferPointer >= gLoopPointsInputBufferSize) { robert@464: // Find min and max values robert@464: float loopMax = 0, loopMin = 1.0; robert@464: for(int i = 0; i < gLoopPointsInputBufferSize; i++) { robert@464: if(gLoopPointsInputBuffer[i] < loopMin) robert@464: loopMin = gLoopPointsInputBuffer[i]; robert@464: if(gLoopPointsInputBuffer[i] > loopMax/* && gLoopPointsInputBuffer[i] != 65535*/) robert@464: loopMax = gLoopPointsInputBuffer[i]; robert@464: } robert@464: robert@464: if(loopMin >= loopMax) robert@464: loopMax = loopMin; robert@464: robert@464: gLoopPointMax = loopMax; robert@464: gLoopPointMin = loopMin; robert@464: gLoopPointsInputBufferPointer = 0; robert@464: } robert@464: robert@464: /* Matrix Out 5 robert@464: * robert@464: * Audio In 1 peak detection and peak burst output robert@464: */ robert@464: robert@464: filterOut[0][n*2+1] *= filterGain; robert@464: float burstOut = PeakBurst[0].getOutput(); robert@464: if( burstOut < 0.1) robert@464: { robert@464: if( (prevFiltered[0]>=peakThresh) && (prevFiltered[0]>=filterOut[0][n*2+1]) ) robert@464: { robert@464: peak[0] = prevFiltered[0]; robert@464: PeakBurst[0].gate(1); robert@464: } robert@464: } robert@464: robert@464: PeakBurst[0].process(1); robert@464: robert@464: float convAudio = burstOut*peak[0]; robert@464: context->analogOut[n*8 + DAC_PIN5] = convAudio; robert@464: prevFiltered[0] = filterOut[0][n*2+1]; robert@464: if(prevFiltered[0]>1) robert@464: prevFiltered[0] = 1; robert@464: robert@464: /* Matrix In 5 robert@464: * robert@464: * Dissonance, via changing frequency motion of partials robert@464: */ robert@464: float amount = (float)context->analogIn[n*8 + ADC_PIN5]; robert@464: gOscBanks[gCurrentOscBank]->freqMovement = 1.0 - amount; robert@464: robert@464: robert@464: robert@464: robert@464: /* Matrix Out 6 robert@464: * robert@464: * Audio In 2 peak detection and peak burst output robert@464: */ robert@464: robert@464: filterOut[1][n*2+1] *= filterGain; robert@464: burstOut = PeakBurst[1].getOutput(); robert@464: if( burstOut < 0.1) robert@464: { robert@464: if( (prevFiltered[1]>=peakThresh) && (prevFiltered[1]>=filterOut[1][n*2+1]) ) robert@464: { robert@464: peak[1] = prevFiltered[1]; robert@464: PeakBurst[1].gate(1); robert@464: } robert@464: } robert@464: robert@464: PeakBurst[1].process(1); robert@464: robert@464: convAudio = burstOut*peak[1]; robert@464: context->analogOut[n*8 + DAC_PIN6] = convAudio; robert@464: prevFiltered[1] = filterOut[1][n*2+1]; robert@464: if(prevFiltered[1]>1) robert@464: prevFiltered[1] = 1; robert@464: robert@464: /* Matrix In 6 robert@464: * robert@464: * Sound selector robert@464: */ robert@464: if(!gIsLoading) { robert@464: // Use hysteresis to avoid jumping back and forth between sounds robert@464: if(gOscBanks.size() > 1) { robert@464: float input = context->analogIn[n*8 + ADC_PIN6]; robert@464: const float hystValue = 16000.0 / 65536.0; robert@464: robert@464: float upHysteresisValue = ((gCurrentOscBank + 1) + hystValue) / gOscBanks.size(); robert@464: float downHysteresisValue = (gCurrentOscBank - hystValue) / gOscBanks.size(); robert@464: robert@464: if(input > upHysteresisValue || input < downHysteresisValue) { robert@464: gNextOscBank = input * gOscBanks.size(); robert@464: if(gNextOscBank < 0) robert@464: gNextOscBank = 0; robert@464: if((unsigned)gNextOscBank >= gOscBanks.size()) robert@464: gNextOscBank = gOscBanks.size() - 1; robert@464: } robert@464: } robert@464: } robert@464: robert@464: /* robert@464: * Matrix In 7 robert@464: * robert@464: * FSR from primary touch sensor robert@464: * Value ranges from 0-1799 robert@464: */ robert@464: gLastFSRValue = context->analogIn[n*8 + ADC_PIN7] * 1799.0; robert@464: //gLastFSRValue = 1799 - context->analogIn[n*8 + ADC_PIN7] * (1799.0 / 65535.0); robert@464: //dbox_printf("%i\n",gLastFSRValue); robert@464: robert@464: gMatrixSampleCount++; robert@464: } robert@464: robert@464: #endif /* DBOX_CAPE_TEST */ robert@464: } robert@464: robert@464: // Medium-priority render function used for audio hop calculations robert@464: void render_medium_prio() robert@464: { robert@464: robert@464: if(gOscillatorNeedsRender) { robert@464: gOscillatorNeedsRender = false; robert@464: robert@464: /* Render one frame into the write buffer */ robert@464: memset(gOscillatorBufferWrite, 0, gOscBanks[gCurrentOscBank]->hopCounter * 2 * sizeof(float)); /* assumes 2 audio channels */ robert@464: robert@464: oscillator_bank_neon(gOscBanks[gCurrentOscBank]->hopCounter, gOscillatorBufferWrite, robert@464: gOscBanks[gCurrentOscBank]->actPartNum, gOscBanks[gCurrentOscBank]->lookupTableSize, robert@464: gOscBanks[gCurrentOscBank]->oscillatorPhases, gOscBanks[gCurrentOscBank]->oscillatorNormFrequencies, robert@464: gOscBanks[gCurrentOscBank]->oscillatorAmplitudes, robert@464: gOscBanks[gCurrentOscBank]->oscillatorNormFreqDerivatives, robert@464: gOscBanks[gCurrentOscBank]->oscillatorAmplitudeDerivatives, robert@464: /*gOscBanks[gCurrentOscBank]->lookupTable*/gDynamicWavetable); robert@464: robert@464: gOscillatorBufferWriteCurrentSize = gOscBanks[gCurrentOscBank]->hopCounter * 2; robert@464: robert@464: /* Update the pitch right before the hop robert@464: * Total CV range +/- N_OCT octaves robert@464: */ robert@464: float pitch = (float)gPitchLatestInput / octaveSplitter - N_OCT/2; robert@464: //gOscBanks[gCurrentOscBank]->pitchMultiplier = powf(2.0f, pitch); robert@464: gOscBanks[gCurrentOscBank]->pitchMultiplier = pow(2.0f, pitch); robert@464: robert@464: #ifdef FIXME_LATER // This doesn't work very well yet robert@464: gOscBanks[gCurrentOscBank]->filterNum = gSensor1LatestTouchCount; robert@464: float freqScaler = gOscBanks[gCurrentOscBank]->getFrequencyScaler(); robert@464: for(int i=0; i < gOscBanks[gCurrentOscBank]->filterNum; i++) robert@464: { robert@464: // touch pos is linear but freqs are log robert@464: gOscBanks[gCurrentOscBank]->filterFreqs[i] = ((expf(gSensor1MatrixTouchPos[i]*4)-1)/(expf(4)-1))*gOscBanks[gCurrentOscBank]->filterMaxF*freqScaler; robert@464: gOscBanks[gCurrentOscBank]->filterQ[i] = gSensor1LatestTouchSizes[i]; robert@464: if(gOscBanks[gCurrentOscBank]->filterFreqs[i]>500*freqScaler) robert@464: gOscBanks[gCurrentOscBank]->filterPadding[i] = 1+100000*( (gOscBanks[gCurrentOscBank]->filterFreqs[i]-500*freqScaler)/(gOscBanks[gCurrentOscBank]->filterMaxF-500)*freqScaler ); robert@464: else robert@464: gOscBanks[gCurrentOscBank]->filterPadding[i] = 1; robert@464: } robert@464: #endif robert@464: robert@464: RTIME ticks = rt_timer_read(); robert@464: SRTIME ns = rt_timer_tsc2ns(ticks); robert@464: SRTIME delta = ns-prevChangeNs; robert@464: robert@464: // 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: if(gNextOscBank != gCurrentOscBank && delta>100000000) { robert@464: robert@464: /*printf("ticks %llu\n", (unsigned long long)ticks); robert@464: printf("ns %llu\n", (unsigned long long)ns); robert@464: printf("prevChangeNs %llu\n", (unsigned long long)prevChangeNs); robert@464: printf("-------------------------->%llud\n", (unsigned long long)(ns-prevChangeNs));*/ robert@464: robert@464: prevChangeNs = ns; robert@464: dbox_printf("Changing to bank %d...\n", gNextOscBank); robert@464: if(gOscBanks[gCurrentOscBank]->state==bank_playing){ robert@464: gOscBanks[gCurrentOscBank]->stop(); robert@464: } robert@464: robert@464: gCurrentOscBank = gNextOscBank; robert@464: gOscBanks[gCurrentOscBank]->hopNumTh = 0; robert@464: } robert@464: else { robert@464: /* Advance to the next oscillator frame */ robert@464: gOscBanks[gCurrentOscBank]->nextHop(); robert@464: } robert@464: } robert@464: } robert@464: robert@464: // Lower-priority render function which performs matrix calculations robert@464: // State should be transferred in via global variables robert@464: void render_low_prio() robert@464: { robert@464: gPRU->setGPIOTestPin(); robert@464: if(gDynamicWavetableNeedsRender) { robert@464: // Find amplitude of wavetable robert@464: float meanAmplitude = 0; robert@464: float sineMix; robert@464: robert@464: for(int i = 0; i < gFeedbackOscillatorTableLength; i++) { robert@464: //meanAmplitude += fabsf(gFeedbackOscillatorTable[i]); robert@464: meanAmplitude += fabs(gFeedbackOscillatorTable[i]); robert@464: } robert@464: meanAmplitude /= (float)gFeedbackOscillatorTableLength; robert@464: robert@464: if(meanAmplitude > 0.35) robert@464: sineMix = 0; robert@464: else robert@464: sineMix = (.35 - meanAmplitude) / .35; robert@464: robert@464: //dbox_printf("amp %f mix %f\n", meanAmplitude, sineMix); robert@464: robert@464: // Copy to main wavetable robert@464: wavetable_interpolate(gFeedbackOscillatorTableLength, gDynamicWavetableLength, robert@464: gFeedbackOscillatorTable, gDynamicWavetable, robert@464: gOscBanks[gCurrentOscBank]->lookupTable, sineMix); robert@464: } robert@464: robert@464: if(gLoopPointMin >= 60000.0/65536.0 && gLoopPointMax >= 60000.0/65536.0) { robert@464: // KLUDGE! robert@464: if(gCurrentOscBank == 0) robert@464: gOscBanks[gCurrentOscBank]->setLoopHops(50, ((float)gOscBanks[gCurrentOscBank]->getLastHop() * 0.6) - 1); robert@464: else robert@464: gOscBanks[gCurrentOscBank]->setLoopHops(5, ((float)gOscBanks[gCurrentOscBank]->getLastHop() * 0.7) - 1); robert@464: } robert@464: else { robert@464: float normLoopPointMin = (float)gLoopPointMin * gOscBanks[gCurrentOscBank]->getLastHop(); robert@464: float normLoopPointMax = (float)gLoopPointMax * gOscBanks[gCurrentOscBank]->getLastHop(); robert@464: robert@464: int intLoopPointMin = normLoopPointMin; robert@464: if(intLoopPointMin < 1) robert@464: intLoopPointMin = 1; robert@464: int intLoopPointMax = normLoopPointMax; robert@464: if(intLoopPointMax <= intLoopPointMin) robert@464: intLoopPointMax = intLoopPointMin + 1; robert@464: if(intLoopPointMax > gOscBanks[gCurrentOscBank]->getLastHop() - 1) robert@464: intLoopPointMax = gOscBanks[gCurrentOscBank]->getLastHop() - 1; robert@464: robert@464: //dbox_printf("Loop points %d-%d / %d-%d\n", gLoopPointMin, gLoopPointMax, intLoopPointMin, intLoopPointMax); robert@464: robert@464: /* WORKS, jsut need to fix the glitch when jumps! robert@464: * *int currentHop = gOscBanks[gCurrentOscBank]->getCurrentHop(); robert@464: if(currentHop < intLoopPointMin -1 ) robert@464: gOscBanks[gCurrentOscBank]->setJumpHop(intLoopPointMin + 1); robert@464: else if(currentHop > intLoopPointMax + 1) robert@464: gOscBanks[gCurrentOscBank]->setJumpHop(intLoopPointMax - 1);*/ robert@464: gOscBanks[gCurrentOscBank]->setLoopHops(intLoopPointMin, intLoopPointMax); robert@464: } robert@464: robert@464: if(gIsLoading) robert@464: gStatusLED.blink(25, 75); // Blink quickly until load finished robert@464: else robert@464: gStatusLED.blink(250 / gOscBanks[gCurrentOscBank]->getSpeed(), 250 / gOscBanks[gCurrentOscBank]->getSpeed()); robert@464: gPRU->clearGPIOTestPin(); robert@464: robert@464: // static int counter = 32; robert@464: // if(--counter == 0) { robert@464: // for(int i = 0; i < gLoopPointsInputBufferSize; i++) { robert@464: // dbox_printf("%d ", gLoopPointsInputBuffer[i]); robert@464: // if(i % 32 == 31) robert@464: // dbox_printf("\n"); robert@464: // } robert@464: // dbox_printf("\n\n"); robert@464: // counter = 32; robert@464: // } robert@464: robert@464: //dbox_printf("min %d max %d\n", gLoopPointMin, gLoopPointMax); robert@464: } robert@464: robert@464: // Clean up at the end of render robert@464: void cleanup(BelaContext *context, void *userData) robert@464: { robert@464: free(gOscillatorBuffer1); robert@464: free(gOscillatorBuffer2); robert@464: free(gDynamicWavetable); robert@464: } robert@464: robert@464: // Interpolate one wavetable into another. The output size robert@464: // does not include the guard point at the end which will be identical robert@464: // to the first point robert@464: void wavetable_interpolate(int numSamplesIn, int numSamplesOut, robert@464: float *tableIn, float *tableOut, robert@464: float *sineTable, float sineMix) robert@464: { robert@464: float fractionalScaler = (float)numSamplesIn / (float)numSamplesOut; robert@464: robert@464: for(int k = 0; k < numSamplesOut; k++) { robert@464: float fractionalIndex = (float) k * fractionalScaler; robert@464: //int sB = (int)floorf(fractionalIndex); robert@464: int sB = (int)floor(fractionalIndex); robert@464: int sA = sB + 1; robert@464: if(sA >= numSamplesIn) robert@464: sA = 0; robert@464: float fraction = fractionalIndex - sB; robert@464: tableOut[k] = fraction * tableIn[sA] + (1.0f - fraction) * tableIn[sB]; robert@464: tableOut[k] = sineMix * sineTable[k] + (1.0 - sineMix) * tableOut[k]; robert@464: } robert@464: robert@464: tableOut[numSamplesOut] = tableOut[0]; robert@464: } robert@464: robert@464: // Create a hysteresis oscillator with a matrix input and output robert@464: inline float hysteresis_oscillator(float input, float risingThreshold, float fallingThreshold, bool *rising) robert@464: { robert@464: float value; robert@464: robert@464: if(*rising) { robert@464: if(input > risingThreshold) { robert@464: *rising = false; robert@464: value = 0; robert@464: } robert@464: else robert@464: value = 1.0; robert@464: } robert@464: else { robert@464: if(input < fallingThreshold) { robert@464: *rising = true; robert@464: value = 1.0; robert@464: } robert@464: else robert@464: value = 0; robert@464: } robert@464: robert@464: return value; robert@464: } robert@464: robert@464: #ifdef DBOX_CAPE_TEST robert@464: // Test the functionality of the D-Box cape by checking each input and output robert@464: // Loopback cable from ADC to DAC needed robert@464: void render_capetest(int numMatrixFrames, int numAudioFrames, float *audioIn, float *audioOut, robert@464: uint16_t *matrixIn, uint16_t *matrixOut) robert@464: { robert@464: static float phase = 0.0; robert@464: static int sampleCounter = 0; robert@464: static int invertChannel = 0; robert@464: robert@464: // Play a sine wave on the audio output robert@464: for(int n = 0; n < numAudioFrames; n++) { robert@464: audioOut[2*n] = audioOut[2*n + 1] = 0.5*sinf(phase); robert@464: phase += 2.0 * M_PI * 440.0 / 44100.0; robert@464: if(phase >= 2.0 * M_PI) robert@464: phase -= 2.0 * M_PI; robert@464: } robert@464: robert@464: for(int n = 0; n < numMatrixFrames; n++) { robert@464: // Change outputs every 512 samples robert@464: if(sampleCounter < 512) { robert@464: for(int k = 0; k < 8; k++) { robert@464: if(k == invertChannel) robert@464: matrixOut[n*8 + k] = 50000; robert@464: else robert@464: matrixOut[n*8 + k] = 0; robert@464: } robert@464: } robert@464: else { robert@464: for(int k = 0; k < 8; k++) { robert@464: if(k == invertChannel) robert@464: matrixOut[n*8 + k] = 0; robert@464: else robert@464: matrixOut[n*8 + k] = 50000; robert@464: } robert@464: } robert@464: robert@464: // Read after 256 samples: input should be low robert@464: if(sampleCounter == 256) { robert@464: for(int k = 0; k < 8; k++) { robert@464: if(k == invertChannel) { robert@464: if(matrixIn[n*8 + k] < 50000) { robert@464: dbox_printf("FAIL channel %d -- output HIGH input %d (inverted)\n", k, matrixIn[n*8 + k]); robert@464: } robert@464: } robert@464: else { robert@464: if(matrixIn[n*8 + k] > 2048) { robert@464: dbox_printf("FAIL channel %d -- output LOW input %d\n", k, matrixIn[n*8 + k]); robert@464: } robert@464: } robert@464: } robert@464: } robert@464: else if(sampleCounter == 768) { robert@464: for(int k = 0; k < 8; k++) { robert@464: if(k == invertChannel) { robert@464: if(matrixIn[n*8 + k] > 2048) { robert@464: dbox_printf("FAIL channel %d -- output LOW input %d (inverted)\n", k, matrixIn[n*8 + k]); robert@464: } robert@464: } robert@464: else { robert@464: if(matrixIn[n*8 + k] < 50000) { robert@464: dbox_printf("FAIL channel %d -- output HIGH input %d\n", k, matrixIn[n*8 + k]); robert@464: } robert@464: } robert@464: } robert@464: } robert@464: robert@464: if(++sampleCounter >= 1024) { robert@464: sampleCounter = 0; robert@464: invertChannel++; robert@464: if(invertChannel >= 8) robert@464: invertChannel = 0; robert@464: } robert@464: } robert@464: } robert@464: #endif robert@464: robert@464: