Skip to content
Browse files

onsetd

  • Loading branch information...
1 parent d7e2c86 commit 9c53305716a0770dcadb4c909cbda62a2dae3ecc @Akira-Hayasaka committed Apr 11, 2012
View
BIN addons/ofxOnsetDetection/.DS_Store
Binary file not shown.
View
7 addons/ofxOnsetDetection/README.md
@@ -0,0 +1,7 @@
+# ofxOnsetDetection #
+Audio onset detection addon using ofSoundGetSpectrum (or any frequency-domain data. e.g. FFT on the input audio)
+
+core lib => http://onsetsds.sourceforge.net/
+
+### usage ###
+see the example testApp.h/.cpp
View
547 addons/ofxOnsetDetection/src/OnsetsDS/onsetsds.c
@@ -0,0 +1,547 @@
+/*
+ OnsetsDS - real time musical onset detection library.
+ Copyright (c) 2007 Dan Stowell. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+
+#include "onsetsds.h"
+
+
+#define ODS_DEBUG_POST_CSV 0
+
+// Inline
+inline float onsetsds_phase_rewrap(float phase);
+inline float onsetsds_phase_rewrap(float phase){
+ return (phase>MINUSPI && phase<PI) ? phase : phase + TWOPI * (1.f + floorf((MINUSPI - phase) * INV_TWOPI));
+}
+
+
+size_t onsetsds_memneeded (int odftype, size_t fftsize, unsigned int medspan){
+
+ /*
+ Need memory for:
+ - median calculation (2 * medspan floats)
+ - storing old values (whether as OdsPolarBuf or as weirder float lists)
+ - storing the OdsPolarBuf (size is NOT sizeof(OdsPolarBuf) but is fftsize)
+ - storing the PSP (numbins + 2 values)
+ All these are floats.
+ */
+
+ int numbins = (fftsize >> 1) - 1; // No of bins, not counting DC/nyq
+
+ switch(odftype){
+ case ODS_ODF_POWER:
+ case ODS_ODF_MAGSUM:
+
+ // No old FFT frames needed, easy:
+ return (medspan+medspan + fftsize + numbins + 2) * sizeof(float);
+
+ case ODS_ODF_COMPLEX:
+ case ODS_ODF_RCOMPLEX:
+
+ return (medspan+medspan + fftsize + numbins + 2
+ // For each bin (NOT dc/nyq) we store mag, phase and d_phase
+ + numbins + numbins + numbins
+ ) * sizeof(float);
+
+ case ODS_ODF_PHASE:
+ case ODS_ODF_WPHASE:
+
+ return (medspan+medspan + fftsize + numbins + 2
+ // For each bin (NOT dc/nyq) we store phase and d_phase
+ + numbins + numbins
+ ) * sizeof(float);
+
+ case ODS_ODF_MKL:
+
+ return (medspan+medspan + fftsize + numbins + 2
+ // For each bin (NOT dc/nyq) we store mag
+ + numbins
+ ) * sizeof(float);
+
+
+ break;
+
+ }
+ return -1; //bleh
+}
+
+
+void onsetsds_init(OnsetsDS *ods, float *odsdata, int fftformat,
+ int odftype, size_t fftsize, unsigned int medspan, float srate){
+
+ // The main pointer to the processing area - other pointers will indicate areas within this
+ ods->data = odsdata;
+ // Set all vals in processing area to zero
+ memset(odsdata, 0, onsetsds_memneeded(odftype, fftsize, medspan));
+
+ ods->srate = srate;
+
+ int numbins = (fftsize >> 1) - 1; // No of bins, not counting DC/nyq
+ int realnumbins = numbins + 2;
+
+ // Also point the other pointers to the right places
+ ods->curr = (OdsPolarBuf*) odsdata;
+ ods->psp = odsdata + fftsize;
+ ods->odfvals = odsdata + fftsize + realnumbins;
+ ods->sortbuf = odsdata + fftsize + realnumbins + medspan;
+ ods->other = odsdata + fftsize + realnumbins + medspan + medspan;
+
+ // Default settings for Adaptive Whitening, user can set own values after init
+ onsetsds_setrelax(ods, 1.f, fftsize>>1);
+ ods->floor = 0.1;
+
+ switch(odftype){
+ case ODS_ODF_POWER:
+ ods->odfparam = 0.01; // "powthresh" in SC code
+ ods->normfactor = 2560.f / (realnumbins * fftsize);
+ break;
+ case ODS_ODF_MAGSUM:
+ ods->odfparam = 0.01; // "powthresh" in SC code
+ ods->normfactor = 113.137085f / (realnumbins * sqrt(fftsize));
+ break;
+ case ODS_ODF_COMPLEX:
+ ods->odfparam = 0.01; // "powthresh" in SC code
+ ods->normfactor = 231.70475f / pow(fftsize, 1.5);// / fftsize;
+ break;
+ case ODS_ODF_RCOMPLEX:
+ ods->odfparam = 0.01; // "powthresh" in SC code
+ ods->normfactor = 231.70475f / pow(fftsize, 1.5);// / fftsize;
+ break;
+ case ODS_ODF_PHASE:
+ ods->odfparam = 0.01; // "powthresh" in SC code
+ ods->normfactor = 5.12f / fftsize;// / fftsize;
+ break;
+ case ODS_ODF_WPHASE:
+ ods->odfparam = 0.0001; // "powthresh" in SC code. For WPHASE it's kind of superfluous.
+ ods->normfactor = 115.852375f / pow(fftsize, 1.5);// / fftsize;
+ break;
+ case ODS_ODF_MKL:
+ ods->odfparam = 0.01; // EPSILON parameter. Brossier recommends 1e-6 but I (ICMC 2007) found larger vals (e.g 0.01) to work better
+ ods->normfactor = 7.68f * 0.25f / fftsize;
+ break;
+ default:
+ printf("onsetsds_init ERROR: \"odftype\" is not a recognised value\n");
+ }
+
+ ods->odfvalpost = 0.f;
+ ods->odfvalpostprev = 0.f;
+ ods->thresh = 0.5f;
+ ods->logmags = false;
+
+ ods->odftype = odftype;
+ ods->whtype = ODS_WH_ADAPT_MAX1;
+ ods->fftformat = fftformat;
+
+ ods->whiten = (odftype != ODS_ODF_MKL); // Deactivate whitening for MKL by default
+ ods->detected = false;
+ ods->med_odd = (medspan & 1) != 0;
+
+ ods->medspan = medspan;
+
+ ods->mingap = 0;
+ ods->gapleft = 0;
+
+ ods->fftsize = fftsize;
+ ods->numbins = numbins;
+
+ //printf("End of _init: normfactor is %g\n", ods->normfactor);
+
+}
+
+bool onsetsds_process(OnsetsDS* ods, float* fftbuf){
+ onsetsds_loadframe(ods, fftbuf);
+
+ onsetsds_whiten(ods);
+ onsetsds_odf(ods);
+ onsetsds_detect(ods);
+
+ return ods->detected;
+}
+
+
+void onsetsds_setrelax(OnsetsDS* ods, float time, size_t hopsize){
+ ods->relaxtime = time;
+ ods->relaxcoef = (time == 0.0f) ? 0.0f : exp((ods_log1 * hopsize)/(time * ods->srate));
+}
+
+
+
+void onsetsds_loadframe(OnsetsDS* ods, float* fftbuf){
+
+ float *pos, *pos2, imag, real;
+ int i;
+
+ switch(ods->fftformat){
+ case ODS_FFT_SC3_POLAR:
+ // The format is the same! dc, nyq, mag[1], phase[1], ...
+ memcpy(ods->curr, fftbuf, ods->fftsize * sizeof(float));
+ break;
+
+ case ODS_FFT_SC3_COMPLEX:
+
+ ods->curr->dc = fftbuf[0];
+ ods->curr->nyq = fftbuf[1];
+
+ // Then convert cartesian to polar:
+ pos = fftbuf + 2;
+ for(i=0; i< (ods->numbins << 1); i += 2){
+ real = pos[i];
+ imag = pos[i+1]; // Plus 1 rather than increment; seems to avoid LSU reject on my PPC
+ ods->curr->bin[i].mag = hypotf(imag, real);
+ ods->curr->bin[i].phase = atan2f(imag, real);
+ }
+ break;
+
+ case ODS_FFT_FFTW3_HC:
+
+ ods->curr->dc = fftbuf[0];
+ ods->curr->nyq = fftbuf[ods->fftsize>>1];
+
+ // Then convert cartesian to polar:
+ // (Starting positions: real and imag for bin 1)
+ pos = fftbuf + 1;
+ pos2 = fftbuf + ods->fftsize - 1;
+ for(i=0; i<ods->numbins; i++){
+ real = *(pos++);
+ imag = *(pos2--);
+ ods->curr->bin[i].mag = hypotf(imag, real);
+ ods->curr->bin[i].phase = atan2f(imag, real);
+ }
+ break;
+
+ case ODS_FFT_FFTW3_R2C:
+
+ ods->curr->dc = fftbuf[0];
+ ods->curr->nyq = fftbuf[ods->fftsize];
+
+ // Then convert cartesian to polar:
+ pos = fftbuf + 2;
+ for(i=0; i<ods->numbins; i++){
+ real = *(pos++);
+ imag = *(pos++);
+ ods->curr->bin[i].mag = hypotf(imag, real);
+ ods->curr->bin[i].phase = atan2f(imag, real);
+ }
+ break;
+
+ }
+
+ // Conversion to log-domain magnitudes, including re-scaling to aim back at the zero-to-one range.
+ // Not well tested yet.
+ if(ods->logmags){
+ for(i=0; i<ods->numbins; i++){
+ ods->curr->bin[i].mag =
+ (log(ods_max(ods->curr->bin[i].mag, ODS_LOG_LOWER_LIMIT)) - ODS_LOGOF_LOG_LOWER_LIMIT) * ODS_ABSINVOF_LOGOF_LOG_LOWER_LIMIT;
+ }
+ ods->curr->dc =
+ (log(ods_max(ods_abs(ods->curr->dc ), ODS_LOG_LOWER_LIMIT)) - ODS_LOGOF_LOG_LOWER_LIMIT) * ODS_ABSINVOF_LOGOF_LOG_LOWER_LIMIT;
+ ods->curr->nyq =
+ (log(ods_max(ods_abs(ods->curr->nyq), ODS_LOG_LOWER_LIMIT)) - ODS_LOGOF_LOG_LOWER_LIMIT) * ODS_ABSINVOF_LOGOF_LOG_LOWER_LIMIT;
+ }
+
+}
+
+void onsetsds_whiten(OnsetsDS* ods){
+
+ if(ods->whtype == ODS_WH_NONE){
+ //printf("onsetsds_whiten(): ODS_WH_NONE, skipping\n");
+ return;
+ }
+
+ // NB: Apart from the above, ods->whtype is currently IGNORED and only one mode is used.
+
+
+ float val,oldval, relaxcoef, floor;
+ int numbins, i;
+ OdsPolarBuf *curr;
+ float *psp;
+ float *pspp1; // Offset by 1, avoids quite a lot of "+1"s in the following code
+
+ relaxcoef = ods->relaxcoef;
+ numbins = ods->numbins;
+ curr = ods->curr;
+ psp = ods->psp;
+ pspp1 = psp + 1;
+ floor = ods->floor;
+
+ //printf("onsetsds_whiten: relaxcoef=%g, relaxtime=%g, floor=%g\n", relaxcoef, ods->relaxtime, floor);
+
+ ////////////////////// For each bin, update the record of the peak value /////////////////////
+
+ val = fabs(curr->dc); // Grab current magnitude
+ oldval = psp[0];
+ // If it beats the amplitude stored then that's our new amplitude;
+ // otherwise our new amplitude is a decayed version of the old one
+ if(val < oldval) {
+ val = val + (oldval - val) * relaxcoef;
+ }
+ psp[0] = val; // Store the "amplitude trace" back
+
+ val = fabs(curr->nyq);
+ oldval = pspp1[numbins];
+ if(val < oldval) {
+ val = val + (oldval - val) * relaxcoef;
+ }
+ pspp1[numbins] = val;
+
+ for(i=0; i<numbins; ++i){
+ val = fabs(curr->bin[i].mag);
+ oldval = pspp1[i];
+ if(val < oldval) {
+ val = val + (oldval - val) * relaxcoef;
+ }
+ pspp1[i] = val;
+ }
+
+ //////////////////////////// Now for each bin, rescale the current magnitude ////////////////////////////
+ curr->dc /= ods_max(floor, psp[0]);
+ curr->nyq /= ods_max(floor, pspp1[numbins]);
+ for(i=0; i<numbins; ++i){
+ curr->bin[i].mag /= ods_max(floor, pspp1[i]);
+ }
+}
+
+void onsetsds_odf(OnsetsDS* ods){
+
+ int numbins = ods->numbins;
+ OdsPolarBuf *curr = ods->curr;
+ float* val = ods->odfvals;
+ int i, tbpointer;
+ float deviation, diff, curmag;
+ double totdev;
+
+ bool rectify = true;
+
+ // Here we shunt the "old" ODF values down one place
+ memcpy(val + 1, val, (ods->medspan - 1)*sizeof(float));
+
+ // Now calculate a new value and store in ods->odfvals[0]
+ switch(ods->odftype){
+ case ODS_ODF_POWER:
+
+ *val = (curr->nyq * curr->nyq) + (curr->dc * curr->dc);
+ for(i=0; i<numbins; i++){
+ *val += curr->bin[i].mag * curr->bin[i].mag;
+ }
+ break;
+
+ case ODS_ODF_MAGSUM:
+
+ *val = ods_abs(curr->nyq) + ods_abs(curr->dc);
+
+ for(i=0; i<numbins; i++){
+ *val += ods_abs(curr->bin[i].mag);
+ }
+ break;
+
+ case ODS_ODF_COMPLEX:
+ rectify = false;
+ // ...and then drop through to:
+ case ODS_ODF_RCOMPLEX:
+
+ // Note: "other" buf is stored in this format: mag[0],phase[0],d_phase[0],mag[1],phase[1],d_phase[1], ...
+
+ // Iterate through, calculating the deviation from expected value.
+ totdev = 0.0;
+ tbpointer = 0;
+ float predmag, predphase, yesterphase, yesterphasediff;
+ for (i=0; i<numbins; ++i) {
+ curmag = ods_abs(curr->bin[i].mag);
+
+ // Predict mag as yestermag
+ predmag = ods->other[tbpointer++];
+ yesterphase = ods->other[tbpointer++];
+ yesterphasediff = ods->other[tbpointer++];
+
+ // Thresholding as Brossier did - discard (ignore) bin's deviation if bin's power is minimal
+ if(curmag > ods->odfparam) {
+ // If rectifying, ignore decreasing bins
+ if((!rectify) || !(curmag < predmag)){
+
+ // Predict phase as yesterval + yesterfirstdiff
+ predphase = yesterphase + yesterphasediff;
+
+ // Here temporarily using the "deviation" var to store the phase difference
+ // so that the rewrap macro can use it more efficiently
+ deviation = predphase - curr->bin[i].phase;
+
+ // Deviation is Euclidean distance between predicted and actual.
+ // In polar coords: sqrt(r1^2 + r2^2 - r1r2 cos (theta1 - theta2))
+ deviation = sqrtf(predmag * predmag + curmag * curmag
+ - predmag * curmag * cosf(onsetsds_phase_rewrap(deviation))
+ );
+
+ totdev += deviation;
+ }
+ }
+ }
+
+ // totdev will be the output, but first we need to fill tempbuf with today's values, ready for tomorrow.
+ tbpointer = 0;
+ for (i=0; i<numbins; ++i) {
+ ods->other[tbpointer++] = ods_abs(curr->bin[i].mag); // Storing mag
+ diff = curr->bin[i].phase - ods->other[tbpointer]; // Retrieving yesterphase from buf
+ ods->other[tbpointer++] = curr->bin[i].phase; // Storing phase
+ // Wrap onto +-PI range
+ diff = onsetsds_phase_rewrap(diff);
+
+ ods->other[tbpointer++] = diff; // Storing first diff to buf
+
+ }
+ *val = (float)totdev;
+
+ break;
+
+
+ case ODS_ODF_PHASE:
+ rectify = false; // So, actually, "rectify" means "useweighting" in this context
+ // ...and then drop through to:
+ case ODS_ODF_WPHASE:
+
+ // Note: "other" buf is stored in this format: phase[0],d_phase[0],phase[1],d_phase[1], ...
+
+ // Iterate through, calculating the deviation from expected value.
+ totdev = 0.0;
+ tbpointer = 0;
+ for (i=0; i<numbins; ++i) {
+ // Thresholding as Brossier did - discard (ignore) bin's phase deviation if bin's power is low
+ if(ods_abs(curr->bin[i].mag) > ods->odfparam) {
+
+ // Deviation is the *second difference* of the phase, which is calc'ed as curval - yesterval - yesterfirstdiff
+ deviation = curr->bin[i].phase - ods->other[tbpointer++] - ods->other[tbpointer++];
+ // Wrap onto +-PI range
+ deviation = onsetsds_phase_rewrap(deviation);
+
+ if(rectify){ // "rectify" meaning "useweighting"...
+ totdev += fabs(deviation * ods_abs(curr->bin[i].mag));
+ } else {
+ totdev += fabs(deviation);
+ }
+ }
+ }
+
+ // totdev will be the output, but first we need to fill tempbuf with today's values, ready for tomorrow.
+ tbpointer = 0;
+ for (i=0; i<numbins; ++i) {
+ diff = curr->bin[i].phase - ods->other[tbpointer]; // Retrieving yesterphase from buf
+ ods->other[tbpointer++] = curr->bin[i].phase; // Storing phase
+ // Wrap onto +-PI range
+ diff = onsetsds_phase_rewrap(diff);
+
+ ods->other[tbpointer++] = diff; // Storing first diff to buf
+
+ }
+ *val = (float)totdev;
+ break;
+
+
+ case ODS_ODF_MKL:
+
+ // Iterate through, calculating the Modified Kullback-Liebler distance
+ totdev = 0.0;
+ tbpointer = 0;
+ float yestermag;
+ for (i=0; i<numbins; ++i) {
+ curmag = ods_abs(curr->bin[i].mag);
+ yestermag = ods->other[tbpointer];
+
+ // Here's the main implementation of Brossier's MKL eq'n (eqn 2.9 from his thesis):
+ deviation = ods_abs(curmag) / (ods_abs(yestermag) + ods->odfparam);
+ totdev += log(1.f + deviation);
+
+ // Store the mag as yestermag
+ ods->other[tbpointer++] = curmag;
+ }
+ *val = (float)totdev;
+ break;
+
+ }
+
+#if ODS_DEBUG_POST_CSV
+ printf("%g,", *val);
+ printf("%g,", ods->odfvals[0] * ods->normfactor);
+#endif
+
+ ods->odfvals[0] *= ods->normfactor;
+}
+// End of ODF function
+
+void SelectionSort(float *array, int length);
+void SelectionSort(float *array, int length)
+{
+ // Algo is simply based on http://en.wikibooks.org/wiki/Algorithm_implementation/Sorting/Selection_sort
+ int max, i;
+ float temp;
+ while(length > 0)
+ {
+ max = 0;
+ for(i = 1; i < length; i++)
+ if(array[i] > array[max])
+ max = i;
+ temp = array[length-1];
+ array[length-1] = array[max];
+ array[max] = temp;
+ length--;
+ }
+}
+
+
+void onsetsds_detect(OnsetsDS* ods){
+
+ // Shift the yesterval to its rightful place
+ ods->odfvalpostprev = ods->odfvalpost;
+
+ ///////// MEDIAN REMOVAL ////////////
+
+ float* sortbuf = ods->sortbuf;
+ int medspan = ods->medspan;
+
+ // Copy odfvals to sortbuf
+ memcpy(sortbuf, ods->odfvals, medspan * sizeof(float));
+
+ // Sort sortbuf
+ SelectionSort(sortbuf, medspan);
+
+ // Subtract the middlest value === the median
+ if(ods->med_odd){
+ ods->odfvalpost = ods->odfvals[0]
+ - sortbuf[(medspan - 1) >> 1];
+ }else{
+ ods->odfvalpost = ods->odfvals[0]
+ - ((sortbuf[medspan >> 1]
+ + sortbuf[(medspan >> 1) - 1]) * 0.5f);
+
+ }
+
+ // Detection not allowed if we're too close to a previous detection.
+ if(ods->gapleft != 0) {
+ ods->gapleft--;
+ ods->detected = false;
+ } else {
+ // Now do the detection.
+ ods->detected = (ods->odfvalpost > ods->thresh) && (ods->odfvalpostprev <= ods->thresh);
+ if(ods->detected){
+ ods->gapleft = ods->mingap;
+ }
+ }
+#if ODS_DEBUG_POST_CSV
+ printf("%g\n", ods->odfvalpost);
+#endif
+}
+
+
View
275 addons/ofxOnsetDetection/src/OnsetsDS/onsetsds.h
@@ -0,0 +1,275 @@
+/*
+ OnsetsDS - real time musical onset detection library.
+ Copyright (c) 2007 Dan Stowell. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+/** \file */
+
+#ifndef _OnsetsDS_
+#define _OnsetsDS_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <math.h>
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Macros and consts
+
+//log(0.1)
+#define ods_log1 -2.30258509
+
+#define PI 3.1415926535898f
+#define MINUSPI -3.1415926535898f
+#define TWOPI 6.28318530717952646f
+#define INV_TWOPI 0.1591549430919f
+
+#define ods_abs(a) ((a)<0? -(a) : (a))
+#define ods_max(a,b) (((a) > (b)) ? (a) : (b))
+#define ods_min(a,b) (((a) < (b)) ? (a) : (b))
+
+#define ODS_LOG_LOWER_LIMIT 2e-42
+#define ODS_LOGOF_LOG_LOWER_LIMIT -96.0154267
+#define ODS_ABSINVOF_LOGOF_LOG_LOWER_LIMIT 0.010414993
+
+////////////////////////////////////////////////////////////////////////////////
+// Constants
+
+/**
+* Types of incoming FFT data format. OnsetsDS needs to know where the FFT
+* data comes from in order to interpret it correctly.
+*/
+enum onsetsds_fft_types {
+ ODS_FFT_SC3_COMPLEX, ///< SuperCollider, cartesian co-ords ("SCComplexBuf") [dc, nyq, real[1], imag[1], real[2], imag[2]...] - NB it's more efficient to provide polar data from SC
+ ODS_FFT_SC3_POLAR, ///< SuperCollider, polar co-ords ("SCPolarBuf") [dc, nyq, mag[1], phase[1], mag[2], phase[2]...]
+ ODS_FFT_FFTW3_HC, ///< FFTW <a href="http://www.fftw.org/fftw3_doc/The-Halfcomplex_002dformat-DFT.html">"halfcomplex"</a> format - [dc, real[1], real[2] ... nyq, imag[nyq-1] ... imag[1]]
+ ODS_FFT_FFTW3_R2C ///< FFTW regular format, typically produced using <a href="http://www.fftw.org/fftw3_doc/One_002dDimensional-DFTs-of-Real-Data.html#One_002dDimensional-DFTs-of-Real-Data">real-to-complex</a> transform
+};
+
+/**
+* Types of onset detection function
+*/
+enum onsetsds_odf_types {
+ ODS_ODF_POWER, ///< Power
+ ODS_ODF_MAGSUM, ///< Sum of magnitudes
+ ODS_ODF_COMPLEX, ///< Complex-domain deviation
+ ODS_ODF_RCOMPLEX, ///< Complex-domain deviation, rectified (only increases counted)
+ ODS_ODF_PHASE, ///< Phase deviation
+ ODS_ODF_WPHASE, ///< Weighted phase deviation
+ ODS_ODF_MKL ///< Modified Kullback-Liebler deviation
+};
+
+/**
+* Types of whitening - may not all be implemented yet.
+*/
+enum onsetsds_wh_types {
+ ODS_WH_NONE, ///< No whitening - onsetsds_whiten() becomes a no-op
+ ODS_WH_ADAPT_MAX1, ///< Adaptive whitening - tracks recent-peak-magnitude in each bin, normalises that to 1
+ ODS_WH_NORMMAX, ///< Simple normalisation - each frame is normalised (independent of others) so largest magnitude becomes 1. Not implemented.
+ ODS_WH_NORMMEAN ///< Simple normalisation - each frame is normalised (independent of others) so mean magnitude becomes 1. Not implemented.
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Structs
+
+typedef struct OdsPolarBin { float mag, phase; } OdsPolarBin;
+
+typedef struct OdsPolarBuf {
+ float dc, nyq;
+ OdsPolarBin bin[1];
+} OdsPolarBuf;
+
+/// The main data structure for the onset detection routine
+typedef struct OnsetsDS {
+ /// "data" is a pointer to the memory that must be EXTERNALLY allocated.
+ /// Other pointers will point to locations within this memory.
+ float *data,
+ *psp, ///< Peak Spectral Profile - size is numbins+2, data is stored in order dc through to nyquist
+ *odfvals, // odfvals[0] will be the current val, odfvals[1] prev, etc
+ *sortbuf, // Used to calculate the median
+ *other; // Typically stores data about the previous frame
+ OdsPolarBuf* curr; // Current FFT frame, as polar
+
+ float
+ srate, ///< The sampling rate of the input audio. Set by onsetsds_init()
+ // Adaptive whitening params
+ relaxtime, ///< Do NOT set this directly. Use onsetsds_setrelax() which will also update relaxcoef.
+ relaxcoef, ///< Relaxation coefficient (memory coefficient). See also onsetsds_setrelax()
+ floor, ///< floor - the lowest value that a PSP magnitude can take.
+ /// A parameter for the ODF. For most this is a magnitude threshold for a single bin to be considered;
+ /// but for #ODS_ODF_MKL it is the "epsilon" parameter.
+ odfparam,
+ /// Value used internally to scale ODF value according to the FFT frame size. Automatically set by onsetsds_init()
+ normfactor,
+ // ODF val after median processing
+ odfvalpost,
+ // Previous val is needed for threshold-crossing detection
+ odfvalpostprev,
+ /// Threshold (of ODF value, after median processing) for detection.
+ /// Values between 0 and 1 are expected, but outside this range may
+ /// sometimes be appropriate too.
+ thresh;
+
+ int odftype, ///< Choose from #onsetsds_odf_types
+ whtype, ///< Choose from #onsetsds_wh_types
+ fftformat; ///< Choose from #onsetsds_fft_types
+ bool whiten, ///< Whether to apply whitening - onsetsds_init() decides this on your behalf
+ detected,///< Output val - true if onset detected in curr frame
+ /**
+ NOT YET USED: Whether to convert magnitudes to log domain before processing. This is done as follows:
+ Magnitudes below a log-lower-limit threshold (ODS_LOG_LOWER_LIMIT) are pushed up to that threshold (to avoid log(0) infinity problems),
+ then the log is taken. The values are re-scaled to a similar range as the linear-domain values (assumed to lie
+ between zero and approximately one) by subtracting log(ODS_LOG_LOWER_LIMIT) and then dividing by abs(log(ODS_LOG_LOWER_LIMIT)).
+ */
+ logmags,
+ med_odd; ///< Whether median span is odd or not (used internally)
+
+ unsigned int
+ /// Number of frames used in median calculation
+ medspan,
+ /// Size of enforced gap between detections, measured in FFT frames.
+ mingap, gapleft;
+ size_t fftsize, numbins; // numbins is the count not including DC/nyq
+} OnsetsDS;
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Function prototypes
+
+
+/**
+ * \defgroup MainUserFuncs Main user functions
+ */
+ //@{
+
+/**
+* Determine how many bytes of memory must be allocated (e.g. using malloc) to
+* accompany the OnsetsDS struct, operating using the specified settings (used to
+* store part-processed FFT data etc). The user must
+* call this, and then allocate the memory, BEFORE calling onsetsds_init().
+* @param odftype Which onset detection function (ODF) you'll be using, chosen from #onsetsds_odf_types
+* @param fftsize Size of FFT: 512 is recommended.
+* @param medspan The number of past frames that will be used for median calculation during triggering
+*/
+size_t onsetsds_memneeded (int odftype, size_t fftsize, unsigned int medspan);
+
+/**
+* Initialise the OnsetsDS struct and its associated memory, ready to detect
+* onsets using the specified settings. Must be called before any call to
+* onsetsds_process().
+*
+* Note: you can change the onset detection function type in mid-operation
+* by calling onsetsds_init() again, but because memory will be reset this
+* will behave as if starting from scratch (rather than being aware of the past
+* few frames of sound). Do not attempt to change the
+* onset detection function in a more hacky way (e.g. fiddling with the struct)
+* because memory is set up differently for each of the different ODFs.
+* @param ods An instance of the OnsetsDS struct
+* @param odsdata A pointer to the memory allocated, size given by onsetsds_memneeded().
+* @param fftformat Which format of FFT data is to be expected, chosen from #onsetsds_fft_types
+* @param odftype Which onset detection function (ODF) you'll be using, chosen from #onsetsds_odf_types
+* @param fftsize Size of FFT: 512 or 1024 is recommended.
+* @param medspan The number of past frames that will be used for median calculation during triggering
+* @param srate The sampling rate of the input audio
+*/
+void onsetsds_init(OnsetsDS* ods, float* odsdata, int fftformat,
+ int odftype, size_t fftsize, unsigned int medspan, float srate);
+
+/**
+* Process a single FFT data frame in the audio signal. Note that processing
+* assumes that each call to onsetsds_process() is on a subsequent frame in
+* the same audio stream - to handle multiple streams you must use separate
+* OnsetsDS structs and memory!
+*
+* This function's main purpose is to call some of the library's other functions,
+* in the expected sequence.
+*/
+bool onsetsds_process(OnsetsDS* ods, float* fftbuf);
+
+//@}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Function prototypes less commonly called by users
+
+/**
+ * \defgroup LessCommonFuncs Other useful functions
+ */
+ //@{
+/**
+* Set the "memory coefficient" indirectly via the time for the
+* memory to decay by 60 dB.
+* @param ods The OnsetsDS
+* @param time The time in seconds
+* @param hopsize The FFT frame hopsize (typically this will be half the FFT frame size)
+*/
+void onsetsds_setrelax(OnsetsDS* ods, float time, size_t hopsize);
+
+//@}
+
+////////////////////////////////////////////////////////////////////////////////
+// Function prototypes not typically called by users
+
+/**
+ * \defgroup OtherFuncs Other functions, not typically called by users
+ */
+ //@{
+/**
+* Load the current frame of FFT data into the OnsetsDS struct.
+*
+* Not typically called directly by users since onsetsds_process() calls this.
+*/
+void onsetsds_loadframe(OnsetsDS* ods, float* fftbuf);
+
+/**
+* Apply adaptive whitening to the FFT data in the OnsetsDS struct.
+*
+* Not typically called directly by users since onsetsds_process() calls this.
+*/
+void onsetsds_whiten(OnsetsDS* ods);
+
+/**
+* Calculate the Onset Detection Function (includes scaling ODF outputs to
+* similar range)
+*
+* Not typically called directly by users since onsetsds_process() calls this.
+*/
+void onsetsds_odf(OnsetsDS* ods);
+
+/**
+* Detects salient peaks in Onset Detection Function by removing the median,
+* then thresholding. Afterwards, the member ods.detected will indicate whether
+* or not an onset was detected.
+*
+* Not typically called directly by users since onsetsds_process() calls this.
+*/
+void onsetsds_detect(OnsetsDS* ods);
+
+//@}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
View
63 addons/ofxOnsetDetection/src/ofxOnsetDetection.h
@@ -0,0 +1,63 @@
+//
+// ofxOnsetDetection.h
+//
+// Created by Akira Hayasaka on 3/30/12.
+// Copyright (c) 2012 ゚・:,。゚・:,。★゚・:,。゚・:,。☆゚・:,。゚・:,。★゚・:,。゚・:,。☆. All rights reserved.
+//
+
+#ifndef example_ofxOnsetDetection_h
+#define example_ofxOnsetDetection_h
+
+#include "ofMain.h"
+#include "onsetsds.h"
+
+/*
+ http://onsetsds.sourceforge.net/
+ */
+
+
+/**
+ * Types of incoming FFT data format. OnsetsDS needs to know where the FFT
+ * data comes from in order to interpret it correctly.
+ */
+enum ofx_onsetsds_fft_types {
+ OFX_ODS_FFT_SC3_COMPLEX, ///< SuperCollider, cartesian co-ords ("SCComplexBuf") [dc, nyq, real[1], imag[1], real[2], imag[2]...] - NB it's more efficient to provide polar data from SC
+ OFX_ODS_FFT_SC3_POLAR, ///< SuperCollider, polar co-ords ("SCPolarBuf") [dc, nyq, mag[1], phase[1], mag[2], phase[2]...]
+ OFX_ODS_FFT_FFTW3_HC, ///< FFTW <a href="http://www.fftw.org/fftw3_doc/The-Halfcomplex_002dformat-DFT.html">"halfcomplex"</a> format - [dc, real[1], real[2] ... nyq, imag[nyq-1] ... imag[1]]
+ OFX_ODS_FFT_FFTW3_R2C ///< FFTW regular format, typically produced using <a href="http://www.fftw.org/fftw3_doc/One_002dDimensional-DFTs-of-Real-Data.html#One_002dDimensional-DFTs-of-Real-Data">real-to-complex</a> transform
+};
+
+/**
+ * Types of onset detection function
+ */
+enum ofx_onsetsds_odf_types {
+ OFX_ODS_ODF_POWER, ///< Power
+ OFX_ODS_ODF_MAGSUM, ///< Sum of magnitudes
+ OFX_ODS_ODF_COMPLEX, ///< Complex-domain deviation
+ OFX_ODS_ODF_RCOMPLEX, ///< Complex-domain deviation, rectified (only increases counted)
+ OFX_ODS_ODF_PHASE, ///< Phase deviation
+ OFX_ODS_ODF_WPHASE, ///< Weighted phase deviation
+ OFX_ODS_ODF_MKL ///< Modified Kullback-Liebler deviation
+};
+
+
+class ofxOnsetDetection
+{
+public:
+ void setup(int odftype = OFX_ODS_ODF_COMPLEX, int fftformat = OFX_ODS_FFT_FFTW3_HC,
+ int fftsize = 1024, int medspan = 11, int sampleRate = 44100)
+ {
+ float* odsdata = (float*) malloc( onsetsds_memneeded(odftype, fftsize, medspan) );
+ onsetsds_init(&ods, odsdata, fftformat, odftype, fftsize, medspan, sampleRate);
+ }
+
+ bool isOnsetting(float * fftArray)
+ {
+ return onsetsds_process(&ods, fftArray);
+ }
+
+private:
+ OnsetsDS ods;
+};
+
+#endif

0 comments on commit 9c53305

Please sign in to comment.
Something went wrong with that request. Please try again.