Newer
Older
Dr-Dan
committed
/*
* 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"
Dr-Dan
committed
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));
Dr-Dan
committed
magnitudesDB = (float *) malloc(bins * sizeof(float));
// phases = (float *) malloc(bins * sizeof(float));
Dr-Dan
committed
avgPower = new float;
memset(buffer, 0, fftSize * sizeof(float));
// memset(magnitudes, 0, bins * sizeof(float));
Dr-Dan
committed
memset(magnitudesDB, 0, bins * sizeof(float));
// memset(phases, 0, bins * sizeof(float));
Dr-Dan
committed
*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());
Dr-Dan
committed
#else
_fft->powerSpectrum(0, buffer, window, magnitudes.data(), phases.data());
Dr-Dan
committed
#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() {
Dr-Dan
committed
#if defined(__APPLE_CC__) && !defined(_NO_VDSP)
_fft->convToDB_vdsp(magnitudes.data(), magnitudesDB);
Dr-Dan
committed
#else
_fft->convToDB(magnitudes.data(), magnitudesDB);
Dr-Dan
committed
#endif
return *magnitudesDB;
Dr-Dan
committed
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
}
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;
Dr-Dan
committed
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//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) {
Dr-Dan
committed
if (0==pos) {
//do ifft
memset(ifftOut, 0, fftSize * sizeof(float));
// use data() to pass first adrress of vectors
Dr-Dan
committed
#if defined(__APPLE_CC__) && !defined(_NO_VDSP)
_fft->inversePowerSpectrum_vdsp(0, ifftOut, window, magnitudes.data(), phases.data());
Dr-Dan
committed
#else
_fft->inversePowerSpectrum(0, ifftOut, window, magnitudes.data(), phases.data());
Dr-Dan
committed
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#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){
Dr-Dan
committed
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
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;
}
}
}
}