Skip to content
Snippets Groups Projects
maxiFFT.cpp 10.8 KiB
Newer Older
/*
 *  maximilian
 *  platform independent synthesis library using portaudio or rtaudio
 *
 *  Created by Mick Grierson on 29/12/2009.
 *  Copyright 2009 Mick Grierson & Strangeloop Limited. All rights reserved.
 *	Thanks to the Goldsmiths Creative Computing Team.
 *	Special thanks to Arturo Castro for the PortAudio implementation.
 * 
 *	Permission is hereby granted, free of charge, to any person
 *	obtaining a copy of this software and associated documentation
 *	files (the "Software"), to deal in the Software without
 *	restriction, including without limitation the rights to use,
 *	copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the
 *	Software is furnished to do so, subject to the following
 *	conditions:
 *	
 *	The above copyright notice and this permission notice shall be
 *	included in all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,	
 *	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *	OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *	HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *	WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *	OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#include "maxiFFT.h"
#include <iostream>
#include "math.h"
#include "maxiFFT_embind.h"


using namespace std;


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//F F T
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void maxiFFT::setup(int _fftSize, int _windowSize, int _hopSize) {
	_fft = new fft(_fftSize);
	fftSize = _fftSize;
	windowSize = _windowSize;
	bins = fftSize / 2;
	hopSize = _hopSize;
	buffer = (float *) malloc(fftSize * sizeof(float));
//	magnitudes = (float *) malloc(bins * sizeof(float));
	magnitudes.resize(bins);
	magnitudesDB = (float *) malloc(bins * sizeof(float));
//	phases = (float *) malloc(bins * sizeof(float));
	phases.resize(bins);
	avgPower = new float;
	memset(buffer, 0, fftSize * sizeof(float));
//	memset(magnitudes, 0, bins * sizeof(float));
	memset(magnitudesDB, 0, bins * sizeof(float));
//	memset(phases, 0, bins * sizeof(float));
	*avgPower = 0;
	pos =windowSize - hopSize;
	newFFT = 0;
	window = (float*) malloc(fftSize * sizeof(float));
	memset(window, 0, fftSize * sizeof(float));
	fft::genWindow(3, windowSize, window);
}

bool maxiFFT::process(float value) {
	//add value to buffer at current pos
	buffer[pos++] = value;
	//if buffer full, run fft
	newFFT = pos == windowSize;
	if (newFFT) {
#if defined(__APPLE_CC__) && !defined(_NO_VDSP)
		_fft->powerSpectrum_vdsp(0, buffer, window, magnitudes.data(), phases.data());
		_fft->powerSpectrum(0, buffer, window, magnitudes.data(), phases.data());
#endif
		//shift buffer back by one hop size
		memcpy(buffer, buffer + hopSize, (windowSize - hopSize) * sizeof(float));
		//set the new data to zero (is this necessary?, it will get overwritten)
		memset(buffer + windowSize - hopSize, 0, hopSize * sizeof(float));
		//reset pos to start of hop
		pos= windowSize - hopSize;
	}
	return newFFT;
}

float maxiFFT::magsToDB() {
#if defined(__APPLE_CC__) && !defined(_NO_VDSP)
	_fft->convToDB_vdsp(magnitudes.data(), magnitudesDB);
	_fft->convToDB(magnitudes.data(), magnitudesDB);
}

float maxiFFT::spectralFlatness() {
	float geometricMean=0, arithmaticMean=0;
	for(int i=0; i < bins; i++) {
		if (magnitudes[i] != 0)
			geometricMean += logf(magnitudes[i]);
		arithmaticMean += magnitudes[i];
	}
	geometricMean = expf(geometricMean / (float)bins);
	arithmaticMean /= (float)bins;
	return arithmaticMean !=0 ?  geometricMean / arithmaticMean : 0;
}

float maxiFFT::spectralCentroid() {
	float x=0, y=0;
	for(int i=0; i < bins; i++) {
		x += fabs(magnitudes[i]) * i;
		y += fabs(magnitudes[i]);
	}
	return y != 0 ? x / y * ((float) maxiSettings::sampleRate / fftSize) : 0;
}



maxiFFT::~maxiFFT() {
	delete _fft;
	if (buffer)
		delete[] buffer,/*magnitudes,phases,*/window, avgPower, magnitudesDB;
}




//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//I N V E R S E  F F T
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void maxiIFFT::setup(int _fftSize, int _windowSize, int _hopSize) {
	_fft = new fft(_fftSize);	
	fftSize = _fftSize;
	windowSize = _windowSize;
	bins = fftSize / 2;
	hopSize = _hopSize;
	buffer = (float *) malloc(fftSize * sizeof(float));
	ifftOut = (float *) malloc(fftSize * sizeof(float));
	memset(buffer, 0, windowSize * sizeof(float));
	pos =0;
	window = (float*) malloc(fftSize * sizeof(float));
	memset(window, 0, fftSize * sizeof(float));
	fft::genWindow(3, windowSize, window);
}

//float maxiIFFT::process(float *magnitudes, float *phases) {
	float maxiIFFT::process(std::vector<float>& magnitudes, std::vector<float>& phases) {

	if (0==pos) {
		//do ifft
		memset(ifftOut, 0, fftSize * sizeof(float));
		// use data() to pass first adrress of vectors
#if defined(__APPLE_CC__) && !defined(_NO_VDSP)
		_fft->inversePowerSpectrum_vdsp(0, ifftOut, window, magnitudes.data(), phases.data());
		_fft->inversePowerSpectrum(0, ifftOut, window, magnitudes.data(), phases.data());
#endif
		//add to output
		//shift back by one hop
		memcpy(buffer, buffer+hopSize, (fftSize - hopSize) * sizeof(float));
		//clear the end chunk
		memset(buffer + (fftSize - hopSize), 0, hopSize * sizeof(float)); 
		//merge new output
		for(int i=0; i < fftSize; i++) {
			buffer[i] += ifftOut[i];
		}
	}
	
	nextValue = buffer[pos];
	//limit the values, this alg seems to spike occasionally (and break the audio drivers)
	if (nextValue > 0.99999f) nextValue = 0.99999f;
	if (nextValue < -0.99999f) nextValue = -0.99999f;
	if (hopSize == ++pos ) {
		pos=0;
	}
	
	return nextValue;
}

