1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtDeclarative module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:BSD$
9 ** You may use this file under the terms of the BSD license as follows:
10 **
11 ** "Redistribution and use in source and binary forms, with or without
12 ** modification, are permitted provided that the following conditions are
13 ** met:
14 **   * Redistributions of source code must retain the above copyright
15 **     notice, this list of conditions and the following disclaimer.
16 **   * Redistributions in binary form must reproduce the above copyright
17 **     notice, this list of conditions and the following disclaimer in
18 **     the documentation and/or other materials provided with the
19 **     distribution.
20 **   * Neither the name of The Qt Company Ltd nor the names of its
21 **     contributors may be used to endorse or promote products derived
22 **     from this software without specific prior written permission.
23 **
24 **
25 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40 
41 #include "voiceanalyzer.h"
42 
43 /**
44   * Constant used to scale the cut-off density for the fft helper.
45   */
46 const static float CutOffScaler = 0.05;
47 
48 /**
49   * Force the precision to be "1/PrecisionPerNote" notes
50   * near the target frequency.
51   */
52 const static int PrecisionPerNote = 4;
53 
54 /**
55   * TargetFrequencyParameter is a constant which implies the index at
56   * which corresponds to the target frequency.
57   * 0.5 * N * 1/TargetFrequencyParameter is (about) the index which
58   * corresponds to the given target frequency.
59   * Effectively TargetFrequencyParameter = 2^z, and the z*TargetFrequency
60   * is the maximum frequency that can be noticed.
61   */
62 const static int TargetFrequencyParameter = 4;
63 
64 
VoiceAnalyzer(const QAudioFormat & format,QObject * parent)65 VoiceAnalyzer::VoiceAnalyzer(const QAudioFormat &format, QObject *parent):
66     QIODevice(parent),
67     m_format(format),
68     m_frequency(0),
69     m_position(0),
70     m_fftHelper(new FastFourierTransformer(this))
71 {
72     Q_ASSERT(qFuzzyCompare(M_SAMPLE_COUNT_MULTIPLIER,
73                            float(2)/(M_TWELTH_ROOT_OF_2 -1.0)));
74     m_totalSampleCount = qRound(qreal(PrecisionPerNote)
75                                 *TargetFrequencyParameter
76                                 *M_SAMPLE_COUNT_MULTIPLIER);
77     m_samples.reserve(m_totalSampleCount);
78     int i = 2;
79     int j = 1;
80     for (; i < TargetFrequencyParameter; i *= 2) {
81         j++;
82     }
83     m_maximumVoiceDifference = j*12;
84 
85     setCutOffPercentage(CutOffScaler);
86 }
87 
88 /**
89   * Opens the parent QIODevice. Sets up the analysation parameters.
90   */
start(qreal frequency)91 void VoiceAnalyzer::start(qreal frequency)
92 {
93     m_stepSize = (qreal) 1.0 * m_format.sampleRate()
94                          / (TargetFrequencyParameter*2*frequency);
95     m_frequency = frequency;
96     open(QIODevice::WriteOnly);
97 }
98 
99 /**
100   * Closes the parent QIODevice, thus the voice is not analysed anymore.
101   * Resets the m_samples QList.
102   */
stop()103 void VoiceAnalyzer::stop()
104 {
105     m_samples.clear();
106     m_samples.reserve(m_totalSampleCount);
107     close();
108 }
109 
110 /**
111   * Called when data is obtained. Stores each m_stepSize sample
112   * into a QList to be analysed.
113   */
writeData(const char * data,qint64 maxlen)114 qint64 VoiceAnalyzer::writeData(const char *data, qint64 maxlen)
115 {
116     const int channelBytes = m_format.sampleSize() / 8;
117     int sampleSize = m_format.channels() * channelBytes;
118     int m_stepSizeInBytes = m_stepSize*sampleSize;
119     // assert that each sample fits fully into the data
120     Q_ASSERT((m_position % sampleSize)==0);
121     const uchar *ptr = reinterpret_cast<const uchar *>(data);
122     while (m_position < maxlen) {
123         if (m_samples.size() < m_totalSampleCount) {
124             m_samples.append(getValueInt16(ptr+m_position));
125         }
126         else {
127             analyzeVoice();
128             m_samples.clear();
129             m_samples.reserve(m_totalSampleCount);
130             // fast forward position to the first position after maxlen or to the maxlen
131             m_position += ((m_stepSizeInBytes - 1 + maxlen - m_position) /
132                            m_stepSizeInBytes) * m_stepSizeInBytes;
133             break;
134         }
135         m_position += m_stepSizeInBytes;
136     }
137     m_position -= maxlen;
138     return maxlen;
139 }
140 
141 /**
142   * Interprets ptr as a pointer to int value and returns it.
143   */
getValueInt16(const uchar * ptr)144 qint16 VoiceAnalyzer::getValueInt16(const uchar *ptr)
145 {
146     qint16 realValue = 0;
147     if (m_format.sampleSize() == 8)
148     {
149         const qint16 value = *reinterpret_cast<const quint8*>(ptr);
150         if (m_format.sampleType() == QAudioFormat::UnSignedInt) {
151             realValue = value - M_MAX_AMPLITUDE_8BIT_SIGNED - 1;
152         } else if (m_format.sampleType() == QAudioFormat::SignedInt) {
153             realValue = value;
154         }
155     } else if (m_format.sampleSize() == 16) {
156         qint16 value = 0;
157         if (m_format.byteOrder() == QAudioFormat::LittleEndian)
158             value = qFromLittleEndian<quint16>(ptr);
159         else
160             value = qFromBigEndian<quint16>(ptr);
161 
162         if (m_format.sampleType() == QAudioFormat::UnSignedInt) {
163             realValue = value - M_MAX_AMPLITUDE_16BIT_SIGNED;
164         } else if (m_format.sampleType() == QAudioFormat::SignedInt) {
165             realValue = value;
166         }
167     }
168     return realValue;
169 }
170 
171 /**
172   * Takes a number between 0 and 1, scales it with CutOffScaler,
173   * multiplies it with maximum density, and then gives it
174   * to the fft helper.
175   */
setCutOffPercentage(qreal cutoff)176 void VoiceAnalyzer::setCutOffPercentage(qreal cutoff)
177 {
178     cutoff = CutOffScaler*cutoff;
179     if (m_format.sampleSize() == 8) {
180         float t = cutoff*m_totalSampleCount*M_MAX_AMPLITUDE_8BIT_SIGNED;
181         m_fftHelper->setCutOffForDensity(t);
182     }
183     else if (m_format.sampleSize() == 16) {
184         float t = cutoff*m_totalSampleCount*M_MAX_AMPLITUDE_16BIT_SIGNED;
185         m_fftHelper->setCutOffForDensity(t);
186     }
187 }
188 
189 /**
190   * Returns the current target frequency.
191   */
frequency()192 qreal VoiceAnalyzer::frequency()
193 {
194     return m_frequency;
195 }
196 
197 /**
198   * Returns the maximum absolute value sent by
199   * the voiceDifference() signal.
200   */
getMaximumVoiceDifference()201 int VoiceAnalyzer::getMaximumVoiceDifference()
202 {
203     return m_maximumVoiceDifference;
204 }
205 
206 /**
207   * Returns the maximum precision per note
208   * near the target frequency.
209   */
getMaximumPrecisionPerNote()210 int VoiceAnalyzer::getMaximumPrecisionPerNote()
211 {
212     return PrecisionPerNote;
213 }
214 
215 /**
216   * Analyzes the voice frequency and emits appropriate signals.
217   */
analyzeVoice()218 void VoiceAnalyzer::analyzeVoice()
219 {
220     m_fftHelper->calculateFFT(m_samples);
221     int index = m_fftHelper->getMaximumDensityIndex();
222 
223     // If index == -1
224     if (index == -1) {
225         // The voice is to be filtered away.
226         // Emit the lowVoice signal and return.
227         emit lowVoice();
228         qDebug() << "low voice";
229         return;
230     }
231     // Else, continue
232 
233     // Let the correctIndex to be
234     // the nearest index corresponding to the correct frequency.
235     qreal stepSizeInFrequency = (qreal)m_format.sampleRate()
236             / (m_totalSampleCount * m_stepSize);
237     qreal newFrequency = qreal(index) * stepSizeInFrequency;
238     // Calculate the nearest index corresponding to the correct frequency.
239     int correctIndex = qRound(m_frequency / stepSizeInFrequency);
240     qreal value = 0;
241 
242     // If the obtained frequency is more than
243     // log_2(TargetFrequencyParameter) octaves less than the m_frequency:
244 
245     // Note:
246     // Instead of m_frequency/TargetFrequencyParameter > newFrequency,
247     // the comparison is done without a div instructions by
248     // m_frequency > newFrequency * TargetFrequencyParameter.
249 
250     if (m_frequency > newFrequency * TargetFrequencyParameter) {
251         // Set the difference value to be -m_maximumVoiceDifference.
252         qDebug() << "compare" << "low" << newFrequency << m_frequency - stepSizeInFrequency * correctIndex << (m_frequency - stepSizeInFrequency * correctIndex) / stepSizeInFrequency;
253         value = -m_maximumVoiceDifference;
254     }
255     // Else, if the obtained frequency is more than
256     // log_2(TargetFrequencyParameter) octaves more than the m_frequency:
257     else if (m_frequency*TargetFrequencyParameter < newFrequency) {
258         // Set the difference value to be m_maximumVoiceDifference.
259         qDebug() << "compare" << "high" << newFrequency << m_frequency - stepSizeInFrequency * correctIndex << (m_frequency - stepSizeInFrequency * correctIndex) / stepSizeInFrequency;
260         value = m_maximumVoiceDifference;
261     }
262     // Else:
263     else {
264         // Calculate the difference between the obtained and the correct
265         // frequency in tones.
266         // Use stepSizeInFrequency * correctIndex instead of
267         // m_frequency so that the value is zero when there is correct
268         // voice obtained. Set the difference value to be
269         // log(frequency / target frequency) * 12 / log(2).
270         value = log(newFrequency / (stepSizeInFrequency * correctIndex))
271                 * 12 / M_LN2;
272         qDebug() << "compare" << value << newFrequency << m_frequency - stepSizeInFrequency * correctIndex << (m_frequency - stepSizeInFrequency * correctIndex) / stepSizeInFrequency;
273     }
274 
275     // Emit voiceDifference signal.
276     QVariant valueVar(value); //Has to be QVariant for QML
277     emit voiceDifference(valueVar);
278 
279     // If the correctIndex is index, emit the correctFrequency signal.
280     if (correctIndex == index) {
281         emit(correctFrequency());
282     }
283 }
284 
285 /**
286   * Empty implementation for readData, since no data is provided
287   * by the VoiceAnalyzer class.
288   */
readData(char * data,qint64 maxlen)289 qint64 VoiceAnalyzer::readData(char *data, qint64 maxlen)
290 {
291     Q_UNUSED(data);
292     Q_UNUSED(maxlen);
293 
294     return 0;
295 }
296