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