summaryrefslogtreecommitdiffstats
path: root/demos/spectrum/app
diff options
context:
space:
mode:
Diffstat (limited to 'demos/spectrum/app')
-rw-r--r--demos/spectrum/app/app.pro121
-rw-r--r--demos/spectrum/app/engine.cpp752
-rw-r--r--demos/spectrum/app/engine.h311
-rw-r--r--demos/spectrum/app/frequencyspectrum.cpp90
-rw-r--r--demos/spectrum/app/frequencyspectrum.h95
-rw-r--r--demos/spectrum/app/images/record.pngbin0 -> 670 bytes
-rw-r--r--demos/spectrum/app/images/settings.pngbin0 -> 3649 bytes
-rw-r--r--demos/spectrum/app/levelmeter.cpp143
-rw-r--r--demos/spectrum/app/levelmeter.h113
-rw-r--r--demos/spectrum/app/main.cpp58
-rw-r--r--demos/spectrum/app/mainwidget.cpp455
-rw-r--r--demos/spectrum/app/mainwidget.h138
-rw-r--r--demos/spectrum/app/progressbar.cpp141
-rw-r--r--demos/spectrum/app/progressbar.h71
-rw-r--r--demos/spectrum/app/settingsdialog.cpp149
-rw-r--r--demos/spectrum/app/settingsdialog.h84
-rw-r--r--demos/spectrum/app/spectrograph.cpp242
-rw-r--r--demos/spectrum/app/spectrograph.h96
-rw-r--r--demos/spectrum/app/spectrum.h141
-rw-r--r--demos/spectrum/app/spectrum.qrc7
-rw-r--r--demos/spectrum/app/spectrum.sh9
-rw-r--r--demos/spectrum/app/spectrumanalyser.cpp280
-rw-r--r--demos/spectrum/app/spectrumanalyser.h190
-rw-r--r--demos/spectrum/app/tonegenerator.cpp92
-rw-r--r--demos/spectrum/app/tonegenerator.h53
-rw-r--r--demos/spectrum/app/tonegeneratordialog.cpp148
-rw-r--r--demos/spectrum/app/tonegeneratordialog.h78
-rw-r--r--demos/spectrum/app/utils.cpp138
-rw-r--r--demos/spectrum/app/utils.h109
-rw-r--r--demos/spectrum/app/waveform.cpp419
-rw-r--r--demos/spectrum/app/waveform.h198
-rw-r--r--demos/spectrum/app/wavfile.cpp247
-rw-r--r--demos/spectrum/app/wavfile.h80
33 files changed, 5248 insertions, 0 deletions
diff --git a/demos/spectrum/app/app.pro b/demos/spectrum/app/app.pro
new file mode 100644
index 0000000..2adb605
--- /dev/null
+++ b/demos/spectrum/app/app.pro
@@ -0,0 +1,121 @@
+include(../spectrum.pri)
+
+TEMPLATE = app
+
+TARGET = spectrum
+unix: !macx: !symbian: TARGET = spectrum.bin
+
+QT += multimedia
+
+SOURCES += main.cpp \
+ engine.cpp \
+ frequencyspectrum.cpp \
+ levelmeter.cpp \
+ mainwidget.cpp \
+ progressbar.cpp \
+ settingsdialog.cpp \
+ spectrograph.cpp \
+ spectrumanalyser.cpp \
+ tonegenerator.cpp \
+ tonegeneratordialog.cpp \
+ utils.cpp \
+ waveform.cpp \
+ wavfile.cpp
+
+HEADERS += engine.h \
+ frequencyspectrum.h \
+ levelmeter.h \
+ mainwidget.h \
+ progressbar.h \
+ settingsdialog.h \
+ spectrograph.h \
+ spectrum.h \
+ spectrumanalyser.h \
+ tonegenerator.h \
+ tonegeneratordialog.h \
+ utils.h \
+ waveform.h \
+ wavfile.h
+
+fftreal_dir = ../3rdparty/fftreal
+
+INCLUDEPATH += $${fftreal_dir}
+
+RESOURCES = spectrum.qrc
+
+symbian {
+ # Platform security capability required to record audio on Symbian
+ TARGET.CAPABILITY += UserEnvironment
+
+ # Provide unique ID for the generated binary, required by Symbian OS
+ TARGET.UID3 = 0xA000E3FA
+}
+
+
+# Dynamic linkage against FFTReal DLL
+!contains(DEFINES, DISABLE_FFT) {
+ symbian {
+ # Must explicitly add the .dll suffix to ensure dynamic linkage
+ LIBS += -lfftreal.dll
+ } else {
+ macx {
+ # Link to fftreal framework
+ LIBS += -F$${fftreal_dir}
+ LIBS += -framework fftreal
+ } else {
+ # Link to dynamic library which is written to ../bin
+ LIBS += -L../bin
+ LIBS += -lfftreal
+ }
+ }
+}
+
+
+# Deployment
+
+symbian {
+ include($$QT_SOURCE_TREE/demos/symbianpkgrules.pri)
+
+ !contains(DEFINES, DISABLE_FFT) {
+ # Include FFTReal DLL in the SIS file
+ fftreal.sources = $${EPOCROOT}epoc32/release/$(PLATFORM)/$(TARGET)/fftreal.dll
+ fftreal.path = !:/sys/bin
+ DEPLOYMENT += fftreal
+ }
+} else {
+ macx {
+ # Specify directory in which to create spectrum.app bundle
+ DESTDIR = ..
+
+ !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 {
+ # Specify directory in which to create spectrum application
+ DESTDIR = ../bin
+
+ unix: !symbian {
+ # On unices other than Mac OSX, we copy a shell script into the bin directory.
+ # This script takes care of correctly setting the LD_LIBRARY_PATH so that
+ # the dynamic library can be located.
+ copy_launch_script.target = copy_launch_script
+ copy_launch_script.commands = \
+ install -m 0555 spectrum.sh ../bin/spectrum
+ QMAKE_EXTRA_TARGETS += copy_launch_script
+ POST_TARGETDEPS += copy_launch_script
+ }
+ }
+}
+
+
diff --git a/demos/spectrum/app/engine.cpp b/demos/spectrum/app/engine.cpp
new file mode 100644
index 0000000..5cdfb6d
--- /dev/null
+++ b/demos/spectrum/app/engine.cpp
@@ -0,0 +1,752 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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_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_dataLength(0)
+ , m_rmsLevel(0.0)
+ , m_peakLevel(0.0)
+ , m_spectrumLengthBytes(0)
+ , m_spectrumAnalyser()
+ , m_spectrumPosition(0)
+ , m_count(0)
+{
+ qRegisterMetaType<FrequencySpectrum>("FrequencySpectrum");
+ 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)
+{
+ bool result = false;
+ m_generateTone = false;
+
+ Q_ASSERT(!fileName.isEmpty());
+ Q_ASSERT(!m_file);
+ m_file = new QFile(fileName, this);
+ m_file->setFileName(fileName);
+ Q_ASSERT(m_file->exists());
+ if (m_file->open(QFile::ReadOnly)) {
+ m_wavFile.readHeader(*m_file);
+ if (isPCMS16LE(m_wavFile.format())) {
+ result = initialize();
+ } else {
+ emit errorMessage(tr("Audio format not supported"),
+ formatToString(m_wavFile.format()));
+ }
+ } else {
+ emit errorMessage(tr("Could not open file"), fileName);
+ }
+
+ delete m_file;
+ m_file = 0;
+
+ return result;
+}
+
+bool Engine::generateTone(const Tone &tone)
+{
+ 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_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()
+{
+ ENGINE_DEBUG << "Engine::initializeRecord";
+ Q_ASSERT(!m_file);
+ m_generateTone = false;
+ m_tone = SweptTone();
+ return initialize();
+}
+
+qint64 Engine::bufferDuration() const
+{
+ return BufferDurationUs;
+}
+
+qint64 Engine::dataDuration() const
+{
+ qint64 result = 0;
+ if (QAudioFormat() != m_format)
+ result = audioDuration(m_format, m_dataLength);
+ return result;
+}
+
+qint64 Engine::audioBufferLength() const
+{
+ qint64 length = 0;
+ if (QAudio::ActiveState == m_state || QAudio::IdleState == m_state) {
+ Q_ASSERT(QAudioFormat() != m_format);
+ switch (m_mode) {
+ case QAudio::AudioInput:
+ length = m_audioInput->bufferSize();
+ break;
+ case QAudio::AudioOutput:
+ length = m_audioOutput->bufferSize();
+ break;
+ }
+ }
+ return length;
+}
+
+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 dataDurationChanged(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;
+ 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(BufferDurationUs, m_audioInput->processedUSecs());
+ setRecordPosition(recordPosition);
+
+ // Calculate level of most recently captured data
+ qint64 levelLength = audioLength(m_format, LevelWindowUs);
+ levelLength = qMin(m_dataLength, levelLength);
+ const qint64 levelPosition = m_dataLength - levelLength;
+ calculateLevel(levelPosition, levelLength);
+
+ // Calculate spectrum of most recently captured data
+ if (m_dataLength >= m_spectrumLengthBytes) {
+ const qint64 spectrumPosition = m_dataLength - m_spectrumLengthBytes;
+ calculateSpectrum(spectrumPosition);
+ }
+ }
+ break;
+ case QAudio::AudioOutput: {
+ const qint64 playPosition =
+ qMin(dataDuration(), m_audioOutput->processedUSecs());
+ setPlayPosition(playPosition);
+
+ qint64 analysisPosition = audioLength(m_format, playPosition);
+
+ // Calculate level of data starting at current playback position
+ const qint64 levelLength = audioLength(m_format, LevelWindowUs);
+ if (analysisPosition + levelLength < m_dataLength)
+ calculateLevel(analysisPosition, levelLength);
+
+ if (analysisPosition + m_spectrumLengthBytes < m_dataLength)
+ calculateSpectrum(analysisPosition);
+
+ if (dataDuration() == playPosition)
+ stopPlayback();
+ }
+ break;
+ }
+}
+
+void Engine::audioStateChanged(QAudio::State state)
+{
+ ENGINE_DEBUG << "Engine::audioStateChanged from" << m_state
+ << "to" << state;
+
+ 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()
+{
+ 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;
+
+ const qint64 duration = audioDuration(m_format, m_dataLength);
+ emit dataDurationChanged(duration);
+ }
+
+ if (m_buffer.size() == m_dataLength)
+ stopRecording();
+}
+
+void Engine::spectrumChanged(const FrequencySpectrum &spectrum)
+{
+ ENGINE_DEBUG << "Engine::spectrumChanged" << "pos" << m_spectrumPosition;
+ const qint64 positionUs = audioDuration(m_format, m_spectrumPosition);
+ const qint64 lengthUs = audioDuration(m_format, m_spectrumLengthBytes);
+ emit spectrumChanged(positionUs, lengthUs, spectrum);
+}
+
+
+//-----------------------------------------------------------------------------
+// Private functions
+//-----------------------------------------------------------------------------
+
+void Engine::reset()
+{
+ stopRecording();
+ stopPlayback();
+ setState(QAudio::AudioInput, QAudio::StoppedState);
+ setFormat(QAudioFormat());
+ delete m_audioInput;
+ m_audioInput = 0;
+ m_audioInputIODevice = 0;
+ setRecordPosition(0);
+ delete m_audioOutput;
+ m_audioOutput = 0;
+ setPlayPosition(0);
+ m_buffer.clear();
+ m_dataLength = 0;
+ m_spectrumPosition = 0;
+ emit dataDurationChanged(0);
+ setLevel(0.0, 0.0, 0);
+}
+
+bool Engine::initialize()
+{
+ bool result = false;
+
+ reset();
+
+ if (selectFormat()) {
+ const qint64 bufferLength = audioLength(m_format, BufferDurationUs);
+ m_buffer.resize(bufferLength);
+ m_buffer.fill(0);
+ emit bufferDurationChanged(BufferDurationUs);
+
+ 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_buffer.size();
+ emit dataDurationChanged(bufferDuration());
+ setRecordPosition(bufferDuration());
+ result = true;
+ } else if (m_file) {
+ const qint64 length = m_wavFile.readData(*m_file, m_buffer, m_format);
+ if (length) {
+ m_dataLength = length;
+ emit dataDurationChanged(dataDuration());
+ setRecordPosition(dataDuration());
+ result = true;
+ }
+ } else {
+ 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);
+ m_spectrumLengthBytes = SpectrumLengthSamples *
+ (m_format.sampleSize() / 8) * m_format.channels();
+ } 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" << "format" << m_format;
+
+ return result;
+}
+
+bool Engine::selectFormat()
+{
+ bool foundSupportedFormat = false;
+
+ if (m_file) {
+ // Header is read from the WAV file; just need to check whether
+ // it is supported by the audio output device
+ QAudioFormat format = m_wavFile.format();
+ if (m_audioOutputDevice.isFormatSupported(m_wavFile.format())) {
+ setFormat(m_wavFile.format());
+ foundSupportedFormat = true;
+ } else {
+ // Try flipping mono <-> stereo
+ const int channels = (format.channels() == 1) ? 2 : 1;
+ format.setChannels(channels);
+ 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_dataLength);
+
+ qreal peakLevel = 0.0;
+
+ qreal sum = 0.0;
+ const char *ptr = m_buffer.constData() + position;
+ 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_spectrumLengthBytes <= m_dataLength);
+ Q_ASSERT(0 == m_spectrumLengthBytes % 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_spectrumLengthBytes
+ << "spectrumAnalyser.isReady" << m_spectrumAnalyser.isReady();
+
+ if(m_spectrumAnalyser.isReady()) {
+ m_spectrumBuffer = QByteArray::fromRawData(m_buffer.constData() + position,
+ m_spectrumLengthBytes);
+ 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;
+ 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
diff --git a/demos/spectrum/app/engine.h b/demos/spectrum/app/engine.h
new file mode 100644
index 0000000..16088b2
--- /dev/null
+++ b/demos/spectrum/app/engine.h
@@ -0,0 +1,311 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+#ifndef ENGINE_H
+#define ENGINE_H
+
+#include "spectrum.h"
+#include "spectrumanalyser.h"
+#include "wavfile.h"
+
+#include <QObject>
+#include <QByteArray>
+#include <QBuffer>
+#include <QVector>
+#include <QtMultimedia/QAudioDeviceInfo>
+#include <QtMultimedia/QAudioFormat>
+
+#ifdef DUMP_CAPTURED_AUDIO
+#define DUMP_DATA
+#endif
+
+#ifdef DUMP_SPECTRUM
+#define DUMP_DATA
+#endif
+
+#ifdef DUMP_DATA
+#include <QDir>
+#endif
+
+class QAudioInput;
+class QAudioOutput;
+class FrequencySpectrum;
+class QFile;
+
+/**
+ * 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:
+ 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 Reference to internal audio buffer
+ * \note This reference is valid for the lifetime of the Engine
+ */
+ const QByteArray& buffer() const { return m_buffer; }
+
+ /**
+ * \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);
+
+ /**
+ * Generate tone
+ */
+ bool generateTone(const Tone &tone);
+
+ /**
+ * Generate tone
+ */
+ bool generateSweptTone(qreal amplitude);
+
+ /**
+ * Initialize for recording
+ */
+ bool initializeRecord();
+
+ /**
+ * Position of the audio input device.
+ * \return Position in microseconds.
+ */
+ 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 microseconds.
+ */
+ qint64 playPosition() const { return m_playPosition; }
+
+ /**
+ * Length of the internal engine buffer.
+ * \return Buffer length in microseconds.
+ */
+ qint64 bufferDuration() const;
+
+ /**
+ * Amount of data held in the buffer.
+ * \return Data duration in microseconds.
+ */
+ qint64 dataDuration() const;
+
+ /**
+ * Returns the size of the underlying audio buffer in bytes.
+ * This should be an approximation of the capture latency.
+ */
+ qint64 audioBufferLength() const;
+
+ /**
+ * 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);
+
+ /**
+ * Informational message for non-modal display
+ */
+ void infoMessage(const QString &message, int durationMs);
+
+ /**
+ * Error message for modal display
+ */
+ void errorMessage(const QString &heading, const QString &detail);
+
+ /**
+ * Format of audio data has changed
+ */
+ void formatChanged(const QAudioFormat &format);
+
+ /**
+ * Length of buffer has changed.
+ * \param duration Duration in microseconds
+ */
+ void bufferDurationChanged(qint64 duration);
+
+ /**
+ * Amount of data in buffer has changed.
+ * \param duration Duration of data in microseconds
+ */
+ void dataDurationChanged(qint64 duration);
+
+ /**
+ * Position of the audio input device has changed.
+ * \param position Position in microseconds
+ */
+ void recordPositionChanged(qint64 position);
+
+ /**
+ * Position of the audio output device has changed.
+ * \param position Position in microseconds
+ */
+ 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 analysed
+ */
+ void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples);
+
+ /**
+ * Spectrum has changed.
+ * \param position Position of start of window in microseconds
+ * \param length Length of window in microseconds
+ * \param spectrum Resulting frequency spectrum
+ */
+ void spectrumChanged(qint64 position, qint64 length, const FrequencySpectrum &spectrum);
+
+private slots:
+ void audioNotify();
+ void audioStateChanged(QAudio::State state);
+ void audioDataReady();
+ void spectrumChanged(const FrequencySpectrum &spectrum);
+
+private:
+ 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);
+
+#ifdef DUMP_DATA
+ void createOutputDir();
+ QString outputPath() const { return m_outputDir.path(); }
+#endif
+
+#ifdef DUMP_CAPTURED_AUDIO
+ void dumpData();
+#endif
+
+private:
+ QAudio::Mode m_mode;
+ QAudio::State m_state;
+
+ bool m_generateTone;
+ SweptTone m_tone;
+
+ QFile* m_file;
+ WavFile m_wavFile;
+
+ 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_dataLength;
+
+ qreal m_rmsLevel;
+ qreal m_peakLevel;
+
+ int m_spectrumLengthBytes;
+ QByteArray m_spectrumBuffer;
+ SpectrumAnalyser m_spectrumAnalyser;
+ qint64 m_spectrumPosition;
+
+ int m_count;
+
+#ifdef DUMP_DATA
+ QDir m_outputDir;
+#endif
+
+};
+
+#endif // ENGINE_H
diff --git a/demos/spectrum/app/frequencyspectrum.cpp b/demos/spectrum/app/frequencyspectrum.cpp
new file mode 100644
index 0000000..6ab80c2
--- /dev/null
+++ b/demos/spectrum/app/frequencyspectrum.cpp
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#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/demos/spectrum/app/frequencyspectrum.h b/demos/spectrum/app/frequencyspectrum.h
new file mode 100644
index 0000000..0dd814e
--- /dev/null
+++ b/demos/spectrum/app/frequencyspectrum.h
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+#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/demos/spectrum/app/images/record.png b/demos/spectrum/app/images/record.png
new file mode 100644
index 0000000..e7493aa
--- /dev/null
+++ b/demos/spectrum/app/images/record.png
Binary files differ
diff --git a/demos/spectrum/app/images/settings.png b/demos/spectrum/app/images/settings.png
new file mode 100644
index 0000000..12179dc
--- /dev/null
+++ b/demos/spectrum/app/images/settings.png
Binary files differ
diff --git a/demos/spectrum/app/levelmeter.cpp b/demos/spectrum/app/levelmeter.cpp
new file mode 100644
index 0000000..39e43c9
--- /dev/null
+++ b/demos/spectrum/app/levelmeter.cpp
@@ -0,0 +1,143 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "levelmeter.h"
+
+#include <math.h>
+
+#include <QPainter>
+#include <QTimer>
+#include <QDebug>
+
+
+// Constants
+const int RedrawInterval = 100; // ms
+const qreal PeakDecayRate = 0.001;
+const int PeakHoldLevelDuration = 2000; // ms
+
+
+LevelMeter::LevelMeter(QWidget *parent)
+ : QWidget(parent)
+ , m_rmsLevel(0.0)
+ , m_peakLevel(0.0)
+ , m_decayedPeakLevel(0.0)
+ , m_peakDecayRate(PeakDecayRate)
+ , m_peakHoldLevel(0.0)
+ , m_redrawTimer(new QTimer(this))
+ , m_rmsColor(Qt::red)
+ , m_peakColor(255, 200, 200, 255)
+{
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+ setMinimumWidth(30);
+
+ connect(m_redrawTimer, SIGNAL(timeout()), this, SLOT(redrawTimerExpired()));
+ m_redrawTimer->start(RedrawInterval);
+}
+
+LevelMeter::~LevelMeter()
+{
+
+}
+
+void LevelMeter::reset()
+{
+ m_rmsLevel = 0.0;
+ m_peakLevel = 0.0;
+ update();
+}
+
+void LevelMeter::levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples)
+{
+ // Smooth the RMS signal
+ const qreal smooth = pow(qreal(0.9), static_cast<qreal>(numSamples) / 256); // TODO: remove this magic number
+ m_rmsLevel = (m_rmsLevel * smooth) + (rmsLevel * (1.0 - smooth));
+
+ if (peakLevel > m_decayedPeakLevel) {
+ m_peakLevel = peakLevel;
+ m_decayedPeakLevel = peakLevel;
+ m_peakLevelChanged.start();
+ }
+
+ if (peakLevel > m_peakHoldLevel) {
+ m_peakHoldLevel = peakLevel;
+ m_peakHoldLevelChanged.start();
+ }
+
+ update();
+}
+
+void LevelMeter::redrawTimerExpired()
+{
+ // Decay the peak signal
+ const int elapsedMs = m_peakLevelChanged.elapsed();
+ const qreal decayAmount = m_peakDecayRate * elapsedMs;
+ if (decayAmount < m_peakLevel)
+ m_decayedPeakLevel = m_peakLevel - decayAmount;
+ else
+ m_decayedPeakLevel = 0.0;
+
+ // Check whether to clear the peak hold level
+ if (m_peakHoldLevelChanged.elapsed() > PeakHoldLevelDuration)
+ m_peakHoldLevel = 0.0;
+
+ update();
+}
+
+void LevelMeter::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event)
+
+ QPainter painter(this);
+ painter.fillRect(rect(), Qt::black);
+
+ QRect bar = rect();
+
+ bar.setTop(rect().top() + (1.0 - m_peakHoldLevel) * rect().height());
+ bar.setBottom(bar.top() + 5);
+ painter.fillRect(bar, m_rmsColor);
+ bar.setBottom(rect().bottom());
+
+ bar.setTop(rect().top() + (1.0 - m_decayedPeakLevel) * rect().height());
+ painter.fillRect(bar, m_peakColor);
+
+ bar.setTop(rect().top() + (1.0 - m_rmsLevel) * rect().height());
+ painter.fillRect(bar, m_rmsColor);
+}
diff --git a/demos/spectrum/app/levelmeter.h b/demos/spectrum/app/levelmeter.h
new file mode 100644
index 0000000..ab8340b
--- /dev/null
+++ b/demos/spectrum/app/levelmeter.h
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+#ifndef LEVELMETER_H
+#define LEVELMETER_H
+
+#include <QTime>
+#include <QWidget>
+
+/**
+ * Widget which displays a vertical audio level meter, indicating the
+ * RMS and peak levels of the window of audio samples most recently analysed
+ * by the Engine.
+ */
+class LevelMeter : public QWidget {
+ Q_OBJECT
+public:
+ LevelMeter(QWidget *parent = 0);
+ ~LevelMeter();
+
+ void paintEvent(QPaintEvent *event);
+
+public slots:
+ void reset();
+ void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples);
+
+private slots:
+ void redrawTimerExpired();
+
+private:
+ /**
+ * Height of RMS level bar.
+ * Range 0.0 - 1.0.
+ */
+ qreal m_rmsLevel;
+
+ /**
+ * Most recent peak level.
+ * Range 0.0 - 1.0.
+ */
+ qreal m_peakLevel;
+
+ /**
+ * Height of peak level bar.
+ * This is calculated by decaying m_peakLevel depending on the
+ * elapsed time since m_peakLevelChanged, and the value of m_decayRate.
+ */
+ qreal m_decayedPeakLevel;
+
+ /**
+ * Time at which m_peakLevel was last changed.
+ */
+ QTime m_peakLevelChanged;
+
+ /**
+ * Rate at which peak level bar decays.
+ * Expressed in level units / millisecond.
+ */
+ qreal m_peakDecayRate;
+
+ /**
+ * High watermark of peak level.
+ * Range 0.0 - 1.0.
+ */
+ qreal m_peakHoldLevel;
+
+ /**
+ * Time at which m_peakHoldLevel was last changed.
+ */
+ QTime m_peakHoldLevelChanged;
+
+ QTimer *m_redrawTimer;
+
+ QColor m_rmsColor;
+ QColor m_peakColor;
+
+};
+
+#endif // LEVELMETER_H
diff --git a/demos/spectrum/app/main.cpp b/demos/spectrum/app/main.cpp
new file mode 100644
index 0000000..6e2b6fc
--- /dev/null
+++ b/demos/spectrum/app/main.cpp
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtGui/QApplication>
+#include "mainwidget.h"
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+ app.setApplicationName("QtMultimedia spectrum analyser");
+ MainWidget w;
+
+#ifdef Q_OS_SYMBIAN
+ w.showMaximized();
+#else
+ w.show();
+#endif
+
+ return app.exec();
+}
diff --git a/demos/spectrum/app/mainwidget.cpp b/demos/spectrum/app/mainwidget.cpp
new file mode 100644
index 0000000..3b7c306
--- /dev/null
+++ b/demos/spectrum/app/mainwidget.cpp
@@ -0,0 +1,455 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "engine.h"
+#include "levelmeter.h"
+#include "mainwidget.h"
+#include "waveform.h"
+#include "progressbar.h"
+#include "settingsdialog.h"
+#include "spectrograph.h"
+#include "tonegeneratordialog.h"
+#include "utils.h"
+
+#include <QLabel>
+#include <QPushButton>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QStyle>
+#include <QMenu>
+#include <QFileDialog>
+#include <QTimerEvent>
+#include <QMessageBox>
+
+const int NullTimerId = -1;
+
+MainWidget::MainWidget(QWidget *parent)
+ : QWidget(parent)
+ , m_mode(NoMode)
+ , m_engine(new Engine(this))
+#ifndef DISABLE_WAVEFORM
+ , m_waveform(new Waveform(m_engine->buffer(), this))
+#endif
+ , m_progressBar(new ProgressBar(this))
+ , m_spectrograph(new Spectrograph(this))
+ , m_levelMeter(new LevelMeter(this))
+ , m_modeButton(new QPushButton(this))
+ , m_recordButton(new QPushButton(this))
+ , m_pauseButton(new QPushButton(this))
+ , m_playButton(new QPushButton(this))
+ , m_settingsButton(new QPushButton(this))
+ , m_infoMessage(new QLabel(tr("Select a mode to begin"), this))
+ , m_infoMessageTimerId(NullTimerId)
+ , m_settingsDialog(new SettingsDialog(
+ m_engine->availableAudioInputDevices(),
+ m_engine->availableAudioOutputDevices(),
+ this))
+ , m_toneGeneratorDialog(new ToneGeneratorDialog(this))
+ , m_modeMenu(new QMenu(this))
+ , m_loadFileAction(0)
+ , m_generateToneAction(0)
+ , m_recordAction(0)
+{
+ m_spectrograph->setParams(SpectrumNumBands, SpectrumLowFreq, SpectrumHighFreq);
+
+ createUi();
+ connectUi();
+}
+
+MainWidget::~MainWidget()
+{
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Public slots
+//-----------------------------------------------------------------------------
+
+void MainWidget::stateChanged(QAudio::Mode mode, QAudio::State state)
+{
+ Q_UNUSED(mode);
+
+ updateButtonStates();
+
+ if (QAudio::ActiveState != state && QAudio::SuspendedState != state) {
+ m_levelMeter->reset();
+ m_spectrograph->reset();
+ }
+}
+
+void MainWidget::formatChanged(const QAudioFormat &format)
+{
+ infoMessage(formatToString(format), NullMessageTimeout);
+
+#ifndef DISABLE_WAVEFORM
+ if (QAudioFormat() != format) {
+ m_waveform->initialize(format, WaveformTileLength,
+ WaveformWindowDuration);
+ }
+#endif
+}
+
+void MainWidget::spectrumChanged(qint64 position, qint64 length,
+ const FrequencySpectrum &spectrum)
+{
+ m_progressBar->windowChanged(position, length);
+ m_spectrograph->spectrumChanged(spectrum);
+}
+
+void MainWidget::infoMessage(const QString &message, int timeoutMs)
+{
+ m_infoMessage->setText(message);
+
+ if (NullTimerId != m_infoMessageTimerId) {
+ killTimer(m_infoMessageTimerId);
+ m_infoMessageTimerId = NullTimerId;
+ }
+
+ if (NullMessageTimeout != timeoutMs)
+ m_infoMessageTimerId = startTimer(timeoutMs);
+}
+
+void MainWidget::errorMessage(const QString &heading, const QString &detail)
+{
+#ifdef Q_OS_SYMBIAN
+ const QString message = heading + "\n" + detail;
+ QMessageBox::warning(this, "", message, QMessageBox::Close);
+#else
+ QMessageBox::warning(this, heading, detail, QMessageBox::Close);
+#endif
+}
+
+void MainWidget::timerEvent(QTimerEvent *event)
+{
+ Q_ASSERT(event->timerId() == m_infoMessageTimerId);
+ Q_UNUSED(event) // suppress warnings in release builds
+ killTimer(m_infoMessageTimerId);
+ m_infoMessageTimerId = NullTimerId;
+ m_infoMessage->setText("");
+}
+
+void MainWidget::positionChanged(qint64 positionUs)
+{
+#ifndef DISABLE_WAVEFORM
+ qint64 positionBytes = audioLength(m_engine->format(), positionUs);
+ m_waveform->positionChanged(positionBytes);
+#else
+ Q_UNUSED(positionUs)
+#endif
+}
+
+void MainWidget::bufferDurationChanged(qint64 durationUs)
+{
+ m_progressBar->bufferDurationChanged(durationUs);
+}
+
+
+//-----------------------------------------------------------------------------
+// Private slots
+//-----------------------------------------------------------------------------
+
+void MainWidget::dataDurationChanged(qint64 duration)
+{
+#ifndef DISABLE_WAVEFORM
+ const qint64 dataLength = audioLength(m_engine->format(), duration);
+ m_waveform->dataLengthChanged(dataLength);
+#else
+ Q_UNUSED(duration)
+#endif
+
+ updateButtonStates();
+}
+
+void MainWidget::showFileDialog()
+{
+ reset();
+ const QString dir;
+ const QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open WAV file"), dir, "*.wav");
+ if (fileNames.count()) {
+ setMode(LoadFileMode);
+ m_engine->loadFile(fileNames.front());
+ updateButtonStates();
+ }
+}
+
+void MainWidget::showSettingsDialog()
+{
+ reset();
+ m_settingsDialog->exec();
+ if (m_settingsDialog->result() == QDialog::Accepted) {
+ m_engine->setAudioInputDevice(m_settingsDialog->inputDevice());
+ m_engine->setAudioOutputDevice(m_settingsDialog->outputDevice());
+ m_engine->setWindowFunction(m_settingsDialog->windowFunction());
+ }
+}
+
+void MainWidget::showToneGeneratorDialog()
+{
+ reset();
+ m_toneGeneratorDialog->exec();
+ if (m_toneGeneratorDialog->result() == QDialog::Accepted) {
+ setMode(GenerateToneMode);
+ const qreal amplitude = m_toneGeneratorDialog->amplitude();
+ if (m_toneGeneratorDialog->isFrequencySweepEnabled()) {
+ m_engine->generateSweptTone(amplitude);
+ } else {
+ const qreal frequency = m_toneGeneratorDialog->frequency();
+ const Tone tone(frequency, amplitude);
+ m_engine->generateTone(tone);
+ updateButtonStates();
+ }
+ }
+}
+
+void MainWidget::initializeRecord()
+{
+ reset();
+ setMode(RecordMode);
+ if (m_engine->initializeRecord())
+ updateButtonStates();
+}
+
+
+//-----------------------------------------------------------------------------
+// Private functions
+//-----------------------------------------------------------------------------
+
+void MainWidget::createUi()
+{
+ createMenus();
+
+ setWindowTitle(tr("Spectrum Analyser"));
+
+ QVBoxLayout *windowLayout = new QVBoxLayout(this);
+
+ m_infoMessage->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ m_infoMessage->setAlignment(Qt::AlignHCenter);
+ windowLayout->addWidget(m_infoMessage);
+
+#ifdef SUPERIMPOSE_PROGRESS_ON_WAVEFORM
+ QScopedPointer<QHBoxLayout> waveformLayout(new QHBoxLayout);
+ waveformLayout->addWidget(m_progressBar);
+ m_progressBar->setMinimumHeight(m_waveform->minimumHeight());
+ waveformLayout->setMargin(0);
+ m_waveform->setLayout(waveformLayout.data());
+ waveformLayout.take();
+ windowLayout->addWidget(m_waveform);
+#else
+#ifndef DISABLE_WAVEFORM
+ windowLayout->addWidget(m_waveform);
+#endif // DISABLE_WAVEFORM
+ windowLayout->addWidget(m_progressBar);
+#endif // SUPERIMPOSE_PROGRESS_ON_WAVEFORM
+
+ // Spectrograph and level meter
+
+ QScopedPointer<QHBoxLayout> analysisLayout(new QHBoxLayout);
+ analysisLayout->addWidget(m_spectrograph);
+ analysisLayout->addWidget(m_levelMeter);
+ windowLayout->addLayout(analysisLayout.data());
+ analysisLayout.take();
+
+ // Button panel
+
+ const QSize buttonSize(30, 30);
+
+ m_modeButton->setText(tr("Mode"));
+
+ m_recordIcon = QIcon(":/images/record.png");
+ m_recordButton->setIcon(m_recordIcon);
+ m_recordButton->setEnabled(false);
+ m_recordButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ m_recordButton->setMinimumSize(buttonSize);
+
+ m_pauseIcon = style()->standardIcon(QStyle::SP_MediaPause);
+ m_pauseButton->setIcon(m_pauseIcon);
+ m_pauseButton->setEnabled(false);
+ m_pauseButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ m_pauseButton->setMinimumSize(buttonSize);
+
+ m_playIcon = style()->standardIcon(QStyle::SP_MediaPlay);
+ m_playButton->setIcon(m_playIcon);
+ m_playButton->setEnabled(false);
+ m_playButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ m_playButton->setMinimumSize(buttonSize);
+
+ m_settingsIcon = QIcon(":/images/settings.png");
+ m_settingsButton->setIcon(m_settingsIcon);
+ m_settingsButton->setEnabled(true);
+ m_settingsButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ m_settingsButton->setMinimumSize(buttonSize);
+
+ QScopedPointer<QHBoxLayout> buttonPanelLayout(new QHBoxLayout);
+ buttonPanelLayout->addStretch();
+ buttonPanelLayout->addWidget(m_modeButton);
+ buttonPanelLayout->addWidget(m_recordButton);
+ buttonPanelLayout->addWidget(m_pauseButton);
+ buttonPanelLayout->addWidget(m_playButton);
+ buttonPanelLayout->addWidget(m_settingsButton);
+
+ QWidget *buttonPanel = new QWidget(this);
+ buttonPanel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ buttonPanel->setLayout(buttonPanelLayout.data());
+ buttonPanelLayout.take(); // ownership transferred to buttonPanel
+
+ QScopedPointer<QHBoxLayout> bottomPaneLayout(new QHBoxLayout);
+ bottomPaneLayout->addWidget(buttonPanel);
+ windowLayout->addLayout(bottomPaneLayout.data());
+ bottomPaneLayout.take(); // ownership transferred to windowLayout
+
+ // Apply layout
+
+ setLayout(windowLayout);
+}
+
+void MainWidget::connectUi()
+{
+ CHECKED_CONNECT(m_recordButton, SIGNAL(clicked()),
+ m_engine, SLOT(startRecording()));
+
+ CHECKED_CONNECT(m_pauseButton, SIGNAL(clicked()),
+ m_engine, SLOT(suspend()));
+
+ CHECKED_CONNECT(m_playButton, SIGNAL(clicked()),
+ m_engine, SLOT(startPlayback()));
+
+ CHECKED_CONNECT(m_settingsButton, SIGNAL(clicked()),
+ this, SLOT(showSettingsDialog()));
+
+ CHECKED_CONNECT(m_engine, SIGNAL(stateChanged(QAudio::Mode,QAudio::State)),
+ this, SLOT(stateChanged(QAudio::Mode,QAudio::State)));
+
+ CHECKED_CONNECT(m_engine, SIGNAL(formatChanged(const QAudioFormat &)),
+ this, SLOT(formatChanged(const QAudioFormat &)));
+
+ m_progressBar->bufferDurationChanged(m_engine->bufferDuration());
+
+ CHECKED_CONNECT(m_engine, SIGNAL(bufferDurationChanged(qint64)),
+ this, SLOT(bufferDurationChanged(qint64)));
+
+ CHECKED_CONNECT(m_engine, SIGNAL(dataDurationChanged(qint64)),
+ this, SLOT(dataDurationChanged(qint64)));
+
+ CHECKED_CONNECT(m_engine, SIGNAL(recordPositionChanged(qint64)),
+ m_progressBar, SLOT(recordPositionChanged(qint64)));
+
+ CHECKED_CONNECT(m_engine, SIGNAL(playPositionChanged(qint64)),
+ m_progressBar, SLOT(playPositionChanged(qint64)));
+
+ CHECKED_CONNECT(m_engine, SIGNAL(recordPositionChanged(qint64)),
+ this, SLOT(positionChanged(qint64)));
+
+ CHECKED_CONNECT(m_engine, SIGNAL(playPositionChanged(qint64)),
+ this, SLOT(positionChanged(qint64)));
+
+ CHECKED_CONNECT(m_engine, SIGNAL(levelChanged(qreal, qreal, int)),
+ m_levelMeter, SLOT(levelChanged(qreal, qreal, int)));
+
+ CHECKED_CONNECT(m_engine, SIGNAL(spectrumChanged(qint64, qint64, const FrequencySpectrum &)),
+ this, SLOT(spectrumChanged(qint64, qint64, const FrequencySpectrum &)));
+
+ CHECKED_CONNECT(m_engine, SIGNAL(infoMessage(QString, int)),
+ this, SLOT(infoMessage(QString, int)));
+
+ CHECKED_CONNECT(m_engine, SIGNAL(errorMessage(QString, QString)),
+ this, SLOT(errorMessage(QString, QString)));
+
+ CHECKED_CONNECT(m_spectrograph, SIGNAL(infoMessage(QString, int)),
+ this, SLOT(infoMessage(QString, int)));
+}
+
+void MainWidget::createMenus()
+{
+ m_modeButton->setMenu(m_modeMenu);
+
+ m_generateToneAction = m_modeMenu->addAction(tr("Play generated tone"));
+ m_recordAction = m_modeMenu->addAction(tr("Record and play back"));
+ m_loadFileAction = m_modeMenu->addAction(tr("Play file"));
+
+ m_loadFileAction->setCheckable(true);
+ m_generateToneAction->setCheckable(true);
+ m_recordAction->setCheckable(true);
+
+ connect(m_loadFileAction, SIGNAL(triggered(bool)), this, SLOT(showFileDialog()));
+ connect(m_generateToneAction, SIGNAL(triggered(bool)), this, SLOT(showToneGeneratorDialog()));
+ connect(m_recordAction, SIGNAL(triggered(bool)), this, SLOT(initializeRecord()));
+}
+
+void MainWidget::updateButtonStates()
+{
+ const bool recordEnabled = ((QAudio::AudioOutput == m_engine->mode() ||
+ (QAudio::ActiveState != m_engine->state() &&
+ QAudio::IdleState != m_engine->state())) &&
+ RecordMode == m_mode);
+ m_recordButton->setEnabled(recordEnabled);
+
+ const bool pauseEnabled = (QAudio::ActiveState == m_engine->state() ||
+ QAudio::IdleState == m_engine->state());
+ m_pauseButton->setEnabled(pauseEnabled);
+
+ const bool playEnabled = (m_engine->dataDuration() &&
+ (QAudio::AudioOutput != m_engine->mode() ||
+ (QAudio::ActiveState != m_engine->state() &&
+ QAudio::IdleState != m_engine->state())));
+ m_playButton->setEnabled(playEnabled);
+}
+
+void MainWidget::reset()
+{
+#ifndef DISABLE_WAVEFORM
+ m_waveform->reset();
+#endif
+ m_engine->reset();
+ m_levelMeter->reset();
+ m_spectrograph->reset();
+ m_progressBar->reset();
+}
+
+void MainWidget::setMode(Mode mode)
+{
+
+ m_mode = mode;
+ m_loadFileAction->setChecked(LoadFileMode == mode);
+ m_generateToneAction->setChecked(GenerateToneMode == mode);
+ m_recordAction->setChecked(RecordMode == mode);
+}
+
diff --git a/demos/spectrum/app/mainwidget.h b/demos/spectrum/app/mainwidget.h
new file mode 100644
index 0000000..846b97a
--- /dev/null
+++ b/demos/spectrum/app/mainwidget.h
@@ -0,0 +1,138 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+#ifndef MAINWIDGET_H
+#define MAINWIDGET_H
+
+#include <QWidget>
+#include <QIcon>
+#include <QtMultimedia/qaudio.h>
+
+class Engine;
+class FrequencySpectrum;
+class ProgressBar;
+class Spectrograph;
+class Waveform;
+class LevelMeter;
+class SettingsDialog;
+class ToneGeneratorDialog;
+class QAudioFormat;
+class QLabel;
+class QPushButton;
+class QMenu;
+class QAction;
+
+/**
+ * Main application widget, responsible for connecting the various UI
+ * elements to the Engine.
+ */
+class MainWidget : public QWidget {
+ Q_OBJECT
+public:
+ MainWidget(QWidget *parent = 0);
+ ~MainWidget();
+
+ // QObject
+ void timerEvent(QTimerEvent *event);
+
+public slots:
+ void stateChanged(QAudio::Mode mode, QAudio::State state);
+ void formatChanged(const QAudioFormat &format);
+ void spectrumChanged(qint64 position, qint64 length,
+ const FrequencySpectrum &spectrum);
+ void infoMessage(const QString &message, int timeoutMs);
+ void errorMessage(const QString &heading, const QString &detail);
+ void positionChanged(qint64 position);
+ void bufferDurationChanged(qint64 duration);
+
+private slots:
+ void showFileDialog();
+ void showSettingsDialog();
+ void showToneGeneratorDialog();
+ void initializeRecord();
+ void dataDurationChanged(qint64 duration);
+
+private:
+ void createUi();
+ void createMenus();
+ void connectUi();
+ void updateButtonStates();
+ void reset();
+
+ enum Mode {
+ NoMode,
+ RecordMode,
+ GenerateToneMode,
+ LoadFileMode
+ };
+
+ void setMode(Mode mode);
+
+private:
+ Mode m_mode;
+
+ Engine* m_engine;
+
+ Waveform* m_waveform;
+ ProgressBar* m_progressBar;
+ Spectrograph* m_spectrograph;
+ LevelMeter* m_levelMeter;
+
+ QPushButton* m_modeButton;
+ QPushButton* m_recordButton;
+ QIcon m_recordIcon;
+ QPushButton* m_pauseButton;
+ QIcon m_pauseIcon;
+ QPushButton* m_playButton;
+ QIcon m_playIcon;
+ QPushButton* m_settingsButton;
+ QIcon m_settingsIcon;
+
+ QLabel* m_infoMessage;
+ int m_infoMessageTimerId;
+
+ SettingsDialog* m_settingsDialog;
+ ToneGeneratorDialog* m_toneGeneratorDialog;
+
+ QMenu* m_modeMenu;
+ QAction* m_loadFileAction;
+ QAction* m_generateToneAction;
+ QAction* m_recordAction;
+
+};
+
+#endif // MAINWIDGET_H
diff --git a/demos/spectrum/app/progressbar.cpp b/demos/spectrum/app/progressbar.cpp
new file mode 100644
index 0000000..256acf0
--- /dev/null
+++ b/demos/spectrum/app/progressbar.cpp
@@ -0,0 +1,141 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "progressbar.h"
+#include "spectrum.h"
+#include <QPainter>
+
+ProgressBar::ProgressBar(QWidget *parent)
+ : QWidget(parent)
+ , m_bufferDuration(0)
+ , m_recordPosition(0)
+ , m_playPosition(0)
+ , m_windowPosition(0)
+ , m_windowLength(0)
+{
+ setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ setMinimumHeight(30);
+#ifdef SUPERIMPOSE_PROGRESS_ON_WAVEFORM
+ setAutoFillBackground(false);
+#endif
+}
+
+ProgressBar::~ProgressBar()
+{
+
+}
+
+void ProgressBar::reset()
+{
+ m_bufferDuration = 0;
+ m_recordPosition = 0;
+ m_playPosition = 0;
+ m_windowPosition = 0;
+ m_windowLength = 0;
+ update();
+}
+
+void ProgressBar::paintEvent(QPaintEvent * /*event*/)
+{
+ QPainter painter(this);
+
+ QColor bufferColor(0, 0, 255);
+ QColor windowColor(0, 255, 0);
+
+#ifdef SUPERIMPOSE_PROGRESS_ON_WAVEFORM
+ bufferColor.setAlphaF(0.5);
+ windowColor.setAlphaF(0.5);
+#else
+ painter.fillRect(rect(), Qt::black);
+#endif
+
+ if (m_bufferDuration) {
+ QRect bar = rect();
+ const qreal play = qreal(m_playPosition) / m_bufferDuration;
+ bar.setLeft(rect().left() + play * rect().width());
+ const qreal record = qreal(m_recordPosition) / m_bufferDuration;
+ bar.setRight(rect().left() + record * rect().width());
+ painter.fillRect(bar, bufferColor);
+
+ QRect window = rect();
+ const qreal windowLeft = qreal(m_windowPosition) / m_bufferDuration;
+ window.setLeft(rect().left() + windowLeft * rect().width());
+ const qreal windowWidth = qreal(m_windowLength) / m_bufferDuration;
+ window.setWidth(windowWidth * rect().width());
+ painter.fillRect(window, windowColor);
+ }
+}
+
+void ProgressBar::bufferDurationChanged(qint64 bufferSize)
+{
+ m_bufferDuration = bufferSize;
+ m_recordPosition = 0;
+ m_playPosition = 0;
+ m_windowPosition = 0;
+ m_windowLength = 0;
+ repaint();
+}
+
+void ProgressBar::recordPositionChanged(qint64 recordPosition)
+{
+ Q_ASSERT(recordPosition >= 0);
+ Q_ASSERT(recordPosition <= m_bufferDuration);
+ m_recordPosition = recordPosition;
+ repaint();
+}
+
+void ProgressBar::playPositionChanged(qint64 playPosition)
+{
+ Q_ASSERT(playPosition >= 0);
+ Q_ASSERT(playPosition <= m_bufferDuration);
+ m_playPosition = playPosition;
+ repaint();
+}
+
+void ProgressBar::windowChanged(qint64 position, qint64 length)
+{
+ Q_ASSERT(position >= 0);
+ Q_ASSERT(position <= m_bufferDuration);
+ Q_ASSERT(position + length <= m_bufferDuration);
+ m_windowPosition = position;
+ m_windowLength = length;
+ repaint();
+}
diff --git a/demos/spectrum/app/progressbar.h b/demos/spectrum/app/progressbar.h
new file mode 100644
index 0000000..de9e5a9
--- /dev/null
+++ b/demos/spectrum/app/progressbar.h
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+#ifndef PROGRESSBAR_H
+#define PROGRESSBAR_H
+
+#include <QWidget>
+
+/**
+ * Widget which displays a the current fill state of the Engine's internal
+ * buffer, and the current play/record position within that buffer.
+ */
+class ProgressBar : public QWidget {
+ Q_OBJECT
+public:
+ ProgressBar(QWidget *parent = 0);
+ ~ProgressBar();
+
+ void reset();
+ void paintEvent(QPaintEvent *event);
+
+public slots:
+ void bufferDurationChanged(qint64 bufferSize);
+ void recordPositionChanged(qint64 recordPosition);
+ void playPositionChanged(qint64 playPosition);
+ void windowChanged(qint64 position, qint64 length);
+
+private:
+ qint64 m_bufferDuration;
+ qint64 m_recordPosition;
+ qint64 m_playPosition;
+ qint64 m_windowPosition;
+ qint64 m_windowLength;
+
+};
+
+#endif // PROGRESSBAR_H
diff --git a/demos/spectrum/app/settingsdialog.cpp b/demos/spectrum/app/settingsdialog.cpp
new file mode 100644
index 0000000..204b43f
--- /dev/null
+++ b/demos/spectrum/app/settingsdialog.cpp
@@ -0,0 +1,149 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "settingsdialog.h"
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QLabel>
+#include <QPushButton>
+#include <QVBoxLayout>
+#include <QCheckBox>
+#include <QSlider>
+#include <QSpinBox>
+
+SettingsDialog::SettingsDialog(
+ const QList<QAudioDeviceInfo> &availableInputDevices,
+ const QList<QAudioDeviceInfo> &availableOutputDevices,
+ QWidget *parent)
+ : QDialog(parent)
+ , m_windowFunction(DefaultWindowFunction)
+ , m_inputDeviceComboBox(new QComboBox(this))
+ , m_outputDeviceComboBox(new QComboBox(this))
+ , m_windowFunctionComboBox(new QComboBox(this))
+{
+ QVBoxLayout *dialogLayout = new QVBoxLayout(this);
+
+ // Populate combo boxes
+
+ QAudioDeviceInfo device;
+ foreach (device, availableInputDevices)
+ m_inputDeviceComboBox->addItem(device.deviceName(),
+ qVariantFromValue(device));
+ foreach (device, availableOutputDevices)
+ m_outputDeviceComboBox->addItem(device.deviceName(),
+ qVariantFromValue(device));
+
+ m_windowFunctionComboBox->addItem(tr("None"), qVariantFromValue(int(NoWindow)));
+ m_windowFunctionComboBox->addItem("Hann", qVariantFromValue(int(HannWindow)));
+ m_windowFunctionComboBox->setCurrentIndex(m_windowFunction);
+
+ // Initialize default devices
+ if (!availableInputDevices.empty())
+ m_inputDevice = availableInputDevices.front();
+ if (!availableOutputDevices.empty())
+ m_outputDevice = availableOutputDevices.front();
+
+ // Add widgets to layout
+
+ QScopedPointer<QHBoxLayout> inputDeviceLayout(new QHBoxLayout);
+ QLabel *inputDeviceLabel = new QLabel(tr("Input device"), this);
+ inputDeviceLayout->addWidget(inputDeviceLabel);
+ inputDeviceLayout->addWidget(m_inputDeviceComboBox);
+ dialogLayout->addLayout(inputDeviceLayout.data());
+ inputDeviceLayout.take(); // ownership transferred to dialogLayout
+
+ QScopedPointer<QHBoxLayout> outputDeviceLayout(new QHBoxLayout);
+ QLabel *outputDeviceLabel = new QLabel(tr("Output device"), this);
+ outputDeviceLayout->addWidget(outputDeviceLabel);
+ outputDeviceLayout->addWidget(m_outputDeviceComboBox);
+ dialogLayout->addLayout(outputDeviceLayout.data());
+ outputDeviceLayout.take(); // ownership transferred to dialogLayout
+
+ QScopedPointer<QHBoxLayout> windowFunctionLayout(new QHBoxLayout);
+ QLabel *windowFunctionLabel = new QLabel(tr("Window function"), this);
+ windowFunctionLayout->addWidget(windowFunctionLabel);
+ windowFunctionLayout->addWidget(m_windowFunctionComboBox);
+ dialogLayout->addLayout(windowFunctionLayout.data());
+ windowFunctionLayout.take(); // ownership transferred to dialogLayout
+
+ // Connect
+ CHECKED_CONNECT(m_inputDeviceComboBox, SIGNAL(activated(int)),
+ this, SLOT(inputDeviceChanged(int)));
+ CHECKED_CONNECT(m_outputDeviceComboBox, SIGNAL(activated(int)),
+ this, SLOT(outputDeviceChanged(int)));
+ CHECKED_CONNECT(m_windowFunctionComboBox, SIGNAL(activated(int)),
+ this, SLOT(windowFunctionChanged(int)));
+
+ // Add standard buttons to layout
+ QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
+ buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ dialogLayout->addWidget(buttonBox);
+
+ // Connect standard buttons
+ CHECKED_CONNECT(buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()),
+ this, SLOT(accept()));
+ CHECKED_CONNECT(buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()),
+ this, SLOT(reject()));
+
+ setLayout(dialogLayout);
+}
+
+SettingsDialog::~SettingsDialog()
+{
+
+}
+
+void SettingsDialog::windowFunctionChanged(int index)
+{
+ m_windowFunction = static_cast<WindowFunction>(
+ m_windowFunctionComboBox->itemData(index).value<int>());
+}
+
+void SettingsDialog::inputDeviceChanged(int index)
+{
+ m_inputDevice = m_inputDeviceComboBox->itemData(index).value<QAudioDeviceInfo>();
+}
+
+void SettingsDialog::outputDeviceChanged(int index)
+{
+ m_outputDevice = m_outputDeviceComboBox->itemData(index).value<QAudioDeviceInfo>();
+}
+
diff --git a/demos/spectrum/app/settingsdialog.h b/demos/spectrum/app/settingsdialog.h
new file mode 100644
index 0000000..7215a50
--- /dev/null
+++ b/demos/spectrum/app/settingsdialog.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+#ifndef SETTINGSDIALOG_H
+#define SETTINGSDIALOG_H
+
+#include "spectrum.h"
+#include <QDialog>
+#include <QtMultimedia/QAudioDeviceInfo>
+
+class QComboBox;
+class QCheckBox;
+class QSlider;
+class QSpinBox;
+class QGridLayout;
+
+/**
+ * Dialog used to control settings such as the audio input / output device
+ * and the windowing function.
+ */
+class SettingsDialog : public QDialog {
+ Q_OBJECT
+public:
+ SettingsDialog(const QList<QAudioDeviceInfo> &availableInputDevices,
+ const QList<QAudioDeviceInfo> &availableOutputDevices,
+ QWidget *parent = 0);
+ ~SettingsDialog();
+
+ WindowFunction windowFunction() const { return m_windowFunction; }
+ const QAudioDeviceInfo& inputDevice() const { return m_inputDevice; }
+ const QAudioDeviceInfo& outputDevice() const { return m_outputDevice; }
+
+private slots:
+ void windowFunctionChanged(int index);
+ void inputDeviceChanged(int index);
+ void outputDeviceChanged(int index);
+
+private:
+ WindowFunction m_windowFunction;
+ QAudioDeviceInfo m_inputDevice;
+ QAudioDeviceInfo m_outputDevice;
+
+ QComboBox* m_inputDeviceComboBox;
+ QComboBox* m_outputDeviceComboBox;
+
+ QComboBox* m_windowFunctionComboBox;
+
+};
+
+#endif // SETTINGSDIALOG_H
diff --git a/demos/spectrum/app/spectrograph.cpp b/demos/spectrum/app/spectrograph.cpp
new file mode 100644
index 0000000..1fcf434
--- /dev/null
+++ b/demos/spectrum/app/spectrograph.cpp
@@ -0,0 +1,242 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "spectrograph.h"
+#include <QPainter>
+#include <QMouseEvent>
+#include <QDebug>
+#include <QTimerEvent>
+
+const int NullTimerId = -1;
+const int NullIndex = -1;
+const int BarSelectionInterval = 2000;
+
+Spectrograph::Spectrograph(QWidget *parent)
+ : QWidget(parent)
+ , m_barSelected(NullIndex)
+ , m_timerId(NullTimerId)
+ , m_lowFreq(0.0)
+ , m_highFreq(0.0)
+{
+ setMinimumHeight(100);
+}
+
+Spectrograph::~Spectrograph()
+{
+
+}
+
+void Spectrograph::setParams(int numBars, qreal lowFreq, qreal highFreq)
+{
+ Q_ASSERT(numBars > 0);
+ Q_ASSERT(highFreq > lowFreq);
+ m_bars.resize(numBars);
+ m_lowFreq = lowFreq;
+ m_highFreq = highFreq;
+ updateBars();
+}
+
+void Spectrograph::timerEvent(QTimerEvent *event)
+{
+ Q_ASSERT(event->timerId() == m_timerId);
+ Q_UNUSED(event) // suppress warnings in release builds
+ killTimer(m_timerId);
+ m_timerId = NullTimerId;
+ m_barSelected = NullIndex;
+ update();
+}
+
+void Spectrograph::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event)
+
+ QPainter painter(this);
+ painter.fillRect(rect(), Qt::black);
+
+ const int numBars = m_bars.count();
+
+ // Highlight region of selected bar
+ if (m_barSelected != NullIndex && numBars) {
+ QRect regionRect = rect();
+ regionRect.setLeft(m_barSelected * rect().width() / numBars);
+ regionRect.setWidth(rect().width() / numBars);
+ QColor regionColor(202, 202, 64);
+ painter.setBrush(Qt::DiagCrossPattern);
+ painter.fillRect(regionRect, regionColor);
+ painter.setBrush(Qt::NoBrush);
+ }
+
+ QColor barColor(51, 204, 102);
+ QColor clipColor(255, 255, 0);
+
+ // Draw the outline
+ const QColor gridColor = barColor.darker();
+ QPen gridPen(gridColor);
+ painter.setPen(gridPen);
+ painter.drawLine(rect().topLeft(), rect().topRight());
+ painter.drawLine(rect().topRight(), rect().bottomRight());
+ painter.drawLine(rect().bottomRight(), rect().bottomLeft());
+ painter.drawLine(rect().bottomLeft(), rect().topLeft());
+
+ QVector<qreal> dashes;
+ dashes << 2 << 2;
+ gridPen.setDashPattern(dashes);
+ painter.setPen(gridPen);
+
+ // Draw vertical lines between bars
+ if (numBars) {
+ const int numHorizontalSections = numBars;
+ QLine line(rect().topLeft(), rect().bottomLeft());
+ for (int i=1; i<numHorizontalSections; ++i) {
+ line.translate(rect().width()/numHorizontalSections, 0);
+ painter.drawLine(line);
+ }
+ }
+
+ // Draw horizontal lines
+ const int numVerticalSections = 10;
+ QLine line(rect().topLeft(), rect().topRight());
+ for (int i=1; i<numVerticalSections; ++i) {
+ line.translate(0, rect().height()/numVerticalSections);
+ painter.drawLine(line);
+ }
+
+ barColor = barColor.lighter();
+ barColor.setAlphaF(0.75);
+ clipColor.setAlphaF(0.75);
+
+ // Draw the bars
+ if (numBars) {
+ // Calculate width of bars and gaps
+ const int widgetWidth = rect().width();
+ const int barPlusGapWidth = widgetWidth / numBars;
+ const int barWidth = 0.8 * barPlusGapWidth;
+ const int gapWidth = barPlusGapWidth - barWidth;
+ const int paddingWidth = widgetWidth - numBars * (barWidth + gapWidth);
+ const int leftPaddingWidth = (paddingWidth + gapWidth) / 2;
+ const int barHeight = rect().height() - 2 * gapWidth;
+
+ for (int i=0; i<numBars; ++i) {
+ const qreal value = m_bars[i].value;
+ Q_ASSERT(value >= 0.0 && value <= 1.0);
+ QRect bar = rect();
+ bar.setLeft(rect().left() + leftPaddingWidth + (i * (gapWidth + barWidth)));
+ bar.setWidth(barWidth);
+ bar.setTop(rect().top() + gapWidth + (1.0 - value) * barHeight);
+ bar.setBottom(rect().bottom() - gapWidth);
+
+ QColor color = barColor;
+ if (m_bars[i].clipped)
+ color = clipColor;
+
+ painter.fillRect(bar, color);
+ }
+ }
+}
+
+void Spectrograph::mousePressEvent(QMouseEvent *event)
+{
+ const QPoint pos = event->pos();
+ const int index = m_bars.count() * (pos.x() - rect().left()) / rect().width();
+ selectBar(index);
+}
+
+void Spectrograph::reset()
+{
+ m_spectrum.reset();
+ spectrumChanged(m_spectrum);
+}
+
+void Spectrograph::spectrumChanged(const FrequencySpectrum &spectrum)
+{
+ m_spectrum = spectrum;
+ updateBars();
+}
+
+int Spectrograph::barIndex(qreal frequency) const
+{
+ Q_ASSERT(frequency >= m_lowFreq && frequency < m_highFreq);
+ const qreal bandWidth = (m_highFreq - m_lowFreq) / m_bars.count();
+ const int index = (frequency - m_lowFreq) / bandWidth;
+ if(index <0 || index >= m_bars.count())
+ Q_ASSERT(false);
+ return index;
+}
+
+QPair<qreal, qreal> Spectrograph::barRange(int index) const
+{
+ Q_ASSERT(index >= 0 && index < m_bars.count());
+ const qreal bandWidth = (m_highFreq - m_lowFreq) / m_bars.count();
+ return QPair<qreal, qreal>(index * bandWidth, (index+1) * bandWidth);
+}
+
+void Spectrograph::updateBars()
+{
+ m_bars.fill(Bar());
+ FrequencySpectrum::const_iterator i = m_spectrum.begin();
+ const FrequencySpectrum::const_iterator end = m_spectrum.end();
+ for ( ; i != end; ++i) {
+ const FrequencySpectrum::Element e = *i;
+ if (e.frequency >= m_lowFreq && e.frequency < m_highFreq) {
+ Bar &bar = m_bars[barIndex(e.frequency)];
+ bar.value = qMax(bar.value, e.amplitude);
+ bar.clipped |= e.clipped;
+ }
+ }
+ update();
+}
+
+void Spectrograph::selectBar(int index) {
+ const QPair<qreal, qreal> frequencyRange = barRange(index);
+ const QString message = QString("%1 - %2 Hz")
+ .arg(frequencyRange.first)
+ .arg(frequencyRange.second);
+ emit infoMessage(message, BarSelectionInterval);
+
+ if (NullTimerId != m_timerId)
+ killTimer(m_timerId);
+ m_timerId = startTimer(BarSelectionInterval);
+
+ m_barSelected = index;
+ update();
+}
+
+
diff --git a/demos/spectrum/app/spectrograph.h b/demos/spectrum/app/spectrograph.h
new file mode 100644
index 0000000..6bfef33
--- /dev/null
+++ b/demos/spectrum/app/spectrograph.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+#ifndef SPECTROGRAPH_H
+#define SPECTROGRAPH_H
+
+#include <QWidget>
+#include "frequencyspectrum.h"
+
+class QMouseEvent;
+
+/**
+ * Widget which displays a spectrograph showing the frequency spectrum
+ * of the window of audio samples most recently analysed by the Engine.
+ */
+class Spectrograph : public QWidget {
+ Q_OBJECT
+public:
+ Spectrograph(QWidget *parent = 0);
+ ~Spectrograph();
+
+ void setParams(int numBars, qreal lowFreq, qreal highFreq);
+
+ // QObject
+ void timerEvent(QTimerEvent *event);
+
+ // QWidget
+ void paintEvent(QPaintEvent *event);
+ void mousePressEvent(QMouseEvent *event);
+
+signals:
+ void infoMessage(const QString &message, int intervalMs);
+
+public slots:
+ void reset();
+ void spectrumChanged(const FrequencySpectrum &spectrum);
+
+private:
+ int barIndex(qreal frequency) const;
+ QPair<qreal, qreal> barRange(int barIndex) const;
+ void updateBars();
+
+ void selectBar(int index);
+
+private:
+ struct Bar {
+ Bar() : value(0.0), clipped(false) { }
+ qreal value;
+ bool clipped;
+ };
+
+ QVector<Bar> m_bars;
+ int m_barSelected;
+ int m_timerId;
+ qreal m_lowFreq;
+ qreal m_highFreq;
+ FrequencySpectrum m_spectrum;
+
+
+};
+
+#endif // SPECTROGRAPH_H
diff --git a/demos/spectrum/app/spectrum.h b/demos/spectrum/app/spectrum.h
new file mode 100644
index 0000000..6cfe29f
--- /dev/null
+++ b/demos/spectrum/app/spectrum.h
@@ -0,0 +1,141 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+#ifndef SPECTRUM_H
+#define SPECTRUM_H
+
+#include <QtCore/qglobal.h>
+#include "utils.h"
+#include "fftreal_wrapper.h" // For FFTLengthPowerOfTwo
+
+//-----------------------------------------------------------------------------
+// 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 = 10;
+
+// 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;
+
+// Length of waveform tiles in bytes
+// Ideally, these would match the QAudio*::bufferSize(), but that isn't
+// available until some time after QAudio*::start() has been called, and we
+// need this value in order to initialize the waveform display.
+// We therefore just choose a sensible value.
+const int WaveformTileLength = 4096;
+
+// 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__);
+
+// Handle some dependencies between macros defined in the .pro file
+
+#ifdef DISABLE_WAVEFORM
+#undef SUPERIMPOSE_PROGRESS_ON_WAVEFORM
+#endif
+
+#endif // SPECTRUM_H
+
diff --git a/demos/spectrum/app/spectrum.qrc b/demos/spectrum/app/spectrum.qrc
new file mode 100644
index 0000000..6100479
--- /dev/null
+++ b/demos/spectrum/app/spectrum.qrc
@@ -0,0 +1,7 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>images/record.png</file>
+ <file>images/settings.png</file>
+</qresource>
+</RCC>
+
diff --git a/demos/spectrum/app/spectrum.sh b/demos/spectrum/app/spectrum.sh
new file mode 100644
index 0000000..75ad6c2
--- /dev/null
+++ b/demos/spectrum/app/spectrum.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# Shell script for launching spectrum application on Unix systems other than Mac OSX
+
+bindir=`dirname "$0"`
+LD_LIBRARY_PATH="${bindir}:${LD_LIBRARY_PATH}"
+export LD_LIBRARY_PATH
+exec "${bindir}/spectrum.bin" ${1+"$@"}
+
diff --git a/demos/spectrum/app/spectrumanalyser.cpp b/demos/spectrum/app/spectrumanalyser.cpp
new file mode 100644
index 0000000..54d3f5e
--- /dev/null
+++ b/demos/spectrum/app/spectrumanalyser.cpp
@@ -0,0 +1,280 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "spectrumanalyser.h"
+#include "utils.h"
+
+#include <QtCore/qmath.h>
+#include <QtCore/qmetatype.h>
+#include <QtMultimedia/QAudioFormat>
+#include <QThread>
+
+#include "fftreal_wrapper.h"
+
+SpectrumAnalyserThread::SpectrumAnalyserThread(QObject *parent)
+ : QObject(parent)
+#ifndef DISABLE_FFT
+ , m_fft(new FFTRealWrapper)
+#endif
+ , 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(m_thread);
+ m_thread->start();
+#endif
+ calculateWindow();
+}
+
+SpectrumAnalyserThread::~SpectrumAnalyserThread()
+{
+#ifndef DISABLE_FFT
+ delete m_fft;
+#endif
+}
+
+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)
+{
+#ifndef DISABLE_FFT
+ 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());
+
+ // Analyse 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;
+ }
+#endif
+
+ emit calculationComplete(m_spectrum);
+}
+
+
+//=============================================================================
+// SpectrumAnalyser
+//=============================================================================
+
+SpectrumAnalyser::SpectrumAnalyser(QObject *parent)
+ : QObject(parent)
+ , m_thread(new SpectrumAnalyserThread(this))
+ , m_state(Idle)
+#ifdef DUMP_SPECTRUMANALYSER
+ , m_count(0)
+#endif
+{
+ CHECKED_CONNECT(m_thread, SIGNAL(calculationComplete(FrequencySpectrum)),
+ this, SLOT(calculationComplete(FrequencySpectrum)));
+}
+
+SpectrumAnalyser::~SpectrumAnalyser()
+{
+
+}
+
+#ifdef DUMP_SPECTRUMANALYSER
+void SpectrumAnalyser::setOutputPath(const QString &outputDir)
+{
+ m_outputDir.setPath(outputDir);
+ m_textFile.setFileName(m_outputDir.filePath("spectrum.txt"));
+ m_textFile.open(QIODevice::WriteOnly | QIODevice::Text);
+ m_textStream.setDevice(&m_textFile);
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// 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.channels() / 8;
+
+#ifdef DUMP_SPECTRUMANALYSER
+ m_count++;
+ const QString pcmFileName = m_outputDir.filePath(QString("spectrum_%1.pcm").arg(m_count, 4, 10, QChar('0')));
+ QFile pcmFile(pcmFileName);
+ pcmFile.open(QIODevice::WriteOnly);
+ const int bufferLength = m_numSamples * bytesPerSample;
+ pcmFile.write(buffer, bufferLength);
+
+ m_textStream << "TimeDomain " << m_count << "\n";
+ const qint16* input = reinterpret_cast<const qint16*>(buffer);
+ for (int i=0; i<m_numSamples; ++i) {
+ m_textStream << i << "\t" << *input << "\n";
+ input += format.channels();
+ }
+#endif
+
+ 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.frequency()),
+ Q_ARG(int, bytesPerSample));
+ Q_ASSERT(b);
+ Q_UNUSED(b) // suppress warnings in release builds
+
+#ifdef DUMP_SPECTRUMANALYSER
+ m_textStream << "FrequencySpectrum " << m_count << "\n";
+ FrequencySpectrum::const_iterator x = m_spectrum.begin();
+ for (int i=0; i<m_numSamples; ++i, ++x)
+ m_textStream << i << "\t"
+ << x->frequency << "\t"
+ << x->amplitude<< "\t"
+ << x->phase << "\n";
+#endif
+ }
+}
+
+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/demos/spectrum/app/spectrumanalyser.h b/demos/spectrum/app/spectrumanalyser.h
new file mode 100644
index 0000000..caeb1c1
--- /dev/null
+++ b/demos/spectrum/app/spectrumanalyser.h
@@ -0,0 +1,190 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+#ifndef SPECTRUMANALYSER_H
+#define SPECTRUMANALYSER_H
+
+#include <QByteArray>
+#include <QObject>
+#include <QVector>
+
+#ifdef DUMP_SPECTRUMANALYSER
+#include <QDir>
+#include <QFile>
+#include <QTextStream>
+#endif
+
+#include "frequencyspectrum.h"
+#include "spectrum.h"
+
+#ifndef DISABLE_FFT
+#include "FFTRealFixLenParam.h"
+#endif
+
+class QAudioFormat;
+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:
+#ifndef DISABLE_FFT
+ FFTRealWrapper* m_fft;
+#endif
+
+ const int m_numSamples;
+
+ WindowFunction m_windowFunction;
+
+#ifdef DISABLE_FFT
+ typedef qreal DataType;
+#else
+ typedef FFTRealFixLenParam::DataType DataType;
+#endif
+ 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();
+
+#ifdef DUMP_SPECTRUMANALYSER
+ void setOutputPath(const QString &outputPath);
+#endif
+
+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;
+
+#ifdef DUMP_SPECTRUMANALYSER
+ QDir m_outputDir;
+ int m_count;
+ QFile m_textFile;
+ QTextStream m_textStream;
+#endif
+};
+
+#endif // SPECTRUMANALYSER_H
+
diff --git a/demos/spectrum/app/tonegenerator.cpp b/demos/spectrum/app/tonegenerator.cpp
new file mode 100644
index 0000000..6458a7d
--- /dev/null
+++ b/demos/spectrum/app/tonegenerator.cpp
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "spectrum.h"
+#include "utils.h"
+#include <QByteArray>
+#include <QtMultimedia/QAudioFormat>
+#include <QtCore/qmath.h>
+#include <QtCore/qendian.h>
+
+void generateTone(const SweptTone &tone, const QAudioFormat &format, QByteArray &buffer)
+{
+ Q_ASSERT(isPCMS16LE(format));
+
+ const int channelBytes = format.sampleSize() / 8;
+ const int sampleBytes = format.channels() * channelBytes;
+ int length = buffer.size();
+ const int numSamples = buffer.size() / sampleBytes;
+
+ Q_ASSERT(length % sampleBytes == 0);
+ Q_UNUSED(sampleBytes) // suppress warning in release builds
+
+ unsigned char *ptr = reinterpret_cast<unsigned char *>(buffer.data());
+
+ qreal phase = 0.0;
+
+ const qreal d = 2 * M_PI / format.frequency();
+
+ // We can't generate a zero-frequency sine wave
+ const qreal startFreq = tone.startFreq ? tone.startFreq : 1.0;
+
+ // Amount by which phase increases on each sample
+ qreal phaseStep = d * startFreq;
+
+ // Amount by which phaseStep increases on each sample
+ // If this is non-zero, the output is a frequency-swept tone
+ const qreal phaseStepStep = d * (tone.endFreq - startFreq) / numSamples;
+
+ while (length) {
+ const qreal x = tone.amplitude * qSin(phase);
+ const qint16 value = realToPcm(x);
+ for (int i=0; i<format.channels(); ++i) {
+ qToLittleEndian<qint16>(value, ptr);
+ ptr += channelBytes;
+ length -= channelBytes;
+ }
+
+ phase += phaseStep;
+ while (phase > 2 * M_PI)
+ phase -= 2 * M_PI;
+ phaseStep += phaseStepStep;
+ }
+}
+
diff --git a/demos/spectrum/app/tonegenerator.h b/demos/spectrum/app/tonegenerator.h
new file mode 100644
index 0000000..419f7e4
--- /dev/null
+++ b/demos/spectrum/app/tonegenerator.h
@@ -0,0 +1,53 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+#ifndef TONEGENERATOR_H
+#define TONEGENERATOR_H
+
+#include <QtCore/qglobal.h>
+#include "spectrum.h"
+
+class QAudioFormat;
+class QByteArray;
+
+/**
+ * Generate a sine wave
+ */
+void generateTone(const SweptTone &tone, const QAudioFormat &format, QByteArray &buffer);
+
+#endif // TONEGENERATOR_H
+
diff --git a/demos/spectrum/app/tonegeneratordialog.cpp b/demos/spectrum/app/tonegeneratordialog.cpp
new file mode 100644
index 0000000..06e453c
--- /dev/null
+++ b/demos/spectrum/app/tonegeneratordialog.cpp
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "tonegeneratordialog.h"
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QLabel>
+#include <QPushButton>
+#include <QVBoxLayout>
+#include <QCheckBox>
+#include <QSlider>
+#include <QSpinBox>
+
+const int ToneGeneratorFreqMin = 1;
+const int ToneGeneratorFreqMax = 1000;
+const int ToneGeneratorFreqDefault = 440;
+const int ToneGeneratorAmplitudeDefault = 75;
+
+ToneGeneratorDialog::ToneGeneratorDialog(QWidget *parent)
+ : QDialog(parent)
+ , m_toneGeneratorSweepCheckBox(new QCheckBox(tr("Frequency sweep"), this))
+ , m_frequencySweepEnabled(true)
+ , m_toneGeneratorControl(new QWidget(this))
+ , m_toneGeneratorFrequencyControl(new QWidget(this))
+ , m_frequencySlider(new QSlider(Qt::Horizontal, this))
+ , m_frequencySpinBox(new QSpinBox(this))
+ , m_frequency(ToneGeneratorFreqDefault)
+ , m_amplitudeSlider(new QSlider(Qt::Horizontal, this))
+{
+ QVBoxLayout *dialogLayout = new QVBoxLayout(this);
+
+ m_toneGeneratorSweepCheckBox->setChecked(true);
+
+ // Configure tone generator controls
+ m_frequencySlider->setRange(ToneGeneratorFreqMin, ToneGeneratorFreqMax);
+ m_frequencySlider->setValue(ToneGeneratorFreqDefault);
+ m_frequencySpinBox->setRange(ToneGeneratorFreqMin, ToneGeneratorFreqMax);
+ m_frequencySpinBox->setValue(ToneGeneratorFreqDefault);
+ m_amplitudeSlider->setRange(0, 100);
+ m_amplitudeSlider->setValue(ToneGeneratorAmplitudeDefault);
+
+ // Add widgets to layout
+
+ QScopedPointer<QGridLayout> frequencyControlLayout(new QGridLayout);
+ QLabel *frequencyLabel = new QLabel(tr("Frequency (Hz)"), this);
+ frequencyControlLayout->addWidget(frequencyLabel, 0, 0, 2, 1);
+ frequencyControlLayout->addWidget(m_frequencySlider, 0, 1);
+ frequencyControlLayout->addWidget(m_frequencySpinBox, 1, 1);
+ m_toneGeneratorFrequencyControl->setLayout(frequencyControlLayout.data());
+ frequencyControlLayout.take(); // ownership transferred to m_toneGeneratorFrequencyControl
+ m_toneGeneratorFrequencyControl->setEnabled(false);
+
+ QScopedPointer<QGridLayout> toneGeneratorLayout(new QGridLayout);
+ QLabel *amplitudeLabel = new QLabel(tr("Amplitude"), this);
+ toneGeneratorLayout->addWidget(m_toneGeneratorSweepCheckBox, 0, 1);
+ toneGeneratorLayout->addWidget(m_toneGeneratorFrequencyControl, 1, 0, 1, 2);
+ toneGeneratorLayout->addWidget(amplitudeLabel, 2, 0);
+ toneGeneratorLayout->addWidget(m_amplitudeSlider, 2, 1);
+ m_toneGeneratorControl->setLayout(toneGeneratorLayout.data());
+ m_toneGeneratorControl->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ dialogLayout->addWidget(m_toneGeneratorControl);
+ toneGeneratorLayout.take(); // ownership transferred
+
+ // Connect
+ CHECKED_CONNECT(m_toneGeneratorSweepCheckBox, SIGNAL(toggled(bool)),
+ this, SLOT(frequencySweepEnabled(bool)));
+ CHECKED_CONNECT(m_frequencySlider, SIGNAL(valueChanged(int)),
+ m_frequencySpinBox, SLOT(setValue(int)));
+ CHECKED_CONNECT(m_frequencySpinBox, SIGNAL(valueChanged(int)),
+ m_frequencySlider, SLOT(setValue(int)));
+
+ // Add standard buttons to layout
+ QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
+ buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ dialogLayout->addWidget(buttonBox);
+
+ // Connect standard buttons
+ CHECKED_CONNECT(buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()),
+ this, SLOT(accept()));
+ CHECKED_CONNECT(buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()),
+ this, SLOT(reject()));
+
+ setLayout(dialogLayout);
+}
+
+ToneGeneratorDialog::~ToneGeneratorDialog()
+{
+
+}
+
+bool ToneGeneratorDialog::isFrequencySweepEnabled() const
+{
+ return m_toneGeneratorSweepCheckBox->isChecked();
+}
+
+qreal ToneGeneratorDialog::frequency() const
+{
+ return qreal(m_frequencySlider->value());
+}
+
+qreal ToneGeneratorDialog::amplitude() const
+{
+ return qreal(m_amplitudeSlider->value()) / 100.0;
+}
+
+void ToneGeneratorDialog::frequencySweepEnabled(bool enabled)
+{
+ m_frequencySweepEnabled = enabled;
+ m_toneGeneratorFrequencyControl->setEnabled(!enabled);
+}
diff --git a/demos/spectrum/app/tonegeneratordialog.h b/demos/spectrum/app/tonegeneratordialog.h
new file mode 100644
index 0000000..35d69c2
--- /dev/null
+++ b/demos/spectrum/app/tonegeneratordialog.h
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+#ifndef TONEGENERATORDIALOG_H
+#define TONEGENERATORDIALOG_H
+
+#include "spectrum.h"
+#include <QDialog>
+#include <QtMultimedia/QAudioDeviceInfo>
+
+class QCheckBox;
+class QSlider;
+class QSpinBox;
+class QGridLayout;
+
+/**
+ * Dialog which controls the parameters of the tone generator.
+ */
+class ToneGeneratorDialog : public QDialog {
+ Q_OBJECT
+public:
+ ToneGeneratorDialog(QWidget *parent = 0);
+ ~ToneGeneratorDialog();
+
+ bool isFrequencySweepEnabled() const;
+ qreal frequency() const;
+ qreal amplitude() const;
+
+private slots:
+ void frequencySweepEnabled(bool enabled);
+
+private:
+ QCheckBox* m_toneGeneratorSweepCheckBox;
+ bool m_frequencySweepEnabled;
+ QWidget* m_toneGeneratorControl;
+ QWidget* m_toneGeneratorFrequencyControl;
+ QSlider* m_frequencySlider;
+ QSpinBox* m_frequencySpinBox;
+ qreal m_frequency;
+ QSlider* m_amplitudeSlider;
+
+};
+
+#endif // TONEGENERATORDIALOG_H
diff --git a/demos/spectrum/app/utils.cpp b/demos/spectrum/app/utils.cpp
new file mode 100644
index 0000000..97dc6e3
--- /dev/null
+++ b/demos/spectrum/app/utils.cpp
@@ -0,0 +1,138 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtMultimedia/QAudioFormat>
+#include "utils.h"
+
+qint64 audioDuration(const QAudioFormat &format, qint64 bytes)
+{
+ return (bytes * 1000000) /
+ (format.frequency() * format.channels() * (format.sampleSize() / 8));
+}
+
+qint64 audioLength(const QAudioFormat &format, qint64 microSeconds)
+{
+ return (format.frequency() * format.channels() * (format.sampleSize() / 8))
+ * microSeconds / 1000000;
+}
+
+qreal nyquistFrequency(const QAudioFormat &format)
+{
+ return format.frequency() / 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.channels());
+ switch (format.channels()) {
+ case 1:
+ formatChannels = "mono";
+ break;
+ case 2:
+ formatChannels = "stereo";
+ break;
+ }
+
+ result = QString("%1 Hz %2 bit %3 %4 %5")
+ .arg(format.frequency())
+ .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/demos/spectrum/app/utils.h b/demos/spectrum/app/utils.h
new file mode 100644
index 0000000..83467cd
--- /dev/null
+++ b/demos/spectrum/app/utils.h
@@ -0,0 +1,109 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <QtCore/qglobal.h>
+#include <QDebug>
+
+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/demos/spectrum/app/waveform.cpp b/demos/spectrum/app/waveform.cpp
new file mode 100644
index 0000000..3fc4f76
--- /dev/null
+++ b/demos/spectrum/app/waveform.cpp
@@ -0,0 +1,419 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "waveform.h"
+#include "utils.h"
+#include <QPainter>
+#include <QResizeEvent>
+#include <QDebug>
+
+
+Waveform::Waveform(const QByteArray &buffer, QWidget *parent)
+ : QWidget(parent)
+ , m_buffer(buffer)
+ , m_dataLength(0)
+ , m_position(0)
+ , m_active(false)
+ , m_tileLength(0)
+ , m_tileArrayStart(0)
+ , m_windowPosition(0)
+ , m_windowLength(0)
+{
+ setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ setMinimumHeight(50);
+}
+
+Waveform::~Waveform()
+{
+ deletePixmaps();
+}
+
+void Waveform::paintEvent(QPaintEvent * /*event*/)
+{
+ QPainter painter(this);
+
+ painter.fillRect(rect(), Qt::black);
+
+ if (m_active) {
+ WAVEFORM_DEBUG << "Waveform::paintEvent"
+ << "windowPosition" << m_windowPosition
+ << "windowLength" << m_windowLength;
+ qint64 pos = m_windowPosition;
+ const qint64 windowEnd = m_windowPosition + m_windowLength;
+ int destLeft = 0;
+ int destRight = 0;
+ while (pos < windowEnd) {
+ const TilePoint point = tilePoint(pos);
+ WAVEFORM_DEBUG << "Waveform::paintEvent" << "pos" << pos
+ << "tileIndex" << point.index
+ << "positionOffset" << point.positionOffset
+ << "pixelOffset" << point.pixelOffset;
+
+ if (point.index != NullIndex) {
+ const Tile &tile = m_tiles[point.index];
+ if (tile.painted) {
+ const qint64 sectionLength = qMin((m_tileLength - point.positionOffset),
+ (windowEnd - pos));
+ Q_ASSERT(sectionLength > 0);
+
+ const int sourceRight = tilePixelOffset(point.positionOffset + sectionLength);
+ destRight = windowPixelOffset(pos - m_windowPosition + sectionLength);
+
+ QRect destRect = rect();
+ destRect.setLeft(destLeft);
+ destRect.setRight(destRight);
+
+ QRect sourceRect(QPoint(), m_pixmapSize);
+ sourceRect.setLeft(point.pixelOffset);
+ sourceRect.setRight(sourceRight);
+
+ WAVEFORM_DEBUG << "Waveform::paintEvent" << "tileIndex" << point.index
+ << "source" << point.pixelOffset << sourceRight
+ << "dest" << destLeft << destRight;
+
+ painter.drawPixmap(destRect, *tile.pixmap, sourceRect);
+
+ destLeft = destRight;
+
+ if (point.index < m_tiles.count()) {
+ pos = tilePosition(point.index + 1);
+ WAVEFORM_DEBUG << "Waveform::paintEvent" << "pos ->" << pos;
+ } else {
+ // Reached end of tile array
+ WAVEFORM_DEBUG << "Waveform::paintEvent" << "reached end of tile array";
+ break;
+ }
+ } else {
+ // Passed last tile which is painted
+ WAVEFORM_DEBUG << "Waveform::paintEvent" << "tile" << point.index << "not painted";
+ break;
+ }
+ } else {
+ // pos is past end of tile array
+ WAVEFORM_DEBUG << "Waveform::paintEvent" << "pos" << pos << "past end of tile array";
+ break;
+ }
+ }
+
+ WAVEFORM_DEBUG << "Waveform::paintEvent" << "final pos" << pos << "final x" << destRight;
+ }
+}
+
+void Waveform::resizeEvent(QResizeEvent *event)
+{
+ if (event->size() != event->oldSize())
+ createPixmaps(event->size());
+}
+
+void Waveform::initialize(const QAudioFormat &format, qint64 audioBufferSize, qint64 windowDurationUs)
+{
+ WAVEFORM_DEBUG << "Waveform::initialize"
+ << "audioBufferSize" << audioBufferSize
+ << "m_buffer.size()" << m_buffer.size()
+ << "windowDurationUs" << windowDurationUs;
+
+ reset();
+
+ m_format = format;
+
+ // Calculate tile size
+ m_tileLength = audioBufferSize;
+
+ // Calculate window size
+ m_windowLength = audioLength(m_format, windowDurationUs);
+
+ // Calculate number of tiles required
+ int nTiles;
+ if (m_tileLength > m_windowLength) {
+ nTiles = 2;
+ } else {
+ nTiles = m_windowLength / m_tileLength + 1;
+ if (m_windowLength % m_tileLength)
+ ++nTiles;
+ }
+
+ WAVEFORM_DEBUG << "Waveform::initialize"
+ << "tileLength" << m_tileLength
+ << "windowLength" << m_windowLength
+ << "nTiles" << nTiles;
+
+ m_pixmaps.fill(0, nTiles);
+ m_tiles.resize(nTiles);
+
+ createPixmaps(rect().size());
+
+ m_active = true;
+}
+
+void Waveform::reset()
+{
+ WAVEFORM_DEBUG << "Waveform::reset";
+
+ m_dataLength = 0;
+ m_position = 0;
+ m_format = QAudioFormat();
+ m_active = false;
+ deletePixmaps();
+ m_tiles.clear();
+ m_tileLength = 0;
+ m_tileArrayStart = 0;
+ m_windowPosition = 0;
+ m_windowLength = 0;
+}
+
+void Waveform::dataLengthChanged(qint64 length)
+{
+ WAVEFORM_DEBUG << "Waveform::dataLengthChanged" << length;
+ const qint64 oldLength = m_dataLength;
+ m_dataLength = length;
+
+ if (m_active) {
+ if (m_dataLength < oldLength)
+ positionChanged(m_dataLength);
+ else
+ paintTiles();
+ }
+}
+
+void Waveform::positionChanged(qint64 position)
+{
+ WAVEFORM_DEBUG << "Waveform::positionChanged" << position;
+
+ if (position + m_windowLength > m_dataLength)
+ position = m_dataLength - m_windowLength;
+
+ m_position = position;
+
+ setWindowPosition(position);
+}
+
+void Waveform::deletePixmaps()
+{
+ QPixmap *pixmap;
+ foreach (pixmap, m_pixmaps)
+ delete pixmap;
+ m_pixmaps.clear();
+}
+
+void Waveform::createPixmaps(const QSize &widgetSize)
+{
+ m_pixmapSize = widgetSize;
+ m_pixmapSize.setWidth(qreal(widgetSize.width()) * m_tileLength / m_windowLength);
+
+ WAVEFORM_DEBUG << "Waveform::createPixmaps"
+ << "widgetSize" << widgetSize
+ << "pixmapSize" << m_pixmapSize;
+
+ Q_ASSERT(m_tiles.count() == m_pixmaps.count());
+
+ // (Re)create pixmaps
+ for (int i=0; i<m_pixmaps.size(); ++i) {
+ delete m_pixmaps[i];
+ m_pixmaps[i] = 0;
+ m_pixmaps[i] = new QPixmap(m_pixmapSize);
+ }
+
+ // Update tile pixmap pointers, and mark for repainting
+ for (int i=0; i<m_tiles.count(); ++i) {
+ m_tiles[i].pixmap = m_pixmaps[i];
+ m_tiles[i].painted = false;
+ }
+
+ paintTiles();
+}
+
+void Waveform::setWindowPosition(qint64 position)
+{
+ WAVEFORM_DEBUG << "Waveform::setWindowPosition"
+ << "old" << m_windowPosition << "new" << position
+ << "tileArrayStart" << m_tileArrayStart;
+
+ const qint64 oldPosition = m_windowPosition;
+ m_windowPosition = position;
+
+ if((m_windowPosition >= oldPosition) &&
+ (m_windowPosition - m_tileArrayStart < (m_tiles.count() * m_tileLength))) {
+ // Work out how many tiles need to be shuffled
+ const qint64 offset = m_windowPosition - m_tileArrayStart;
+ const int nTiles = offset / m_tileLength;
+ shuffleTiles(nTiles);
+ } else {
+ resetTiles(m_windowPosition);
+ }
+
+ if(!paintTiles() && m_windowPosition != oldPosition)
+ update();
+}
+
+qint64 Waveform::tilePosition(int index) const
+{
+ return m_tileArrayStart + index * m_tileLength;
+}
+
+Waveform::TilePoint Waveform::tilePoint(qint64 position) const
+{
+ TilePoint result;
+ if (position >= m_tileArrayStart) {
+ const qint64 tileArrayEnd = m_tileArrayStart + m_tiles.count() * m_tileLength;
+ if (position < tileArrayEnd) {
+ const qint64 offsetIntoTileArray = position - m_tileArrayStart;
+ result.index = offsetIntoTileArray / m_tileLength;
+ Q_ASSERT(result.index >= 0 && result.index <= m_tiles.count());
+ result.positionOffset = offsetIntoTileArray % m_tileLength;
+ result.pixelOffset = tilePixelOffset(result.positionOffset);
+ Q_ASSERT(result.pixelOffset >= 0 && result.pixelOffset <= m_pixmapSize.width());
+ }
+ }
+
+ return result;
+}
+
+int Waveform::tilePixelOffset(qint64 positionOffset) const
+{
+ Q_ASSERT(positionOffset >= 0 && positionOffset <= m_tileLength);
+ const int result = (qreal(positionOffset) / m_tileLength) * m_pixmapSize.width();
+ return result;
+}
+
+int Waveform::windowPixelOffset(qint64 positionOffset) const
+{
+ Q_ASSERT(positionOffset >= 0 && positionOffset <= m_windowLength);
+ const int result = (qreal(positionOffset) / m_windowLength) * rect().width();
+ return result;
+}
+
+bool Waveform::paintTiles()
+{
+ WAVEFORM_DEBUG << "Waveform::paintTiles";
+ bool updateRequired = false;
+
+ for (int i=0; i<m_tiles.count(); ++i) {
+ const Tile &tile = m_tiles[i];
+ if (!tile.painted) {
+ const qint64 tileEnd = m_tileArrayStart + (i + 1) * m_tileLength;
+ if (m_dataLength >= tileEnd) {
+ paintTile(i);
+ updateRequired = true;
+ }
+ }
+ }
+
+ if (updateRequired)
+ update();
+
+ return updateRequired;
+}
+
+void Waveform::paintTile(int index)
+{
+ WAVEFORM_DEBUG << "Waveform::paintTile" << "index" << index;
+
+ const qint64 tileStart = m_tileArrayStart + index * m_tileLength;
+ Q_ASSERT(m_dataLength >= tileStart + m_tileLength);
+
+ Tile &tile = m_tiles[index];
+ Q_ASSERT(!tile.painted);
+
+ const qint16* base = reinterpret_cast<const qint16*>(m_buffer.constData());
+ const qint16* buffer = base + (tileStart / 2);
+ const int numSamples = m_tileLength / (2 * m_format.channels());
+
+ QPainter painter(tile.pixmap);
+
+ painter.fillRect(tile.pixmap->rect(), Qt::black);
+
+ QPen pen(Qt::white);
+ painter.setPen(pen);
+
+ // Calculate initial PCM value
+ qint16 previousPcmValue = 0;
+ if (buffer > base)
+ previousPcmValue = *(buffer - m_format.channels());
+
+ // Calculate initial point
+ const qreal previousRealValue = pcmToReal(previousPcmValue);
+ const int originY = ((previousRealValue + 1.0) / 2) * m_pixmapSize.height();
+ const QPoint origin(0, originY);
+
+ QLine line(origin, origin);
+
+ for (int i=0; i<numSamples; ++i) {
+ const qint16* ptr = buffer + i * m_format.channels();
+ const qint16 pcmValue = *ptr;
+ const qreal realValue = pcmToReal(pcmValue);
+
+ const int x = tilePixelOffset(i * 2 * m_format.channels());
+ const int y = ((realValue + 1.0) / 2) * m_pixmapSize.height();
+
+ line.setP2(QPoint(x, y));
+ painter.drawLine(line);
+ line.setP1(line.p2());
+ }
+
+ tile.painted = true;
+}
+
+void Waveform::shuffleTiles(int n)
+{
+ WAVEFORM_DEBUG << "Waveform::shuffleTiles" << "n" << n;
+
+ while (n--) {
+ Tile tile = m_tiles.first();
+ tile.painted = false;
+ m_tiles.erase(m_tiles.begin());
+ m_tiles += tile;
+ m_tileArrayStart += m_tileLength;
+ }
+
+ WAVEFORM_DEBUG << "Waveform::shuffleTiles" << "tileArrayStart" << m_tileArrayStart;
+}
+
+void Waveform::resetTiles(qint64 newStartPos)
+{
+ WAVEFORM_DEBUG << "Waveform::resetTiles" << "newStartPos" << newStartPos;
+
+ QVector<Tile>::iterator i = m_tiles.begin();
+ for ( ; i != m_tiles.end(); ++i)
+ i->painted = false;
+
+ m_tileArrayStart = newStartPos;
+}
+
diff --git a/demos/spectrum/app/waveform.h b/demos/spectrum/app/waveform.h
new file mode 100644
index 0000000..4de527f
--- /dev/null
+++ b/demos/spectrum/app/waveform.h
@@ -0,0 +1,198 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+#ifndef WAVEFORM_H
+#define WAVEFORM_H
+
+#include <QWidget>
+#include <QtMultimedia/QAudioFormat>
+#include <QPixmap>
+#include <QScopedPointer>
+
+class QByteArray;
+
+/**
+ * Widget which displays a section of the audio waveform.
+ *
+ * The waveform is rendered on a set of QPixmaps which form a group of tiles
+ * whose extent covers the widget. As the audio position is updated, these
+ * tiles are scrolled from left to right; when the left-most tile scrolls
+ * outside the widget, it is moved to the right end of the tile array and
+ * painted with the next section of the waveform.
+ */
+class Waveform : public QWidget {
+ Q_OBJECT
+public:
+ Waveform(const QByteArray &buffer, QWidget *parent = 0);
+ ~Waveform();
+
+ // QWidget
+ void paintEvent(QPaintEvent *event);
+ void resizeEvent(QResizeEvent *event);
+
+ void initialize(const QAudioFormat &format, qint64 audioBufferSize, qint64 windowDurationUs);
+ void reset();
+
+ void setAutoUpdatePosition(bool enabled);
+
+public slots:
+ void dataLengthChanged(qint64 length);
+ void positionChanged(qint64 position);
+
+private:
+ static const int NullIndex = -1;
+
+ void deletePixmaps();
+
+ /*
+ * (Re)create all pixmaps, repaint and update the display.
+ * Triggers an update();
+ */
+ void createPixmaps(const QSize &newSize);
+
+ /*
+ * Update window position.
+ * Triggers an update().
+ */
+ void setWindowPosition(qint64 position);
+
+ /*
+ * Base position of tile
+ */
+ qint64 tilePosition(int index) const;
+
+ /*
+ * Structure which identifies a point within a given
+ * tile.
+ */
+ struct TilePoint
+ {
+ TilePoint(int idx = 0, qint64 pos = 0, qint64 pix = 0)
+ : index(idx), positionOffset(pos), pixelOffset(pix)
+ { }
+
+ // Index of tile
+ int index;
+
+ // Number of bytes from start of tile
+ qint64 positionOffset;
+
+ // Number of pixels from left of corresponding pixmap
+ int pixelOffset;
+ };
+
+ /*
+ * Convert position in m_buffer into a tile index and an offset in pixels
+ * into the corresponding pixmap.
+ *
+ * \param position Offset into m_buffer, in bytes
+
+ * If position is outside the tile array, index is NullIndex and
+ * offset is zero.
+ */
+ TilePoint tilePoint(qint64 position) const;
+
+ /*
+ * Convert offset in bytes into a tile into an offset in pixels
+ * within that tile.
+ */
+ int tilePixelOffset(qint64 positionOffset) const;
+
+ /*
+ * Convert offset in bytes into the window into an offset in pixels
+ * within the widget rect().
+ */
+ int windowPixelOffset(qint64 positionOffset) const;
+
+ /*
+ * Paint all tiles which can be painted.
+ * \return true iff update() was called
+ */
+ bool paintTiles();
+
+ /*
+ * Paint the specified tile
+ *
+ * \pre Sufficient data is available to completely paint the tile, i.e.
+ * m_dataLength is greater than the upper bound of the tile.
+ */
+ void paintTile(int index);
+
+ /*
+ * Move the first n tiles to the end of the array, and mark them as not
+ * painted.
+ */
+ void shuffleTiles(int n);
+
+ /*
+ * Reset tile array
+ */
+ void resetTiles(qint64 newStartPos);
+
+private:
+ const QByteArray& m_buffer;
+ qint64 m_dataLength;
+ qint64 m_position;
+ QAudioFormat m_format;
+
+ bool m_active;
+
+ QSize m_pixmapSize;
+ QVector<QPixmap*> m_pixmaps;
+
+ struct Tile {
+ // Pointer into parent m_pixmaps array
+ QPixmap* pixmap;
+
+ // Flag indicating whether this tile has been painted
+ bool painted;
+ };
+
+ QVector<Tile> m_tiles;
+
+ // Length of audio data in bytes depicted by each tile
+ qint64 m_tileLength;
+
+ // Position in bytes of the first tile, relative to m_buffer
+ qint64 m_tileArrayStart;
+
+ qint64 m_windowPosition;
+ qint64 m_windowLength;
+
+};
+
+#endif // WAVEFORM_H
diff --git a/demos/spectrum/app/wavfile.cpp b/demos/spectrum/app/wavfile.cpp
new file mode 100644
index 0000000..ec911ad
--- /dev/null
+++ b/demos/spectrum/app/wavfile.cpp
@@ -0,0 +1,247 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/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;
+ DATAHeader data;
+};
+
+static const int HeaderLength = sizeof(CombinedHeader);
+
+
+WavFile::WavFile(const QAudioFormat &format, qint64 dataLength)
+ : m_format(format)
+ , m_dataLength(dataLength)
+{
+
+}
+
+bool WavFile::readHeader(QIODevice &device)
+{
+ bool result = true;
+
+ if (!device.isSequential())
+ result = device.seek(0);
+ // else, assume that current position is the start of the header
+
+ if (result) {
+ CombinedHeader header;
+ result = (device.read(reinterpret_cast<char *>(&header), HeaderLength) == HeaderLength);
+ 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 // PCM
+ ) {
+ if (memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0)
+ m_format.setByteOrder(QAudioFormat::LittleEndian);
+ else
+ m_format.setByteOrder(QAudioFormat::BigEndian);
+
+ m_format.setChannels(qFromLittleEndian<quint16>(header.wave.numChannels));
+ m_format.setCodec("audio/pcm");
+ m_format.setFrequency(qFromLittleEndian<quint32>(header.wave.sampleRate));
+ m_format.setSampleSize(qFromLittleEndian<quint16>(header.wave.bitsPerSample));
+
+ switch(header.wave.bitsPerSample) {
+ case 8:
+ m_format.setSampleType(QAudioFormat::UnSignedInt);
+ break;
+ case 16:
+ m_format.setSampleType(QAudioFormat::SignedInt);
+ break;
+ default:
+ result = false;
+ }
+
+ m_dataLength = device.size() - HeaderLength;
+ } else {
+ result = false;
+ }
+ }
+ }
+
+ return result;
+}
+
+bool WavFile::writeHeader(QIODevice &device)
+{
+ CombinedHeader header;
+
+ memset(&header, 0, HeaderLength);
+
+ // RIFF header
+ if (m_format.byteOrder() == QAudioFormat::LittleEndian)
+ strncpy(&header.riff.descriptor.id[0], "RIFF", 4);
+ else
+ strncpy(&header.riff.descriptor.id[0], "RIFX", 4);
+ qToLittleEndian<quint32>(quint32(m_dataLength + HeaderLength - 8),
+ reinterpret_cast<unsigned char*>(&header.riff.descriptor.size));
+ strncpy(&header.riff.type[0], "WAVE", 4);
+
+ // WAVE header
+ strncpy(&header.wave.descriptor.id[0], "fmt ", 4);
+ qToLittleEndian<quint32>(quint32(16),
+ reinterpret_cast<unsigned char*>(&header.wave.descriptor.size));
+ qToLittleEndian<quint16>(quint16(1),
+ reinterpret_cast<unsigned char*>(&header.wave.audioFormat));
+ qToLittleEndian<quint16>(quint16(m_format.channels()),
+ reinterpret_cast<unsigned char*>(&header.wave.numChannels));
+ qToLittleEndian<quint32>(quint32(m_format.frequency()),
+ reinterpret_cast<unsigned char*>(&header.wave.sampleRate));
+ qToLittleEndian<quint32>(quint32(m_format.frequency() * m_format.channels() * m_format.sampleSize() / 8),
+ reinterpret_cast<unsigned char*>(&header.wave.byteRate));
+ qToLittleEndian<quint16>(quint16(m_format.channels() * m_format.sampleSize() / 8),
+ reinterpret_cast<unsigned char*>(&header.wave.blockAlign));
+ qToLittleEndian<quint16>(quint16(m_format.sampleSize()),
+ reinterpret_cast<unsigned char*>(&header.wave.bitsPerSample));
+
+ // DATA header
+ strncpy(&header.data.descriptor.id[0], "data", 4);
+ qToLittleEndian<quint32>(quint32(m_dataLength),
+ reinterpret_cast<unsigned char*>(&header.data.descriptor.size));
+
+ return (device.write(reinterpret_cast<const char *>(&header), HeaderLength) == HeaderLength);
+}
+
+const QAudioFormat& WavFile::format() const
+{
+ return m_format;
+}
+
+qint64 WavFile::dataLength() const
+{
+ return m_dataLength;
+}
+
+qint64 WavFile::headerLength()
+{
+ return HeaderLength;
+}
+
+bool WavFile::writeDataLength(QIODevice &device, qint64 dataLength)
+{
+ bool result = false;
+ if (!device.isSequential()) {
+ device.seek(40);
+ unsigned char dataLengthLE[4];
+ qToLittleEndian<quint32>(quint32(dataLength), dataLengthLE);
+ result = (device.write(reinterpret_cast<const char *>(dataLengthLE), 4) == 4);
+ }
+ return result;
+}
+
+#include <QFile>
+#include <QTextStream>
+
+qint64 WavFile::readData(QIODevice &device, QByteArray &buffer,
+ QAudioFormat outputFormat)
+{
+ if (QAudioFormat() == outputFormat)
+ outputFormat = m_format;
+
+ qint64 result = 0;
+
+ QFile file("wav.txt");
+ file.open(QIODevice::WriteOnly | QIODevice::Text);
+ QTextStream stream;
+ stream.setDevice(&file);
+
+ if (isPCMS16LE(outputFormat) && isPCMS16LE(m_format)) {
+ QVector<char> inputSample(2 * m_format.channels());
+
+ qint16 *output = reinterpret_cast<qint16*>(buffer.data());
+
+ while (result < buffer.size()) {
+ if (device.read(inputSample.data(), inputSample.count())) {
+ int inputIdx = 0;
+ for (int outputIdx = 0; outputIdx < outputFormat.channels(); ++outputIdx) {
+ const qint16* input = reinterpret_cast<const qint16*>(inputSample.data() + 2 * inputIdx);
+ *output++ = qFromLittleEndian<qint16>(*input);
+ result += 2;
+ if (inputIdx < m_format.channels())
+ ++inputIdx;
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ return result;
+}
+
diff --git a/demos/spectrum/app/wavfile.h b/demos/spectrum/app/wavfile.h
new file mode 100644
index 0000000..05866f7
--- /dev/null
+++ b/demos/spectrum/app/wavfile.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Nokia Corporation and its Subsidiary(-ies) nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+** POSSIBILITY OF SUCH DAMAGE.
+** $QT_END_LICENSE$
+**
+*****************************************************************************/
+
+
+#ifndef WAVFILE_H
+#define WAVFILE_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qfile.h>
+#include <QtMultimedia/qaudioformat.h>
+
+/**
+ * Helper class for reading WAV files
+ *
+ * See https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
+ */
+class WavFile
+{
+public:
+ WavFile(const QAudioFormat &format = QAudioFormat(),
+ qint64 dataLength = 0);
+
+ // Reads WAV header and seeks to start of data
+ bool readHeader(QIODevice &device);
+
+ // Writes WAV header
+ bool writeHeader(QIODevice &device);
+
+ // Read PCM data
+ qint64 readData(QIODevice &device, QByteArray &buffer,
+ QAudioFormat outputFormat = QAudioFormat());
+
+ const QAudioFormat& format() const;
+ qint64 dataLength() const;
+
+ static qint64 headerLength();
+
+ static bool writeDataLength(QIODevice &device, qint64 dataLength);
+
+private:
+ QAudioFormat m_format;
+ qint64 m_dataLength;
+};
+
+#endif
+