robert@493: /* robert@493: ____ _____ _ _ robert@493: | __ )| ____| | / \ robert@493: | _ \| _| | | / _ \ robert@493: | |_) | |___| |___ / ___ \ robert@493: |____/|_____|_____/_/ \_\ robert@493: robert@493: The platform for ultra-low latency audio and sensor processing robert@493: robert@493: http://bela.io robert@493: robert@493: A project of the Augmented Instruments Laboratory within the robert@493: Centre for Digital Music at Queen Mary University of London. robert@493: http://www.eecs.qmul.ac.uk/~andrewm robert@493: robert@493: (c) 2016 Augmented Instruments Laboratory: Andrew McPherson, robert@493: Astrid Bin, Liam Donovan, Christian Heinrichs, Robert Jack, robert@493: Giulio Moro, Laurel Pardue, Victor Zappi. All rights reserved. robert@493: robert@493: The Bela software is distributed under the GNU Lesser General Public License robert@493: (LGPL 3.0), available here: https://www.gnu.org/licenses/lgpl-3.0.txt robert@493: */ robert@493: robert@493: robert@493: #include robert@493: #include robert@493: #include robert@493: #include robert@493: #include robert@493: #include robert@493: robert@493: const float kMinimumFrequency = 20.0f; robert@493: const float kMaximumFrequency = 8000.0f; robert@493: robert@493: float *gWavetable; // Buffer holding the precalculated sine lookup table robert@493: float *gPhases; // Buffer holding the phase of each oscillator robert@493: float *gFrequencies; // Buffer holding the frequencies of each oscillator robert@493: float *gAmplitudes; // Buffer holding the amplitudes of each oscillator robert@493: float *gDFrequencies; // Buffer holding the derivatives of frequency robert@493: float *gDAmplitudes; // Buffer holding the derivatives of amplitude robert@493: robert@493: float gAudioSampleRate; robert@493: int gSampleCount; // Sample counter for indicating when to update frequencies robert@493: float gNewMinFrequency; robert@493: float gNewMaxFrequency; robert@493: robert@493: // Task for handling the update of the frequencies using the matrix robert@493: AuxiliaryTask gFrequencyUpdateTask; robert@493: robert@493: // These settings are carried over from main.cpp robert@493: // Setting global variables is an alternative approach robert@493: // to passing a structure to userData in setup() robert@493: robert@493: extern int gNumOscillators; robert@493: extern int gWavetableLength; robert@493: robert@493: void recalculate_frequencies(); robert@493: robert@493: extern "C" { robert@493: // Function prototype for ARM assembly implementation of oscillator bank robert@493: void oscillator_bank_neon(int numAudioFrames, float *audioOut, robert@493: int activePartialNum, int lookupTableSize, robert@493: float *phases, float *frequencies, float *amplitudes, robert@493: float *freqDerivatives, float *ampDerivatives, robert@493: float *lookupTable); robert@493: } robert@493: robert@493: // setup() is called once before the audio rendering starts. robert@493: // Use it to perform any initialisation and allocation which is dependent robert@493: // on the period size or sample rate. robert@493: // robert@493: // userData holds an opaque pointer to a data structure that was passed robert@493: // in from the call to initAudio(). robert@493: // robert@493: // Return true on success; returning false halts the program. robert@493: bool setup(BelaContext *context, void *userData) robert@493: { robert@493: srandom(time(NULL)); robert@493: chris@543: if(context->audioOutChannels != 2) { robert@493: rt_printf("Error: this example needs stereo audio enabled\n"); robert@493: return false; robert@493: } robert@493: robert@493: // Initialise the sine wavetable robert@493: if(posix_memalign((void **)&gWavetable, 8, (gWavetableLength + 1) * sizeof(float))) { robert@493: rt_printf("Error allocating wavetable\n"); robert@493: return false; robert@493: } robert@493: for(int n = 0; n < gWavetableLength + 1; n++) robert@493: gWavetable[n] = sinf(2.0 * M_PI * (float)n / (float)gWavetableLength); robert@493: robert@493: // Allocate the other buffers robert@493: if(posix_memalign((void **)&gPhases, 16, gNumOscillators * sizeof(float))) { robert@493: rt_printf("Error allocating phase buffer\n"); robert@493: return false; robert@493: } robert@493: if(posix_memalign((void **)&gFrequencies, 16, gNumOscillators * sizeof(float))) { robert@493: rt_printf("Error allocating frequency buffer\n"); robert@493: return false; robert@493: } robert@493: if(posix_memalign((void **)&gAmplitudes, 16, gNumOscillators * sizeof(float))) { robert@493: rt_printf("Error allocating amplitude buffer\n"); robert@493: return false; robert@493: } robert@493: if(posix_memalign((void **)&gDFrequencies, 16, gNumOscillators * sizeof(float))) { robert@493: rt_printf("Error allocating frequency derivative buffer\n"); robert@493: return false; robert@493: } robert@493: if(posix_memalign((void **)&gDAmplitudes, 16, gNumOscillators * sizeof(float))) { robert@493: rt_printf("Error allocating amplitude derivative buffer\n"); robert@493: return false; robert@493: } robert@493: robert@493: // Initialise buffer contents robert@493: robert@493: float freq = kMinimumFrequency; robert@493: float increment = (kMaximumFrequency - kMinimumFrequency) / (float)gNumOscillators; robert@493: robert@493: for(int n = 0; n < gNumOscillators; n++) { robert@493: gPhases[n] = 0.0; robert@493: robert@493: if(context->analogFrames == 0) { robert@493: // Random frequencies when used without matrix robert@493: gFrequencies[n] = kMinimumFrequency + (kMaximumFrequency - kMinimumFrequency) * ((float)random() / (float)RAND_MAX); robert@493: } robert@493: else { robert@493: // Constant spread of frequencies when used with matrix robert@493: gFrequencies[n] = freq; robert@493: freq += increment; robert@493: } robert@493: robert@493: // For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians robert@493: gFrequencies[n] *= (float)gWavetableLength / context->audioSampleRate; robert@493: gAmplitudes[n] = ((float)random() / (float)RAND_MAX) / (float)gNumOscillators; robert@493: gDFrequencies[n] = gDAmplitudes[n] = 0.0; robert@493: } robert@493: robert@493: increment = 0; robert@493: freq = 440.0; robert@493: robert@493: for(int n = 0; n < gNumOscillators; n++) { robert@493: // Update the frequencies to a regular spread, plus a small amount of randomness robert@493: // to avoid weird phase effects robert@493: float randScale = 0.99 + .02 * (float)random() / (float)RAND_MAX; robert@493: float newFreq = freq * randScale; robert@493: robert@493: // For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians robert@493: gFrequencies[n] = newFreq * (float)gWavetableLength / context->audioSampleRate; robert@493: robert@493: freq += increment; robert@493: } robert@493: robert@493: // Initialise auxiliary tasks robert@493: if((gFrequencyUpdateTask = Bela_createAuxiliaryTask(&recalculate_frequencies, 85, "bela-update-frequencies")) == 0) robert@493: return false; robert@493: robert@493: //for(int n = 0; n < gNumOscillators; n++) robert@493: // rt_printf("%f\n", gFrequencies[n]); robert@493: robert@493: gAudioSampleRate = context->audioSampleRate; robert@493: gSampleCount = 0; robert@493: robert@493: return true; robert@493: } robert@493: robert@493: // render() is called regularly at the highest priority by the audio engine. robert@493: // Input and output are given from the audio hardware and the other robert@493: // ADCs and DACs (if available). If only audio is available, numMatrixFrames robert@493: // will be 0. robert@493: robert@493: void render(BelaContext *context, void *userData) robert@493: { robert@493: // Initialise buffer to 0 robert@493: memset(context->audioOut, 0, 2 * context->audioFrames * sizeof(float)); robert@493: robert@493: // Render audio frames robert@493: oscillator_bank_neon(context->audioFrames, context->audioOut, robert@493: gNumOscillators, gWavetableLength, robert@493: gPhases, gFrequencies, gAmplitudes, robert@493: gDFrequencies, gDAmplitudes, robert@493: gWavetable); robert@493: robert@493: if(context->analogFrames != 0 && (gSampleCount += context->audioFrames) >= 128) { robert@493: gSampleCount = 0; robert@493: gNewMinFrequency = map(context->analogIn[0], 0, 1.0, 1000.0f, 8000.0f); robert@493: gNewMaxFrequency = map(context->analogIn[1], 0, 1.0, 1000.0f, 8000.0f); robert@493: robert@493: // Make sure max >= min robert@493: if(gNewMaxFrequency < gNewMinFrequency) { robert@493: float temp = gNewMaxFrequency; robert@493: gNewMaxFrequency = gNewMinFrequency; robert@493: gNewMinFrequency = temp; robert@493: } robert@493: robert@493: // Request that the lower-priority task run at next opportunity robert@493: //Bela_scheduleAuxiliaryTask(gFrequencyUpdateTask); robert@493: } robert@493: } robert@493: robert@493: // This is a lower-priority call to update the frequencies which will happen robert@493: // periodically when the matrix is enabled. By placing it at a lower priority, robert@493: // it has minimal effect on the audio performance but it will take longer to robert@493: // complete if the system is under heavy audio load. robert@493: robert@493: void recalculate_frequencies() robert@493: { robert@493: float freq = gNewMinFrequency; robert@493: float increment = (gNewMaxFrequency - gNewMinFrequency) / (float)gNumOscillators; robert@493: robert@493: for(int n = 0; n < gNumOscillators; n++) { robert@493: // Update the frequencies to a regular spread, plus a small amount of randomness robert@493: // to avoid weird phase effects robert@493: float randScale = 0.99 + .02 * (float)random() / (float)RAND_MAX; robert@493: float newFreq = freq * randScale; robert@493: robert@493: // For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians robert@493: gFrequencies[n] = newFreq * (float)gWavetableLength / gAudioSampleRate; robert@493: robert@493: freq += increment; robert@493: } robert@493: } robert@493: robert@493: robert@493: // cleanup() is called once at the end, after the audio has stopped. robert@493: // Release any resources that were allocated in setup(). robert@493: robert@493: void cleanup(BelaContext *context, void *userData) robert@493: { robert@493: free(gWavetable); robert@493: free(gPhases); robert@493: free(gFrequencies); robert@493: free(gAmplitudes); robert@493: free(gDFrequencies); robert@493: free(gDAmplitudes); robert@493: } robert@493: robert@493: /** robert@500: \example oscillator-bank/render.cpp robert@493: robert@493: Oscillator Bank robert@493: ---------------------- robert@493: robert@493: These files demonstrate an oscillator bank implemented in assembly code robert@493: that is used as part of the d-box project. robert@493: */ robert@493: