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