Skip testing example lua scripts which are not bundled
[ardour.git] / libs / qm-dsp / dsp / keydetection / GetKeyMode.cpp
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */\r
2 /*\r
3     Copyright (c) 2005 Centre for Digital Music ( C4DM )\r
4                        Queen Mary Univesrity of London\r
5 \r
6     This program is free software; you can redistribute it and/or\r
7     modify it under the terms of the GNU General Public License as\r
8     published by the Free Software Foundation; either version 2 of the\r
9     License, or (at your option) any later version.  See the file\r
10     COPYING included with this distribution for more information.\r
11 */\r
12 // GetKeyMode.cpp: implementation of the CGetKeyMode class.\r
13 //\r
14 //////////////////////////////////////////////////////////////////////\r
15 \r
16 #include "GetKeyMode.h"\r
17 #include "maths/MathUtilities.h"\r
18 #include "base/Pitch.h"\r
19 \r
20 #include <iostream>\r
21 \r
22 #include <cstring>\r
23 #include <cstdlib>\r
24 \r
25 // Chords profile\r
26 static double MajProfile[36] = \r
27 { 0.0384, 0.0629, 0.0258, 0.0121, 0.0146, 0.0106, 0.0364, 0.0610, 0.0267,\r
28   0.0126, 0.0121, 0.0086, 0.0364, 0.0623, 0.0279, 0.0275, 0.0414, 0.0186, \r
29   0.0173, 0.0248, 0.0145, 0.0364, 0.0631, 0.0262, 0.0129, 0.0150, 0.0098,\r
30   0.0312, 0.0521, 0.0235, 0.0129, 0.0142, 0.0095, 0.0289, 0.0478, 0.0239};\r
31 \r
32 static double MinProfile[36] =\r
33 { 0.0375, 0.0682, 0.0299, 0.0119, 0.0138, 0.0093, 0.0296, 0.0543, 0.0257,\r
34   0.0292, 0.0519, 0.0246, 0.0159, 0.0234, 0.0135, 0.0291, 0.0544, 0.0248,\r
35   0.0137, 0.0176, 0.0104, 0.0352, 0.0670, 0.0302, 0.0222, 0.0349, 0.0164,\r
36   0.0174, 0.0297, 0.0166, 0.0222, 0.0401, 0.0202, 0.0175, 0.0270, 0.0146};\r
37 //\r
38     \r
39 \r
40 //////////////////////////////////////////////////////////////////////\r
41 // Construction/Destruction\r
42 //////////////////////////////////////////////////////////////////////\r
43 \r
44 GetKeyMode::GetKeyMode( int sampleRate, float tuningFrequency,\r
45                         double hpcpAverage, double medianAverage ) :\r
46     m_hpcpAverage( hpcpAverage ),\r
47     m_medianAverage( medianAverage ),\r
48     m_ChrPointer(0),\r
49     m_DecimatedBuffer(0),\r
50     m_ChromaBuffer(0),\r
51     m_MeanHPCP(0),\r
52     m_MajCorr(0),\r
53     m_MinCorr(0),\r
54     m_Keys(0),\r
55     m_MedianFilterBuffer(0),\r
56     m_SortedBuffer(0),\r
57     m_keyStrengths(0)\r
58 {\r
59     m_DecimationFactor = 8;\r
60         \r
61     // Chromagram configuration parameters\r
62     m_ChromaConfig.normalise = MathUtilities::NormaliseUnitMax;\r
63     m_ChromaConfig.FS = lrint(sampleRate/(double)m_DecimationFactor);\r
64     if (m_ChromaConfig.FS < 1) m_ChromaConfig.FS = 1;\r
65 \r
66     // Set C (= MIDI #12) as our base :\r
67     // This implies that key = 1 => Cmaj, key = 12 => Bmaj, key = 13 => Cmin, etc.\r
68     m_ChromaConfig.min = Pitch::getFrequencyForPitch\r
69         (48, 0, tuningFrequency);\r
70     m_ChromaConfig.max = Pitch::getFrequencyForPitch\r
71         (96, 0, tuningFrequency);\r
72 \r
73     m_ChromaConfig.BPO = 36;\r
74     m_ChromaConfig.CQThresh = 0.0054;\r
75 \r
76     // Chromagram inst.\r
77     m_Chroma = new Chromagram( m_ChromaConfig );\r
78 \r
79     // Get calculated parameters from chroma object\r
80     m_ChromaFrameSize = m_Chroma->getFrameSize();\r
81     // override hopsize for this application\r
82     m_ChromaHopSize = m_ChromaFrameSize;\r
83     m_BPO = m_ChromaConfig.BPO;\r
84 \r
85 //    std::cerr << "chroma frame size = " << m_ChromaFrameSize << ", decimation factor = " << m_DecimationFactor << " therefore block size = " << getBlockSize() << std::endl;\r
86 \r
87     // Chromagram average and estimated key median filter lengths\r
88     m_ChromaBuffersize = (int)ceil( m_hpcpAverage * m_ChromaConfig.FS/m_ChromaFrameSize );\r
89     m_MedianWinsize = (int)ceil( m_medianAverage * m_ChromaConfig.FS/m_ChromaFrameSize );\r
90     \r
91     // Reset counters\r
92     m_bufferindex = 0;\r
93     m_ChromaBufferFilling = 0;\r
94     m_MedianBufferFilling = 0;\r
95 \r
96     // Spawn objectc/arrays\r
97     m_DecimatedBuffer = new double[m_ChromaFrameSize];\r
98     \r
99     m_ChromaBuffer = new double[m_BPO * m_ChromaBuffersize];\r
100     memset( m_ChromaBuffer, 0, sizeof(double) * m_BPO * m_ChromaBuffersize);\r
101     \r
102     m_MeanHPCP = new double[m_BPO];\r
103     \r
104     m_MajCorr = new double[m_BPO];\r
105     m_MinCorr = new double[m_BPO];\r
106     m_Keys  = new double[2*m_BPO];\r
107     \r
108     m_MedianFilterBuffer = new int[ m_MedianWinsize ];\r
109     memset( m_MedianFilterBuffer, 0, sizeof(int)*m_MedianWinsize);\r
110     \r
111     m_SortedBuffer = new int[ m_MedianWinsize ];\r
112     memset( m_SortedBuffer, 0, sizeof(int)*m_MedianWinsize);    \r
113     \r
114     m_Decimator = new Decimator\r
115         ( m_ChromaFrameSize*m_DecimationFactor, m_DecimationFactor );\r
116 \r
117     m_keyStrengths = new double[24];\r
118 }\r
119 \r
120 GetKeyMode::~GetKeyMode()\r
121 {\r
122 \r
123     delete m_Chroma;\r
124     delete m_Decimator;\r
125     \r
126     delete [] m_DecimatedBuffer;\r
127     delete [] m_ChromaBuffer;\r
128     delete [] m_MeanHPCP;\r
129     delete [] m_MajCorr;\r
130     delete [] m_MinCorr;\r
131     delete [] m_Keys;\r
132     delete [] m_MedianFilterBuffer;\r
133     delete [] m_SortedBuffer;\r
134 \r
135     delete[] m_keyStrengths;\r
136 }\r
137 \r
138 double GetKeyMode::krumCorr(double *pData1, double *pData2, unsigned int length)\r
139 {\r
140     double retVal= 0.0;\r
141     \r
142     double num = 0;\r
143     double den = 0;\r
144     double mX = MathUtilities::mean( pData1, length );\r
145     double mY = MathUtilities::mean( pData2, length );\r
146     \r
147     double sum1 = 0;\r
148     double sum2 = 0;\r
149     \r
150     for( unsigned int i = 0; i <length; i++ )\r
151     {\r
152         num += ( pData1[i] - mX ) * ( pData2[i] - mY );\r
153 \r
154         sum1 += ( (pData1[i]-mX) * (pData1[i]-mX) );\r
155         sum2 += ( (pData2[i]-mY) * (pData2[i]-mY) );\r
156     }\r
157         \r
158     den = sqrt(sum1 * sum2);\r
159         \r
160     if( den>0 )\r
161         retVal = num/den;\r
162     else\r
163         retVal = 0;\r
164 \r
165 \r
166     return retVal;\r
167 }\r
168 \r
169 int GetKeyMode::process(double *PCMData)\r
170 {\r
171     int key;\r
172 \r
173     unsigned int j,k;\r
174 \r
175     //////////////////////////////////////////////\r
176     m_Decimator->process( PCMData, m_DecimatedBuffer);\r
177 \r
178     m_ChrPointer = m_Chroma->process( m_DecimatedBuffer );              \r
179 \r
180         \r
181     // Move bins such that the centre of the base note is in the\r
182     // middle of its three bins :\r
183     // Added 21.11.07 by Chris Sutton based on debugging with Katy\r
184     // Noland + comparison with Matlab equivalent.\r
185     MathUtilities::circShift( m_ChrPointer, m_BPO, 1);\r
186 /*\r
187     std::cout << "raw chroma: ";\r
188     for (int ii = 0; ii < m_BPO; ++ii) {\r
189       if (ii % (m_BPO/12) == 0) std::cout << "\n";\r
190         std::cout << m_ChrPointer[ii] << " ";\r
191     }\r
192     std::cout << std::endl;\r
193 */\r
194     // populate hpcp values;\r
195     int cbidx;\r
196     for( j = 0; j < m_BPO; j++ )\r
197     {\r
198         cbidx = (m_bufferindex * m_BPO) + j;\r
199         m_ChromaBuffer[ cbidx ] = m_ChrPointer[j];\r
200     }\r
201 \r
202     //keep track of input buffers;\r
203     if( m_bufferindex++ >= m_ChromaBuffersize - 1) \r
204         m_bufferindex = 0;\r
205 \r
206     // track filling of chroma matrix\r
207     if( m_ChromaBufferFilling++ >= m_ChromaBuffersize)\r
208         m_ChromaBufferFilling = m_ChromaBuffersize;\r
209 \r
210     //calculate mean            \r
211     for( k = 0; k < m_BPO; k++ )\r
212     {\r
213         double mnVal = 0.0;\r
214         for( j = 0; j < m_ChromaBufferFilling; j++ )\r
215         {\r
216             mnVal += m_ChromaBuffer[ k + (j*m_BPO) ];\r
217         }\r
218 \r
219         m_MeanHPCP[k] = mnVal/(double)m_ChromaBufferFilling;\r
220     }\r
221 \r
222 \r
223     for( k = 0; k < m_BPO; k++ )\r
224     {\r
225         m_MajCorr[k] = krumCorr( m_MeanHPCP, MajProfile, m_BPO );\r
226         m_MinCorr[k] = krumCorr( m_MeanHPCP, MinProfile, m_BPO );\r
227 \r
228         MathUtilities::circShift( MajProfile, m_BPO, 1 );\r
229         MathUtilities::circShift( MinProfile, m_BPO, 1 );\r
230     }\r
231         \r
232     for( k = 0; k < m_BPO; k++ )\r
233     {\r
234         m_Keys[k] = m_MajCorr[k];\r
235         m_Keys[k+m_BPO] = m_MinCorr[k];\r
236     }\r
237 \r
238     for (k = 0; k < 24; ++k) {\r
239         m_keyStrengths[k] = 0;\r
240     }\r
241 \r
242     for( k = 0; k < m_BPO*2; k++ )\r
243     {\r
244         int idx = k / (m_BPO/12);\r
245         int rem = k % (m_BPO/12);\r
246         if (rem == 0 || m_Keys[k] > m_keyStrengths[idx]) {\r
247             m_keyStrengths[idx] = m_Keys[k];\r
248         }\r
249 \r
250 //        m_keyStrengths[k/(m_BPO/12)] += m_Keys[k];\r
251     }\r
252 \r
253 /*\r
254   std::cout << "raw keys: ";\r
255   for (int ii = 0; ii < 2*m_BPO; ++ii) {\r
256       if (ii % (m_BPO/12) == 0) std::cout << "\n";\r
257       std::cout << m_Keys[ii] << " ";\r
258   }\r
259   std::cout << std::endl;\r
260 \r
261   std::cout << "key strengths: ";\r
262   for (int ii = 0; ii < 24; ++ii) {\r
263       if (ii % 6 == 0) std::cout << "\n";\r
264       std::cout << m_keyStrengths[ii] << " ";\r
265   }\r
266   std::cout << std::endl;\r
267 */\r
268     double dummy;\r
269     // '1 +' because we number keys 1-24, not 0-23.\r
270     key = 1 + (int)ceil( (double)MathUtilities::getMax( m_Keys, 2* m_BPO, &dummy )/3 );\r
271 \r
272 //    std::cout << "key pre-sorting: " << key << std::endl;\r
273 \r
274 \r
275     //Median filtering\r
276 \r
277     // track Median buffer initial filling\r
278     if( m_MedianBufferFilling++ >= m_MedianWinsize)\r
279         m_MedianBufferFilling = m_MedianWinsize;\r
280                 \r
281     //shift median buffer\r
282     for( k = 1; k < m_MedianWinsize; k++ )\r
283     {\r
284         m_MedianFilterBuffer[ k - 1 ] = m_MedianFilterBuffer[ k ];\r
285     }\r
286 \r
287     //write new key value into median buffer\r
288     m_MedianFilterBuffer[ m_MedianWinsize - 1 ] = key;\r
289 \r
290 \r
291     //Copy median into sorting buffer, reversed\r
292     unsigned int ijx = 0;\r
293     for( k = 0; k < m_MedianWinsize; k++ )\r
294     {\r
295         m_SortedBuffer[k] = m_MedianFilterBuffer[m_MedianWinsize-1-ijx];\r
296         ijx++;\r
297     }\r
298 \r
299     qsort(m_SortedBuffer, m_MedianBufferFilling, sizeof(unsigned int),\r
300           MathUtilities::compareInt);\r
301 /*\r
302   std::cout << "sorted: ";\r
303   for (int ii = 0; ii < m_MedianBufferFilling; ++ii) {\r
304   std::cout << m_SortedBuffer[ii] << " ";\r
305   }\r
306   std::cout << std::endl;\r
307 */\r
308     int sortlength = m_MedianBufferFilling;\r
309     int midpoint = (int)ceil((double)sortlength/2);\r
310 \r
311 //  std::cout << "midpoint = " << midpoint << endl;\r
312 \r
313     if( midpoint <= 0 )\r
314         midpoint = 1;\r
315 \r
316     key = m_SortedBuffer[midpoint-1];\r
317 \r
318 // std::cout << "returning key = " << key << endl;\r
319 \r
320     return key;\r
321 }\r
322 \r
323 \r
324 bool GetKeyMode::isModeMinor( int key )\r
325\r
326     return (key > 12);\r
327 }\r