1 | "use strict";
|
---|
2 |
|
---|
3 | // Key detection algorithm, as described at:
|
---|
4 | // http://rnhart.net/articles/key-finding/
|
---|
5 |
|
---|
6 | // (from this web site) ....
|
---|
7 | // Krumhansl-Schmuckler key-finding algorithm (by Carol L. Krumhansl
|
---|
8 | // and Mark A. Schmuckler). The profile numbers came from experiments
|
---|
9 | // done by Krumhansl and Edward J. Kessler. The experiments consisted
|
---|
10 | // of playing a set of context tones or chords, playing a probe tone,
|
---|
11 | // and asking a listener to rate how well the probe tone fit with the
|
---|
12 | // context. You can read about the experiments and the algorithm in
|
---|
13 | // Krumhansl's book Cognitive Foundations of Musical Pitch. (The
|
---|
14 | // experiments are described in Chapter 2. The key-finding algorithm
|
---|
15 | // is described in Chapter 4.) You may be able to read portions of
|
---|
16 | // the book on Google Books.
|
---|
17 |
|
---|
18 |
|
---|
19 | function khMean(vals)
|
---|
20 | {
|
---|
21 | var len = vals.length;
|
---|
22 | if (len==0) {
|
---|
23 | return 0;
|
---|
24 | }
|
---|
25 |
|
---|
26 | var total = 0;
|
---|
27 |
|
---|
28 | for (var i=0; i<len; i++) {
|
---|
29 | total += vals[i];
|
---|
30 | }
|
---|
31 |
|
---|
32 | return total/len;
|
---|
33 | }
|
---|
34 |
|
---|
35 | function khUnbiased(vals)
|
---|
36 | {
|
---|
37 | var unbiased_vals = [];
|
---|
38 |
|
---|
39 | var len = vals.length;
|
---|
40 | if (len==0) {
|
---|
41 | return unbiased_vals;
|
---|
42 | }
|
---|
43 |
|
---|
44 | var avg = khMean(vals);
|
---|
45 |
|
---|
46 | for (var i=0; i<len; i++) {
|
---|
47 | unbiased_vals.push(vals[i] - avg)
|
---|
48 | }
|
---|
49 |
|
---|
50 | return unbiased_vals;
|
---|
51 | }
|
---|
52 |
|
---|
53 |
|
---|
54 | function khCorrelationCoefficientUnbiased(a1,a2)
|
---|
55 | {
|
---|
56 | var a1_len = a1.length;
|
---|
57 | var a2_len = a2.length;
|
---|
58 |
|
---|
59 | if (a1_len != a2_len) {
|
---|
60 | throw "khCorrelationCoefficientUnbiased(): arrays should be of the same length (" + a1_len + " vs " + a2_len + ")";
|
---|
61 | }
|
---|
62 | var len = a1_len;
|
---|
63 |
|
---|
64 | var a1_a2_pair_prod = 0;
|
---|
65 | var a1_square = 0;
|
---|
66 | var a2_square = 0;
|
---|
67 |
|
---|
68 | for (var i=0; i<len; i++) {
|
---|
69 | a1_a2_pair_prod += (a1[i]*a2[i]);
|
---|
70 | a1_square += (a1[i]*a1[i]);
|
---|
71 | a2_square += (a2[i]*a2[i]);
|
---|
72 | }
|
---|
73 |
|
---|
74 | return a1_a2_pair_prod / Math.sqrt(a1_square * a2_square);
|
---|
75 | }
|
---|
76 |
|
---|
77 |
|
---|
78 | //major profile
|
---|
79 | //do do# re re# mi fa fa# so so# la la# ti
|
---|
80 | //6.35 2.23 3.48 2.33 4.38 4.09 2.52 5.19 2.39 3.66 2.29 2.88
|
---|
81 |
|
---|
82 | //minor profile
|
---|
83 | //la la# ti do do# re re# mi fa fa# so so#
|
---|
84 | //6.33 2.68 3.52 5.38 2.60 3.53 2.54 4.75 3.98 2.69 3.34 3.17
|
---|
85 |
|
---|
86 |
|
---|
87 | // kh = Krumhansl
|
---|
88 |
|
---|
89 | var kh_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 ];
|
---|
90 |
|
---|
91 | var kh_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 ];
|
---|
92 |
|
---|
93 | var kh_major_profile_unbiased = khUnbiased(kh_major_profile);
|
---|
94 |
|
---|
95 | var kh_minor_profile_unbiased = khUnbiased(kh_minor_profile);
|
---|
96 |
|
---|
97 |
|
---|
98 | function khCreatePairing(profile,chromatic_scale_durations,offset)
|
---|
99 | {
|
---|
100 | var p = [];
|
---|
101 | var c = [];
|
---|
102 | for (var i=0; i<12; i++) {
|
---|
103 | p.push(profile[i])
|
---|
104 | c.push(chromatic_scale_durations[(i+offset)%12]);
|
---|
105 | }
|
---|
106 |
|
---|
107 |
|
---|
108 | return {"p":p, "c":c};
|
---|
109 | }
|
---|
110 |
|
---|
111 |
|
---|
112 | function khCreateAllPairings(chromatic_scale_durations_unbiased,major_profile_unbiased,minor_profile_unbiased)
|
---|
113 | {
|
---|
114 | // chromatic_scale_durations.length = 12
|
---|
115 |
|
---|
116 | var chromatic_keys = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
---|
117 |
|
---|
118 | var major_pairings = {};
|
---|
119 | var minor_pairings = {};
|
---|
120 |
|
---|
121 | // Foreach scale
|
---|
122 | for (var s=0; s<12; s++) {
|
---|
123 | var scale = chromatic_keys[s];
|
---|
124 |
|
---|
125 | major_pairings[scale] = khCreatePairing(major_profile_unbiased,chromatic_scale_durations_unbiased,s);
|
---|
126 | minor_pairings[scale] = khCreatePairing(minor_profile_unbiased,chromatic_scale_durations_unbiased,s);
|
---|
127 | }
|
---|
128 |
|
---|
129 | return {"major" : major_pairings, "minor" : minor_pairings};
|
---|
130 | }
|
---|
131 |
|
---|
132 | function khComputeKeyCorrelationCoefficients(key_pairings_unbiased)
|
---|
133 | {
|
---|
134 | var correlations = {};
|
---|
135 |
|
---|
136 | var keys = Object.keys(key_pairings_unbiased);
|
---|
137 | var keys_len = keys.length;
|
---|
138 |
|
---|
139 | for (var k=0; k<keys_len; k++) {
|
---|
140 |
|
---|
141 | var key = keys[k];
|
---|
142 | var key_pairing_unbiased = key_pairings_unbiased[key];
|
---|
143 |
|
---|
144 | var correlation_coeff = khCorrelationCoefficientUnbiased(key_pairing_unbiased.p,key_pairing_unbiased.c);
|
---|
145 | correlations[key] = correlation_coeff;
|
---|
146 | }
|
---|
147 |
|
---|
148 | return correlations;
|
---|
149 | }
|
---|
150 |
|
---|
151 |
|
---|
152 | function khComputeAllCorrelationCoefficients(pairings_unbiased)
|
---|
153 | {
|
---|
154 | var major_correlation = khComputeKeyCorrelationCoefficients(pairings_unbiased.major);
|
---|
155 | var minor_correlation = khComputeKeyCorrelationCoefficients(pairings_unbiased.minor);
|
---|
156 |
|
---|
157 | return {"major" : major_correlation, "minor" : minor_correlation};
|
---|
158 | }
|
---|
159 |
|
---|
160 | function khFindMaxCorrelation(alignments)
|
---|
161 | {
|
---|
162 | var max_val = 0;
|
---|
163 | var max_key = null;
|
---|
164 |
|
---|
165 | var major_minor = [ "major", "minor" ];
|
---|
166 |
|
---|
167 | for (var m=0; m<major_minor.length; m++) {
|
---|
168 | var mm = major_minor[m];
|
---|
169 | var mm_alignment = alignments[mm];
|
---|
170 |
|
---|
171 | var mm_keys = Object.keys(mm_alignment);
|
---|
172 |
|
---|
173 | for (var k=0; k<mm_keys.length; k++) {
|
---|
174 | var key = mm_keys[k];
|
---|
175 | var correlation_coeff = mm_alignment[key];
|
---|
176 | if (correlation_coeff>max_val) {
|
---|
177 | max_val = correlation_coeff;
|
---|
178 | max_key = key + " (" + mm + ")";
|
---|
179 | }
|
---|
180 | }
|
---|
181 | }
|
---|
182 |
|
---|
183 | return { "key": max_key, "score": max_val };
|
---|
184 | }
|
---|
185 |
|
---|
186 | function khKeyDetection(chromatic_scale_durations)
|
---|
187 | {
|
---|
188 | // Work out durations of MIDI events folded into octave (60=Middle-C)
|
---|
189 | // (unbiased data)
|
---|
190 |
|
---|
191 | //var chromatic_scale_durations = [ 432, 231, 0, 405, 12, 316, 4, 126, 612, 0, 191, 1];
|
---|
192 | var chromatic_scale_durations_unbiased = khUnbiased(chromatic_scale_durations);
|
---|
193 |
|
---|
194 | // Generate all (unbiased) pairings
|
---|
195 | var kh_pairings_unbiased = khCreateAllPairings(chromatic_scale_durations_unbiased,kh_major_profile_unbiased,kh_minor_profile_unbiased)
|
---|
196 |
|
---|
197 | // Compute Correlation Coefficients
|
---|
198 | var kh_alignments = khComputeAllCorrelationCoefficients(kh_pairings_unbiased);
|
---|
199 |
|
---|
200 | // Pick highest values
|
---|
201 |
|
---|
202 | var strongest_profile = khFindMaxCorrelation(kh_alignments);
|
---|
203 |
|
---|
204 | console.log("Predicted Key: " + JSON.stringify(strongest_profile));
|
---|
205 |
|
---|
206 | return strongest_profile;
|
---|
207 |
|
---|
208 | }
|
---|
209 |
|
---|
210 |
|
---|