Qt 4.8: engine.cpp Example File (demos/spectrum/app/engine.cpp)

Qt Reference Documentation

- Qt 4.8
- ALL VERSIONS

- A
- A
- A
- Print

# engine.cpp Example File

demos/spectrum/app/engine.cpp

     /****************************************************************************
     **
     ** Copyright (C) 2015 The Qt Company Ltd.
     ** Contact: http://www.qt.io/licensing/
     **
     ** This file is part of the examples 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 The Qt Company Ltd 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 "engine.h"
     #include "tonegenerator.h"
     #include "utils.h"

     #include <math.h>

     #include <QCoreApplication>
     #include <QMetaObject>
     #include <QSet>
     #include <QtMultimedia/QAudioInput>
     #include <QtMultimedia/QAudioOutput>
     #include <QDebug>
     #include <QThread>
     #include <QFile>

     //-----------------------------------------------------------------------------
     // Constants
     //-----------------------------------------------------------------------------

     const qint64 BufferDurationUs       = 10 * 1000000;
     const int    NotifyIntervalMs       = 100;

     // Size of the level calculation window in microseconds
     const int    LevelWindowUs          = 0.1 * 1000000;

     //-----------------------------------------------------------------------------
     // Helper functions
     //-----------------------------------------------------------------------------

     QDebug& operator<<(QDebug &debug, const QAudioFormat &format)
     {
         debug << format.frequency() << "Hz"
               << format.channels() << "channels";
         return debug;
     }

     //-----------------------------------------------------------------------------
     // Constructor and destructor
     //-----------------------------------------------------------------------------

     Engine::Engine(QObject *parent)
         :   QObject(parent)
         ,   m_mode(QAudio::AudioInput)
         ,   m_state(QAudio::StoppedState)
         ,   m_generateTone(false)
         ,   m_file(0)
         ,   m_analysisFile(0)
         ,   m_availableAudioInputDevices
                 (QAudioDeviceInfo::availableDevices(QAudio::AudioInput))
         ,   m_audioInputDevice(QAudioDeviceInfo::defaultInputDevice())
         ,   m_audioInput(0)
         ,   m_audioInputIODevice(0)
         ,   m_recordPosition(0)
         ,   m_availableAudioOutputDevices
                 (QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
         ,   m_audioOutputDevice(QAudioDeviceInfo::defaultOutputDevice())
         ,   m_audioOutput(0)
         ,   m_playPosition(0)
         ,   m_bufferPosition(0)
         ,   m_bufferLength(0)
         ,   m_dataLength(0)
         ,   m_levelBufferLength(0)
         ,   m_rmsLevel(0.0)
         ,   m_peakLevel(0.0)
         ,   m_spectrumBufferLength(0)
         ,   m_spectrumAnalyser()
         ,   m_spectrumPosition(0)
         ,   m_count(0)
     {
         qRegisterMetaType<FrequencySpectrum>("FrequencySpectrum");
         qRegisterMetaType<WindowFunction>("WindowFunction");
         CHECKED_CONNECT(&m_spectrumAnalyser,
                         SIGNAL(spectrumChanged(FrequencySpectrum)),
                         this,
                         SLOT(spectrumChanged(FrequencySpectrum)));

         initialize();

     #ifdef DUMP_DATA
         createOutputDir();
     #endif

     #ifdef DUMP_SPECTRUM
         m_spectrumAnalyser.setOutputPath(outputPath());
     #endif
     }

     Engine::~Engine()
     {

     }

     //-----------------------------------------------------------------------------
     // Public functions
     //-----------------------------------------------------------------------------

     bool Engine::loadFile(const QString &fileName)
     {
         reset();
         bool result = false;
         Q_ASSERT(!m_generateTone);
         Q_ASSERT(!m_file);
         Q_ASSERT(!fileName.isEmpty());
         m_file = new WavFile(this);
         if (m_file->open(fileName)) {
             if (isPCMS16LE(m_file->fileFormat())) {
                 result = initialize();
             } else {
                 emit errorMessage(tr("Audio format not supported"),
                                   formatToString(m_file->fileFormat()));
             }
         } else {
             emit errorMessage(tr("Could not open file"), fileName);
         }
         if (result) {
             m_analysisFile = new WavFile(this);
             m_analysisFile->open(fileName);
         }
         return result;
     }

     bool Engine::generateTone(const Tone &tone)
     {
         reset();
         Q_ASSERT(!m_generateTone);
         Q_ASSERT(!m_file);
         m_generateTone = true;
         m_tone = tone;
         ENGINE_DEBUG << "Engine::generateTone"
                      << "startFreq" << m_tone.startFreq
                      << "endFreq" << m_tone.endFreq
                      << "amp" << m_tone.amplitude;
         return initialize();
     }

     bool Engine::generateSweptTone(qreal amplitude)
     {
         Q_ASSERT(!m_generateTone);
         Q_ASSERT(!m_file);
         m_generateTone = true;
         m_tone.startFreq = 1;
         m_tone.endFreq = 0;
         m_tone.amplitude = amplitude;
         ENGINE_DEBUG << "Engine::generateSweptTone"
                      << "startFreq" << m_tone.startFreq
                      << "amp" << m_tone.amplitude;
         return initialize();
     }

     bool Engine::initializeRecord()
     {
         reset();
         ENGINE_DEBUG << "Engine::initializeRecord";
         Q_ASSERT(!m_generateTone);
         Q_ASSERT(!m_file);
         m_generateTone = false;
         m_tone = SweptTone();
         return initialize();
     }

     qint64 Engine::bufferLength() const
     {
         return m_file ? m_file->size() : m_bufferLength;
     }

     void Engine::setWindowFunction(WindowFunction type)
     {
         m_spectrumAnalyser.setWindowFunction(type);
     }

     //-----------------------------------------------------------------------------
     // Public slots
     //-----------------------------------------------------------------------------

     void Engine::startRecording()
     {
         if (m_audioInput) {
             if (QAudio::AudioInput == m_mode &&
                 QAudio::SuspendedState == m_state) {
                 m_audioInput->resume();
             } else {
                 m_spectrumAnalyser.cancelCalculation();
                 spectrumChanged(0, 0, FrequencySpectrum());

                 m_buffer.fill(0);
                 setRecordPosition(0, true);
                 stopPlayback();
                 m_mode = QAudio::AudioInput;
                 CHECKED_CONNECT(m_audioInput, SIGNAL(stateChanged(QAudio::State)),
                                 this, SLOT(audioStateChanged(QAudio::State)));
                 CHECKED_CONNECT(m_audioInput, SIGNAL(notify()),
                                 this, SLOT(audioNotify()));
                 m_count = 0;
                 m_dataLength = 0;
                 emit dataLengthChanged(0);
                 m_audioInputIODevice = m_audioInput->start();
                 CHECKED_CONNECT(m_audioInputIODevice, SIGNAL(readyRead()),
                                 this, SLOT(audioDataReady()));
             }
         }
     }

     void Engine::startPlayback()
     {
         if (m_audioOutput) {
             if (QAudio::AudioOutput == m_mode &&
                 QAudio::SuspendedState == m_state) {
     #ifdef Q_OS_WIN
                 // The Windows backend seems to internally go back into ActiveState
                 // while still returning SuspendedState, so to ensure that it doesn't
                 // ignore the resume() call, we first re-suspend
                 m_audioOutput->suspend();
     #endif
                 m_audioOutput->resume();
             } else {
                 m_spectrumAnalyser.cancelCalculation();
                 spectrumChanged(0, 0, FrequencySpectrum());
                 setPlayPosition(0, true);
                 stopRecording();
                 m_mode = QAudio::AudioOutput;
                 CHECKED_CONNECT(m_audioOutput, SIGNAL(stateChanged(QAudio::State)),
                                 this, SLOT(audioStateChanged(QAudio::State)));
                 CHECKED_CONNECT(m_audioOutput, SIGNAL(notify()),
                                 this, SLOT(audioNotify()));
                 m_count = 0;
                 if (m_file) {
                     m_file->seek(0);
                     m_bufferPosition = 0;
                     m_dataLength = 0;
                     m_audioOutput->start(m_file);
                 } else {
                     m_audioOutputIODevice.close();
                     m_audioOutputIODevice.setBuffer(&m_buffer);
                     m_audioOutputIODevice.open(QIODevice::ReadOnly);
                     m_audioOutput->start(&m_audioOutputIODevice);
                 }
             }
         }
     }

     void Engine::suspend()
     {
         if (QAudio::ActiveState == m_state ||
             QAudio::IdleState == m_state) {
             switch (m_mode) {
             case QAudio::AudioInput:
                 m_audioInput->suspend();
                 break;
             case QAudio::AudioOutput:
                 m_audioOutput->suspend();
                 break;
             }
         }
     }

     void Engine::setAudioInputDevice(const QAudioDeviceInfo &device)
     {
         if (device.deviceName() != m_audioInputDevice.deviceName()) {
             m_audioInputDevice = device;
             initialize();
         }
     }

     void Engine::setAudioOutputDevice(const QAudioDeviceInfo &device)
     {
         if (device.deviceName() != m_audioOutputDevice.deviceName()) {
             m_audioOutputDevice = device;
             initialize();
         }
     }

     //-----------------------------------------------------------------------------
     // Private slots
     //-----------------------------------------------------------------------------

     void Engine::audioNotify()
     {
         switch (m_mode) {
         case QAudio::AudioInput: {
                 const qint64 recordPosition = qMin(m_bufferLength, audioLength(m_format, m_audioInput->processedUSecs()));
                 setRecordPosition(recordPosition);
                 const qint64 levelPosition = m_dataLength - m_levelBufferLength;
                 if (levelPosition >= 0)
                     calculateLevel(levelPosition, m_levelBufferLength);
                 if (m_dataLength >= m_spectrumBufferLength) {
                     const qint64 spectrumPosition = m_dataLength - m_spectrumBufferLength;
                     calculateSpectrum(spectrumPosition);
                 }
                 emit bufferChanged(0, m_dataLength, m_buffer);
             }
             break;
         case QAudio::AudioOutput: {
                 const qint64 playPosition = audioLength(m_format, m_audioOutput->processedUSecs());
                 setPlayPosition(qMin(bufferLength(), playPosition));
                 const qint64 levelPosition = playPosition - m_levelBufferLength;
                 const qint64 spectrumPosition = playPosition - m_spectrumBufferLength;
                 if (m_file) {
                     if (levelPosition > m_bufferPosition ||
                         spectrumPosition > m_bufferPosition ||
                         qMax(m_levelBufferLength, m_spectrumBufferLength) > m_dataLength) {
                         m_bufferPosition = 0;
                         m_dataLength = 0;
                         // Data needs to be read into m_buffer in order to be analysed
                         const qint64 readPos = qMax(qint64(0), qMin(levelPosition, spectrumPosition));
                         const qint64 readEnd = qMin(m_analysisFile->size(), qMax(levelPosition + m_levelBufferLength, spectrumPosition + m_spectrumBufferLength));
                         const qint64 readLen = readEnd - readPos + audioLength(m_format, WaveformWindowDuration);
                         qDebug() << "Engine::audioNotify [1]"
                                  << "analysisFileSize" << m_analysisFile->size()
                                  << "readPos" << readPos
                                  << "readLen" << readLen;
                         if (m_analysisFile->seek(readPos + m_analysisFile->headerLength())) {
                             m_buffer.resize(readLen);
                             m_bufferPosition = readPos;
                             m_dataLength = m_analysisFile->read(m_buffer.data(), readLen);
                             qDebug() << "Engine::audioNotify [2]" << "bufferPosition" << m_bufferPosition << "dataLength" << m_dataLength;
                         } else {
                             qDebug() << "Engine::audioNotify [2]" << "file seek error";
                         }
                         emit bufferChanged(m_bufferPosition, m_dataLength, m_buffer);
                     }
                 } else {
                     if (playPosition >= m_dataLength)
                         stopPlayback();
                 }
                 if (levelPosition >= 0 && levelPosition + m_levelBufferLength < m_bufferPosition + m_dataLength)
                     calculateLevel(levelPosition, m_levelBufferLength);
                 if (spectrumPosition >= 0 && spectrumPosition + m_spectrumBufferLength < m_bufferPosition + m_dataLength)
                     calculateSpectrum(spectrumPosition);
             }
             break;
         }
     }

     void Engine::audioStateChanged(QAudio::State state)
     {
         ENGINE_DEBUG << "Engine::audioStateChanged from" << m_state
                      << "to" << state;

         if (QAudio::IdleState == state && m_file && m_file->pos() == m_file->size()) {
             stopPlayback();
         } else {
             if (QAudio::StoppedState == state) {
                 // Check error
                 QAudio::Error error = QAudio::NoError;
                 switch (m_mode) {
                 case QAudio::AudioInput:
                     error = m_audioInput->error();
                     break;
                 case QAudio::AudioOutput:
                     error = m_audioOutput->error();
                     break;
                 }
                 if (QAudio::NoError != error) {
                     reset();
                     return;
                 }
             }
             setState(state);
         }
     }

     void Engine::audioDataReady()
     {
         Q_ASSERT(0 == m_bufferPosition);
         const qint64 bytesReady = m_audioInput->bytesReady();
         const qint64 bytesSpace = m_buffer.size() - m_dataLength;
         const qint64 bytesToRead = qMin(bytesReady, bytesSpace);

         const qint64 bytesRead = m_audioInputIODevice->read(
                                            m_buffer.data() + m_dataLength,
                                            bytesToRead);

         if (bytesRead) {
             m_dataLength += bytesRead;
             emit dataLengthChanged(dataLength());
         }

         if (m_buffer.size() == m_dataLength)
             stopRecording();
     }

     void Engine::spectrumChanged(const FrequencySpectrum &spectrum)
     {
         ENGINE_DEBUG << "Engine::spectrumChanged" << "pos" << m_spectrumPosition;
         emit spectrumChanged(m_spectrumPosition, m_spectrumBufferLength, spectrum);
     }

     //-----------------------------------------------------------------------------
     // Private functions
     //-----------------------------------------------------------------------------

     void Engine::resetAudioDevices()
     {
         delete m_audioInput;
         m_audioInput = 0;
         m_audioInputIODevice = 0;
         setRecordPosition(0);
         delete m_audioOutput;
         m_audioOutput = 0;
         setPlayPosition(0);
         m_spectrumPosition = 0;
         setLevel(0.0, 0.0, 0);
     }

     void Engine::reset()
     {
         stopRecording();
         stopPlayback();
         setState(QAudio::AudioInput, QAudio::StoppedState);
         setFormat(QAudioFormat());
         m_generateTone = false;
         delete m_file;
         m_file = 0;
         delete m_analysisFile;
         m_analysisFile = 0;
         m_buffer.clear();
         m_bufferPosition = 0;
         m_bufferLength = 0;
         m_dataLength = 0;
         emit dataLengthChanged(0);
         resetAudioDevices();
     }

     bool Engine::initialize()
     {
         bool result = false;

         QAudioFormat format = m_format;

         if (selectFormat()) {
             if (m_format != format) {
                 resetAudioDevices();
                 if (m_file) {
                     emit bufferLengthChanged(bufferLength());
                     emit dataLengthChanged(dataLength());
                     emit bufferChanged(0, 0, m_buffer);
                     setRecordPosition(bufferLength());
                     result = true;
                 } else {
                     m_bufferLength = audioLength(m_format, BufferDurationUs);
                     m_buffer.resize(m_bufferLength);
                     m_buffer.fill(0);
                     emit bufferLengthChanged(bufferLength());
                     if (m_generateTone) {
                         if (0 == m_tone.endFreq) {
                             const qreal nyquist = nyquistFrequency(m_format);
                             m_tone.endFreq = qMin(qreal(SpectrumHighFreq), nyquist);
                         }
                         // Call function defined in utils.h, at global scope
                         ::generateTone(m_tone, m_format, m_buffer);
                         m_dataLength = m_bufferLength;
                         emit dataLengthChanged(dataLength());
                         emit bufferChanged(0, m_dataLength, m_buffer);
                         setRecordPosition(m_bufferLength);
                         result = true;
                     } else {
                         emit bufferChanged(0, 0, m_buffer);
                         m_audioInput = new QAudioInput(m_audioInputDevice, m_format, this);
                         m_audioInput->setNotifyInterval(NotifyIntervalMs);
                         result = true;
                     }
                 }
                 m_audioOutput = new QAudioOutput(m_audioOutputDevice, m_format, this);
                 m_audioOutput->setNotifyInterval(NotifyIntervalMs);
             }
         } else {
             if (m_file)
                 emit errorMessage(tr("Audio format not supported"),
                                   formatToString(m_format));
             else if (m_generateTone)
                 emit errorMessage(tr("No suitable format found"), "");
             else
                 emit errorMessage(tr("No common input / output format found"), "");
         }

         ENGINE_DEBUG << "Engine::initialize" << "m_bufferLength" << m_bufferLength;
         ENGINE_DEBUG << "Engine::initialize" << "m_dataLength" << m_dataLength;
         ENGINE_DEBUG << "Engine::initialize" << "format" << m_format;

         return result;
     }

     bool Engine::selectFormat()
     {
         bool foundSupportedFormat = false;

         if (m_file || QAudioFormat() != m_format) {
             QAudioFormat format = m_format;
             if (m_file)
                 // Header is read from the WAV file; just need to check whether
                 // it is supported by the audio output device
                 format = m_file->fileFormat();
             if (m_audioOutputDevice.isFormatSupported(format)) {
                 setFormat(format);
                 foundSupportedFormat = true;
             }
         } else {

             QList<int> frequenciesList;
         #ifdef Q_OS_WIN
             // The Windows audio backend does not correctly report format support
             // (see QTBUG-9100).  Furthermore, although the audio subsystem captures
             // at 11025Hz, the resulting audio is corrupted.
             frequenciesList += 8000;
         #endif

             if (!m_generateTone)
                 frequenciesList += m_audioInputDevice.supportedFrequencies();

             frequenciesList += m_audioOutputDevice.supportedFrequencies();
             frequenciesList = frequenciesList.toSet().toList(); // remove duplicates
             qSort(frequenciesList);
             ENGINE_DEBUG << "Engine::initialize frequenciesList" << frequenciesList;

             QList<int> channelsList;
             channelsList += m_audioInputDevice.supportedChannels();
             channelsList += m_audioOutputDevice.supportedChannels();
             channelsList = channelsList.toSet().toList();
             qSort(channelsList);
             ENGINE_DEBUG << "Engine::initialize channelsList" << channelsList;

             QAudioFormat format;
             format.setByteOrder(QAudioFormat::LittleEndian);
             format.setCodec("audio/pcm");
             format.setSampleSize(16);
             format.setSampleType(QAudioFormat::SignedInt);
             int frequency, channels;
             foreach (frequency, frequenciesList) {
                 if (foundSupportedFormat)
                     break;
                 format.setFrequency(frequency);
                 foreach (channels, channelsList) {
                     format.setChannels(channels);
                     const bool inputSupport = m_generateTone ||
                                               m_audioInputDevice.isFormatSupported(format);
                     const bool outputSupport = m_audioOutputDevice.isFormatSupported(format);
                     ENGINE_DEBUG << "Engine::initialize checking " << format
                                  << "input" << inputSupport
                                  << "output" << outputSupport;
                     if (inputSupport && outputSupport) {
                         foundSupportedFormat = true;
                         break;
                     }
                 }
             }

             if (!foundSupportedFormat)
                 format = QAudioFormat();

             setFormat(format);
         }

         return foundSupportedFormat;
     }

     void Engine::stopRecording()
     {
         if (m_audioInput) {
             m_audioInput->stop();
             QCoreApplication::instance()->processEvents();
             m_audioInput->disconnect();
         }
         m_audioInputIODevice = 0;

     #ifdef DUMP_AUDIO
         dumpData();
     #endif
     }

     void Engine::stopPlayback()
     {
         if (m_audioOutput) {
             m_audioOutput->stop();
             QCoreApplication::instance()->processEvents();
             m_audioOutput->disconnect();
             setPlayPosition(0);
         }
     }

     void Engine::setState(QAudio::State state)
     {
         const bool changed = (m_state != state);
         m_state = state;
         if (changed)
             emit stateChanged(m_mode, m_state);
     }

     void Engine::setState(QAudio::Mode mode, QAudio::State state)
     {
         const bool changed = (m_mode != mode || m_state != state);
         m_mode = mode;
         m_state = state;
         if (changed)
             emit stateChanged(m_mode, m_state);
     }

     void Engine::setRecordPosition(qint64 position, bool forceEmit)
     {
         const bool changed = (m_recordPosition != position);
         m_recordPosition = position;
         if (changed || forceEmit)
             emit recordPositionChanged(m_recordPosition);
     }

     void Engine::setPlayPosition(qint64 position, bool forceEmit)
     {
         const bool changed = (m_playPosition != position);
         m_playPosition = position;
         if (changed || forceEmit)
             emit playPositionChanged(m_playPosition);
     }

     void Engine::calculateLevel(qint64 position, qint64 length)
     {
     #ifdef DISABLE_LEVEL
         Q_UNUSED(position)
         Q_UNUSED(length)
     #else
         Q_ASSERT(position + length <= m_bufferPosition + m_dataLength);

         qreal peakLevel = 0.0;

         qreal sum = 0.0;
         const char *ptr = m_buffer.constData() + position - m_bufferPosition;
         const char *const end = ptr + length;
         while (ptr < end) {
             const qint16 value = *reinterpret_cast<const qint16*>(ptr);
             const qreal fracValue = pcmToReal(value);
             peakLevel = qMax(peakLevel, fracValue);
             sum += fracValue * fracValue;
             ptr += 2;
         }
         const int numSamples = length / 2;
         qreal rmsLevel = sqrt(sum / numSamples);

         rmsLevel = qMax(qreal(0.0), rmsLevel);
         rmsLevel = qMin(qreal(1.0), rmsLevel);
         setLevel(rmsLevel, peakLevel, numSamples);

         ENGINE_DEBUG << "Engine::calculateLevel" << "pos" << position << "len" << length
                      << "rms" << rmsLevel << "peak" << peakLevel;
     #endif
     }

     void Engine::calculateSpectrum(qint64 position)
     {
     #ifdef DISABLE_SPECTRUM
         Q_UNUSED(position)
     #else
         Q_ASSERT(position + m_spectrumBufferLength <= m_bufferPosition + m_dataLength);
         Q_ASSERT(0 == m_spectrumBufferLength % 2); // constraint of FFT algorithm

         // QThread::currentThread is marked 'for internal use only', but
         // we're only using it for debug output here, so it's probably OK :)
         ENGINE_DEBUG << "Engine::calculateSpectrum" << QThread::currentThread()
                      << "count" << m_count << "pos" << position << "len" << m_spectrumBufferLength
                      << "spectrumAnalyser.isReady" << m_spectrumAnalyser.isReady();

         if(m_spectrumAnalyser.isReady()) {
             m_spectrumBuffer = QByteArray::fromRawData(m_buffer.constData() + position - m_bufferPosition,
                                                        m_spectrumBufferLength);
             m_spectrumPosition = position;
             m_spectrumAnalyser.calculate(m_spectrumBuffer, m_format);
         }
     #endif
     }

     void Engine::setFormat(const QAudioFormat &format)
     {
         const bool changed = (format != m_format);
         m_format = format;
         m_levelBufferLength = audioLength(m_format, LevelWindowUs);
         m_spectrumBufferLength = SpectrumLengthSamples *
                                 (m_format.sampleSize() / 8) * m_format.channels();
         if (changed)
             emit formatChanged(m_format);
     }

     void Engine::setLevel(qreal rmsLevel, qreal peakLevel, int numSamples)
     {
         m_rmsLevel = rmsLevel;
         m_peakLevel = peakLevel;
         emit levelChanged(m_rmsLevel, m_peakLevel, numSamples);
     }

     #ifdef DUMP_DATA
     void Engine::createOutputDir()
     {
         m_outputDir.setPath("output");

         // Ensure output directory exists and is empty
         if (m_outputDir.exists()) {
             const QStringList files = m_outputDir.entryList(QDir::Files);
             QString file;
             foreach (file, files)
                 m_outputDir.remove(file);
         } else {
             QDir::current().mkdir("output");
         }
     }
     #endif // DUMP_DATA

     #ifdef DUMP_AUDIO
     void Engine::dumpData()
     {
         const QString txtFileName = m_outputDir.filePath("data.txt");
         QFile txtFile(txtFileName);
         txtFile.open(QFile::WriteOnly | QFile::Text);
         QTextStream stream(&txtFile);
         const qint16 *ptr = reinterpret_cast<const qint16*>(m_buffer.constData());
         const int numSamples = m_dataLength / (2 * m_format.channels());
         for (int i=0; i<numSamples; ++i) {
             stream << i << "\t" << *ptr << "\n";
             ptr += m_format.channels();
         }

         const QString pcmFileName = m_outputDir.filePath("data.pcm");
         QFile pcmFile(pcmFileName);
         pcmFile.open(QFile::WriteOnly);
         pcmFile.write(m_buffer.constData(), m_dataLength);
     }
     #endif // DUMP_AUDIO