1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <ctype.h>
5 #include <math.h>
6 #ifndef __MACH__
7 #include <malloc.h>
8 #endif
9 #include <fstream>
10 #include "allegro.h"
11 #include "audioreader.h"
12 #include "scorealign.h"
13 #include "gen_chroma.h"
14 #include "comp_chroma.h"
15 #include "curvefit.h"
16 #include "mfmidi.h"
17 #include "regression.h"
18 #include "sautils.h"
19
20 #if (defined (WIN32) || defined (_WIN32)) && _MSC_VER < 1900
21 #define snprintf _snprintf
22 #endif
23
24 #define LOW_CUTOFF 40
25 #define HIGH_CUTOFF 2000
26
27 // Note: There is a "verbose" flag in Score_align objects that
28 // enable some printing. The SA_VERBOSE compiler flag causes a
29 // lot more debugging output, so it could be called VERY_VERBOSE
30 // as opposed to the quieter verbose flags.
31
32 #ifdef SA_VERBOSE
33 #include "main.h"
34 #endif
35
36 // for presmoothing, how near does a point have to be to be "on the line"
37 #define NEAR 1.5
38
39 // path is file0_frames by file1_frames array, so first index
40 // (rows) is in [0 .. file0_frames]. Array is sequence of rows.
41 // columns (j) ranges from [0 .. file1_frames]
42 #define PATH(i,j) (path[(i) * file1_frames + (j)])
43
44 /*===========================================================================*/
45
46 #if DEBUG_LOG
47 FILE *dbf = NULL;
48 #endif
49
50
Scorealign()51 Scorealign::Scorealign() {
52 frame_period = SA_DFT_FRAME_PERIOD;
53 window_size = SA_DFT_WINDOW_SIZE;
54 force_final_alignment = SA_DFT_FORCE_FINAL_ALIGNMENT;
55 ignore_silence = SA_DFT_IGNORE_SILENCE;
56 silence_threshold = SA_DFT_SILENCE_THRESHOLD;
57 presmooth_time = SA_DFT_PRESMOOTH_TIME;
58 line_time = SA_DFT_LINE_TIME;
59 smooth_time = SA_DFT_SMOOTH_TIME;
60 pathlen = 0;
61 path_count = 0;
62 pathx = NULL;
63 pathy = NULL;
64 verbose = false;
65 progress = NULL;
66 #if DEBUG_LOG
67 dbf = fopen("debug-log.txt", "w");
68 assert(dbf);
69 #endif
70 }
71
72
~Scorealign()73 Scorealign::~Scorealign() {
74 if (pathx) free(pathx);
75 if (pathy) free(pathy);
76 #if DEBUG_LOG
77 fclose(dbf);
78 #endif
79 }
80
81
82 /* MAP_TIME
83 lookup time of file0 in smooth_time_map and interpolate
84 to get time in file1
85 */
86
map_time(float t1)87 float Scorealign::map_time(float t1)
88 {
89 t1 /= (float) actual_frame_period_0; // convert from seconds to frames
90 int i = (int) t1; // round down
91 if (i < 0) i = 0;
92 if (i >= file0_frames - 1) i = file0_frames - 2;
93 // interpolate to get time
94 return float(actual_frame_period_1 *
95 interpolate(i, smooth_time_map[i], i+1, smooth_time_map[i+1],
96 t1));
97 }
98
99
100 /* FIND_MIDI_DURATION
101 Finds the duration of a midi song where the end
102 is defined by where the last note off occurs. Duration
103 in seconds is given in DUR, and returns in int the number
104 of notes in the song
105 */
106
find_midi_duration(Alg_seq & seq,float * dur)107 int find_midi_duration(Alg_seq &seq, float *dur)
108 {
109 *dur = 0.0F;
110 int nnotes = 0;
111 int i, j;
112 seq.convert_to_seconds();
113 for (j = 0; j < seq.track_list.length(); j++) {
114 Alg_events ¬es = (seq.track_list[j]);
115
116 for (i = 0; i < notes.length(); i++) {
117 Alg_event_ptr e = notes[i];
118 if (e->is_note()) {
119 Alg_note_ptr n = (Alg_note_ptr) e;
120 float note_end = float(n->time + n->dur);
121 if (note_end > *dur) *dur = note_end;
122 nnotes++;
123 }
124 }
125 }
126 return nnotes;
127 }
128
129
130
131 /* Returns the minimum of three values */
min3(double x,double y,double z)132 double min3(double x, double y, double z)
133 {
134 return (x < y ?
135 (x < z ? x : z) :
136 (y < z ? y : z));
137 }
138
139
save_frames(char * name,int frames,float ** chrom_energy)140 void save_frames(char *name, int frames, float **chrom_energy)
141 {
142 FILE *outf = fopen(name, "w");
143 int i,j;
144 for (j=0; j < frames; j++) {
145 float *chrom_energy_frame = chrom_energy[j];
146 for (i = 0; i <= CHROMA_BIN_COUNT; i++) {
147 fprintf(outf, "%g ", chrom_energy_frame[i]);
148 }
149 fprintf(outf, "\n");
150 }
151 fclose(outf);
152 }
153
154
155 /* steps through the dynamic programming path
156 */
path_step(int i,int j)157 void Scorealign::path_step(int i, int j)
158 {
159 #if DEBUG_LOG
160 fprintf(dbf, "(%i,%i) ", i, j);
161 if (++path_count % 5 == 0 ||
162 (i == first_x && j == first_y))
163 fprintf(dbf, "\n");
164 #endif
165 pathx[pathlen] = i;
166 pathy[pathlen] = j;
167 pathlen++;
168 }
169
170
171 /* path_reverse -- path is computed from last to first, flip it */
172 /**/
path_reverse()173 void Scorealign::path_reverse()
174 {
175 int i = 0;
176 int j = pathlen - 1;
177 while (i < j) {
178 short tempx = pathx[i]; short tempy = pathy[i];
179 pathx[i] = pathx[j]; pathy[i] = pathy[j];
180 pathx[j] = tempx; pathy[j] = tempy;
181 i++; j--;
182 }
183 }
184
185 /*
186 Sees if the chroma energy vector is silent (indicated by the 12th element being one)
187 Returns true if it is silent. False if it is not silent
188 */
silent(int i,float * chrom_energy)189 bool silent( int i, float *chrom_energy)
190 {
191 if (AREF2(chrom_energy, i,CHROMA_BIN_COUNT) == 1.0F)
192 return true;
193 else
194 return false;
195
196 }
197
198 /*
199 returns the first index in pathy where the element is bigger than sec
200 */
sec_to_pathy_index(float sec)201 int Scorealign::sec_to_pathy_index(float sec)
202 {
203 for (int i = 0 ; i < (file0_frames + file1_frames); i++) {
204 if (smooth_time_map[i] * actual_frame_period_1 >= sec) {
205 return i;
206 }
207 //printf("%i\n" ,pathy[i]);
208 }
209 return -1;
210 }
211
212
213 /*
214 given a chrom_energy vector, sees how many
215 of the inital frames are designated as silent
216 */
217
frames_of_init_silence(float * chrom_energy,int frame_count)218 int frames_of_init_silence(float *chrom_energy, int frame_count)
219 {
220 int frames;
221 for (frames = 0; frames < frame_count; frames++) {
222 if (!silent(frames, chrom_energy)) break;
223 }
224 return frames;
225 }
226
last_non_silent_frame(float * chrom_energy,int frame_count)227 int last_non_silent_frame(float *chrom_energy, int frame_count)
228 {
229 int frames;
230 for (frames = frame_count - 1; frames > 0; frames--) {
231 if (!silent(frames, chrom_energy)) break;
232 }
233 return frames;
234 }
235
236
237 /* COMPARE_CHROMA
238 Perform Dynamic Programming to find optimal alignment
239 */
compare_chroma()240 int Scorealign::compare_chroma()
241 {
242 float *path;
243
244 /* Allocate the distance matrix */
245 path = (float *) calloc(file0_frames * file1_frames, sizeof(float));
246
247 /* skip over initial silence in signals */
248 if (ignore_silence) {
249 first_x = frames_of_init_silence(chrom_energy0, file0_frames);
250 last_x = last_non_silent_frame(chrom_energy0, file0_frames);
251 first_y = frames_of_init_silence(chrom_energy1, file1_frames);
252 last_y = last_non_silent_frame(chrom_energy1, file1_frames);
253 } else {
254 first_x = 0;
255 last_x = file0_frames - 1;
256 first_y = 0;
257 last_y = file1_frames - 1;
258 }
259
260 if (last_x - first_x <= 0 || last_y - first_y <= 0) {
261 return SA_TOOSHORT;
262 }
263
264 /* Initialize first row and column */
265 if (verbose) printf("Performing DP\n");
266 PATH(first_x, first_y) = gen_dist(first_x, first_y);
267 for (int x = first_x + 1; x <= last_x; x++)
268 PATH(x, first_y) = gen_dist(x, first_y) + PATH(x - 1, first_y);
269 for (int y = 1; y <= last_y; y++)
270 PATH(first_x, y) = gen_dist(first_x, y) + PATH(first_x, y - 1);
271
272 #if DEBUG_LOG
273 fprintf(dbf, "DISTANCE MATRIX ***************************\n");
274 #endif
275 /* Perform DP for the rest of the matrix */
276 for (int x = first_x + 1; x <= last_x; x++) {
277 for (int y = first_y + 1; y <= last_y; y++) {
278 PATH(x, y) = gen_dist(x, y) +
279 float(min3(PATH(x-1, y-1), PATH(x-1, y), PATH(x, y-1)));
280 #if DEBUG_LOG
281 fprintf(dbf, "(%d %d %g) ", x, y, gen_dist(x, y), PATH(x, y));
282 #endif
283 }
284 #if DEBUG_LOG
285 fprintf(dbf, "\n");
286 #endif
287 // report progress for each file0_frame (column)
288 // This is not quite right if we are ignoring silence because
289 // then only a sub-matrix is computed.
290 if (progress && !progress->set_matrix_progress(file1_frames))
291 return SA_CANCEL;
292 }
293 #if DEBUG_LOG
294 fprintf(dbf, "END OF DISTANCE MATRIX ********************\n");
295 #endif
296
297 if (verbose) printf("Completed Dynamic Programming.\n");
298
299
300 //x and y are the ending points, it can end at either the end of midi,
301 // or end of audio or both
302 pathx = ALLOC(short, (file0_frames + file1_frames));
303 pathy = ALLOC(short, (file0_frames + file1_frames));
304
305 assert(pathx != NULL);
306 assert(pathy != NULL);
307
308 // map from file0 time to file1 time
309 time_map = ALLOC(float, file0_frames);
310 smooth_time_map = ALLOC(float, file0_frames);
311
312 int x = last_x;
313 int y = last_y;
314
315 if (!force_final_alignment) {
316 #if DEBUG_LOG
317 fprintf(dbf, "\nOptimal Path: ");
318 #endif
319 // find end point, the lowest cost matrix value at one of the
320 // sequence endings
321 float min_cost = 1.0E10;
322 for (int i = first_x; i <= last_x; i++) {
323 if (PATH(i, last_y) <= min_cost) {
324 min_cost = PATH(i, last_y);
325 x = i;
326 y = last_y;
327 }
328 }
329 for (int j = first_y; j <= last_y; j++) {
330 if (PATH(last_x, j) <= min_cost) {
331 min_cost = PATH(last_x, j);
332 x = last_x;
333 y = j;
334 }
335 }
336 #if DEBUG_LOG
337 fprintf(dbf, "Min cost at %d %d\n\nPATH:\n", x, y);
338 #endif
339 }
340
341 while ((x != first_x) || (y != first_y)) {
342 path_step(x, y);
343
344 /* Check for the optimal path backwards*/
345 if (x > first_x && y > first_y && PATH(x-1, y-1) <= PATH(x-1, y) &&
346 PATH(x-1, y-1) <= PATH(x, y-1)) {
347 x--;
348 y--;
349 } else if (x > first_x && y > first_y && PATH(x-1, y) <= PATH(x, y-1)) {
350 x--;
351 } else if (y > first_y) {
352 y--;
353 } else if (x > first_x) {
354 x--;
355 }
356 }
357 path_step(x, y);
358 path_reverse();
359 free(path);
360 return SA_SUCCESS; // success
361 }
362
363
linear_regression(int n,int width,float & a,float & b)364 void Scorealign::linear_regression(int n, int width, float &a, float &b)
365 {
366 int hw = (width - 1) / 2; // a more convenient form: 1/2 width
367 // compute average of x = avg of time_map[i]
368 float xsum = 0;
369 float ysum = 0;
370 float xavg, yavg;
371 int i;
372 for (i = n - hw; i <= n + hw; i++) {
373 xsum += i;
374 ysum += time_map[i];
375 }
376 xavg = xsum / width;
377 yavg = ysum / width;
378 float num = 0;
379 float den = 0;
380 for (i = n - hw; i <= n + hw; i++) {
381 num += (i - xavg) * (time_map[i] - yavg);
382 den += (i - xavg) * (i - xavg);
383 }
384 b = num / den;
385 a = yavg - b * xavg;
386 }
387
388
389 /* COMPUTE_SMOOTH_TIME_MAP
390 compute regression line and estimate point at i
391
392 Number of points in regression is smooth (an odd number). First
393 index to compute is (smooth-1)/2. Use that line for the first
394 (smooth+1)/2 points. The last index to compute is
395 (file0_frames - (smooth+1)/2). Use that line for the last
396 (smooth+1)/2 points.
397 */
compute_smooth_time_map()398 void Scorealign::compute_smooth_time_map()
399 {
400 int i;
401 int hw = (smooth - 1) / 2; // half width of smoothing window
402
403 // find the first point
404 for (i = 0; i < first_x; i++) {
405 smooth_time_map[i] = NOT_MAPPED;
406 }
407
408 // do the first points:
409 float a, b;
410 linear_regression(first_x + hw, smooth, a, b);
411 for (i = first_x; i <= first_x + hw; i++) {
412 smooth_time_map[i] = a + b * i;
413 }
414
415 // do the middle points:
416 for (i = first_x + hw + 1; i < last_x - hw; i++) {
417 linear_regression(i, smooth, a, b);
418 smooth_time_map[i] = a + b * i;
419
420 #if DEBUG_LOG
421 fprintf(dbf, "time_map[%d] = %g, smooth_time_map[%d] = %g\n",
422 i, time_map[i], i, a + b*i);
423 #endif
424
425 }
426
427 // do the last points
428 linear_regression(last_x - hw, smooth, a, b);
429 for (i = last_x - hw; i <= last_x; i++) {
430 smooth_time_map[i] = a + b * i;
431 }
432 // finally, fill with NOT_MAPPED
433 for (i = last_x + 1; i < file0_frames; i++)
434 smooth_time_map[i] = NOT_MAPPED;
435 }
436
437
438 /* near_line -- see if point is near line */
439 /**/
near_line(float x1,float y1,float x2,float y2,float x,float y)440 bool near_line(float x1, float y1, float x2, float y2, float x, float y)
441 {
442 float exact_y;
443 if (x1 == x) {
444 exact_y = y1;
445 } else {
446 assert(x1 != x2);
447 exact_y = y1 + (y2 - y1) * ((x - x1) / (x2 - x1));
448 }
449 y = y - exact_y;
450 return y < NEAR && y > -NEAR;
451 }
452
453
454 // path_copy -- copy a path for debugging
path_copy(short * path,int len)455 short *path_copy(short *path, int len)
456 {
457 short *new_path = ALLOC(short, len);
458 memcpy(new_path, path, len * sizeof(path[0]));
459 return new_path;
460 }
461
462
463 /* presmooth -- try to remove typical dynamic programming errors
464 *
465 * A common problem is that the best path wanders off track a ways
466 * and then comes back. The idea of presmoothing is to see if the
467 * path is mostly a straight line. If so, adjust the points off of
468 * the line to fall along the line. The variable presmooth_time is
469 * the duration of the line. It is drawn between every pair of
470 * points presmooth_time apart. If 25% of the first half of the line
471 * falls within one frame of the path, and 25% of the second half of
472 * the line falls within one frame of the path, then find the best
473 * fit of the line to the points within 1 frame. Then adjust the middle
474 * part of the line (from 25% to 75%) to fall along the line.
475 * Note that all this curve fitting is done on integer coordinates.
476 */
presmooth()477 void Scorealign::presmooth()
478 {
479 int n = ROUND(presmooth_time / actual_frame_period_1);
480 n = (n + 3) & ~3; // round up to multiple of 4
481 if (n < 4) {
482 SA_V(printf("presmooth time %g rounded to zero %gs frame periods.\n",
483 presmooth_time, actual_frame_period_1););
484 return;
485 }
486 int i = 0;
487 while (i < pathlen - n && pathx[i] + n <= last_x) {
488 /* line goes from i to i+n-1 */
489 int x1 = pathx[i];
490 int xmid = x1 + n/2;
491 int x2 = x1 + n;
492 int y1 = pathy[i];
493 int y2 = pathy[i + 1]; // make sure it has a value. y2 should be
494 // set in the loop below.
495 int j;
496 /* search for y2 = pathy[j] s.t. pathx[j] == x2 */
497 for (j = i + n; j < pathlen; j++) {
498 if (pathx[j] == x2) {
499 y2 = pathy[j];
500 break;
501 }
502 }
503 // this should not happen, but this guarantees that we found
504 // y2 and it is within the path:
505 if (j >= pathlen) break;
506
507 Regression regr;
508 /* see if line fits the data */
509 int k = i;
510 int count = 0;
511 while (pathx[k] < xmid) { // search first half
512 if (near_line(float(x1), float(y1), float(x2), float(y2),
513 pathx[k], pathy[k])) {
514 count++;
515 regr.point(pathx[k], pathy[k]);
516 }
517 k++;
518 }
519 /* see if points were close to line */
520 if (count < n/4) {
521 i++;
522 continue;
523 }
524 /* see if line fits top half of the data */
525 while (pathx[k] < x2) {
526 if (near_line(float(x1), float(y1), float(x2), float(y2),
527 pathx[k], pathy[k])) {
528 count++;
529 regr.point(pathx[k], pathy[k]);
530 }
531 k++;
532 }
533 /* see if points were close to line */
534 if (count < n/4) {
535 i++;
536 continue;
537 }
538 /* debug: */
539 SA_V(printf("presmoothing path from %d to %d:\n", i, j);)
540 SA_V(print_path_range(pathx, pathy, i, j);)
541 /* fit line to nearby points */
542 regr.regress();
543 /* adjust points to fall along line */
544 // basically reconstruct pathx and pathy from i to j
545 short x = pathx[i];
546 short y = pathy[i];
547 k = i + 1;
548 SA_V(printf("start loop: j %d, pathx %d, pathy %d\n",
549 j, pathx[j], pathy[j]);)
550 while (x < pathx[j] || y < pathy[j]) {
551 SA_V(printf("top of loop: x %d, y %d\n", x, y);)
552 // iteratively make an optional move in the +y direction
553 // then make a move in the x direction
554 // check y direction: want to move to y+1 if either we are below
555 // the desired y coordinate or we are below the maximum slope
556 // line (if y is too low, we'll have to go at sharper than 2:1
557 // slope to get to pathx[j], pathy[j], which is bad
558 int target_y = ROUND(regr.f(x));
559 SA_V(printf("target_y@%d %d, r %g, ", x, target_y, regr.f(x));)
560 // but what if the line goes way below the last point?
561 // we don't want to go below a diagonal through the last point
562 int dist_to_last_point = pathx[j] - x;
563 int minimum_y = pathy[j] - 2 * dist_to_last_point;
564 if (target_y < minimum_y) {
565 target_y = minimum_y;
566 SA_V(printf("minimum_y %d, ", minimum_y);)
567 }
568 // alternatively, if line goes too high:
569 int maximum_y = pathy[j] - dist_to_last_point / 2;
570 if (target_y > maximum_y) {
571 target_y = maximum_y;
572 SA_V(printf("maximum y %d, ", maximum_y);)
573 }
574 // now advance to target_y
575 if (target_y > y) {
576 pathx[k] = x;
577 pathy[k] = y + 1;
578 SA_V(printf("up: pathx[%d] %d, pathy[%d] %d\n",
579 k, pathx[k], k, pathy[k]);)
580 k++;
581 y++;
582 }
583 if (x < pathx[j]) {
584 // now advance x
585 x++;
586 // y can either go horizontal or diagonal, i.e. y either
587 // stays the same or increments by one
588 target_y = ROUND(regr.f(x));
589 SA_V(printf("target_y@%d %d, r %g, ", x, target_y, regr.f(x));)
590 if (target_y > y) y++;
591 pathx[k] = x;
592 pathy[k] = y;
593 SA_V(printf("pathx[%d] %d, pathy[%d] %d\n",
594 k, pathx[k], k, pathy[k]);)
595 k++;
596 }
597 }
598 // make sure new path is no longer than original path
599 // the last point we wrote was k - 1
600 k = k - 1; // the last point we wrote is now k
601 assert(k <= j);
602 // if new path is shorter than original, then fix up path
603 if (k < j) {
604 memmove(&pathx[k], &pathx[j], sizeof(pathx[0]) * (pathlen - j));
605 memmove(&pathy[k], &pathy[j], sizeof(pathy[0]) * (pathlen - j));
606 pathlen -= (j - k);
607 }
608 /* debug */
609 SA_V(printf("after presmoothing:\n");)
610 SA_V(print_path_range(pathx, pathy, i, k);)
611 /* since we adjusted the path, skip by 3/4 of n */
612 i = i + 3 * n/4;
613 }
614 }
615
616
617 /* COMPUTE_REGRESSION_LINES
618 computes the smooth time map from the path computed
619 by dynamic programming
620
621 */
compute_regression_lines()622 void Scorealign::compute_regression_lines()
623 {
624 int i;
625 // fill in time_map with NOT_MAPPED until the first point
626 // of the path
627 for (i = 0; i < pathx[0]; i++) {
628 time_map[i] = NOT_MAPPED;
629 }
630 // now, compute the y value of the path at
631 // each x value. If the path has multiple values
632 // on x, take the average.
633 int p = 0;
634 int upper, lower;
635 for (i = pathx[0]; p < pathlen; i++) {
636 lower = pathy[p];
637 while (p < pathlen && pathx[p] == i) {
638 upper = pathy[p];
639 p = p + 1;
640 }
641 time_map[i] = (lower + upper) * 0.5F;
642 }
643 // fill in rest of time_map with NOT_MAPPED
644 for (i = pathx[pathlen - 1] + 1; i <= last_x; i++) {
645 time_map[i] = NOT_MAPPED;
646 }
647 // now fit a line to the nearest WINDOW points and record the
648 // line's y value for each x.
649 compute_smooth_time_map();
650 }
651
652
midi_tempo_align(Alg_seq & seq)653 void Scorealign::midi_tempo_align(Alg_seq &seq)
654 {
655 // We create a new time map out of the alignment, and replace
656 // the original time map in the Alg_seq sequence
657 Alg_seq new_time_map_seq;
658
659 /** align at all integer beats **/
660 // totalbeats = lastbeat + 1 and round up the beat
661 int totalbeats = (int) seq.get_beat_dur() + 2;
662 if (verbose) {
663 double dur_in_sec = seq.get_real_dur();
664 printf("midi duration = %f, totalbeats=%i \n", dur_in_sec, totalbeats);
665 }
666 #ifdef DEBUG_LOG
667 fprintf(dbf, "***************** CONSTRUCTING TIME MAP ***************\n");
668 #endif
669 // turn off last tempo flag so last tempo will extrapolate
670 new_time_map_seq.get_time_map()->last_tempo_flag = false;
671 int first_beat = -1;
672 for (int i = 0; i < totalbeats; i++) {
673 double newtime = map_time(float(seq.get_time_map()->beat_to_time(i)));
674 if (newtime > 0) {
675 new_time_map_seq.insert_beat(newtime, (double) i);
676 // remember where the new time map begins
677 if (first_beat < 0) first_beat = i;
678 #ifdef DEBUG_LOG
679 fprintf(dbf, "map beat %d to time %g\n", i, newtime);
680 #endif
681 }
682 }
683 seq.convert_to_beats();
684 double end_beat = seq.get_dur();
685 Alg_time_map_ptr map = new_time_map_seq.get_time_map();
686 seq.set_time_map(map);
687 // the new time map begins where the alignment began, but due to
688 // smoothing and rounding, there may be some edge effects.
689 // Try to set the tempo before the first_beat to match the tempo
690 // at the first beat by introducing another time map point at least
691 // one beat before the first_beat. To do this, we need at least
692 // 2 beats before first_beat and at least 2 beats in the time map
693 // (needed to compute initial tempo). Furthermore, the tempo at
694 // first_beat could be so slow that we do not have enough time
695 // before first_beat to anticipate the tempo.
696 if (first_beat >= 2 && totalbeats > first_beat + 1) {
697 int new_beat = first_beat / 2;
698 // compute initial tempo from first_beat and first_beat + 1
699 int i = map->locate_beat(first_beat);
700 double t1 = map->beats[i].time;
701 double t2 = map->beats[i + 1].time;
702 double spb = (t2 - t1); // seconds per beat, beat period
703 double new_time = t1 - (first_beat - new_beat) * spb;
704 if (new_time <= 0.2) {
705 // not enough time to start at new_time, new_beat
706 // let's try using half the time rather than half the beats
707 new_time = t1 / 2.0;
708 // this will round down, so new_beat < first_beat
709 new_beat = int(first_beat - (t1 / 2) / spb);
710 new_time = t1 - (first_beat - new_beat) * spb;
711 }
712 // need to check again if new_beat would be too early
713 if (new_time > 0.2) {
714 map->insert_beat(new_time, new_beat);
715 }
716 }
717 // Note: final tempo is extrapolated, so no need to insert new
718 // time map points beyond the last one
719 seq.set_dur(end_beat);
720 #ifdef DEBUG_LOG
721 fprintf(dbf, "\nend_beat %g end time %g\n",
722 seq.get_beat_dur(), seq.get_real_dur());
723 #endif
724 }
725
726
727 // this routine performs an alignment by adjusting midi to match audio
728 //
align_midi_to_audio(Alg_seq & seq,Audio_reader & reader)729 int Scorealign::align_midi_to_audio(Alg_seq &seq, Audio_reader &reader)
730 {
731 float dur = 0.0F;
732 int nnotes = find_midi_duration(seq, &dur);
733 if (progress) {
734 progress->set_frame_period(frame_period);
735 progress->set_smoothing(line_time > 0.0);
736 progress->set_duration(0, false, dur);
737 progress->set_duration(1, true, float(reader.actual_frame_period *
738 reader.frame_count));
739 progress->set_phase(0);
740 }
741 /* Generate the chroma for file 0
742 * This will always be the MIDI File when aligning midi with audio.
743 */
744 file0_frames = gen_chroma_midi(seq, dur, nnotes, HIGH_CUTOFF, LOW_CUTOFF,
745 &chrom_energy0, &actual_frame_period_0, 0);
746
747 /* Generate the chroma for file 1 */
748 if (progress) progress->set_phase(1);
749 file1_frames = gen_chroma_audio(reader, HIGH_CUTOFF, LOW_CUTOFF,
750 &chrom_energy1, &actual_frame_period_1, 1);
751 return align_chromagrams();
752 }
753
align_audio_to_audio(Audio_reader & reader0,Audio_reader & reader1)754 int Scorealign::align_audio_to_audio(Audio_reader &reader0,
755 Audio_reader &reader1)
756 {
757 if (progress) {
758 progress->set_frame_period(frame_period);
759 progress->set_duration(0, true, float(reader0.actual_frame_period *
760 reader0.frame_count));
761 progress->set_duration(1, true, float(reader1.actual_frame_period *
762 reader1.frame_count));
763
764 progress->set_phase(0);
765 progress->set_smoothing(line_time > 0.0);
766 }
767 file0_frames = gen_chroma_audio(reader0, HIGH_CUTOFF, LOW_CUTOFF,
768 &chrom_energy0, &actual_frame_period_0, 0);
769
770 if (progress) progress->set_phase(1);
771 file1_frames = gen_chroma_audio(reader1, HIGH_CUTOFF, LOW_CUTOFF,
772 &chrom_energy1, &actual_frame_period_1, 1);
773
774 return align_chromagrams();
775 }
776
777
align_midi_to_midi(Alg_seq & seq0,Alg_seq & seq1)778 int Scorealign::align_midi_to_midi(Alg_seq &seq0, Alg_seq &seq1)
779 {
780 float dur0 = 0.0F;
781 int nnotes0 = find_midi_duration(seq0, &dur0);
782 float dur1 = 0.0F;
783 int nnotes1 = find_midi_duration(seq1, &dur1);
784 if (progress) {
785 progress->set_frame_period(frame_period);
786 progress->set_smoothing(line_time > 0.0);
787 progress->set_duration(0, false, dur0);
788 progress->set_duration(1, false, dur1);
789
790 progress->set_phase(0);
791 }
792 file0_frames = gen_chroma_midi(seq0, dur0, nnotes0,
793 HIGH_CUTOFF, LOW_CUTOFF,
794 &chrom_energy0, &actual_frame_period_0, 0);
795
796 if (progress) progress->set_phase(1);
797 file1_frames = gen_chroma_midi(seq1, dur1, nnotes1,
798 HIGH_CUTOFF, LOW_CUTOFF,
799 &chrom_energy1, &actual_frame_period_1, 1);
800
801 return align_chromagrams();
802 }
803
align_chromagrams()804 int Scorealign::align_chromagrams()
805 {
806 if (progress) progress->set_phase(2);
807 if (verbose)
808 printf("\nGenerated Chroma.\n");
809 /* now that we have actual_frame_period_1, we can compute smooth */
810 // smooth is an odd number of frames that spans about smooth_time
811 smooth = ROUND(smooth_time / actual_frame_period_1);
812 if (smooth < 3) smooth = 3;
813 if (!(smooth & 1)) smooth++; // must be odd
814 if (verbose) {
815 printf("smoothing time is %g\n", smooth_time);
816 printf("smooth count is %d\n", smooth);
817 }
818 SA_V(printf("Chromagram data for file 0:\n");)
819 SA_V(print_chroma_table(chrom_energy0, file0_frames);)
820 SA_V(printf("Chromagram data for file 1:\n");)
821 SA_V(print_chroma_table(chrom_energy1, file1_frames);)
822
823 /* Compare the chroma frames */
824 int result = compare_chroma();
825 if (result != SA_SUCCESS) {
826 return result;
827 }
828 if (progress) progress->set_phase(3);
829 /* Compute the smooth time map now for use by curve-fitting */
830 compute_regression_lines();
831 /* if presmooth_time is set, do presmoothing */
832 if (presmooth_time > 0.0) {
833 presmooth();
834 /* Redo the smooth time map after curve fitting or smoothing */
835 compute_regression_lines();
836 }
837 /* if line_time is set, do curve-fitting */
838 if (line_time > 0.0) {
839 curve_fitting(this, verbose);
840 /* Redo the smooth time map after curve fitting or smoothing */
841 compute_regression_lines();
842 }
843 if (progress) progress->set_phase(4);
844 return SA_SUCCESS;
845 }
846