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 @ 0:a7c2bda0dfd9

History | View | Annotate | Download (15.6 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
inline float onsetsds_phase_rewrap(float phase);
28
inline float onsetsds_phase_rewrap(float phase){
29
        return (phase>MINUSPI && phase<PI) ? phase : phase + TWOPI * (1.f + floorf((MINUSPI - phase) * INV_TWOPI));
30
}
31

    
32

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

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

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

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

    
76

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

    
83

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

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

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

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

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

    
164
}
165

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

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

    
176

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

    
182

    
183

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

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

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

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

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

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

    
503

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

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

    
547