File: keyfinder.cpp

package info (click to toggle)
musescore3 3.2.3%2Bdfsg2-11
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 210,672 kB
  • sloc: cpp: 291,093; xml: 200,238; sh: 3,779; ansic: 1,447; python: 393; makefile: 240; perl: 82; pascal: 79
file content (929 lines) | stat: -rw-r--r-- 36,538 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2007-2011 Werner Schweer
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2
//  as published by the Free Software Foundation and appearing in
//  the file LICENCE.GPL
//=============================================================================


//=============================================================================
//  Keyfinder.cpp uses code from the "Melisma Music Analyzer"
//  project:
//
//  Copyright (C) 2000 Daniel Sleator and David Temperley
//  See http://www.link.cs.cmu.edu/music-analysis
//  for information about commercial use of this system
//=============================================================================

/* _Important comment about how pitches and keys are represented_

  Notes may be inputted in either Note format (with just a pitch) or
  TPCNote format (with a TPC). The user may also specify
  "npc_or_tpc_profile": 0 for npc, 1 for tpc. (A TPC profile is like
  an NPC profile except that a user-settable default value is used for
  TPC's outside the range of b2 to #4.) We assume that Note format
  will be used with an npc profile; if not, a fatal error is
  reported. TPCNote format could be used with either; if you use
  TPCNote input with an npc profile, the notes are simply mapped on to
  one cycle of the LOF.  (Scoring mode 0 or 3 requires an npc profile;
  otherwise a fatal error is reported.)

  Pitches (e.g. note[].tpc values) are always represented in
  line-of-fifths order, with C = 14. (This is done when the pitches
  are first read in). (If there's Note input, or TPCNote input and
  npc_or_tpc_profile = 0, all pitches are shifted into one cycle of
  the line of fifths, from 9 to 20 inclusive; again, this is done when
  the input is read.)

  Keys are also represented in line-of-fifths order. Major keys are 0
  to 27, C=14; minor keys are 28-55, C minor = 42. If
  npc_or_tpc_profile = 0, the search is nominally done on all keys,
  but only keys from 9 to 20 (inclusive) and 37 to 48 (inclusive) are
  looked at; others are given large negative values.

  Although key-profiles are read in in pitch-height order, they are
  then adjusted (in generate_tpc/npc_profiles) to line-of-fifths
  order, with the tonic as 5. */

#include <string.h>
#include <math.h>
#include "keyfinder.h"
#include "sig.h"
#include "pitchspelling.h"
#include "synthesizer/event.h"

