Qt Reference Documentation

# voiceanalyzer.cpp Example File

demos/mobile/guitartuner/src/voiceanalyzer.cpp

     /****************************************************************************
     **
     ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
     ** All rights reserved.
     ** Contact: Nokia Corporation (qt-info@nokia.com)
     **
     ** This file is part of the QtDeclarative module of the Qt Toolkit.
     **
     ** $QT_BEGIN_LICENSE:BSD$
     ** You may use this file under the terms of the BSD license as follows:
     **
     ** "Redistribution and use in source and binary forms, with or without
     ** modification, are permitted provided that the following conditions are
     ** met:
     **   * Redistributions of source code must retain the above copyright
     **     notice, this list of conditions and the following disclaimer.
     **   * Redistributions in binary form must reproduce the above copyright
     **     notice, this list of conditions and the following disclaimer in
     **     the documentation and/or other materials provided with the
     **     distribution.
     **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
     **     the names of its contributors may be used to endorse or promote
     **     products derived from this software without specific prior written
     **     permission.
     **
     ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
     ** $QT_END_LICENSE$
     **
     ****************************************************************************/

     #include "voiceanalyzer.h"

     /**
       * Constant used to scale the cut-off density for the fft helper.
       */
     const static float CutOffScaler = 0.05;

     /**
       * Force the precision to be "1/PrecisionPerNote" notes
       * near the target frequency.
       */
     const static int PrecisionPerNote = 4;

     /**
       * TargetFrequencyParameter is a constant which implies the index at
       * which corresponds to the target frequency.
       * 0.5 * N * 1/TargetFrequencyParameter is (about) the index which
       * corresponds to the given target frequency.
       * Effectively TargetFrequencyParameter = 2^z, and the z*TargetFrequency
       * is the maximum frequency that can be noticed.
       */
     const static int TargetFrequencyParameter = 4;

     VoiceAnalyzer::VoiceAnalyzer(const QAudioFormat &format, QObject *parent):
         QIODevice(parent),
         m_format(format),
         m_frequency(0),
         m_position(0),
         m_fftHelper(new FastFourierTransformer(this))
     {
         Q_ASSERT(qFuzzyCompare(M_SAMPLE_COUNT_MULTIPLIER,
                                float(2)/(M_TWELTH_ROOT_OF_2 -1.0)));
         m_totalSampleCount = qRound(qreal(PrecisionPerNote)
                                     *TargetFrequencyParameter
                                     *M_SAMPLE_COUNT_MULTIPLIER);
         m_samples.reserve(m_totalSampleCount);
         int i = 2;
         int j = 1;
         for (; i < TargetFrequencyParameter; i *= 2) {
             j++;
         }
         m_maximumVoiceDifference = j*12;

         setCutOffPercentage(CutOffScaler);
     }

     /**
       * Opens the parent QIODevice. Sets up the analysation parameters.
       */
     void VoiceAnalyzer::start(qreal frequency)
     {
         m_stepSize = (qreal) 1.0 * m_format.sampleRate()
                              / (TargetFrequencyParameter*2*frequency);
         m_frequency = frequency;
         open(QIODevice::WriteOnly);
     }

     /**
       * Closes the parent QIODevice, thus the voice is not analysed anymore.
       * Resets the m_samples QList.
       */
     void VoiceAnalyzer::stop()
     {
         m_samples.clear();
         m_samples.reserve(m_totalSampleCount);
         close();
     }

     /**
       * Called when data is obtained. Stores each m_stepSize sample
       * into a QList to be analysed.
       */
     qint64 VoiceAnalyzer::writeData(const char *data, qint64 maxlen)
     {
         const int channelBytes = m_format.sampleSize() / 8;
         int sampleSize = m_format.channels() * channelBytes;
         int m_stepSizeInBytes = m_stepSize*sampleSize;
         // assert that each sample fits fully into the data
         Q_ASSERT((m_position % sampleSize)==0);
         const uchar *ptr = reinterpret_cast<const uchar *>(data);
         while (m_position < maxlen) {
             if (m_samples.size() < m_totalSampleCount) {
                 m_samples.append(getValueInt16(ptr+m_position));
             }
             else {
                 analyzeVoice();
                 m_samples.clear();
                 m_samples.reserve(m_totalSampleCount);
                 // fast forward position to the first position after maxlen or to the maxlen
                 m_position += ((m_stepSizeInBytes - 1 + maxlen - m_position) /
                                m_stepSizeInBytes) * m_stepSizeInBytes;
                 break;
             }
             m_position += m_stepSizeInBytes;
         }
         m_position -= maxlen;
         return maxlen;
     }

     /**
       * Interprets ptr as a pointer to int value and returns it.
       */
     qint16 VoiceAnalyzer::getValueInt16(const uchar *ptr)
     {
         qint16 realValue = 0;
         if (m_format.sampleSize() == 8)
         {
             const qint16 value = *reinterpret_cast<const quint8*>(ptr);
             if (m_format.sampleType() == QAudioFormat::UnSignedInt) {
                 realValue = value - M_MAX_AMPLITUDE_8BIT_SIGNED - 1;
             } else if (m_format.sampleType() == QAudioFormat::SignedInt) {
                 realValue = value;
             }
         } else if (m_format.sampleSize() == 16) {
             qint16 value = 0;
             if (m_format.byteOrder() == QAudioFormat::LittleEndian)
                 value = qFromLittleEndian<quint16>(ptr);
             else
                 value = qFromBigEndian<quint16>(ptr);

             if (m_format.sampleType() == QAudioFormat::UnSignedInt) {
                 realValue = value - M_MAX_AMPLITUDE_16BIT_SIGNED;
             } else if (m_format.sampleType() == QAudioFormat::SignedInt) {
                 realValue = value;
             }
         }
         return realValue;
     }

     /**
       * Takes a number between 0 and 1, scales it with CutOffScaler,
       * multiplies it with maximum density, and then gives it
       * to the fft helper.
       */
     void VoiceAnalyzer::setCutOffPercentage(qreal cutoff)
     {
         cutoff = CutOffScaler*cutoff;
         if (m_format.sampleSize() == 8) {
             float t = cutoff*m_totalSampleCount*M_MAX_AMPLITUDE_8BIT_SIGNED;
             m_fftHelper->setCutOffForDensity(t);
         }
         else if (m_format.sampleSize() == 16) {
             float t = cutoff*m_totalSampleCount*M_MAX_AMPLITUDE_16BIT_SIGNED;
             m_fftHelper->setCutOffForDensity(t);
         }
     }

     /**
       * Returns the current target frequency.
       */
     qreal VoiceAnalyzer::frequency()
     {
         return m_frequency;
     }

     /**
       * Returns the maximum absolute value sent by
       * the voiceDifference() signal.
       */
     int VoiceAnalyzer::getMaximumVoiceDifference()
     {
         return m_maximumVoiceDifference;
     }

     /**
       * Returns the maximum precision per note
       * near the target frequency.
       */
     int VoiceAnalyzer::getMaximumPrecisionPerNote()
     {
         return PrecisionPerNote;
     }

     /**
       * Analyzes the voice frequency and emits appropriate signals.
       */
     void VoiceAnalyzer::analyzeVoice()
     {
         m_fftHelper->calculateFFT(m_samples);
         int index = m_fftHelper->getMaximumDensityIndex();

         // If index == -1
         if (index == -1) {
             // The voice is to be filtered away.
             // Emit the lowVoice signal and return.
             emit lowVoice();
             qDebug() << "low voice";
             return;
         }
         // Else, continue

         // Let the correctIndex to be
         // the nearest index corresponding to the correct frequency.
         qreal stepSizeInFrequency = (qreal)m_format.sampleRate()
                 / (m_totalSampleCount * m_stepSize);
         qreal newFrequency = qreal(index) * stepSizeInFrequency;
         // Calculate the nearest index corresponding to the correct frequency.
         int correctIndex = qRound(m_frequency / stepSizeInFrequency);
         qreal value = 0;

         // If the obtained frequency is more than
         // log_2(TargetFrequencyParameter) octaves less than the m_frequency:

         // Note:
         // Instead of m_frequency/TargetFrequencyParameter > newFrequency,
         // the comparison is done without a div instructions by
         // m_frequency > newFrequency * TargetFrequencyParameter.

         if (m_frequency > newFrequency * TargetFrequencyParameter) {
             // Set the difference value to be -m_maximumVoiceDifference.
             qDebug() << "compare" << "low" << newFrequency << m_frequency - stepSizeInFrequency * correctIndex << (m_frequency - stepSizeInFrequency * correctIndex) / stepSizeInFrequency;
             value = -m_maximumVoiceDifference;
         }
         // Else, if the obtained frequency is more than
         // log_2(TargetFrequencyParameter) octaves more than the m_frequency:
         else if (m_frequency*TargetFrequencyParameter < newFrequency) {
             // Set the difference value to be m_maximumVoiceDifference.
             qDebug() << "compare" << "high" << newFrequency << m_frequency - stepSizeInFrequency * correctIndex << (m_frequency - stepSizeInFrequency * correctIndex) / stepSizeInFrequency;
             value = m_maximumVoiceDifference;
         }
         // Else:
         else {
             // Calculate the difference between the obtained and the correct
             // frequency in tones.
             // Use stepSizeInFrequency * correctIndex instead of
             // m_frequency so that the value is zero when there is correct
             // voice obtained. Set the difference value to be
             // log(frequency / target frequency) * 12 / log(2).
             value = log(newFrequency / (stepSizeInFrequency * correctIndex))
                     * 12 / M_LN2;
             qDebug() << "compare" << value << newFrequency << m_frequency - stepSizeInFrequency * correctIndex << (m_frequency - stepSizeInFrequency * correctIndex) / stepSizeInFrequency;
         }

         // Emit voiceDifference signal.
         QVariant valueVar(value); //Has to be QVariant for QML
         emit voiceDifference(valueVar);

         // If the correctIndex is index, emit the correctFrequency signal.
         if (correctIndex == index) {
             emit(correctFrequency());
         }
     }

     /**
       * Empty implementation for readData, since no data is provided
       * by the VoiceAnalyzer class.
       */
     qint64 VoiceAnalyzer::readData(char *data, qint64 maxlen)
     {
         Q_UNUSED(data);
         Q_UNUSED(maxlen);

         return 0;
     }