Mercurial > hg > beaglert
changeset 250:aec268b5d1b4
Added phase vocoder example (optionally Midi controllable)
author | Giulio Moro <giuliomoro@yahoo.it> |
---|---|
date | Thu, 28 Apr 2016 19:30:12 +0100 |
parents | f2d47df23c68 |
children | cbf70fe3711b |
files | projects/basic_FFT_phase_vocoder/SampleData.h projects/basic_FFT_phase_vocoder/main.cpp projects/basic_FFT_phase_vocoder/render.cpp projects/basic_FFT_phase_vocoder/sample.wav |
diffstat | 4 files changed, 486 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/projects/basic_FFT_phase_vocoder/SampleData.h Thu Apr 28 19:30:12 2016 +0100 @@ -0,0 +1,19 @@ +/* + * SampleData.h + * + * Created on: Nov 5, 2014 + * Author: Victor Zappi + */ + +#ifndef SAMPLEDATA_H_ +#define SAMPLEDATA_H_ + +// User defined structure to pass between main and rendere complex data retrieved from file +struct SampleData { + float *samples; // Samples in file + int sampleLen; // Total nume of samples +}; + + + +#endif /* SAMPLEDATA_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/projects/basic_FFT_phase_vocoder/main.cpp Thu Apr 28 19:30:12 2016 +0100 @@ -0,0 +1,196 @@ +/* + * main.cpp + * + * Created on: Oct 24, 2014 + * Author: parallels + */ + +#include <iostream> +#include <cstdlib> +#include <cstdio> +#include <libgen.h> +#include <signal.h> +#include <getopt.h> +#include <unistd.h> +#include <sys/time.h> +#include <sndfile.h> // to load audio files +#include "SampleData.h" +#include <BeagleRT.h> + +using namespace std; + +// Global variables used by getCurrentTime() +unsigned long long gFirstSeconds, gFirstMicroseconds; + +// Load samples from file +int initFile(string file, SampleData *smp)//float *& smp) +{ + SNDFILE *sndfile ; + SF_INFO sfinfo ; + + if (!(sndfile = sf_open (file.c_str(), SFM_READ, &sfinfo))) { + cout << "Couldn't open file " << file << endl; + return 1; + } + + int numChan = sfinfo.channels; + if(numChan != 1) + { + cout << "Error: " << file << " is not a mono file" << endl; + return 1; + } + + smp->sampleLen = sfinfo.frames * numChan; + smp->samples = new float[smp->sampleLen]; + if(smp == NULL){ + cout << "Could not allocate buffer" << endl; + return 1; + } + + int subformat = sfinfo.format & SF_FORMAT_SUBMASK; + int readcount = sf_read_float(sndfile, smp->samples, smp->sampleLen); + + // Pad with zeros in case we couldn't read whole file + for(int k = readcount; k <smp->sampleLen; k++) + smp->samples[k] = 0; + + if (subformat == SF_FORMAT_FLOAT || subformat == SF_FORMAT_DOUBLE) { + double scale ; + int m ; + + sf_command (sndfile, SFC_CALC_SIGNAL_MAX, &scale, sizeof (scale)) ; + if (scale < 1e-10) + scale = 1.0 ; + else + scale = 32700.0 / scale ; + cout << "File samples scale = " << scale << endl; + + for (m = 0; m < smp->sampleLen; m++) + smp->samples[m] *= scale; + } + + sf_close(sndfile); + + return 0; +} + + +// Handle Ctrl-C by requesting that the audio rendering stop +void interrupt_handler(int var) +{ + gShouldStop = true; +} + +// Print usage information +void usage(const char * processName) +{ + cerr << "Usage: " << processName << " [options]" << endl; + + BeagleRT_usage(); + + cerr << " --help [-h]: Print this menu\n"; +} + +/* Function which returns the time since start of the program + * in (fractional) seconds. + */ +double getCurrentTime(void) { + unsigned long long result; + struct timeval tv; + + gettimeofday(&tv, NULL); + result = (tv.tv_sec - gFirstSeconds) * 1000000ULL + (tv.tv_usec - gFirstMicroseconds); + return (double)result / 1000000.0; +} +extern SampleData gSampleData; +int main(int argc, char *argv[]) +{ + BeagleRTInitSettings settings; // Standard audio settings + struct timeval tv; + string fileName; // Name of the sample to load + + struct option customOptions[] = + { + {"help", 0, NULL, 'h'}, + {"file", 1, NULL, 'f'}, + {NULL, 0, NULL, 0} + }; + + gSampleData.samples = 0; + gSampleData.sampleLen = -1; + + // Set default settings + BeagleRT_defaultSettings(&settings); + + settings.periodSize = 32; // Larger period size by default, for testing + + // Parse command-line arguments + while (1) { + int c; + if ((c = BeagleRT_getopt_long(argc, argv, "hf:", customOptions, &settings)) < 0) + break; + switch (c) { + case 'h': + usage(basename(argv[0])); + exit(0); + case 'f': + fileName = string((char *)optarg); + break; + case '?': + default: + usage(basename(argv[0])); + exit(1); + } + } + + if(fileName.empty()){ + fileName = "sample.wav"; + } + + + // Load file + if(initFile(fileName, &gSampleData) != 0) + { + cout << "Error: unable to load samples " << endl; + return -1; + } + + if(settings.verbose) + cout << "File contains " << gSampleData.sampleLen << " samples" << endl; + + + // Initialise the PRU audio device + if(BeagleRT_initAudio(&settings, &gSampleData) != 0) { + cout << "Error: unable to initialise audio" << endl; + return -1; + } + + // Initialise time + gettimeofday(&tv, NULL); + gFirstSeconds = tv.tv_sec; + gFirstMicroseconds = tv.tv_usec; + + // Start the audio device running + if(BeagleRT_startAudio()) { + cout << "Error: unable to start real-time audio" << endl; + return -1; + } + + // Set up interrupt handler to catch Control-C + signal(SIGINT, interrupt_handler); + signal(SIGTERM, interrupt_handler); + + // Run until told to stop + while(!gShouldStop) { + usleep(100000); + } + + // Stop the audio device + BeagleRT_stopAudio(); + + // Clean up any resources allocated for audio + BeagleRT_cleanupAudio(); + + // All done! + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/projects/basic_FFT_phase_vocoder/render.cpp Thu Apr 28 19:30:12 2016 +0100 @@ -0,0 +1,271 @@ +/* + * render.cpp + * + * Created on: Oct 24, 2014 + * Author: parallels + */ + + +#include <BeagleRT.h> +#include <rtdk.h> +#include <NE10.h> // NEON FFT library +#include <cmath> +#include "SampleData.h" +#include <Midi.h> + +#define BUFFER_SIZE 16384 + +// TODO: your buffer and counter go here! +float gInputBuffer[BUFFER_SIZE]; +int gInputBufferPointer = 0; +float gOutputBuffer[BUFFER_SIZE]; +int gOutputBufferWritePointer = 0; +int gOutputBufferReadPointer = 0; +int gSampleCount = 0; + +float *gWindowBuffer; + +// ----------------------------------------------- +// These variables used internally in the example: +int gFFTSize = 2048; +int gHopSize = 512; +int gPeriod = 512; +float gFFTScaleFactor = 0; + +// FFT vars +ne10_fft_cpx_float32_t* timeDomainIn; +ne10_fft_cpx_float32_t* timeDomainOut; +ne10_fft_cpx_float32_t* frequencyDomain; +ne10_fft_cfg_float32_t cfg; + +// Sample info +SampleData gSampleData; // User defined structure to get complex data from main +int gReadPtr = 0; // Position of last read sample from file + +// Auxiliary task for calculating FFT +AuxiliaryTask gFFTTask; +int gFFTInputBufferPointer = 0; +int gFFTOutputBufferPointer = 0; + +void process_fft_background(); + + +int gEffect = 0; // change this here or with midi CC +enum{ + kBypass, + kRobot, + kWhisper, +}; + +float gDryWet = 1; // mix between the unprocessed and processed sound +float gPlaybackLive = 0.5f; // mix between the file playback and the live audio input +float gGain = 1; // overall gain +Midi midi; +void midiCallback(MidiChannelMessage message, void* arg){ + if(message.getType() == kmmNoteOn){ + if(message.getDataByte(1) > 0){ + int note = message.getDataByte(0); + float frequency = powf(2, (note-69)/12.f)*440; + gPeriod = (int)(44100 / frequency + 0.5); + printf("\nnote: %d, frequency: %f, hop: %d\n", note, frequency, gPeriod); + } + } + + bool shouldPrint = false; + if(message.getType() == kmmControlChange){ + float data = message.getDataByte(1) / 127.0f; + switch (message.getDataByte(0)){ + case 2 : + gEffect = (int)(data * 2 + 0.5); // CC2 selects an effect between 0,1,2 + break; + case 3 : + gPlaybackLive = data; + break; + case 4 : + gDryWet = data; + break; + case 5: + gGain = data*10; + break; + default: + shouldPrint = true; + } + } + if(shouldPrint){ + message.prettyPrint(); + } +} + +// userData holds an opaque pointer to a data structure that was passed +// in from the call to initAudio(). +// +// Return true on success; returning false halts the program. +bool setup(BeagleRTContext* context, void* userData) +{ + midi.readFrom(0); + midi.setParserCallback(midiCallback); + // Retrieve a parameter passed in from the initAudio() call + gSampleData = *(SampleData *)userData; + + gFFTScaleFactor = 1.0f / (float)gFFTSize; + gOutputBufferWritePointer += gHopSize; + + timeDomainIn = (ne10_fft_cpx_float32_t*) NE10_MALLOC (gFFTSize * sizeof (ne10_fft_cpx_float32_t)); + timeDomainOut = (ne10_fft_cpx_float32_t*) NE10_MALLOC (gFFTSize * sizeof (ne10_fft_cpx_float32_t)); + frequencyDomain = (ne10_fft_cpx_float32_t*) NE10_MALLOC (gFFTSize * sizeof (ne10_fft_cpx_float32_t)); + cfg = ne10_fft_alloc_c2c_float32 (gFFTSize); + + memset(timeDomainOut, 0, gFFTSize * sizeof (ne10_fft_cpx_float32_t)); + memset(gOutputBuffer, 0, BUFFER_SIZE * sizeof(float)); + + // Allocate the window buffer based on the FFT size + gWindowBuffer = (float *)malloc(gFFTSize * sizeof(float)); + if(gWindowBuffer == 0) + return false; + + // Calculate a Hann window + for(int n = 0; n < gFFTSize; n++) { + gWindowBuffer[n] = 0.5f * (1.0f - cosf(2.0 * M_PI * n / (float)(gFFTSize - 1))); + } + + // Initialise auxiliary tasks + if((gFFTTask = BeagleRT_createAuxiliaryTask(&process_fft_background, 90, "fft-calculation")) == 0) + return false; + rt_printf("You are listening to an FFT phase-vocoder with overlap-and-add " + "Use Midi Control Change to control:\n" + "CC 2: effect type (bypass/robotization/whisperization)" + "CC 3: mix between recorded sample and live audio input" + "CC 4: mix between the unprocessed and processed sound" + "CC 5: gain" + ); + return true; +} + +// This function handles the FFT processing in this example once the buffer has +// been assembled. +void process_fft(float *inBuffer, int inWritePointer, float *outBuffer, int outWritePointer) +{ + // Copy buffer into FFT input + int pointer = (inWritePointer - gFFTSize + BUFFER_SIZE) % BUFFER_SIZE; + for(int n = 0; n < gFFTSize; n++) { + timeDomainIn[n].r = (ne10_float32_t) inBuffer[pointer] * gWindowBuffer[n]; + timeDomainIn[n].i = 0; + + pointer++; + if(pointer >= BUFFER_SIZE) + pointer = 0; + } + + // Run the FFT + ne10_fft_c2c_1d_float32_neon (frequencyDomain, timeDomainIn, cfg->twiddles, cfg->factors, gFFTSize, 0); + + switch (gEffect){ + case kRobot : + // Robotise the output + for(int n = 0; n < gFFTSize; n++) { + float amplitude = sqrtf(frequencyDomain[n].r * frequencyDomain[n].r + frequencyDomain[n].i * frequencyDomain[n].i); + frequencyDomain[n].r = amplitude; + frequencyDomain[n].i = 0; + } + break; + case kWhisper : + for(int n = 0; n < gFFTSize; n++) { + float amplitude = sqrtf(frequencyDomain[n].r * frequencyDomain[n].r + frequencyDomain[n].i * frequencyDomain[n].i); + float phase = rand()/(float)RAND_MAX * 2 * M_PI; + frequencyDomain[n].r = cosf(phase) * amplitude; + frequencyDomain[n].i = sinf(phase) * amplitude; + } + break; + case kBypass: + //bypass + break; + } + + // Run the inverse FFT + ne10_fft_c2c_1d_float32_neon (timeDomainOut, frequencyDomain, cfg->twiddles, cfg->factors, gFFTSize, 1); + // Overlap-and-add timeDomainOut into the output buffer + pointer = outWritePointer; + for(int n = 0; n < gFFTSize; n++) { + outBuffer[pointer] += (timeDomainOut[n].r) * gFFTScaleFactor; + if(isnan(outBuffer[pointer])) + rt_printf("outBuffer OLA\n"); + pointer++; + if(pointer >= BUFFER_SIZE) + pointer = 0; + } +} + +// Function to process the FFT in a thread at lower priority +void process_fft_background() { + process_fft(gInputBuffer, gFFTInputBufferPointer, gOutputBuffer, gFFTOutputBufferPointer); +} + +// render() is called regularly at the highest priority by the audio engine. +// Input and output are given from the audio hardware and the other +// ADCs and DACs (if available). If only audio is available, numMatrixFrames +// will be 0. +void render(BeagleRTContext* context, void* userData) +{ + float* audioIn = context->audioIn; + float* audioOut = context->audioOut; + int numAudioFrames = context->audioFrames; + int numAudioChannels = context->audioChannels; + // ------ this code internal to the demo; leave as is ---------------- + + // Prep the "input" to be the sound file played in a loop + for(int n = 0; n < numAudioFrames; n++) { + if(gReadPtr < gSampleData.sampleLen) + audioIn[2*n] = audioIn[2*n+1] = gSampleData.samples[gReadPtr]*(1-gPlaybackLive) + + gPlaybackLive*0.5f*(audioReadFrame(context,n,0)+audioReadFrame(context,n,1)); + else + audioIn[2*n] = audioIn[2*n+1] = 0; + if(++gReadPtr >= gSampleData.sampleLen) + gReadPtr = 0; + } + // ------------------------------------------------------------------- + + for(int n = 0; n < numAudioFrames; n++) { + gInputBuffer[gInputBufferPointer] = ((audioIn[n*numAudioChannels] + audioIn[n*numAudioChannels+1]) * 0.5); + + // Copy output buffer to output + for(int channel = 0; channel < numAudioChannels; channel++){ + audioOut[n * numAudioChannels + channel] = gOutputBuffer[gOutputBufferReadPointer] * gGain * gDryWet + (1 - gDryWet) * audioIn[n * numAudioChannels + channel]; + } + + // Clear the output sample in the buffer so it is ready for the next overlap-add + gOutputBuffer[gOutputBufferReadPointer] = 0; + gOutputBufferReadPointer++; + if(gOutputBufferReadPointer >= BUFFER_SIZE) + gOutputBufferReadPointer = 0; + gOutputBufferWritePointer++; + if(gOutputBufferWritePointer >= BUFFER_SIZE) + gOutputBufferWritePointer = 0; + + gInputBufferPointer++; + if(gInputBufferPointer >= BUFFER_SIZE) + gInputBufferPointer = 0; + + gSampleCount++; + if(gSampleCount >= gHopSize) { + //process_fft(gInputBuffer, gInputBufferPointer, gOutputBuffer, gOutputBufferPointer); + gFFTInputBufferPointer = gInputBufferPointer; + gFFTOutputBufferPointer = gOutputBufferWritePointer; + BeagleRT_scheduleAuxiliaryTask(gFFTTask); + + gSampleCount = 0; + } + } + gHopSize = gPeriod; +} + +// cleanup_render() is called once at the end, after the audio has stopped. +// Release any resources that were allocated in initialise_render(). + +void cleanup(BeagleRTContext* context, void* userData) +{ + NE10_FREE(timeDomainIn); + NE10_FREE(timeDomainOut); + NE10_FREE(frequencyDomain); + NE10_FREE(cfg); + free(gWindowBuffer); +}