summaryrefslogtreecommitdiffstats
path: root/tests/spectrum/spectrumapp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/spectrum/spectrumapp')
-rw-r--r--tests/spectrum/spectrumapp/engine.cpp619
-rw-r--r--tests/spectrum/spectrumapp/engine.h247
-rw-r--r--tests/spectrum/spectrumapp/frequencyspectrum.cpp67
-rw-r--r--tests/spectrum/spectrumapp/frequencyspectrum.h76
-rw-r--r--tests/spectrum/spectrumapp/main.cpp201
-rw-r--r--tests/spectrum/spectrumapp/soundFiles/Rockhop.wavbin0 -> 1059308 bytes
-rw-r--r--tests/spectrum/spectrumapp/soundFiles/futurebells_beat.wavbin0 -> 352084 bytes
-rw-r--r--tests/spectrum/spectrumapp/soundFiles/onclassical_demo_fiati-di-parma_thuille_terzo-tempo_sestetto_small-version.wavbin0 -> 1055502 bytes
-rw-r--r--tests/spectrum/spectrumapp/spectrum.h111
-rw-r--r--tests/spectrum/spectrumapp/spectrum.qrc7
-rw-r--r--tests/spectrum/spectrumapp/spectrumanalyser.cpp209
-rw-r--r--tests/spectrum/spectrumapp/spectrumanalyser.h150
-rw-r--r--tests/spectrum/spectrumapp/spectrumapp.pro80
-rw-r--r--tests/spectrum/spectrumapp/utils.cpp117
-rw-r--r--tests/spectrum/spectrumapp/utils.h90
-rw-r--r--tests/spectrum/spectrumapp/wavfile.cpp129
-rw-r--r--tests/spectrum/spectrumapp/wavfile.h44
17 files changed, 2147 insertions, 0 deletions
diff --git a/tests/spectrum/spectrumapp/engine.cpp b/tests/spectrum/spectrumapp/engine.cpp
new file mode 100644
index 00000000..60605223
--- /dev/null
+++ b/tests/spectrum/spectrumapp/engine.cpp
@@ -0,0 +1,619 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVis3D module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#include "engine.h"
+#include "utils.h"
+
+#include <math.h>
+
+#include <QAudioInput>
+#include <QAudioOutput>
+#include <QCoreApplication>
+#include <QDebug>
+#include <QFile>
+#include <QMetaObject>
+#include <QSet>
+#include <QThread>
+
+//-----------------------------------------------------------------------------
+// Constants
+//-----------------------------------------------------------------------------
+
+const qint64 BufferDurationUs = 10 * 1000000;
+const int NotifyIntervalMs = 100;
+
+// Size of the level calculation window in microseconds
+const int LevelWindowUs = 0.1 * 1000000;
+
+//-----------------------------------------------------------------------------
+// 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();
+ qDebug() << "output devices:";
+ foreach (QAudioDeviceInfo device, m_availableAudioOutputDevices)
+ qDebug() << device.deviceName();
+ qDebug() << "input devices:";
+ foreach (QAudioDeviceInfo device, m_availableAudioInputDevices)
+ qDebug() << device.deviceName();
+}
+
+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 {
+ ENGINE_DEBUG << "Audio format not supported" << formatToString(m_file->fileFormat());
+ }
+ } else {
+ ENGINE_DEBUG << "Could not open file" << fileName;
+ }
+ if (result) {
+ m_analysisFile = new WavFile(this);
+ m_analysisFile->open(fileName);
+ }
+ return result;
+}
+
+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();
+ changedSpectrum(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();
+ changedSpectrum(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);
+ ENGINE_DEBUG << "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);
+ ENGINE_DEBUG << "Engine::audioNotify [2]" << "bufferPosition" << m_bufferPosition << "dataLength" << m_dataLength;
+ } else {
+ ENGINE_DEBUG << "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 changedSpectrum(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()) {
+ resetAudioDevices();
+ if (m_file) {
+ emit bufferLengthChanged(bufferLength());
+ emit dataLengthChanged(dataLength());
+ emit bufferChanged(0, 0, m_buffer);
+ setRecordPosition(bufferLength());
+ result = true;
+ }
+ m_audioOutput = new QAudioOutput(m_audioOutputDevice, m_format, this);
+ m_audioOutput->setNotifyInterval(NotifyIntervalMs);
+ } else {
+ if (m_file)
+ ENGINE_DEBUG << "Audio format not supported" << formatToString(m_format);
+ else
+ ENGINE_DEBUG << "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> sampleRatesList;
+#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.
+ sampleRatesList += 8000;
+#endif
+
+ if (!m_generateTone)
+ sampleRatesList += m_audioInputDevice.supportedSampleRates();
+
+ sampleRatesList += m_audioOutputDevice.supportedSampleRates();
+ sampleRatesList = sampleRatesList.toSet().toList(); // remove duplicates
+ qSort(sampleRatesList);
+ ENGINE_DEBUG << "Engine::initialize frequenciesList" << sampleRatesList;
+
+ QList<int> channelsList;
+ channelsList += m_audioInputDevice.supportedChannelCounts();
+ channelsList += m_audioOutputDevice.supportedChannelCounts();
+ 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 sampleRate, channels;
+ foreach (sampleRate, sampleRatesList) {
+ if (foundSupportedFormat)
+ break;
+ format.setSampleRate(sampleRate);
+ foreach (channels, channelsList) {
+ format.setChannelCount(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;
+}
+
+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.channelCount();
+ 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);
+}
diff --git a/tests/spectrum/spectrumapp/engine.h b/tests/spectrum/spectrumapp/engine.h
new file mode 100644
index 00000000..cdd8373f
--- /dev/null
+++ b/tests/spectrum/spectrumapp/engine.h
@@ -0,0 +1,247 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVis3D module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#ifndef ENGINE_H
+#define ENGINE_H
+
+#include "spectrum.h"
+#include "spectrumanalyser.h"
+#include "wavfile.h"
+
+#include <QAudioDeviceInfo>
+#include <QAudioFormat>
+#include <QBuffer>
+#include <QByteArray>
+#include <QDir>
+#include <QObject>
+#include <QVector>
+
+class FrequencySpectrum;
+QT_BEGIN_NAMESPACE
+class QAudioInput;
+class QAudioOutput;
+QT_END_NAMESPACE
+
+/**
+ * This class interfaces with the QtMultimedia audio classes, and also with
+ * the SpectrumAnalyser class. Its role is to manage the capture and playback
+ * of audio data, meanwhile performing real-time analysis of the audio level
+ * and frequency spectrum.
+ */
+class Engine : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit Engine(QObject *parent = 0);
+ ~Engine();
+
+ const QList<QAudioDeviceInfo> &availableAudioInputDevices() const
+ { return m_availableAudioInputDevices; }
+
+ const QList<QAudioDeviceInfo> &availableAudioOutputDevices() const
+ { return m_availableAudioOutputDevices; }
+
+ QAudio::Mode mode() const { return m_mode; }
+ QAudio::State state() const { return m_state; }
+
+ /**
+ * \return Current audio format
+ * \note May be QAudioFormat() if engine is not initialized
+ */
+ const QAudioFormat& format() const { return m_format; }
+
+ /**
+ * Stop any ongoing recording or playback, and reset to ground state.
+ */
+ void reset();
+
+ /**
+ * Load data from WAV file
+ */
+ bool loadFile(const QString &fileName);
+
+ /**
+ * Position of the audio input device.
+ * \return Position in bytes.
+ */
+ qint64 recordPosition() const { return m_recordPosition; }
+
+ /**
+ * RMS level of the most recently processed set of audio samples.
+ * \return Level in range (0.0, 1.0)
+ */
+ qreal rmsLevel() const { return m_rmsLevel; }
+
+ /**
+ * Peak level of the most recently processed set of audio samples.
+ * \return Level in range (0.0, 1.0)
+ */
+ qreal peakLevel() const { return m_peakLevel; }
+
+ /**
+ * Position of the audio output device.
+ * \return Position in bytes.
+ */
+ qint64 playPosition() const { return m_playPosition; }
+
+ /**
+ * Length of the internal engine buffer.
+ * \return Buffer length in bytes.
+ */
+ qint64 bufferLength() const;
+
+ /**
+ * Amount of data held in the buffer.
+ * \return Data length in bytes.
+ */
+ qint64 dataLength() const { return m_dataLength; }
+
+ /**
+ * Set window function applied to audio data before spectral analysis.
+ */
+ void setWindowFunction(WindowFunction type);
+
+public slots:
+ void startRecording();
+ void startPlayback();
+ void suspend();
+ void setAudioInputDevice(const QAudioDeviceInfo &device);
+ void setAudioOutputDevice(const QAudioDeviceInfo &device);
+
+signals:
+ void stateChanged(QAudio::Mode mode, QAudio::State state);
+
+ /**
+ * Format of audio data has changed
+ */
+ void formatChanged(const QAudioFormat &format);
+
+ /**
+ * Length of buffer has changed.
+ * \param duration Duration in microseconds
+ */
+ void bufferLengthChanged(qint64 duration);
+
+ /**
+ * Amount of data in buffer has changed.
+ * \param Length of data in bytes
+ */
+ void dataLengthChanged(qint64 duration);
+
+ /**
+ * Position of the audio input device has changed.
+ * \param position Position in bytes
+ */
+ void recordPositionChanged(qint64 position);
+
+ /**
+ * Position of the audio output device has changed.
+ * \param position Position in bytes
+ */
+ void playPositionChanged(qint64 position);
+
+ /**
+ * Level changed
+ * \param rmsLevel RMS level in range 0.0 - 1.0
+ * \param peakLevel Peak level in range 0.0 - 1.0
+ * \param numSamples Number of audio samples analyzed
+ */
+ void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples);
+
+ /**
+ * Spectrum has changed.
+ * \param position Position of start of window in bytes
+ * \param length Length of window in bytes
+ * \param spectrum Resulting frequency spectrum
+ */
+ void changedSpectrum(qint64 position, qint64 length, const FrequencySpectrum &spectrum);
+
+ /**
+ * Buffer containing audio data has changed.
+ * \param position Position of start of buffer in bytes
+ * \param buffer Buffer
+ */
+ void bufferChanged(qint64 position, qint64 length, const QByteArray &buffer);
+
+private slots:
+ void audioNotify();
+ void audioStateChanged(QAudio::State state);
+ void audioDataReady();
+ void spectrumChanged(const FrequencySpectrum &spectrum);
+
+private:
+ void resetAudioDevices();
+ bool initialize();
+ bool selectFormat();
+ void stopRecording();
+ void stopPlayback();
+ void setState(QAudio::State state);
+ void setState(QAudio::Mode mode, QAudio::State state);
+ void setFormat(const QAudioFormat &format);
+ void setRecordPosition(qint64 position, bool forceEmit = false);
+ void setPlayPosition(qint64 position, bool forceEmit = false);
+ void calculateLevel(qint64 position, qint64 length);
+ void calculateSpectrum(qint64 position);
+ void setLevel(qreal rmsLevel, qreal peakLevel, int numSamples);
+
+private:
+ QAudio::Mode m_mode;
+ QAudio::State m_state;
+
+ bool m_generateTone;
+ SweptTone m_tone;
+
+ WavFile* m_file;
+ // We need a second file handle via which to read data into m_buffer
+ // for analysis
+ WavFile* m_analysisFile;
+
+ QAudioFormat m_format;
+
+ const QList<QAudioDeviceInfo> m_availableAudioInputDevices;
+ QAudioDeviceInfo m_audioInputDevice;
+ QAudioInput* m_audioInput;
+ QIODevice* m_audioInputIODevice;
+ qint64 m_recordPosition;
+
+ const QList<QAudioDeviceInfo> m_availableAudioOutputDevices;
+ QAudioDeviceInfo m_audioOutputDevice;
+ QAudioOutput* m_audioOutput;
+ qint64 m_playPosition;
+ QBuffer m_audioOutputIODevice;
+
+ QByteArray m_buffer;
+ qint64 m_bufferPosition;
+ qint64 m_bufferLength;
+ qint64 m_dataLength;
+
+ int m_levelBufferLength;
+ qreal m_rmsLevel;
+ qreal m_peakLevel;
+
+ int m_spectrumBufferLength;
+ QByteArray m_spectrumBuffer;
+ SpectrumAnalyser m_spectrumAnalyser;
+ qint64 m_spectrumPosition;
+
+ int m_count;
+
+};
+
+#endif // ENGINE_H
diff --git a/tests/spectrum/spectrumapp/frequencyspectrum.cpp b/tests/spectrum/spectrumapp/frequencyspectrum.cpp
new file mode 100644
index 00000000..013d0454
--- /dev/null
+++ b/tests/spectrum/spectrumapp/frequencyspectrum.cpp
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVis3D module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#include "frequencyspectrum.h"
+
+FrequencySpectrum::FrequencySpectrum(int numPoints)
+ : m_elements(numPoints)
+{
+
+}
+
+void FrequencySpectrum::reset()
+{
+ iterator i = begin();
+ for ( ; i != end(); ++i)
+ *i = Element();
+}
+
+int FrequencySpectrum::count() const
+{
+ return m_elements.count();
+}
+
+FrequencySpectrum::Element &FrequencySpectrum::operator[](int index)
+{
+ return m_elements[index];
+}
+
+const FrequencySpectrum::Element &FrequencySpectrum::operator[](int index) const
+{
+ return m_elements[index];
+}
+
+FrequencySpectrum::iterator FrequencySpectrum::begin()
+{
+ return m_elements.begin();
+}
+
+FrequencySpectrum::iterator FrequencySpectrum::end()
+{
+ return m_elements.end();
+}
+
+FrequencySpectrum::const_iterator FrequencySpectrum::begin() const
+{
+ return m_elements.begin();
+}
+
+FrequencySpectrum::const_iterator FrequencySpectrum::end() const
+{
+ return m_elements.end();
+}
diff --git a/tests/spectrum/spectrumapp/frequencyspectrum.h b/tests/spectrum/spectrumapp/frequencyspectrum.h
new file mode 100644
index 00000000..fac9a1b7
--- /dev/null
+++ b/tests/spectrum/spectrumapp/frequencyspectrum.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVis3D module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#ifndef FREQUENCYSPECTRUM_H
+#define FREQUENCYSPECTRUM_H
+
+#include <QtCore/QVector>
+
+/**
+ * Represents a frequency spectrum as a series of elements, each of which
+ * consists of a frequency, an amplitude and a phase.
+ */
+class FrequencySpectrum {
+public:
+ FrequencySpectrum(int numPoints = 0);
+
+ struct Element {
+ Element()
+ : frequency(0.0), amplitude(0.0), phase(0.0), clipped(false)
+ { }
+
+ /**
+ * Frequency in Hertz
+ */
+ qreal frequency;
+
+ /**
+ * Amplitude in range [0.0, 1.0]
+ */
+ qreal amplitude;
+
+ /**
+ * Phase in range [0.0, 2*PI]
+ */
+ qreal phase;
+
+ /**
+ * Indicates whether value has been clipped during spectrum analysis
+ */
+ bool clipped;
+ };
+
+ typedef QVector<Element>::iterator iterator;
+ typedef QVector<Element>::const_iterator const_iterator;
+
+ void reset();
+
+ int count() const;
+ Element& operator[](int index);
+ const Element& operator[](int index) const;
+ iterator begin();
+ iterator end();
+ const_iterator begin() const;
+ const_iterator end() const;
+
+private:
+ QVector<Element> m_elements;
+
+};
+
+#endif // FREQUENCYSPECTRUM_H
diff --git a/tests/spectrum/spectrumapp/main.cpp b/tests/spectrum/spectrumapp/main.cpp
new file mode 100644
index 00000000..32076b88
--- /dev/null
+++ b/tests/spectrum/spectrumapp/main.cpp
@@ -0,0 +1,201 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVis3D module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#include "engine.h"
+#include "utils.h"
+
+#include <QtDataVis3D/q3dbars.h>
+#include <QtDataVis3D/qbardataproxy.h>
+#include <QtDataVis3D/qvalueaxis.h>
+
+#include <QGuiApplication>
+#include <QAudio>
+#include <QTimer>
+
+//#define USE_CONES
+
+using namespace QtDataVis3D;
+
+class MainApp : public QObject
+{
+public:
+ MainApp(Q3DBars *window);
+ ~MainApp();
+
+ void start(QString fileName);
+
+public slots:
+ void spectrumChanged(qint64 position, qint64 length, const FrequencySpectrum &spectrum);
+ void stateChanged(QAudio::Mode mode, QAudio::State state);
+
+private slots:
+ void restart();
+
+private:
+ int barIndex(qreal frequency) const;
+
+private:
+ Q3DBars *m_chart;
+ Engine *m_engine;
+ QTimer *m_restartTimer;
+ // Lower bound of first band in the spectrum in Hz
+ qreal m_lowFreq;
+ // Upper band of last band in the spectrum in Hz
+ qreal m_highFreq;
+};
+
+MainApp::MainApp(Q3DBars *window)
+ : m_chart(window),
+ m_engine(new Engine(this)),
+ m_restartTimer(new QTimer(this)),
+ m_lowFreq(SpectrumLowFreq),
+ m_highFreq(SpectrumHighFreq)
+{
+ m_chart->setDataWindow(SpectrumNumBands * 2, SpectrumNumBands);
+ // Disable grid
+ m_chart->setGridVisible(false);
+ // Disable auto-scaling of height by defining explicit range
+ m_chart->valueAxis()->setRange(0.0, 1.0);
+ // Disable shadows
+ m_chart->setShadowQuality(QDataVis::ShadowNone);
+#if USE_CONES
+ // Set bar specifications; make them a bit wider than deep and make them be drawn 75%
+ // inside each other
+ m_chart->setBarSpecs(1.25), QSizeF(0.2, -0.75));
+ // Set bar type, smooth cones
+ m_chart->setBarType(QDataVis::Cones, true);
+ // Adjust zoom manually; automatic zoom level calculation does not work well with negative
+ // spacings (in setBarSpecs)
+ m_chart->setCameraPosition(10.0f, 5.0f, 70);
+#else
+ // Set bar specifications; make them twice as wide as they're deep
+ m_chart->setBarSpecs(2.0, QSizeF(0.0, 0.0));
+ // Set bar type, flat bars
+ m_chart->setBarType(QDataVis::Bars, false);
+ // Adjust camera position
+ m_chart->setCameraPosition(10.0f, 7.5f, 75);
+#endif
+ // Set color scheme
+ m_chart->setBarColor(QColor(Qt::black), QColor(Qt::red), QColor(Qt::darkYellow));
+ // Disable selection
+ m_chart->setSelectionMode(QDataVis::ModeNone);
+ QObject::connect(m_engine, &Engine::changedSpectrum, this, &MainApp::spectrumChanged);
+ QObject::connect(m_engine, &Engine::stateChanged, this, &MainApp::stateChanged);
+ m_restartTimer->setSingleShot(true);
+ QObject::connect(m_restartTimer, &QTimer::timeout, this, &MainApp::restart);
+
+ QBarDataProxy *proxy = new QBarDataProxy;
+ m_chart->setActiveDataProxy(proxy);
+}
+
+MainApp::~MainApp()
+{
+ delete m_engine;
+ delete m_restartTimer;
+}
+
+void MainApp::start(QString fileName)
+{
+ m_engine->loadFile(fileName);
+ m_engine->startPlayback();
+}
+
+//-----------------------------------------------------------------------------
+// Public slots
+//-----------------------------------------------------------------------------
+
+void MainApp::spectrumChanged(qint64 position, qint64 length, const FrequencySpectrum &spectrum)
+{
+ Q_UNUSED(position);
+ Q_UNUSED(length);
+ //qDebug() << "updating bar values" << position << length;
+ QBarDataRow *data = new QBarDataRow(SpectrumNumBands);
+ for (int bar = 0; bar < SpectrumNumBands; bar++) {
+ // init data set
+ (*data)[bar].setValue(qreal(0.0));
+ }
+ FrequencySpectrum::const_iterator i = spectrum.begin();
+ const FrequencySpectrum::const_iterator end = spectrum.end();
+ for ( ; i != end; ++i) {
+ const FrequencySpectrum::Element e = *i;
+ if (e.frequency >= m_lowFreq && e.frequency < m_highFreq) {
+ (*data)[barIndex(e.frequency)].setValue(qMax(data->at(barIndex(e.frequency)).value(), qreal(e.amplitude)));
+ }
+ }
+ static_cast<QBarDataProxy *>(m_chart->activeDataProxy())->insertRow(0, data);
+}
+
+void MainApp::stateChanged(QAudio::Mode mode, QAudio::State state)
+{
+ //qDebug() << "mode:" << mode << " state: " << state;
+ // Restart once playback is finished
+ if (QAudio::AudioOutput == mode && QAudio::StoppedState == state)
+ m_restartTimer->start(500);
+}
+
+//-----------------------------------------------------------------------------
+// Private slots
+//-----------------------------------------------------------------------------
+
+void MainApp::restart()
+{
+ // Change file each time
+ QString fileToLoad = QStringLiteral(":/file");
+ static int fileNo = 3;
+ QString nrStr;
+ nrStr.setNum(fileNo);
+ fileToLoad.append(nrStr);
+ //qDebug() << fileToLoad;
+ start(fileToLoad);
+ fileNo++;
+ if (fileNo > 3)
+ fileNo = 1;
+}
+
+//-----------------------------------------------------------------------------
+// Private functions
+//-----------------------------------------------------------------------------
+
+int MainApp::barIndex(qreal frequency) const
+{
+ Q_ASSERT(frequency >= m_lowFreq && frequency < m_highFreq);
+ const qreal bandWidth = (m_highFreq - m_lowFreq) / SpectrumNumBands;
+ const int index = (frequency - m_lowFreq) / bandWidth;
+ if (index < 0 || index >= SpectrumNumBands)
+ Q_ASSERT(false);
+ //qDebug() << "insert to" << index;
+ return index;
+}
+
+//-----------------------------------------------------------------------------
+// main
+//-----------------------------------------------------------------------------
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+ app.setApplicationName("QtDataVis3D spectrum analyzer");
+
+ Q3DBars window;
+ window.resize(1024, 768);
+ window.show();
+
+ MainApp *mainApp = new MainApp(&window);
+ mainApp->start(QStringLiteral(":/file2"));
+
+ return app.exec();
+}
diff --git a/tests/spectrum/spectrumapp/soundFiles/Rockhop.wav b/tests/spectrum/spectrumapp/soundFiles/Rockhop.wav
new file mode 100644
index 00000000..e56e1c0f
--- /dev/null
+++ b/tests/spectrum/spectrumapp/soundFiles/Rockhop.wav
Binary files differ
diff --git a/tests/spectrum/spectrumapp/soundFiles/futurebells_beat.wav b/tests/spectrum/spectrumapp/soundFiles/futurebells_beat.wav
new file mode 100644
index 00000000..c45cbc71
--- /dev/null
+++ b/tests/spectrum/spectrumapp/soundFiles/futurebells_beat.wav
Binary files differ
diff --git a/tests/spectrum/spectrumapp/soundFiles/onclassical_demo_fiati-di-parma_thuille_terzo-tempo_sestetto_small-version.wav b/tests/spectrum/spectrumapp/soundFiles/onclassical_demo_fiati-di-parma_thuille_terzo-tempo_sestetto_small-version.wav
new file mode 100644
index 00000000..78b8dbda
--- /dev/null
+++ b/tests/spectrum/spectrumapp/soundFiles/onclassical_demo_fiati-di-parma_thuille_terzo-tempo_sestetto_small-version.wav
Binary files differ
diff --git a/tests/spectrum/spectrumapp/spectrum.h b/tests/spectrum/spectrumapp/spectrum.h
new file mode 100644
index 00000000..015989d5
--- /dev/null
+++ b/tests/spectrum/spectrumapp/spectrum.h
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVis3D module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#ifndef SPECTRUM_H
+#define SPECTRUM_H
+
+#include "utils.h"
+#include "fftreal_wrapper.h" // For FFTLengthPowerOfTwo
+#include <qglobal.h>
+
+//-----------------------------------------------------------------------------
+// Constants
+//-----------------------------------------------------------------------------
+
+// Number of audio samples used to calculate the frequency spectrum
+const int SpectrumLengthSamples = PowerOfTwo<FFTLengthPowerOfTwo>::Result;
+
+// Number of bands in the frequency spectrum
+const int SpectrumNumBands = 30;
+
+// Lower bound of first band in the spectrum
+const qreal SpectrumLowFreq = 0.0; // Hz
+
+// Upper band of last band in the spectrum
+const qreal SpectrumHighFreq = 1000.0; // Hz
+
+// Waveform window size in microseconds
+const qint64 WaveformWindowDuration = 500 * 1000;
+
+// Fudge factor used to calculate the spectrum bar heights
+const qreal SpectrumAnalyserMultiplier = 0.15;
+
+// Disable message timeout
+const int NullMessageTimeout = -1;
+
+
+//-----------------------------------------------------------------------------
+// Types and data structures
+//-----------------------------------------------------------------------------
+
+enum WindowFunction {
+ NoWindow,
+ HannWindow
+};
+
+const WindowFunction DefaultWindowFunction = HannWindow;
+
+struct Tone
+{
+ Tone(qreal freq = 0.0, qreal amp = 0.0)
+ : frequency(freq), amplitude(amp)
+ { }
+
+ // Start and end frequencies for swept tone generation
+ qreal frequency;
+
+ // Amplitude in range [0.0, 1.0]
+ qreal amplitude;
+};
+
+struct SweptTone
+{
+ SweptTone(qreal start = 0.0, qreal end = 0.0, qreal amp = 0.0)
+ : startFreq(start), endFreq(end), amplitude(amp)
+ { Q_ASSERT(end >= start); }
+
+ SweptTone(const Tone &tone)
+ : startFreq(tone.frequency), endFreq(tone.frequency), amplitude(tone.amplitude)
+ { }
+
+ // Start and end frequencies for swept tone generation
+ qreal startFreq;
+ qreal endFreq;
+
+ // Amplitude in range [0.0, 1.0]
+ qreal amplitude;
+};
+
+
+//-----------------------------------------------------------------------------
+// Macros
+//-----------------------------------------------------------------------------
+
+// Macro which connects a signal to a slot, and which causes application to
+// abort if the connection fails. This is intended to catch programming errors
+// such as mis-typing a signal or slot name. It is necessary to write our own
+// macro to do this - the following idiom
+// Q_ASSERT(connect(source, signal, receiver, slot));
+// will not work because Q_ASSERT compiles to a no-op in release builds.
+
+#define CHECKED_CONNECT(source, signal, receiver, slot) \
+ if (!connect(source, signal, receiver, slot)) \
+ qt_assert_x(Q_FUNC_INFO, "CHECKED_CONNECT failed", __FILE__, __LINE__);
+
+#endif // SPECTRUM_H
+
diff --git a/tests/spectrum/spectrumapp/spectrum.qrc b/tests/spectrum/spectrumapp/spectrum.qrc
new file mode 100644
index 00000000..9368abc7
--- /dev/null
+++ b/tests/spectrum/spectrumapp/spectrum.qrc
@@ -0,0 +1,7 @@
+<RCC>
+ <qresource prefix="/">
+ <file alias="file1">soundFiles/onclassical_demo_fiati-di-parma_thuille_terzo-tempo_sestetto_small-version.wav</file>
+ <file alias="file2">soundFiles/Rockhop.wav</file>
+ <file alias="file3">soundFiles/futurebells_beat.wav</file>
+ </qresource>
+</RCC>
diff --git a/tests/spectrum/spectrumapp/spectrumanalyser.cpp b/tests/spectrum/spectrumapp/spectrumanalyser.cpp
new file mode 100644
index 00000000..4cebfde9
--- /dev/null
+++ b/tests/spectrum/spectrumapp/spectrumanalyser.cpp
@@ -0,0 +1,209 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVis3D module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#include "spectrumanalyser.h"
+#include "utils.h"
+#include "fftreal_wrapper.h"
+
+#include <qmath.h>
+#include <qmetatype.h>
+#include <QAudioFormat>
+#include <QThread>
+
+SpectrumAnalyserThread::SpectrumAnalyserThread(QObject *parent)
+ : QObject(parent),
+ m_fft(new FFTRealWrapper),
+ m_numSamples(SpectrumLengthSamples),
+ m_windowFunction(DefaultWindowFunction),
+ m_window(SpectrumLengthSamples, 0.0),
+ m_input(SpectrumLengthSamples, 0.0),
+ m_output(SpectrumLengthSamples, 0.0),
+ m_spectrum(SpectrumLengthSamples)
+ #ifdef SPECTRUM_ANALYSER_SEPARATE_THREAD
+ , m_thread(new QThread(this))
+ #endif
+{
+#ifdef SPECTRUM_ANALYSER_SEPARATE_THREAD
+ // moveToThread() cannot be called on a QObject with a parent
+ setParent(0);
+ moveToThread(m_thread);
+ m_thread->start();
+#endif
+ calculateWindow();
+}
+
+SpectrumAnalyserThread::~SpectrumAnalyserThread()
+{
+ delete m_fft;
+}
+
+void SpectrumAnalyserThread::setWindowFunction(WindowFunction type)
+{
+ m_windowFunction = type;
+ calculateWindow();
+}
+
+void SpectrumAnalyserThread::calculateWindow()
+{
+ for (int i=0; i<m_numSamples; ++i) {
+ DataType x = 0.0;
+
+ switch (m_windowFunction) {
+ case NoWindow:
+ x = 1.0;
+ break;
+ case HannWindow:
+ x = 0.5 * (1 - qCos((2 * M_PI * i) / (m_numSamples - 1)));
+ break;
+ default:
+ Q_ASSERT(false);
+ }
+
+ m_window[i] = x;
+ }
+}
+
+void SpectrumAnalyserThread::calculateSpectrum(const QByteArray &buffer,
+ int inputFrequency,
+ int bytesPerSample)
+{
+ Q_ASSERT(buffer.size() == m_numSamples * bytesPerSample);
+
+ // Initialize data array
+ const char *ptr = buffer.constData();
+ for (int i=0; i<m_numSamples; ++i) {
+ const qint16 pcmSample = *reinterpret_cast<const qint16*>(ptr);
+ // Scale down to range [-1.0, 1.0]
+ const DataType realSample = pcmToReal(pcmSample);
+ const DataType windowedSample = realSample * m_window[i];
+ m_input[i] = windowedSample;
+ ptr += bytesPerSample;
+ }
+
+ // Calculate the FFT
+ m_fft->calculateFFT(m_output.data(), m_input.data());
+
+ // Analyze output to obtain amplitude and phase for each frequency
+ for (int i=2; i<=m_numSamples/2; ++i) {
+ // Calculate frequency of this complex sample
+ m_spectrum[i].frequency = qreal(i * inputFrequency) / (m_numSamples);
+
+ const qreal real = m_output[i];
+ qreal imag = 0.0;
+ if (i>0 && i<m_numSamples/2)
+ imag = m_output[m_numSamples/2 + i];
+
+ const qreal magnitude = sqrt(real*real + imag*imag);
+ qreal amplitude = SpectrumAnalyserMultiplier * log(magnitude);
+
+ // Bound amplitude to [0.0, 1.0]
+ m_spectrum[i].clipped = (amplitude > 1.0);
+ amplitude = qMax(qreal(0.0), amplitude);
+ amplitude = qMin(qreal(1.0), amplitude);
+ m_spectrum[i].amplitude = amplitude;
+ }
+
+ emit calculationComplete(m_spectrum);
+}
+
+
+//=============================================================================
+// SpectrumAnalyser
+//=============================================================================
+
+SpectrumAnalyser::SpectrumAnalyser(QObject *parent)
+ : QObject(parent),
+ m_thread(new SpectrumAnalyserThread(this)),
+ m_state(Idle)
+{
+ CHECKED_CONNECT(m_thread, SIGNAL(calculationComplete(FrequencySpectrum)),
+ this, SLOT(calculationComplete(FrequencySpectrum)));
+}
+
+SpectrumAnalyser::~SpectrumAnalyser()
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+//-----------------------------------------------------------------------------
+
+void SpectrumAnalyser::setWindowFunction(WindowFunction type)
+{
+ const bool b = QMetaObject::invokeMethod(m_thread, "setWindowFunction",
+ Qt::AutoConnection,
+ Q_ARG(WindowFunction, type));
+ Q_ASSERT(b);
+ Q_UNUSED(b) // suppress warnings in release builds
+}
+
+void SpectrumAnalyser::calculate(const QByteArray &buffer,
+ const QAudioFormat &format)
+{
+ // QThread::currentThread is marked 'for internal use only', but
+ // we're only using it for debug output here, so it's probably OK :)
+ SPECTRUMANALYSER_DEBUG << "SpectrumAnalyser::calculate"
+ << QThread::currentThread()
+ << "state" << m_state;
+
+ if (isReady()) {
+ Q_ASSERT(isPCMS16LE(format));
+
+ const int bytesPerSample = format.sampleSize() * format.channelCount() / 8;
+
+ m_state = Busy;
+
+ // Invoke SpectrumAnalyserThread::calculateSpectrum using QMetaObject. If
+ // m_thread is in a different thread from the current thread, the
+ // calculation will be done in the child thread.
+ // Once the calculation is finished, a calculationChanged signal will be
+ // emitted by m_thread.
+ const bool b = QMetaObject::invokeMethod(m_thread, "calculateSpectrum",
+ Qt::AutoConnection,
+ Q_ARG(QByteArray, buffer),
+ Q_ARG(int, format.sampleRate()),
+ Q_ARG(int, bytesPerSample));
+ Q_ASSERT(b);
+ Q_UNUSED(b) // suppress warnings in release builds
+ }
+}
+
+bool SpectrumAnalyser::isReady() const
+{
+ return (Idle == m_state);
+}
+
+void SpectrumAnalyser::cancelCalculation()
+{
+ if (Busy == m_state)
+ m_state = Cancelled;
+}
+
+
+//-----------------------------------------------------------------------------
+// Private slots
+//-----------------------------------------------------------------------------
+
+void SpectrumAnalyser::calculationComplete(const FrequencySpectrum &spectrum)
+{
+ Q_ASSERT(Idle != m_state);
+ if (Busy == m_state)
+ emit spectrumChanged(spectrum);
+ m_state = Idle;
+}
diff --git a/tests/spectrum/spectrumapp/spectrumanalyser.h b/tests/spectrum/spectrumapp/spectrumanalyser.h
new file mode 100644
index 00000000..6d9291ef
--- /dev/null
+++ b/tests/spectrum/spectrumapp/spectrumanalyser.h
@@ -0,0 +1,150 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVis3D module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#ifndef SPECTRUMANALYSER_H
+#define SPECTRUMANALYSER_H
+
+#include <QByteArray>
+#include <QObject>
+#include <QVector>
+
+#include "frequencyspectrum.h"
+#include "spectrum.h"
+
+#include "FFTRealFixLenParam.h"
+
+QT_FORWARD_DECLARE_CLASS(QAudioFormat)
+QT_FORWARD_DECLARE_CLASS(QThread)
+
+class FFTRealWrapper;
+
+class SpectrumAnalyserThreadPrivate;
+
+/**
+ * Implementation of the spectrum analysis which can be run in a
+ * separate thread.
+ */
+class SpectrumAnalyserThread : public QObject
+{
+ Q_OBJECT
+
+public:
+ SpectrumAnalyserThread(QObject *parent);
+ ~SpectrumAnalyserThread();
+
+public slots:
+ void setWindowFunction(WindowFunction type);
+ void calculateSpectrum(const QByteArray &buffer,
+ int inputFrequency,
+ int bytesPerSample);
+
+signals:
+ void calculationComplete(const FrequencySpectrum &spectrum);
+
+private:
+ void calculateWindow();
+
+private:
+ FFTRealWrapper* m_fft;
+
+ const int m_numSamples;
+
+ WindowFunction m_windowFunction;
+
+ typedef FFTRealFixLenParam::DataType DataType;
+
+ QVector<DataType> m_window;
+
+ QVector<DataType> m_input;
+ QVector<DataType> m_output;
+
+ FrequencySpectrum m_spectrum;
+
+#ifdef SPECTRUM_ANALYSER_SEPARATE_THREAD
+ QThread* m_thread;
+#endif
+};
+
+/**
+ * Class which performs frequency spectrum analysis on a window of
+ * audio samples, provided to it by the Engine.
+ */
+class SpectrumAnalyser : public QObject
+{
+ Q_OBJECT
+
+public:
+ SpectrumAnalyser(QObject *parent = 0);
+ ~SpectrumAnalyser();
+
+public:
+ /*
+ * Set the windowing function which is applied before calculating the FFT
+ */
+ void setWindowFunction(WindowFunction type);
+
+ /*
+ * Calculate a frequency spectrum
+ *
+ * \param buffer Audio data
+ * \param format Format of audio data
+ *
+ * Frequency spectrum is calculated asynchronously. The result is returned
+ * via the spectrumChanged signal.
+ *
+ * An ongoing calculation can be cancelled by calling cancelCalculation().
+ *
+ */
+ void calculate(const QByteArray &buffer, const QAudioFormat &format);
+
+ /*
+ * Check whether the object is ready to perform another calculation
+ */
+ bool isReady() const;
+
+ /*
+ * Cancel an ongoing calculation
+ *
+ * Note that cancelling is asynchronous.
+ */
+ void cancelCalculation();
+
+signals:
+ void spectrumChanged(const FrequencySpectrum &spectrum);
+
+private slots:
+ void calculationComplete(const FrequencySpectrum &spectrum);
+
+private:
+ void calculateWindow();
+
+private:
+
+ SpectrumAnalyserThread* m_thread;
+
+ enum State {
+ Idle,
+ Busy,
+ Cancelled
+ };
+
+ State m_state;
+};
+
+#endif // SPECTRUMANALYSER_H
+
diff --git a/tests/spectrum/spectrumapp/spectrumapp.pro b/tests/spectrum/spectrumapp/spectrumapp.pro
new file mode 100644
index 00000000..0fc0584c
--- /dev/null
+++ b/tests/spectrum/spectrumapp/spectrumapp.pro
@@ -0,0 +1,80 @@
+!include( ../../tests.pri ) {
+ error( "Couldn't find the tests.pri file!" )
+}
+
+!include( ../spectrum.pri ) {
+ error( "Couldn't find the spectrum.pri file!" )
+}
+
+static: error(This application cannot be statically linked to the fftreal library)
+
+TEMPLATE = app
+
+TARGET = spectrum
+
+QT += multimedia
+
+SOURCES += main.cpp \
+ engine.cpp \
+ frequencyspectrum.cpp \
+ spectrumanalyser.cpp \
+ utils.cpp \
+ wavfile.cpp
+
+HEADERS += engine.h \
+ frequencyspectrum.h \
+ spectrum.h \
+ spectrumanalyser.h \
+ utils.h \
+ wavfile.h
+
+fftreal_dir = ../3rdparty/fftreal
+
+INCLUDEPATH += $${fftreal_dir}
+
+RESOURCES = spectrum.qrc
+
+# Dynamic linkage against FFTReal DLL
+!contains(DEFINES, DISABLE_FFT) {
+ macx {
+ # Link to fftreal framework
+ LIBS += -F$${fftreal_dir}
+ LIBS += -framework fftreal
+ } else {
+ LIBS += -L..$${spectrum_build_dir}
+ LIBS += -lfftreal
+ }
+}
+
+
+android {
+ target.path = /libs/$$ANDROID_TARGET_ARCH
+} else {
+ target.path = $$[QT_INSTALL_EXAMPLES]/datavis3d/spectrum
+}
+INSTALLS += target
+
+# Deployment
+
+DESTDIR = ..$${spectrum_build_dir}
+macx {
+ !contains(DEFINES, DISABLE_FFT) {
+ # Relocate fftreal.framework into spectrum.app bundle
+ framework_dir = ../spectrum.app/Contents/Frameworks
+ framework_name = fftreal.framework/Versions/1/fftreal
+ QMAKE_POST_LINK = \
+ mkdir -p $${framework_dir} &&\
+ rm -rf $${framework_dir}/fftreal.framework &&\
+ cp -R $${fftreal_dir}/fftreal.framework $${framework_dir} &&\
+ install_name_tool -id @executable_path/../Frameworks/$${framework_name} \
+ $${framework_dir}/$${framework_name} &&\
+ install_name_tool -change $${framework_name} \
+ @executable_path/../Frameworks/$${framework_name} \
+ ../spectrum.app/Contents/MacOS/spectrum
+ }
+} else {
+ linux-g++*: {
+ # Provide relative path from application to fftreal library
+ QMAKE_LFLAGS += -Wl,--rpath=\\\$\$ORIGIN
+ }
+}
diff --git a/tests/spectrum/spectrumapp/utils.cpp b/tests/spectrum/spectrumapp/utils.cpp
new file mode 100644
index 00000000..bad6cc48
--- /dev/null
+++ b/tests/spectrum/spectrumapp/utils.cpp
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVis3D module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#include <QAudioFormat>
+#include "utils.h"
+
+qint64 audioDuration(const QAudioFormat &format, qint64 bytes)
+{
+ return (bytes * 1000000) /
+ (format.sampleRate() * format.channelCount() * (format.sampleSize() / 8));
+}
+
+qint64 audioLength(const QAudioFormat &format, qint64 microSeconds)
+{
+ qint64 result = (format.sampleRate() * format.channelCount() * (format.sampleSize() / 8))
+ * microSeconds / 1000000;
+ result -= result % (format.channelCount() * format.sampleSize());
+ return result;
+}
+
+qreal nyquistFrequency(const QAudioFormat &format)
+{
+ return format.sampleRate() / 2;
+}
+
+QString formatToString(const QAudioFormat &format)
+{
+ QString result;
+
+ if (QAudioFormat() != format) {
+ if (format.codec() == "audio/pcm") {
+ Q_ASSERT(format.sampleType() == QAudioFormat::SignedInt);
+
+ const QString formatEndian = (format.byteOrder() == QAudioFormat::LittleEndian)
+ ? QString("LE") : QString("BE");
+
+ QString formatType;
+ switch (format.sampleType()) {
+ case QAudioFormat::SignedInt:
+ formatType = "signed";
+ break;
+ case QAudioFormat::UnSignedInt:
+ formatType = "unsigned";
+ break;
+ case QAudioFormat::Float:
+ formatType = "float";
+ break;
+ case QAudioFormat::Unknown:
+ formatType = "unknown";
+ break;
+ }
+
+ QString formatChannels = QString("%1 channels").arg(format.channelCount());
+ switch (format.channelCount()) {
+ case 1:
+ formatChannels = "mono";
+ break;
+ case 2:
+ formatChannels = "stereo";
+ break;
+ }
+
+ result = QString("%1 Hz %2 bit %3 %4 %5")
+ .arg(format.sampleRate())
+ .arg(format.sampleSize())
+ .arg(formatType)
+ .arg(formatEndian)
+ .arg(formatChannels);
+ } else {
+ result = format.codec();
+ }
+ }
+
+ return result;
+}
+
+bool isPCM(const QAudioFormat &format)
+{
+ return (format.codec() == "audio/pcm");
+}
+
+
+bool isPCMS16LE(const QAudioFormat &format)
+{
+ return isPCM(format) &&
+ format.sampleType() == QAudioFormat::SignedInt &&
+ format.sampleSize() == 16 &&
+ format.byteOrder() == QAudioFormat::LittleEndian;
+}
+
+const qint16 PCMS16MaxValue = 32767;
+const quint16 PCMS16MaxAmplitude = 32768; // because minimum is -32768
+
+qreal pcmToReal(qint16 pcm)
+{
+ return qreal(pcm) / PCMS16MaxAmplitude;
+}
+
+qint16 realToPcm(qreal real)
+{
+ return real * PCMS16MaxValue;
+}
diff --git a/tests/spectrum/spectrumapp/utils.h b/tests/spectrum/spectrumapp/utils.h
new file mode 100644
index 00000000..f0ae5633
--- /dev/null
+++ b/tests/spectrum/spectrumapp/utils.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVis3D module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <QtCore/qglobal.h>
+#include <QDebug>
+
+QT_FORWARD_DECLARE_CLASS(QAudioFormat)
+
+//-----------------------------------------------------------------------------
+// Miscellaneous utility functions
+//-----------------------------------------------------------------------------
+
+qint64 audioDuration(const QAudioFormat &format, qint64 bytes);
+qint64 audioLength(const QAudioFormat &format, qint64 microSeconds);
+
+QString formatToString(const QAudioFormat &format);
+
+qreal nyquistFrequency(const QAudioFormat &format);
+
+// Scale PCM value to [-1.0, 1.0]
+qreal pcmToReal(qint16 pcm);
+
+// Scale real value in [-1.0, 1.0] to PCM
+qint16 realToPcm(qreal real);
+
+// Check whether the audio format is PCM
+bool isPCM(const QAudioFormat &format);
+
+// Check whether the audio format is signed, little-endian, 16-bit PCM
+bool isPCMS16LE(const QAudioFormat &format);
+
+// Compile-time calculation of powers of two
+
+template<int N> class PowerOfTwo
+{ public: static const int Result = PowerOfTwo<N-1>::Result * 2; };
+
+template<> class PowerOfTwo<0>
+{ public: static const int Result = 1; };
+
+
+//-----------------------------------------------------------------------------
+// Debug output
+//-----------------------------------------------------------------------------
+
+class NullDebug
+{
+public:
+ template <typename T>
+ NullDebug& operator<<(const T&) { return *this; }
+};
+
+inline NullDebug nullDebug() { return NullDebug(); }
+
+#ifdef LOG_ENGINE
+# define ENGINE_DEBUG qDebug()
+#else
+# define ENGINE_DEBUG nullDebug()
+#endif
+
+#ifdef LOG_SPECTRUMANALYSER
+# define SPECTRUMANALYSER_DEBUG qDebug()
+#else
+# define SPECTRUMANALYSER_DEBUG nullDebug()
+#endif
+
+#ifdef LOG_WAVEFORM
+# define WAVEFORM_DEBUG qDebug()
+#else
+# define WAVEFORM_DEBUG nullDebug()
+#endif
+
+#endif // UTILS_H
diff --git a/tests/spectrum/spectrumapp/wavfile.cpp b/tests/spectrum/spectrumapp/wavfile.cpp
new file mode 100644
index 00000000..24482507
--- /dev/null
+++ b/tests/spectrum/spectrumapp/wavfile.cpp
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVis3D module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#include <qendian.h>
+#include <QVector>
+#include <QDebug>
+#include "utils.h"
+#include "wavfile.h"
+
+struct chunk
+{
+ char id[4];
+ quint32 size;
+};
+
+struct RIFFHeader
+{
+ chunk descriptor; // "RIFF"
+ char type[4]; // "WAVE"
+};
+
+struct WAVEHeader
+{
+ chunk descriptor;
+ quint16 audioFormat;
+ quint16 numChannels;
+ quint32 sampleRate;
+ quint32 byteRate;
+ quint16 blockAlign;
+ quint16 bitsPerSample;
+};
+
+struct DATAHeader
+{
+ chunk descriptor;
+};
+
+struct CombinedHeader
+{
+ RIFFHeader riff;
+ WAVEHeader wave;
+};
+
+WavFile::WavFile(QObject *parent)
+ : QFile(parent),
+ m_headerLength(0)
+{
+
+}
+
+bool WavFile::open(const QString &fileName)
+{
+ close();
+ setFileName(fileName);
+ return QFile::open(QIODevice::ReadOnly) && readHeader();
+}
+
+const QAudioFormat &WavFile::fileFormat() const
+{
+ return m_fileFormat;
+}
+
+qint64 WavFile::headerLength() const
+{
+ return m_headerLength;
+}
+
+bool WavFile::readHeader()
+{
+ seek(0);
+ CombinedHeader header;
+ bool result = read(reinterpret_cast<char *>(&header), sizeof(CombinedHeader)) == sizeof(CombinedHeader);
+ if (result) {
+ if ((memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0
+ || memcmp(&header.riff.descriptor.id, "RIFX", 4) == 0)
+ && memcmp(&header.riff.type, "WAVE", 4) == 0
+ && memcmp(&header.wave.descriptor.id, "fmt ", 4) == 0
+ && (header.wave.audioFormat == 1 || header.wave.audioFormat == 0)) {
+
+ // Read off remaining header information
+ DATAHeader dataHeader;
+
+ if (qFromLittleEndian<quint32>(header.wave.descriptor.size) > sizeof(WAVEHeader)) {
+ // Extended data available
+ quint16 extraFormatBytes;
+ if (peek((char*)&extraFormatBytes, sizeof(quint16)) != sizeof(quint16))
+ return false;
+ const qint64 throwAwayBytes = sizeof(quint16) + qFromLittleEndian<quint16>(extraFormatBytes);
+ if (read(throwAwayBytes).size() != throwAwayBytes)
+ return false;
+ }
+
+ if (read((char*)&dataHeader, sizeof(DATAHeader)) != sizeof(DATAHeader))
+ return false;
+
+ // Establish format
+ if (memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0)
+ m_fileFormat.setByteOrder(QAudioFormat::LittleEndian);
+ else
+ m_fileFormat.setByteOrder(QAudioFormat::BigEndian);
+
+ int bps = qFromLittleEndian<quint16>(header.wave.bitsPerSample);
+ m_fileFormat.setChannelCount(qFromLittleEndian<quint16>(header.wave.numChannels));
+ m_fileFormat.setCodec("audio/pcm");
+ m_fileFormat.setSampleRate(qFromLittleEndian<quint32>(header.wave.sampleRate));
+ m_fileFormat.setSampleSize(qFromLittleEndian<quint16>(header.wave.bitsPerSample));
+ m_fileFormat.setSampleType(bps == 8 ? QAudioFormat::UnSignedInt : QAudioFormat::SignedInt);
+ } else {
+ result = false;
+ }
+ }
+ m_headerLength = pos();
+ return result;
+}
diff --git a/tests/spectrum/spectrumapp/wavfile.h b/tests/spectrum/spectrumapp/wavfile.h
new file mode 100644
index 00000000..e408911b
--- /dev/null
+++ b/tests/spectrum/spectrumapp/wavfile.h
@@ -0,0 +1,44 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVis3D module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#ifndef WAVFILE_H
+#define WAVFILE_H
+
+#include <QObject>
+#include <QFile>
+#include <QAudioFormat>
+
+class WavFile : public QFile
+{
+public:
+ WavFile(QObject *parent = 0);
+
+ using QFile::open;
+ bool open(const QString &fileName);
+ const QAudioFormat &fileFormat() const;
+ qint64 headerLength() const;
+
+private:
+ bool readHeader();
+
+private:
+ QAudioFormat m_fileFormat;
+ qint64 m_headerLength;
+};
+
+#endif // WAVFILE_H