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);
+}
Binary file projects/basic_FFT_phase_vocoder/sample.wav has changed