To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

The primary repository for this project is hosted at svn://svn.code.sf.net/p/onsetsds/code/ .
This repository is a read-only copy which is updated automatically every hour.

Statistics Download as Zip
| Branch: | Revision:

root / src / onsetsds.c

History | View | Annotate | Download (15.5 KB)

1
/*
2
        OnsetsDS - real time musical onset detection library.
3
    Copyright (c) 2007 Dan Stowell. All rights reserved.
4

5
    This program is free software; you can redistribute it and/or modify
6
    it under the terms of the GNU General Public License as published by
7
    the Free Software Foundation; either version 2 of the License, or
8
    (at your option) any later version.
9

10
    This program is distributed in the hope that it will be useful,
11
    but WITHOUT ANY WARRANTY; without even the implied warranty of
12
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
    GNU General Public License for more details.
14

15
    You should have received a copy of the GNU General Public License
16
    along with this program; if not, write to the Free Software
17
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
*/
19

    
20

    
21
#include "onsetsds.h"
22

    
23

    
24
#define ODS_DEBUG_POST_CSV 0
25

    
26
// Inline
27
static inline float onsetsds_phase_rewrap(float phase){
28
        return (phase>MINUSPI && phase<PI) ? phase : phase + TWOPI * (1.f + floorf((MINUSPI - phase) * INV_TWOPI));
29
}
30

    
31

    
32
size_t onsetsds_memneeded (int odftype, size_t fftsize, unsigned int medspan){
33
        
34
        /*
35
        Need memory for:
36
        - median calculation (2 * medspan floats)
37
        - storing old values (whether as OdsPolarBuf or as weirder float lists)
38
        - storing the OdsPolarBuf (size is NOT sizeof(OdsPolarBuf) but is fftsize)
39
        - storing the PSP (numbins + 2 values)
40
        All these are floats.
41
        */
42
        
43
        int numbins = (fftsize >> 1) - 1; // No of bins, not counting DC/nyq
44
        
45
        switch(odftype){
46
                case ODS_ODF_POWER:
47
                case ODS_ODF_MAGSUM:
48
                        
49
                        // No old FFT frames needed, easy:
50
                        return (medspan+medspan + fftsize + numbins + 2) * sizeof(float);
51

    
52
                case ODS_ODF_COMPLEX:
53
                case ODS_ODF_RCOMPLEX:
54
        
55
                        return (medspan+medspan + fftsize + numbins + 2
56
                                        // For each bin (NOT dc/nyq) we store mag, phase and d_phase
57
                                        + numbins + numbins + numbins
58
                                ) * sizeof(float);
59

    
60
                case ODS_ODF_PHASE:
61
                case ODS_ODF_WPHASE:
62
        
63
                        return (medspan+medspan + fftsize + numbins + 2
64
                                        // For each bin (NOT dc/nyq) we store phase and d_phase
65
                                        + numbins + numbins
66
                                ) * sizeof(float);
67

    
68
                case ODS_ODF_MKL:
69
        
70
                        return (medspan+medspan + fftsize + numbins + 2
71
                                        // For each bin (NOT dc/nyq) we store mag
72
                                        + numbins
73
                                ) * sizeof(float);
74

    
75

    
76
                        break;
77
        
78
        }
79
        return -1; //bleh
80
}
81

    
82

    
83
void onsetsds_init(OnsetsDS *ods, float *odsdata, int fftformat, 
84
                           int odftype, size_t fftsize, unsigned int medspan, float srate){
85

    
86
        // The main pointer to the processing area - other pointers will indicate areas within this
87
        ods->data = odsdata;
88
        // Set all vals in processing area to zero
89
        memset(odsdata, 0, onsetsds_memneeded(odftype, fftsize, medspan));
90
        
91
        ods->srate = srate;
92
        
93
        int numbins  = (fftsize >> 1) - 1; // No of bins, not counting DC/nyq
94
        int realnumbins = numbins + 2;
95

    
96
        // Also point the other pointers to the right places
97
        ods->curr     = (OdsPolarBuf*) odsdata;
98
        ods->psp      = odsdata + fftsize;
99
        ods->odfvals  = odsdata + fftsize + realnumbins;
100
        ods->sortbuf  = odsdata + fftsize + realnumbins + medspan;
101
        ods->other    = odsdata + fftsize + realnumbins + medspan + medspan;
102
        
103
        // Default settings for Adaptive Whitening, user can set own values after init
104
        onsetsds_setrelax(ods, 1.f, fftsize>>1);
105
        ods->floor    = 0.1;
106
        
107
        switch(odftype){
108
                case ODS_ODF_POWER:
109
                        ods->odfparam = 0.01; // "powthresh" in SC code
110
                        ods->normfactor = 2560.f / (realnumbins * fftsize);
111
                        break;
112
                case ODS_ODF_MAGSUM:
113
                        ods->odfparam = 0.01; // "powthresh" in SC code
114
                        ods->normfactor = 113.137085f / (realnumbins * sqrt(fftsize));
115
                        break;
116
                case ODS_ODF_COMPLEX:
117
                        ods->odfparam = 0.01; // "powthresh" in SC code
118
                        ods->normfactor = 231.70475f / pow(fftsize, 1.5);// / fftsize;
119
                        break;
120
                case ODS_ODF_RCOMPLEX:
121
                        ods->odfparam = 0.01; // "powthresh" in SC code
122
                        ods->normfactor = 231.70475f / pow(fftsize, 1.5);// / fftsize;
123
                        break;
124
                case ODS_ODF_PHASE:
125
                        ods->odfparam = 0.01; // "powthresh" in SC code
126
                        ods->normfactor = 5.12f / fftsize;// / fftsize;
127
                        break;
128
                case ODS_ODF_WPHASE:
129
                        ods->odfparam = 0.0001; // "powthresh" in SC code. For WPHASE it's kind of superfluous.
130
                        ods->normfactor = 115.852375f / pow(fftsize, 1.5);// / fftsize;
131
                        break;
132
                case ODS_ODF_MKL:
133
                        ods->odfparam = 0.01; // EPSILON parameter. Brossier recommends 1e-6 but I (ICMC 2007) found larger vals (e.g 0.01) to work better
134
                        ods->normfactor = 7.68f * 0.25f / fftsize;
135
                        break;
136
                default:
137
                        printf("onsetsds_init ERROR: \"odftype\" is not a recognised value\n");
138
        }
139
        
140
        ods->odfvalpost = 0.f;
141
        ods->odfvalpostprev = 0.f;
142
        ods->thresh   = 0.5f;
143
        ods->logmags = false;
144
        
145
        ods->odftype  = odftype;
146
        ods->whtype   = ODS_WH_ADAPT_MAX1;
147
        ods->fftformat = fftformat;
148
        
149
        ods->whiten   = (odftype != ODS_ODF_MKL); // Deactivate whitening for MKL by default
150
        ods->detected = false;
151
        ods->med_odd  = (medspan & 1) != 0;
152
        
153
        ods->medspan  = medspan;
154
        
155
        ods->mingap   = 0;
156
        ods->gapleft  = 0;
157

    
158
        ods->fftsize  = fftsize;
159
        ods->numbins  = numbins;
160

    
161
        //printf("End of _init: normfactor is %g\n", ods->normfactor);
162

    
163
}
164

    
165
bool onsetsds_process(OnsetsDS* ods, float* fftbuf){
166
        onsetsds_loadframe(ods, fftbuf);
167

    
168
        onsetsds_whiten(ods);
169
        onsetsds_odf(ods);
170
        onsetsds_detect(ods);
171
        
172
        return ods->detected;
173
}
174

    
175

    
176
void onsetsds_setrelax(OnsetsDS* ods, float time, size_t hopsize){
177
        ods->relaxtime = time;
178
        ods->relaxcoef = (time == 0.0f) ? 0.0f : exp((ods_log1 * hopsize)/(time * ods->srate));
179
}
180

    
181

    
182

    
183
void onsetsds_loadframe(OnsetsDS* ods, float* fftbuf){
184
        
185
        float *pos, *pos2, imag, real;
186
        int i;
187
        
188
        switch(ods->fftformat){
189
                case ODS_FFT_SC3_POLAR:
190
                        // The format is the same! dc, nyq, mag[1], phase[1], ...
191
                        memcpy(ods->curr, fftbuf, ods->fftsize * sizeof(float));
192
                        break;
193
                        
194
                case ODS_FFT_SC3_COMPLEX:
195
                
196
                        ods->curr->dc  = fftbuf[0];
197
                        ods->curr->nyq = fftbuf[1];
198
                        
199
                        // Then convert cartesian to polar:
200
                        pos = fftbuf + 2;
201
                        for(i=0; i< (ods->numbins << 1); i += 2){
202
                                real = pos[i];
203
                                imag = pos[i+1]; // Plus 1 rather than increment; seems to avoid LSU reject on my PPC
204
                                ods->curr->bin[i].mag   = hypotf(imag, real);
205
                                ods->curr->bin[i].phase = atan2f(imag, real);
206
                        }
207
                        break;
208
                        
209
                case ODS_FFT_FFTW3_HC:
210
                        
211
                        ods->curr->dc  = fftbuf[0];
212
                        ods->curr->nyq = fftbuf[ods->fftsize>>1];
213
                        
214
                        // Then convert cartesian to polar:
215
                        // (Starting positions: real and imag for bin 1)
216
                        pos  = fftbuf + 1;
217
                        pos2 = fftbuf + ods->fftsize - 1;
218
                        for(i=0; i<ods->numbins; i++){
219
                                real = *(pos++);
220
                                imag = *(pos2--);
221
                                ods->curr->bin[i].mag   = hypotf(imag, real);
222
                                ods->curr->bin[i].phase = atan2f(imag, real);
223
                        }
224
                        break;
225
                        
226
                case ODS_FFT_FFTW3_R2C:
227
                
228
                        ods->curr->dc  = fftbuf[0];
229
                        ods->curr->nyq = fftbuf[ods->fftsize];
230
                        
231
                        // Then convert cartesian to polar:
232
                        pos = fftbuf + 2;
233
                        for(i=0; i<ods->numbins; i++){
234
                                real = *(pos++);
235
                                imag = *(pos++);
236
                                ods->curr->bin[i].mag   = hypotf(imag, real);
237
                                ods->curr->bin[i].phase = atan2f(imag, real);
238
                        }
239
                        break;
240
                        
241
        }
242
        
243
        // Conversion to log-domain magnitudes, including re-scaling to aim back at the zero-to-one range.
244
        // Not well tested yet.
245
        if(ods->logmags){
246
                for(i=0; i<ods->numbins; i++){
247
                        ods->curr->bin[i].mag = 
248
                                (log(ods_max(ods->curr->bin[i].mag, ODS_LOG_LOWER_LIMIT)) - ODS_LOGOF_LOG_LOWER_LIMIT) * ODS_ABSINVOF_LOGOF_LOG_LOWER_LIMIT;
249
                }
250
                ods->curr->dc = 
251
                        (log(ods_max(ods_abs(ods->curr->dc ), ODS_LOG_LOWER_LIMIT)) - ODS_LOGOF_LOG_LOWER_LIMIT) * ODS_ABSINVOF_LOGOF_LOG_LOWER_LIMIT;
252
                ods->curr->nyq = 
253
                        (log(ods_max(ods_abs(ods->curr->nyq), ODS_LOG_LOWER_LIMIT)) - ODS_LOGOF_LOG_LOWER_LIMIT) * ODS_ABSINVOF_LOGOF_LOG_LOWER_LIMIT;
254
        }
255
        
256
}
257

    
258
void onsetsds_whiten(OnsetsDS* ods){
259
        
260
        if(ods->whtype == ODS_WH_NONE){
261
                //printf("onsetsds_whiten(): ODS_WH_NONE, skipping\n");
262
                return;
263
        }
264
        
265
        // NB: Apart from the above, ods->whtype is currently IGNORED and only one mode is used.
266
        
267
        
268
        float val,oldval, relaxcoef, floor;
269
        int numbins, i;
270
        OdsPolarBuf *curr;
271
        float *psp;
272
        float *pspp1; // Offset by 1, avoids quite a lot of "+1"s in the following code
273
        
274
        relaxcoef = ods->relaxcoef;
275
        numbins = ods->numbins;
276
        curr = ods->curr;
277
        psp = ods->psp;
278
        pspp1 = psp + 1;
279
        floor = ods->floor;
280

    
281
        //printf("onsetsds_whiten: relaxcoef=%g, relaxtime=%g, floor=%g\n", relaxcoef, ods->relaxtime, floor);
282

    
283
        ////////////////////// For each bin, update the record of the peak value /////////////////////
284
        
285
        val = fabs(curr->dc);        // Grab current magnitude
286
        oldval = psp[0];
287
        // If it beats the amplitude stored then that's our new amplitude;
288
        // otherwise our new amplitude is a decayed version of the old one
289
        if(val < oldval) {
290
                val = val + (oldval - val) * relaxcoef;
291
        }
292
        psp[0] = val; // Store the "amplitude trace" back
293
        
294
        val = fabs(curr->nyq);
295
        oldval = pspp1[numbins];
296
        if(val < oldval) {
297
                val = val + (oldval - val) * relaxcoef;
298
        }
299
        pspp1[numbins] = val;
300
        
301
        for(i=0; i<numbins; ++i){
302
                val = fabs(curr->bin[i].mag);
303
                oldval = pspp1[i];
304
                if(val < oldval) {
305
                        val = val + (oldval - val) * relaxcoef;
306
                }
307
                pspp1[i] = val;
308
        }
309
        
310
        //////////////////////////// Now for each bin, rescale the current magnitude ////////////////////////////
311
        curr->dc  /= ods_max(floor, psp[0]);
312
        curr->nyq /= ods_max(floor, pspp1[numbins]);
313
        for(i=0; i<numbins; ++i){
314
                curr->bin[i].mag /= ods_max(floor, pspp1[i]);
315
        }
316
}
317

    
318
void onsetsds_odf(OnsetsDS* ods){
319
        
320
        int numbins = ods->numbins;
321
        OdsPolarBuf *curr = ods->curr;
322
        float* val = ods->odfvals;
323
        int i, tbpointer;
324
        float deviation, diff, curmag;
325
        double totdev;
326
        
327
        bool rectify = true;
328
        
329
        // Here we shunt the "old" ODF values down one place
330
        memcpy(val + 1, val, (ods->medspan - 1)*sizeof(float));
331
        
332
        // Now calculate a new value and store in ods->odfvals[0]
333
        switch(ods->odftype){
334
                case ODS_ODF_POWER:
335
                        
336
                        *val = (curr->nyq  *  curr->nyq)  +  (curr->dc  *  curr->dc);
337
                        for(i=0; i<numbins; i++){
338
                                *val += curr->bin[i].mag  *  curr->bin[i].mag;
339
                        }
340
                        break;
341
                        
342
                case ODS_ODF_MAGSUM:
343
        
344
                        *val = ods_abs(curr->nyq) + ods_abs(curr->dc);
345
                        
346
                        for(i=0; i<numbins; i++){
347
                                *val += ods_abs(curr->bin[i].mag);
348
                        }
349
                        break;
350
                        
351
                case ODS_ODF_COMPLEX:
352
                        rectify = false;
353
                        // ...and then drop through to:
354
                case ODS_ODF_RCOMPLEX:
355
                        
356
                        // Note: "other" buf is stored in this format: mag[0],phase[0],d_phase[0],mag[1],phase[1],d_phase[1], ...
357
                        
358
                        // Iterate through, calculating the deviation from expected value.
359
                        totdev = 0.0;
360
                        tbpointer = 0;
361
                        float predmag, predphase, yesterphase, yesterphasediff;
362
                        for (i=0; i<numbins; ++i) {
363
                                curmag = ods_abs(curr->bin[i].mag);
364
                        
365
                                // Predict mag as yestermag
366
                                predmag         = ods->other[tbpointer++];
367
                                yesterphase     = ods->other[tbpointer++];
368
                                yesterphasediff = ods->other[tbpointer++];
369
                                
370
                                // Thresholding as Brossier did - discard (ignore) bin's deviation if bin's power is minimal
371
                                if(curmag > ods->odfparam) {
372
                                        // If rectifying, ignore decreasing bins
373
                                        if((!rectify) || !(curmag < predmag)){
374
                                                
375
                                                // Predict phase as yesterval + yesterfirstdiff
376
                                                predphase = yesterphase + yesterphasediff;
377
                                                
378
                                                // Here temporarily using the "deviation" var to store the phase difference
379
                                                //  so that the rewrap macro can use it more efficiently
380
                                                deviation = predphase - curr->bin[i].phase;
381
                                                
382
                                                // Deviation is Euclidean distance between predicted and actual.
383
                                                // In polar coords: sqrt(r1^2 +  r2^2 - r1r2 cos (theta1 - theta2))
384
                                                deviation = sqrtf(predmag * predmag + curmag * curmag
385
                                                                                  - predmag * curmag * cosf(onsetsds_phase_rewrap(deviation))
386
                                                                                );                        
387
                                                
388
                                                totdev += deviation;
389
                                        }
390
                                }
391
                        }
392
                        
393
                        // totdev will be the output, but first we need to fill tempbuf with today's values, ready for tomorrow.
394
                        tbpointer = 0;
395
                        for (i=0; i<numbins; ++i) {
396
                                ods->other[tbpointer++] = ods_abs(curr->bin[i].mag); // Storing mag
397
                                diff = curr->bin[i].phase - ods->other[tbpointer]; // Retrieving yesterphase from buf
398
                                ods->other[tbpointer++] = curr->bin[i].phase; // Storing phase
399
                                // Wrap onto +-PI range
400
                                diff = onsetsds_phase_rewrap(diff);
401
                                
402
                                ods->other[tbpointer++] = diff; // Storing first diff to buf
403
                                
404
                        }
405
                        *val = (float)totdev;
406
                        
407
                        break;
408
                        
409
                        
410
                case ODS_ODF_PHASE:
411
                        rectify = false; // So, actually, "rectify" means "useweighting" in this context
412
                        // ...and then drop through to:
413
                case ODS_ODF_WPHASE:
414
                        
415
                        // Note: "other" buf is stored in this format: phase[0],d_phase[0],phase[1],d_phase[1], ...
416
                        
417
                        // Iterate through, calculating the deviation from expected value.
418
                        totdev = 0.0;
419
                        tbpointer = 0;
420
                        for (i=0; i<numbins; ++i) {
421
                                // Thresholding as Brossier did - discard (ignore) bin's phase deviation if bin's power is low
422
                                if(ods_abs(curr->bin[i].mag) > ods->odfparam) {
423
                                        
424
                                        // Deviation is the *second difference* of the phase, which is calc'ed as curval - yesterval - yesterfirstdiff
425
                                        deviation = curr->bin[i].phase - ods->other[tbpointer++] - ods->other[tbpointer++];
426
                                        // Wrap onto +-PI range
427
                                        deviation = onsetsds_phase_rewrap(deviation);
428
                                        
429
                                        if(rectify){ // "rectify" meaning "useweighting"...
430
                                                totdev += fabs(deviation * ods_abs(curr->bin[i].mag));
431
                                        } else {
432
                                                totdev += fabs(deviation);
433
                                        }
434
                                }
435
                        }
436
                        
437
                        // totdev will be the output, but first we need to fill tempbuf with today's values, ready for tomorrow.
438
                        tbpointer = 0;
439
                        for (i=0; i<numbins; ++i) {
440
                                diff = curr->bin[i].phase - ods->other[tbpointer]; // Retrieving yesterphase from buf
441
                                ods->other[tbpointer++] = curr->bin[i].phase; // Storing phase
442
                                // Wrap onto +-PI range
443
                                diff = onsetsds_phase_rewrap(diff);
444
                                
445
                                ods->other[tbpointer++] = diff; // Storing first diff to buf
446
                                
447
                        }
448
                        *val = (float)totdev;
449
                        break;
450
                        
451
                        
452
                case ODS_ODF_MKL:
453
                        
454
                        // Iterate through, calculating the Modified Kullback-Liebler distance
455
                        totdev = 0.0;
456
                        tbpointer = 0;
457
                        float yestermag;
458
                        for (i=0; i<numbins; ++i) {
459
                                curmag = ods_abs(curr->bin[i].mag);
460
                                yestermag = ods->other[tbpointer];
461
                                
462
                                // Here's the main implementation of Brossier's MKL eq'n (eqn 2.9 from his thesis):
463
                                deviation = ods_abs(curmag) / (ods_abs(yestermag) + ods->odfparam);
464
                                totdev += log(1.f + deviation);
465
                                
466
                                // Store the mag as yestermag
467
                                ods->other[tbpointer++] = curmag;
468
                        }
469
                        *val = (float)totdev;
470
                        break;
471
        
472
        }
473
                
474
#if ODS_DEBUG_POST_CSV
475
        printf("%g,", *val);
476
        printf("%g,", ods->odfvals[0] * ods->normfactor);
477
#endif
478
        
479
        ods->odfvals[0] *= ods->normfactor;
480
}
481
// End of ODF function
482

    
483
void SelectionSort(float *array, int length);
484
void SelectionSort(float *array, int length)
485
{
486
  // Algo is simply based on http://en.wikibooks.org/wiki/Algorithm_implementation/Sorting/Selection_sort
487
  int max, i;
488
  float temp;
489
  while(length > 0)
490
  {
491
    max = 0;
492
    for(i = 1; i < length; i++)
493
      if(array[i] > array[max])
494
        max = i;
495
    temp = array[length-1];
496
    array[length-1] = array[max];
497
    array[max] = temp;
498
    length--;
499
  }
500
}
501

    
502

    
503
void onsetsds_detect(OnsetsDS* ods){
504
        
505
        // Shift the yesterval to its rightful place
506
        ods->odfvalpostprev = ods->odfvalpost;
507
        
508
        ///////// MEDIAN REMOVAL ////////////
509
        
510
        float* sortbuf = ods->sortbuf;
511
        int medspan = ods->medspan;
512
        
513
        // Copy odfvals to sortbuf
514
        memcpy(sortbuf, ods->odfvals, medspan * sizeof(float));
515
        
516
        // Sort sortbuf
517
        SelectionSort(sortbuf, medspan);
518
                        
519
        // Subtract the middlest value === the median
520
        if(ods->med_odd){
521
                ods->odfvalpost = ods->odfvals[0] 
522
                           - sortbuf[(medspan - 1) >> 1];
523
        }else{
524
                ods->odfvalpost = ods->odfvals[0] 
525
                           - ((sortbuf[medspan >> 1]
526
                                 + sortbuf[(medspan >> 1) - 1]) * 0.5f);
527
                           
528
        }
529

    
530
        // Detection not allowed if we're too close to a previous detection.
531
        if(ods->gapleft != 0) {
532
                ods->gapleft--;
533
                ods->detected = false;
534
        } else {
535
                // Now do the detection.
536
                ods->detected = (ods->odfvalpost > ods->thresh) && (ods->odfvalpostprev <= ods->thresh);
537
                if(ods->detected){
538
                        ods->gapleft = ods->mingap;
539
                }
540
        }
541
#if ODS_DEBUG_POST_CSV
542
        printf("%g\n", ods->odfvalpost);
543
#endif
544
}
545

    
546