source: main/trunk/model-sites-dev/respooled/collect/popup-video-respooled/js/key-detection.js@ 29893

Last change on this file since 29893 was 29893, checked in by davidb, 9 years ago

Completed version of Krumhansl's algorithm. Plus adding in of About bar. Plus tidy up of the 'by xxx' template

  • Property svn:executable set to *
File size: 5.6 KB
Line 
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
19function 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
35function 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
54function 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
89var 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
91var 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
93var kh_major_profile_unbiased = khUnbiased(kh_major_profile);
94
95var kh_minor_profile_unbiased = khUnbiased(kh_minor_profile);
96
97
98function 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
112function 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
132function 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
152function 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
160function 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
186function 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
Note: See TracBrowser for help on using the repository browser.