namespace Ms {

#if 0
//---------------------------------------------------------
//   SBeat
//---------------------------------------------------------

struct SBeat {
      int time;
      };

//---------------------------------------------------------
//   MidiSegment
//---------------------------------------------------------

struct MidiSegment {
      int start;
      int end;
      QList<Event> snote;
      int numnotes;           // number of notes in the segment
      qreal average_dur;     // average input vector value (needed for K-S algorithm)
      };

#define CHANGE_PENALTY        12

static float change_penalty   = CHANGE_PENALTY;
static int npc_or_tpc_profile = 1;
static int scoring_mode       = 1;
static int verbosity          = 1;

static qreal major_profile[12] = {5.0, 2.0, 3.5, 2.0, 4.5, 4.0, 2.0, 4.5, 2.0, 3.5, 1.5, 4.0};
static qreal minor_profile[12] = {5.0, 2.0, 3.5, 4.5, 2.0, 4.0, 2.0, 4.5, 3.5, 2.0, 1.5, 4.0};
static qreal default_profile_value=1.5;

/*

CBMS profiles:

major_profile = 5.0 2.0 3.5 2.0 4.5 4.0 2.0 4.5 2.0 3.5 1.5 4.0
minor_profile = 5.0 2.0 3.5 4.5 2.0 4.0 2.0 4.5 3.5 2.0 1.5 4.0

Bayesian profiles (based on frequencies in Kostka-Payne corpus):

major_profile =  0.748  0.060  0.488  0.082  0.670  0.460  0.096  0.715  0.104  0.366  0.057  0.400
minor_profile =  0.712  0.084  0.474  0.618  0.049  0.460  0.105  0.747  0.404  0.067  0.133  0.330

Krumhansl's profiles:

major_profile = 6.35 2.23 3.48 2.33 4.38 4.09 2.52 5.19 2.39 3.66 2.29 2.88
minor_profile = 6.33 2.68 3.52 5.38 2.60 3.53 2.54 4.75 3.98 2.69 3.34 3.17

Krumhansl's minor, normalized: 5.94 2.51 3.30 5.05 2.44 3.31 2.38 4.46 3.73 2.52 3.13 2.97

*/

static int firstbeat;
static QList<Event> note;
static QList<MidiSegment> segment;  // An array storing the notes in each segment
static int segtotal;                // total number of segments - 1

static qreal seglength;

static QList<int> seg_prof[28];
static QList<qreal> keyscore()[56];
static QList<SBeat> sbeat;
static QList<qreal> analysis[56][56];

static QList<int> best[56];
static QList<int> final;

// static int numnotes, numchords, num_sbeats;
static int num_sbeats;

static QList<int> pc_tally;
static QList<qreal> finalscore();

static qreal key_profile[56][28];
static int final_timepoint;

//---------------------------------------------------------
//   print_keyname
//---------------------------------------------------------

static void print_keyname(int f)
      {
      static const char letter[] = "CGDAEBF";

      int mf = f % 27;
      mf -= 14;

      qDebug("(%d,%d)%c", f, mf, letter[f % 7]);
      if (f < 6 || (f >= 28 && f < 34))
            qDebug("-");
      if ((f >= 6 && f < 13) || (f >= 34 && f < 41))
            qDebug("b");
      if ((f >= 20 && f < 27) || (f >=48 && f < 55))
            qDebug("#");
      if (f == 27 || f == 55)
            qDebug("x");
      if (f >= 28)
            qDebug("m");
      if (f < 28)
            qDebug(" ");
      qDebug(" ");
      }

//---------------------------------------------------------
//   create_segments
//    Each segment starts at a sbeat and ends at the
//    following sbeat
//---------------------------------------------------------

static void create_segments()
      {
      segment.append(MidiSegment());

      segment[0].start = firstbeat; // Always start a segment at the very beginning of the piece (the first beat)
      for (int b = 0; b < num_sbeats; b++) {
            if (b == 0 && (sbeat[0].time - firstbeat) < ((sbeat[1].time-firstbeat) - (sbeat[0].time-firstbeat))/2)
                  continue;
            /* If it's the first beat of the piece, and the upbeat is
               less than half of the first beat interval, don't start a segment
            */
            else {
                  MidiSegment seg;
                  seg.start          = sbeat[b].time;
                  segment.back().end = sbeat[b].time;
                  segment.append(seg);
                  }
            }
      int s = segment.size() - 1;

    /* If final segment starts at or after final timepoint of piece, ignore it,
       decrementing number of segments by 1; if not, set that segment's ending
       to final timepoint of piece
       */
      if (segment[s].start >= final_timepoint) {
            s--;
            }
      else {
            segment[s].end = final_timepoint;
            /* qDebug("Final segment ends at %d", segment[s].end); */
            }
      segtotal = s; // index of final segment
      }

//---------------------------------------------------------
//   fill_segments
//---------------------------------------------------------

static void fill_segments()
      {
      for (int s = 0; s < segment.size(); ++s) {
            foreach (const Event& n, note) {
                  int ontime  = n.ontime();
                  int offtime = n.offtime();
                  int start   = segment[s].start;
                  int end     = segment[s].end;

                  if (ontime >= start && ontime < end && offtime <= end) {
                        // note begins and ends in segment
                        Event sn(ME_NOTE);
                        sn.setDataA(n.dataA());
                        sn.setTpc(n.tpc());
                        sn.setDuration(n.duration());
                        segment[s].snote.append(sn);
                        }
                  if (ontime >= start && ontime < end && offtime > end) {
                        // note begins, doesn't end in segment
                        Event sn(ME_NOTE);
                        sn.setDataA(n.dataA());
                        sn.setTpc(n.tpc());
                        sn.setDuration(end - ontime);
                        segment[s].snote.append(sn);
                        }
                  if (ontime < start && offtime > start && offtime <= end) {
                        // note ends, doesn't begin in segment
                        Event sn(ME_NOTE);
                        sn.setDataA(n.dataA());
                        sn.setTpc(n.tpc());
                        sn.setDuration(offtime - start);
                        segment[s].snote.append(sn);
                        }
                  if (ontime < start && offtime > end) {
                        // note doesn't begin or end in segment
                        Event sn(ME_NOTE);
                        sn.setDataA(n.dataA());
                        sn.setTpc(n.tpc());
                        sn.setDuration(end - start);
                        segment[s].snote.append(sn);
                        }
                  }
            segment[s].numnotes = segment[s].snote.size();
            // qDebug("fillSegments %d: %d-%d  %d", s, segment[s].start,
            //   segment[s].end, segment[s].numnotes);
            }
      }

//---------------------------------------------------------
//   count_segment_notes
//    In each segment, tally up the notes of each TPC
//---------------------------------------------------------

static void count_segment_notes()
      {
      for (int s = 0; s <= segtotal; ++s) {
            pc_tally[s] = 0;

            for (int y = 0; y < 28; ++y)  // cycle through the pc's, make sure all the seg_prof values are zero
                  seg_prof[y].append(0);

            qreal total_dur = 0;

            for (int n = 0; n < segment[s].numnotes; ++n) {
                  if (scoring_mode == 0)
                        total_dur += segment[s].snote[n].duration();
                  for (int y=0; y<28; ++y) {
                        if (segment[s].snote[n].tpc() == y) {
                              if(seg_prof[y][s]==0)
                                    pc_tally[s]++;
                              /* This keeps track of how many different pc's the segment contains. This counts TPCs, not NPCs! */
                              /* If scoring_mode is > 1, set array value to 1. If 0, add the note's duration to the
                                 array value (as in the K-S algorithm) */
                              if (scoring_mode > 0)
                                    seg_prof[y][s] = 1;
                              else {
                                    seg_prof[y][s] += segment[s].snote[n].duration();
                                    }
                              }
                        }
                  }

            if(scoring_mode == 0) {
                  if(pc_tally[s]==0)
                        segment[s].average_dur = 0.0;
                  segment[s].average_dur = total_dur / 12.0;
                  /* qDebug("Segment %d total dur = %6.3f, average dur = %6.3f", s, total_dur, segment[s].average_dur); */
                  }

            if (verbosity>=2) {
                  qDebug("Segment %d: ", s);
                  for (int y=0; y<28; ++y) {
                        if(npc_or_tpc_profile == 0 && (y<9 || y>20))
                              continue;
                        qDebug("%d ", seg_prof[y][s]);
                        }
                  }
            /* qDebug("pc_tally = %d", pc_tally[s]); */
            }
      }

//---------------------------------------------------------
//   prepare_profiles
//    We're only here if scoring_mode is 0 (the K-S algorithm).
//    Sum all the profile values, take the mean,
//    and subtract that from each value
//---------------------------------------------------------

static void prepare_profiles()
      {
      qreal total = 0.0;
      for (int i = 0; i < 12; i++) {
            total += major_profile[i];
            }
      qreal average = total / 12.0;
      for (int i = 0; i < 12; i++)
            major_profile[i]=major_profile[i] - average;

      total = 0;
      for (int i = 0; i < 12; i++) {
            total += minor_profile[i];
            }
      average = total / 12.0;
      for (int i = 0; i < 12; i++)
            minor_profile[i] = minor_profile[i] - average;

      if (verbosity > 2) {
            qDebug("Adjusted major profile: ");
            for(int i = 0; i < 12; i++)
                  qDebug("%6.3f ", major_profile[i]);
            qDebug("Adjusted minor profile: ");
            for (int i = 0; i < 12; i++)
                  qDebug("%6.3f ", minor_profile[i]);
            }
      }

/* Here we generate the key profiles. (This is assuming tpc input.) Key_profile[key] numbers correspond to
   the line of fifths, with C = 14.  Major keys are 0-27, minor keys are 28-55. PCs are also numbered
   according to the line of fifths. The major_step_profile has the tonic in step 5. For a given key, the
   profile value for a given tpc is equal to the line of fifths difference between the tpc and the key, plus
   5. */

//---------------------------------------------------------
//   generate_tpc_profiles
//---------------------------------------------------------

static void generate_tpc_profiles()
      {
      int key, shift, tpc, i;
      float majp[12];
      float minp[12];

      /* First we rearrange the key profile values (inputted in pitch height order) into lof order, C = 5 */
      for(i=0; i<12; i++) {
            majp[((((i * 7) % 12) + 5) % 12)] = major_profile[i];
            minp[((((i * 7) % 12) + 5) % 12)] = minor_profile[i];
            }

      for (key=0, shift=0; key<28; ++key, ++shift) {
            for (tpc=0; tpc<28; ++tpc) {
                  if (tpc-shift >= -5 && tpc-shift <= 6) {
                        key_profile[key][tpc] = majp[5 + (tpc-shift)];    /* For example: for key 14 (C major) and tpc 17 (A),
                                                                             use profile step 5 + (17-14) = 8 */
                        }
                  if (tpc-shift < -5 || tpc-shift > 6) {
                        key_profile[key][tpc] = default_profile_value;
                        }
                  }
            }
      for (key=28, shift=0; key<56; ++key, ++shift) {
            for (tpc=0; tpc<28; ++tpc) {
                  if (tpc-shift >= -5 && tpc-shift <= 6 ) {
                        key_profile[key][tpc] = minp[5 + (tpc-shift)];
                        }
                  if (tpc-shift < -5 || tpc-shift > 6) {
                        key_profile[key][tpc] = default_profile_value;
                        }
                  }
            }
/*
      This routine just prints out the key profiles
      for(key=0; key<56; ++key) {
            for(tpc=0; tpc<28; ++tpc) {
                  qDebug("%1.2f ", key_profile[key][tpc]);
                  }
            }
*/
      }

//-----------------------------------------------------------------------------
//   generate_npc_profiles
//    This is an alternative function for generating profiles given NPC
//    profile. It's similar to the TPC version, except that only keys on
//    a certain range of the line are assigned non-zero values. (This is
//    really an unnecessary step, as steps outside the range will be
//    disqualified in "match_profiles" in any case.) Also, for those keys
//    considered, only key profile slots within the 9-to-20 range are
//    assigned non-zero values. (This is necessary, since the input profile
//    is within this range as well.) We begin by assigning default values
//    of ZERO to everything.
//-----------------------------------------------------------------------------

static void generate_npc_profiles()
      {
      int key, shift, tpc, tpc_to_use, i;
      float majp[12];
      float minp[12];

      /* First we rearrange the key profile values (inputted in pitch height order) into lof order, C = 5 */
      for(i=0; i<12; i++) {
            majp[((((i * 7) % 12) + 5) % 12)] = major_profile[i];
            minp[((((i * 7) % 12) + 5) % 12)] = minor_profile[i];
            }
      for (key=0; key<56; ++key) {
            for(tpc=0; tpc<28; ++tpc) {
                  key_profile[key][tpc]=0;
                  }
            }

      for (key=9, shift=9; key<21; ++key, ++shift) {
            for (tpc=0; tpc<28; ++tpc) {
                  /* tpc_to_use is the profile slot to use for a given value of the key-profile. In this way we keep
                     all profile slots within the 9-to-20 range
                  */
                  if(tpc<9)
                        tpc_to_use=tpc+12;
                  else if(tpc>20)
                        tpc_to_use=tpc-12;
                  else
                        tpc_to_use = tpc;
                  if (tpc-shift >= -5 && tpc-shift <= 6) {
                        /* For example: for key 14 (C major) and tpc 17 (A),
                        read from profile step 5 + (17-14) = 8. For degree
                        6 of B major (key 19), degree 6 (22) is outside the
                        9-to-20 range, so tpc_to_use is 22-12=10; still
                        read from profile step 5 + (22-19) = 8. */

                        key_profile[key][tpc_to_use] = majp[5 + (tpc-shift)];
                        }
                  }
            }
      for (key = 37, shift = 9; key < 49; ++key, ++shift) {
            for (tpc = 0; tpc < 28; ++tpc) {
                  if (tpc<9)
                        tpc_to_use = tpc + 12;
                  else if (tpc > 20)
                        tpc_to_use = tpc - 12;
                  else
                        tpc_to_use = tpc;
                  if (tpc - shift >= -5 && tpc - shift <= 6 ) {
                        key_profile[key][tpc_to_use] = minp[5 + (tpc-shift)];
                        }
                  }
            }

/*
      for(key = 0; key < 56; ++key) {
            for(tpc=0; tpc<28; ++tpc) {
                  qDebug("%1.2f ", key_profile[key][tpc]);
                  }
            }
*/
      }

//---------------------------------------------------------
//   match_profiles
//    Here we generate the "key scores" - the local score for each key
//
//    Notice that we generate profiles for all 56 keys and all 28
//    tpc's, even in the case where npc profiles are being used. In
//    this case, though, all keys outside the allowable NPC range are
//    given large negative values.  And within both the key profiles
//    and the segment profiles, only TPC's within the range 9 to 20
//    have been given nonzero values.
//---------------------------------------------------------

static void match_profiles()
      {
      int key, tpc, s, best_key, i;
      qreal major_sumsq, minor_sumsq, input_sumsq;
      qreal kprob[56];

      for (key = 0; key < 56; ++key) {
            for (s = 0; s <= segtotal; ++s)
                  keyscore()[key].append(0.0);
            }

      if (scoring_mode==0) {
            major_sumsq = 0.0;
            minor_sumsq = 0.0;
            for(i=0; i<12; i++)
                  major_sumsq += major_profile[i]*major_profile[i];
            for(i=0; i<12; i++)
                  minor_sumsq += minor_profile[i]*minor_profile[i];
            if (verbosity==3)
                  qDebug("major_sumsq = %6.3f, minor_sumsq = %6.3f", major_sumsq, minor_sumsq);
            }

      qreal total_prob[segtotal + 1];

      for (s = 0; s <= segtotal; ++s) {
            if (scoring_mode==0) {
                  input_sumsq = 0.0;
                  for (i = 9; i <= 20; i++) {
                        input_sumsq += pow((seg_prof[i][s]-segment[s].average_dur), 2.0);
                        /* qDebug("%d X %6.3f squared is %6.3f", seg_prof[i][s], segment[s].average_dur, pow((seg_prof[i][s]-segment[s].average_dur), 2.0)); */
                        }
                  if (verbosity==3)
                        qDebug("For segment %d: average_dur = %6.3f; input_sumsq = %6.3f", s, segment[s].average_dur, input_sumsq);
                  }
            best_key=0;

            for (key=0; key<56; ++key) {
                  kprob[key] = 0.0;
                  keyscore()[key][s] = -1000000.0;
                  if (npc_or_tpc_profile==0 && (key<9 || (key>20 && key<37) || key>48))
                        continue;
                  kprob[key] = 1.0;
                  keyscore()[key][s] = 0.0;
                  for (tpc=0; tpc<28; ++tpc) {

          /*
             If scoring mode is 0, this is the K-S algorithm (this works for npc mode only). Segment
             profile values represent total duration of each pc (in all other cases, they're just 1
             for present pc's and 0 for absent ones). Key-profiles have been normalized linearly
             around the average key-profile value. We normalize the input values similarly by taking
             (seg_prof[tpc][s]-segment[s].average_dur). Then we multiply each normalized KP value by
             the normalized input value, and sum these products; this gives us the numerator of the
             correlation expression (as commented below). We've summed the squares of the normalized
             key-profile value (major_sumsq and minor_sumsq above) and the normalized input values
             (input_sumsq above), so this allows us to calculate the denominator also.

             If scoring_mode is 1, the key score is the sum of key-profile values for all pc's present
             (this is the algorithm used in CBMS)

             If scoring_mode is 2, calculate key scores as above, but divide each one by the number
             of pc's in the segment

             If scoring_mode is 3: for each key, add the log of the key-profile value for all present pc's;
             subtract values for all absent pc's. (This is the Bayesian approach; assume key-profiles
             represent pc distribution's in a corpus, i.e. the number of segments containing each scale
             degree)
          */

                        if(scoring_mode == 0) {
                              if(tpc<9 || tpc>20)
                                    continue;
                              /* calculate numerator */
                              keyscore()[key][s] += key_profile[key][tpc] * (seg_prof[tpc][s]-segment[s].average_dur);
                              /* qDebug("x-X=%6.3f, y-Y=%6.3f, product=%6.3f, new total=%6.3f", key_profile[key][tpc], seg_prof[tpc][s]-segment[s].average_dur, key_profile[key][tpc] * (seg_prof[tpc][s]-segment[s].average_dur), keyscore()[key][s]); */
                              }

                        if(scoring_mode==1 || scoring_mode==2)
                              keyscore()[key][s] += (key_profile[key][tpc] * seg_prof[tpc][s]);

                        if(scoring_mode == 3) {
                              /* if(tpc>11) continue; */
                              /* if(tpc<9 || tpc>20) continue; */

                              if(seg_prof[tpc][s]==0) {
                                    keyscore()[key][s] += log(1.000 - key_profile[key][tpc]);
                              /* qDebug("kp value = %6.3f: log(1-p) = %6.3f: score = %6.3f", key_profile[key][tpc], log(1.000 - key_profile[key][tpc]), keyscore()[key][s]); */
                                    if(tpc>=9 && tpc<=20)
                                          kprob[key] *= (1.000 - key_profile[key][tpc]);
                                    }
                              else {
                                    keyscore()[key][s] += log(key_profile[key][tpc]);
                                    if(tpc>=9 && tpc<=20)
                                          kprob[key] *= key_profile[key][tpc];
                                    }

                              /* qDebug("kp value = %6.3f: log(p) = %6.3f: score = %6.3f", key_profile[key][tpc], log(key_profile[key][tpc]), keyscore()[key][s]); */
                              }
                        }

                  if(scoring_mode == 0) {
                        /* qDebug("sqrt(major_sumsq * input_sumsq) = %6.3f", sqrt(major_sumsq * input_sumsq)); */
                        /* calculate denominator */
                        if(key<28)
                              keyscore()[key][s] = keyscore()[key][s] / sqrt(major_sumsq * input_sumsq);
                        else
                              keyscore()[key][s] = keyscore()[key][s] / sqrt(minor_sumsq * input_sumsq);
                        }
                  if(scoring_mode == 2) {
                        if(pc_tally[s] == 0)
                              keyscore()[key][s] = 0;
                        else
                              keyscore()[key][s] = keyscore()[key][s] / pc_tally[s];
                        }

                  /* if(s==0) qDebug("local score for key %d on segment %d: %6.3f", key, s, keyscore()[key][s]); */
                  if (keyscore()[key][s] > keyscore()[best_key][s])
                        best_key = key;
                  }

            if(verbosity>=2) {
                  qDebug("The best local key for segment %d at time %d is ", s, segment[s].start);
                  print_keyname(best_key);
                  qDebug("with score %6.3f", keyscore()[best_key][s]);
                  }

            if(scoring_mode==3) {
                  total_prob[s]=0.0;
                  for(key=0; key<56; key++) {
                        total_prob[s] += kprob[key] / 24.0;
                        /* qDebug("  Prob of segment %d given key %d: %6.8f", s, key, kprob[key]);  */
                        }

                  /* Now total_prob[s] is the total probability of the segment occurring: its probability given
                     a key, summed over all keys. But suppose we want to know the probability of ANY major triad
                     occurring? Then we have to multiply this by 12. (But not for something like a diminished
                     seventh which is symmetrical! */

                  if (verbosity>=3) {
                        qDebug("Best key for segment %d = %d, score = %6.8f", s, best_key, kprob[best_key]);
                        qDebug("Total (local) probability of segment %d: %6.8f", s, total_prob[s]);
                        }
                  }
            }
      }

//---------------------------------------------------------
//   choose_best_i
//---------------------------------------------------------

static void choose_best_i(int seg)
      {
      for (int j = 0; j < 56; ++j) {
            int k = 0;
            for (int i = 0; i < 56; ++i) {
                  if (analysis[i][j][seg] > analysis[k][j][seg])
                        k=i;
                  }
            /* For a given segment seg, and the key j at that segment,
               the best previous key is k
            */
            int size = best[j].size();
            for (int i = size; i < seg+1; ++i)
                  best[j].append(0);
            best[j][seg] = k;
            /* qDebug("For segment-%d-key %d, best segment-%d-key is %d, with score %d", seg, j, seg-1, k, analysis[k][j][seg]); */
            }
      }

//---------------------------------------------------------
//   make_first_table
//---------------------------------------------------------

static void make_first_table(int seg)
      {
      int i, j, s;
      qreal seg_factor, mod_factor, nomod_factor;

      if (scoring_mode==3) {
            mod_factor = log(change_penalty);
            nomod_factor = log(1.0 - change_penalty);
            seg_factor = 1.0;
            }
      else {
            mod_factor = -change_penalty;
            nomod_factor = 0.0;
            seg_factor = seglength;
            }

      for(s = 0; s <= segtotal; s++) {
            for(i = 0; i < 56; ++i) {
                  for(j = 0; j < 56; ++j)
                        analysis[i][j].append(-1000.0);
                  }
            }

      for(i = 0; i < 56; ++i) {
            for(j = 0; j < 56; ++j) {
                  if (j != i)
                        analysis[i][j][1] = ((keyscore()[i][0] + keyscore()[j][1]) * seg_factor) + mod_factor;
                  else
                        analysis[i][j][1] = ((keyscore()[i][0] + keyscore()[j][1]) * seg_factor) + nomod_factor;
                  }
            }
      choose_best_i(seg);
      }

//---------------------------------------------------------
//   make_tables
//---------------------------------------------------------

static void make_tables()
      {
      qreal seg_factor, mod_factor, nomod_factor;

      /* When scoring_mode = 3, the change_penalty represents the probability of changing key. So raising
         the penalty actually _increases_ the likelihood of modulations. */

      if (scoring_mode == 3) {
            mod_factor = log(change_penalty / 23.0);
            nomod_factor = log(1.0 - change_penalty);
            seg_factor = 1.0;
            }
      else {
            mod_factor = -change_penalty;
            nomod_factor = 0.0;
            seg_factor = seglength;
            }

      for (int seg = 2; seg <= segtotal; ++seg) {
            /* qDebug("mod_factor = %6.6f; ; nomod_factor = %6.6f", mod_factor, nomod_factor);  */
            for(int j = 0; j < 56; ++j) {
                  for(int i = 0; i < 56; ++i) {
                        int n = best[i][seg-1];
                        if (j != i)
                              analysis[i][j][seg] = analysis[n][i][seg-1] + (keyscore()[j][seg] * seg_factor) + mod_factor;
                        else
                              analysis[i][j][seg] = analysis[n][i][seg-1] + (keyscore()[j][seg] * seg_factor) + nomod_factor;
                        }
                  }
            choose_best_i(seg);
            }
      }

//---------------------------------------------------------
//   best_key_analysis
//---------------------------------------------------------

static void best_key_analysis()
      {
      int n, m, f, tie1=-1, tie2=-1;
      int s = segtotal;
      int k = 0;
      for(int j = 0; j < 56; ++j) {
            n = best[j][s];
            m = best[k][s];

            if (analysis[n][j][s] < analysis[m][k][s] + .001 && analysis[n][j][s] > analysis[m][k][s] - .001 && j!=k) {
                  tie1=j;
                  tie2=k;
                  }

            if (verbosity>1 && !(npc_or_tpc_profile == 0 && (j<9 || (j>20 && j<37) || j>48))) {
                  qDebug("Final score for ");
                  print_keyname(j);
                  /* qDebug("is %6.3f", analysis[n][j][s] * 1000 / (segment[segtotal].end - segment[0].start));   */
                  qDebug("is %6.3f", analysis[n][j][s]);
                  }
            if (analysis[n][j][s] > analysis[m][k][s] + .000001) {
                  /* The .000001 is to fix a strange bug: sometimes it thinks the conditional is satisfied in the case of ties */
                  k = j;   /* compute best key of final segment */
                  }
            }

      // To force a key choice at the final segment, insert key number here

      final[s] = k;
      if (verbosity > 1)
            if (k==tie1 || k==tie2)
                  qDebug("Tie at the end between %d and %d", tie1, tie2);

      // Here's where we take the best key choices and put them into final[s]

      for(s = segtotal; s >= 1; --s) {
            final[s-1] = best[k][s];
            k = final[s-1];
            }

      if (verbosity >= 2) {
            qDebug("Segment 0: key choice %d; total score %6.3f; segment score %6.3f", final[0], keyscore()[final[0]][0],
               keyscore()[final[0]][0]);
            qDebug("Segment 1: key choice %d; total score %6.3f; segment score %6.3f", final[1], analysis[final[0]][final[1]][1], analysis[final[0]][final[1]][1] - keyscore()[final[0]][0]);
            for(s = 2; s <= segtotal; s++) {
                  qDebug("Segment %d: key choice %d; total score %6.3f; segment score %6.3f", s, final[s], analysis[final[s-1]][final[s]][s], analysis[final[s-1]][final[s]][s] - analysis[final[s-2]][final[s-1]][s-1]);
                  }
            }

      if (verbosity > 1)
            qDebug("'Key-fit' scores for preferred analysis:");

      /* This routine calculates the key-fit scores for the final analysis. These are really per-second scores.
         Key-profile scores are not multiplied by seglength (as they would be in actually computing the analyses);
         change penalties are divided by seglength. */

      for (s = 0; s <= segtotal; ++s) {
            f = final[s];
            if (s > 0 && final[s] != final[s-1])
                  finalscore()[s] = (keyscore()[f][s]) - (change_penalty / seglength);
            else
                  finalscore()[s]=keyscore()[f][s];
            if (verbosity > 1) {
                  qDebug(" segment %d: %6.3f", s, finalscore()[s]);
                  }
            }
      }

//---------------------------------------------------------
//   findKey
//---------------------------------------------------------

int findKey(MidiTrack* mt, TimeSigMap* sigmap)
      {
      int tpc_found, npc_found;

      if ((scoring_mode == 0 || scoring_mode == 3) && npc_or_tpc_profile == 1) {
            qDebug("Error: scoring mode %d requires an npc profile", scoring_mode);
            exit(1);
            }

      final_timepoint=0;

      tpc_found = 0;
      npc_found = 0;

      int lastTick = 0;
      const EventList el = mt->events();

      foreach (Event e, el) {
            if (e.type() != ME_NOTE)
                  continue;
            if (e.offtime() > lastTick)
                  lastTick = e.offtime();
            // For note input, generate TPC labels within the 9-to-20 range
            e.setTpc((((((e.pitch() % 12) * 7) % 12) + 5) % 12) + 9);
            note.append(e);
            }
      spell(note, 0);
      npc_found = 1;

      // create one segment for every measure
      for (int i = 0;; ++i) {
            int tick = sigmap->bar2tick(i, 0, 0);
            SBeat b;
            b.time = tick;
            sbeat.append(b);
            if (tick > lastTick)
                  break;
            }

      firstbeat       = sbeat.first().time;
      final_timepoint = sbeat.last().time;
      num_sbeats      = sbeat.size();

      numnotes   = note.size();
      numchords  = 0;

      if (note.empty()) {
            qDebug("Error: No notes in input.");
            return 0;
            }

      seglength  = (sbeat[1].time - sbeat[0].time) / 1000.0; /* define segment length as the length of the first segment */
      if (verbosity > 1)
            qDebug("seglength = %3.3f", seglength);

      create_segments();
      for (int i = 0; i < segtotal+1; ++i) {
            final.append(0);
            pc_tally.append(0);
            finalscore().append(0.0);
            }

      fill_segments();
      count_segment_notes();
      if (scoring_mode==0)
            prepare_profiles();
      if (npc_or_tpc_profile == 1)
            generate_tpc_profiles();
      else
            generate_npc_profiles();
      match_profiles();
      if (segtotal > 0) {
            make_first_table(1);
            make_tables();
            best_key_analysis();
            }

      QList<int> keys;
      for (int i = 0; i < 27; ++i)
            keys.append(0);
      for (int i = 0; i <= segtotal; ++i) {
            keys[final[i] % 27]++;        // fold major/minor
//            qDebug("key %d: %d  %d", i, final[i], (final[i] % 27) - 14);
            }
      int xkey   = 0;
      int xcount = 0;
      for (int i = 0; i < 27; ++i) {
            if (keys[i] > xcount) {
                  xcount = keys[i];
                  xkey   = i;
                  }
            }

      xkey -= 14;
      if (xkey < -7 || xkey > 7) {
            qDebug("findKey(): illegal key %d found", xkey);
            xkey = 0;
            }

      spell(note, xkey);      // spell again with found key

      //
      // clear all arrays
      //
      note.clear();
      segment.clear();
      sbeat.clear();
      for (int i = 0; i < 28; ++i)
            seg_prof[i].clear();
      for (int i = 0; i < 56; ++i) {
            keyscore()[i].clear();
            best[i].clear();
            for (int k = 0; k < 56; ++k)
                  analysis[i][k].clear();
            }
      final.clear();
      pc_tally.clear();
      finalscore().clear();

      return xkey;
      }
#endif

}