maxiIFFT::~maxiIFFT() {
	delete _fft;
	delete[] ifftOut, buffer, window;
};







//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//O C T A V E  A N A L Y S E R
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



void maxiFFTOctaveAnalyzer::setup(float samplingRate, int nBandsInTheFFT, int nAveragesPerOctave){
	
    samplingRate = samplingRate;
    nSpectrum = nBandsInTheFFT;
    spectrumFrequencySpan = (samplingRate / 2.0f) / (float)(nSpectrum);
    nAverages = nBandsInTheFFT;
    // fe:  2f for octave bands, sqrt(2) for half-octave bands, cuberoot(2) for third-octave bands, etc
    if (nAveragesPerOctave==0) // um, wtf?
		nAveragesPerOctave = 1;
//    nAveragesPerOctave = nAveragesPerOctave;
    averageFrequencyIncrement = pow(2.0f, 1.0f/(float)(nAveragesPerOctave));
    // this isn't currently configurable (used once here then no effect), but here's some reasoning:
    // 43 is a good value if you want to approximate "computer" octaves: 44100/2/2/2/2/2/2/2/2/2/2
    // 55 is a good value if you'd rather approximate A-440 octaves: 440/2/2/2
    // 65 is a good value if you'd rather approximate "middle-C" octaves:  ~262/2/2
    // you could easily double it if you felt the lowest band was just rumble noise (as it probably is)
    // but don't go much smaller unless you have a huge fft window size (see below for more why)
    // keep in mind, if you change it, that the number of actual bands may change +/-1, and
    // for some values, the last averaging band may not be very useful (may extend above nyquist)
    firstOctaveFrequency = 55.0f;
    // for each spectrum[] bin, calculate the mapping into the appropriate average[] bin.
    // this gives us roughly log-sized averaging bins, subject to how "fine" the spectrum bins are.
    // with more spectrum bins, you can better map into the averaging bins (especially at low
    // frequencies) or use more averaging bins per octave.  with an fft window size of 2048,
    // sampling rate of 44100, and first octave around 55, that's about enough to do half-octave
    // analysis.  if you don't have enough spectrum bins to map adequately into averaging bins
    // at the requested number per octave then you'll end up with "empty" averaging bins, where
    // there is no spectrum available to map into it.  (so... if you have "nonreactive" averages,
    // either increase fft buffer size, or decrease number of averages per octave, etc)
    spe2avg = new int[nSpectrum];
    int avgidx = 0;
    float averageFreq = firstOctaveFrequency; // the "top" of the first averaging bin
    // we're looking for the "top" of the first spectrum bin, and i'm just sort of
    // guessing that this is where it is (or possibly spectrumFrequencySpan/2?)
    // ... either way it's probably close enough for these purposes
    float spectrumFreq = spectrumFrequencySpan;
    for (int speidx=0; speidx < nSpectrum; speidx++) {
		while (spectrumFreq > averageFreq) {
			avgidx++;
			averageFreq *= averageFrequencyIncrement;
		}
		spe2avg[speidx] = avgidx;
		spectrumFreq += spectrumFrequencySpan;
    }
    nAverages = avgidx;
    averages = new float[nAverages];
    peaks = new float[nAverages];
    peakHoldTimes = new int[nAverages];
    peakHoldTime = 0; // arbitrary
    peakDecayRate = 0.9f; // arbitrary
    linearEQIntercept = 1.0f; // unity -- no eq by default
    linearEQSlope = 0.0f; // unity -- no eq by default
}

//void maxiFFTOctaveAnalyzer::calculate(float * fftData){
void maxiFFTOctaveAnalyzer::calculate(vector<float>& fftData){
	int last_avgidx = 0; // tracks when we've crossed into a new averaging bin, so store current average
    float sum = 0.0f; // running total of spectrum data
    int count = 0; // count of spectrums accumulated (for averaging)
    for (int speidx=0; speidx < nSpectrum; speidx++) {
		count++;
		sum += fftData[speidx] * (linearEQIntercept + (float)(speidx) * linearEQSlope);
		int avgidx = spe2avg[speidx];
		if (avgidx != last_avgidx) {
			
			for (int j = last_avgidx; j < avgidx; j++){
				averages[j] = sum / (float)(count);
			}
			count = 0;
			sum = 0.0f;
		}
		last_avgidx = avgidx;
    }
    // the last average was probably not calculated...
    if ((count > 0) && (last_avgidx < nAverages)){
		averages[last_avgidx] = sum / (float)(count);
	}
	
    // update the peaks separately
    for (int i=0; i < nAverages; i++) {
		if (averages[i] >= peaks[i]) {
			// save new peak level, also reset the hold timer
			peaks[i] = averages[i];
			peakHoldTimes[i] = peakHoldTime;
		} else {
			// current average does not exceed peak, so hold or decay the peak
			if (peakHoldTimes[i] > 0) {
				peakHoldTimes[i]--;
			} else {
				peaks[i] *= peakDecayRate;
			}
		}
    }
}