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 &notes = (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