Initial import from the monolithic Qt.

This is the beginning of revision history for this module. If you
want to look at revision history older than this, please refer to the
Qt Git wiki for how to use Git history grafting. At the time of
writing, this wiki is located here:

http://qt.gitorious.org/qt/pages/GitIntroductionWithQt

If you have already performed the grafting and you don't see any
history beyond this commit, try running "git log" with the "--follow"
argument.

Branched from the monolithic repo, Qt master branch, at commit
896db169ea224deb96c59ce8af800d019de63f12
This commit is contained in:
Qt by Nokia
2011-04-27 12:05:43 +02:00
committed by axis
commit 60941c2741
211 changed files with 87979 additions and 0 deletions

2
demos/spectrum/app/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
spectrum
spectrum.exe

118
demos/spectrum/app/app.pro Normal file
View File

@@ -0,0 +1,118 @@
include(../spectrum.pri)
static: error(This application cannot be statically linked to the fftreal library)
TEMPLATE = app
TARGET = spectrum
QT += multimedia
SOURCES += main.cpp \
engine.cpp \
frequencyspectrum.cpp \
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 = 0xA000E402
}
# Dynamic linkage against FFTReal DLL
!contains(DEFINES, DISABLE_FFT) {
symbian {
# Must explicitly add the .dll suffix to ensure dynamic linkage
LIBS += -lfftreal.dll
QMAKE_LIBDIR += $${fftreal_dir}
} else {
macx {
# Link to fftreal framework
LIBS += -F$${fftreal_dir}
LIBS += -framework fftreal
} else {
LIBS += -L..$${spectrum_build_dir}
LIBS += -lfftreal
}
}
}
# Install
sources.files = $$SOURCES $$HEADERS $$RESOURCES app.pro
sources.path = $$[QT_INSTALL_DEMOS]/qtmultimedia/spectrum/app
images.files += images/record.png images/settings.png
images.path = $$[QT_INSTALL_DEMOS]/qtmultimedia/spectrum/app/images
INSTALLS += sources images
# Deployment
symbian {
include($$QT_SOURCE_TREE/demos/symbianpkgrules.pri)
!contains(DEFINES, DISABLE_FFT) {
# Include FFTReal DLL in the SIS file
fftreal.files = ../fftreal.dll
fftreal.path = !:/sys/bin
DEPLOYMENT += fftreal
}
} else {
DESTDIR = ..$${spectrum_build_dir}
macx {
!contains(DEFINES, DISABLE_FFT) {
# Relocate fftreal.framework into spectrum.app bundle
framework_dir = ../spectrum.app/Contents/Frameworks
framework_name = fftreal.framework/Versions/1/fftreal
QMAKE_POST_LINK = \
mkdir -p $${framework_dir} &&\
rm -rf $${framework_dir}/fftreal.framework &&\
cp -R $${fftreal_dir}/fftreal.framework $${framework_dir} &&\
install_name_tool -id @executable_path/../Frameworks/$${framework_name} \
$${framework_dir}/$${framework_name} &&\
install_name_tool -change $${framework_name} \
@executable_path/../Frameworks/$${framework_name} \
../spectrum.app/Contents/MacOS/spectrum
}
} else {
linux-g++*: {
# Provide relative path from application to fftreal library
QMAKE_LFLAGS += -Wl,--rpath=\\\$\$ORIGIN
}
}
}

View File

@@ -0,0 +1,766 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#include "engine.h"
#include "tonegenerator.h"
#include "utils.h"
#include <math.h>
#include <QCoreApplication>
#include <QMetaObject>
#include <QSet>
#include <QtMultimedia/QAudioInput>
#include <QtMultimedia/QAudioOutput>
#include <QDebug>
#include <QThread>
#include <QFile>
//-----------------------------------------------------------------------------
// Constants
//-----------------------------------------------------------------------------
const qint64 BufferDurationUs = 10 * 1000000;
const int NotifyIntervalMs = 100;
// Size of the level calculation window in microseconds
const int LevelWindowUs = 0.1 * 1000000;
//-----------------------------------------------------------------------------
// Helper functions
//-----------------------------------------------------------------------------
QDebug& operator<<(QDebug &debug, const QAudioFormat &format)
{
debug << format.frequency() << "Hz"
<< format.channels() << "channels";
return debug;
}
//-----------------------------------------------------------------------------
// Constructor and destructor
//-----------------------------------------------------------------------------
Engine::Engine(QObject *parent)
: QObject(parent)
, m_mode(QAudio::AudioInput)
, m_state(QAudio::StoppedState)
, m_generateTone(false)
, m_file(0)
, m_analysisFile(0)
, m_availableAudioInputDevices
(QAudioDeviceInfo::availableDevices(QAudio::AudioInput))
, m_audioInputDevice(QAudioDeviceInfo::defaultInputDevice())
, m_audioInput(0)
, m_audioInputIODevice(0)
, m_recordPosition(0)
, m_availableAudioOutputDevices
(QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
, m_audioOutputDevice(QAudioDeviceInfo::defaultOutputDevice())
, m_audioOutput(0)
, m_playPosition(0)
, m_bufferPosition(0)
, m_bufferLength(0)
, m_dataLength(0)
, m_levelBufferLength(0)
, m_rmsLevel(0.0)
, m_peakLevel(0.0)
, m_spectrumBufferLength(0)
, m_spectrumAnalyser()
, m_spectrumPosition(0)
, m_count(0)
{
qRegisterMetaType<FrequencySpectrum>("FrequencySpectrum");
qRegisterMetaType<WindowFunction>("WindowFunction");
CHECKED_CONNECT(&m_spectrumAnalyser,
SIGNAL(spectrumChanged(FrequencySpectrum)),
this,
SLOT(spectrumChanged(FrequencySpectrum)));
initialize();
#ifdef DUMP_DATA
createOutputDir();
#endif
#ifdef DUMP_SPECTRUM
m_spectrumAnalyser.setOutputPath(outputPath());
#endif
}
Engine::~Engine()
{
}
//-----------------------------------------------------------------------------
// Public functions
//-----------------------------------------------------------------------------
bool Engine::loadFile(const QString &fileName)
{
reset();
bool result = false;
Q_ASSERT(!m_generateTone);
Q_ASSERT(!m_file);
Q_ASSERT(!fileName.isEmpty());
m_file = new WavFile(this);
if (m_file->open(fileName)) {
if (isPCMS16LE(m_file->fileFormat())) {
result = initialize();
} else {
emit errorMessage(tr("Audio format not supported"),
formatToString(m_file->fileFormat()));
}
} else {
emit errorMessage(tr("Could not open file"), fileName);
}
if (result) {
m_analysisFile = new WavFile(this);
m_analysisFile->open(fileName);
}
return result;
}
bool Engine::generateTone(const Tone &tone)
{
reset();
Q_ASSERT(!m_generateTone);
Q_ASSERT(!m_file);
m_generateTone = true;
m_tone = tone;
ENGINE_DEBUG << "Engine::generateTone"
<< "startFreq" << m_tone.startFreq
<< "endFreq" << m_tone.endFreq
<< "amp" << m_tone.amplitude;
return initialize();
}
bool Engine::generateSweptTone(qreal amplitude)
{
Q_ASSERT(!m_generateTone);
Q_ASSERT(!m_file);
m_generateTone = true;
m_tone.startFreq = 1;
m_tone.endFreq = 0;
m_tone.amplitude = amplitude;
ENGINE_DEBUG << "Engine::generateSweptTone"
<< "startFreq" << m_tone.startFreq
<< "amp" << m_tone.amplitude;
return initialize();
}
bool Engine::initializeRecord()
{
reset();
ENGINE_DEBUG << "Engine::initializeRecord";
Q_ASSERT(!m_generateTone);
Q_ASSERT(!m_file);
m_generateTone = false;
m_tone = SweptTone();
return initialize();
}
qint64 Engine::bufferLength() const
{
return m_file ? m_file->size() : m_bufferLength;
}
void Engine::setWindowFunction(WindowFunction type)
{
m_spectrumAnalyser.setWindowFunction(type);
}
//-----------------------------------------------------------------------------
// Public slots
//-----------------------------------------------------------------------------
void Engine::startRecording()
{
if (m_audioInput) {
if (QAudio::AudioInput == m_mode &&
QAudio::SuspendedState == m_state) {
m_audioInput->resume();
} else {
m_spectrumAnalyser.cancelCalculation();
spectrumChanged(0, 0, FrequencySpectrum());
m_buffer.fill(0);
setRecordPosition(0, true);
stopPlayback();
m_mode = QAudio::AudioInput;
CHECKED_CONNECT(m_audioInput, SIGNAL(stateChanged(QAudio::State)),
this, SLOT(audioStateChanged(QAudio::State)));
CHECKED_CONNECT(m_audioInput, SIGNAL(notify()),
this, SLOT(audioNotify()));
m_count = 0;
m_dataLength = 0;
emit dataLengthChanged(0);
m_audioInputIODevice = m_audioInput->start();
CHECKED_CONNECT(m_audioInputIODevice, SIGNAL(readyRead()),
this, SLOT(audioDataReady()));
}
}
}
void Engine::startPlayback()
{
if (m_audioOutput) {
if (QAudio::AudioOutput == m_mode &&
QAudio::SuspendedState == m_state) {
#ifdef Q_OS_WIN
// The Windows backend seems to internally go back into ActiveState
// while still returning SuspendedState, so to ensure that it doesn't
// ignore the resume() call, we first re-suspend
m_audioOutput->suspend();
#endif
m_audioOutput->resume();
} else {
m_spectrumAnalyser.cancelCalculation();
spectrumChanged(0, 0, FrequencySpectrum());
setPlayPosition(0, true);
stopRecording();
m_mode = QAudio::AudioOutput;
CHECKED_CONNECT(m_audioOutput, SIGNAL(stateChanged(QAudio::State)),
this, SLOT(audioStateChanged(QAudio::State)));
CHECKED_CONNECT(m_audioOutput, SIGNAL(notify()),
this, SLOT(audioNotify()));
m_count = 0;
if (m_file) {
m_file->seek(0);
m_bufferPosition = 0;
m_dataLength = 0;
m_audioOutput->start(m_file);
} else {
m_audioOutputIODevice.close();
m_audioOutputIODevice.setBuffer(&m_buffer);
m_audioOutputIODevice.open(QIODevice::ReadOnly);
m_audioOutput->start(&m_audioOutputIODevice);
}
}
}
}
void Engine::suspend()
{
if (QAudio::ActiveState == m_state ||
QAudio::IdleState == m_state) {
switch (m_mode) {
case QAudio::AudioInput:
m_audioInput->suspend();
break;
case QAudio::AudioOutput:
m_audioOutput->suspend();
break;
}
}
}
void Engine::setAudioInputDevice(const QAudioDeviceInfo &device)
{
if (device.deviceName() != m_audioInputDevice.deviceName()) {
m_audioInputDevice = device;
initialize();
}
}
void Engine::setAudioOutputDevice(const QAudioDeviceInfo &device)
{
if (device.deviceName() != m_audioOutputDevice.deviceName()) {
m_audioOutputDevice = device;
initialize();
}
}
//-----------------------------------------------------------------------------
// Private slots
//-----------------------------------------------------------------------------
void Engine::audioNotify()
{
switch (m_mode) {
case QAudio::AudioInput: {
const qint64 recordPosition = qMin(m_bufferLength, audioLength(m_format, m_audioInput->processedUSecs()));
setRecordPosition(recordPosition);
const qint64 levelPosition = m_dataLength - m_levelBufferLength;
if (levelPosition >= 0)
calculateLevel(levelPosition, m_levelBufferLength);
if (m_dataLength >= m_spectrumBufferLength) {
const qint64 spectrumPosition = m_dataLength - m_spectrumBufferLength;
calculateSpectrum(spectrumPosition);
}
emit bufferChanged(0, m_dataLength, m_buffer);
}
break;
case QAudio::AudioOutput: {
const qint64 playPosition = audioLength(m_format, m_audioOutput->processedUSecs());
setPlayPosition(qMin(bufferLength(), playPosition));
const qint64 levelPosition = playPosition - m_levelBufferLength;
const qint64 spectrumPosition = playPosition - m_spectrumBufferLength;
if (m_file) {
if (levelPosition > m_bufferPosition ||
spectrumPosition > m_bufferPosition ||
qMax(m_levelBufferLength, m_spectrumBufferLength) > m_dataLength) {
m_bufferPosition = 0;
m_dataLength = 0;
// Data needs to be read into m_buffer in order to be analysed
const qint64 readPos = qMax(qint64(0), qMin(levelPosition, spectrumPosition));
const qint64 readEnd = qMin(m_analysisFile->size(), qMax(levelPosition + m_levelBufferLength, spectrumPosition + m_spectrumBufferLength));
const qint64 readLen = readEnd - readPos + audioLength(m_format, WaveformWindowDuration);
qDebug() << "Engine::audioNotify [1]"
<< "analysisFileSize" << m_analysisFile->size()
<< "readPos" << readPos
<< "readLen" << readLen;
if (m_analysisFile->seek(readPos + m_analysisFile->headerLength())) {
m_buffer.resize(readLen);
m_bufferPosition = readPos;
m_dataLength = m_analysisFile->read(m_buffer.data(), readLen);
qDebug() << "Engine::audioNotify [2]" << "bufferPosition" << m_bufferPosition << "dataLength" << m_dataLength;
} else {
qDebug() << "Engine::audioNotify [2]" << "file seek error";
}
emit bufferChanged(m_bufferPosition, m_dataLength, m_buffer);
}
} else {
if (playPosition >= m_dataLength)
stopPlayback();
}
if (levelPosition >= 0 && levelPosition + m_levelBufferLength < m_bufferPosition + m_dataLength)
calculateLevel(levelPosition, m_levelBufferLength);
if (spectrumPosition >= 0 && spectrumPosition + m_spectrumBufferLength < m_bufferPosition + m_dataLength)
calculateSpectrum(spectrumPosition);
}
break;
}
}
void Engine::audioStateChanged(QAudio::State state)
{
ENGINE_DEBUG << "Engine::audioStateChanged from" << m_state
<< "to" << state;
if (QAudio::IdleState == state && m_file && m_file->pos() == m_file->size()) {
stopPlayback();
} else {
if (QAudio::StoppedState == state) {
// Check error
QAudio::Error error = QAudio::NoError;
switch (m_mode) {
case QAudio::AudioInput:
error = m_audioInput->error();
break;
case QAudio::AudioOutput:
error = m_audioOutput->error();
break;
}
if (QAudio::NoError != error) {
reset();
return;
}
}
setState(state);
}
}
void Engine::audioDataReady()
{
Q_ASSERT(0 == m_bufferPosition);
const qint64 bytesReady = m_audioInput->bytesReady();
const qint64 bytesSpace = m_buffer.size() - m_dataLength;
const qint64 bytesToRead = qMin(bytesReady, bytesSpace);
const qint64 bytesRead = m_audioInputIODevice->read(
m_buffer.data() + m_dataLength,
bytesToRead);
if (bytesRead) {
m_dataLength += bytesRead;
emit dataLengthChanged(dataLength());
}
if (m_buffer.size() == m_dataLength)
stopRecording();
}
void Engine::spectrumChanged(const FrequencySpectrum &spectrum)
{
ENGINE_DEBUG << "Engine::spectrumChanged" << "pos" << m_spectrumPosition;
emit spectrumChanged(m_spectrumPosition, m_spectrumBufferLength, spectrum);
}
//-----------------------------------------------------------------------------
// Private functions
//-----------------------------------------------------------------------------
void Engine::resetAudioDevices()
{
delete m_audioInput;
m_audioInput = 0;
m_audioInputIODevice = 0;
setRecordPosition(0);
delete m_audioOutput;
m_audioOutput = 0;
setPlayPosition(0);
m_spectrumPosition = 0;
setLevel(0.0, 0.0, 0);
}
void Engine::reset()
{
stopRecording();
stopPlayback();
setState(QAudio::AudioInput, QAudio::StoppedState);
setFormat(QAudioFormat());
m_generateTone = false;
delete m_file;
m_file = 0;
delete m_analysisFile;
m_analysisFile = 0;
m_buffer.clear();
m_bufferPosition = 0;
m_bufferLength = 0;
m_dataLength = 0;
emit dataLengthChanged(0);
resetAudioDevices();
}
bool Engine::initialize()
{
bool result = false;
QAudioFormat format = m_format;
if (selectFormat()) {
if (m_format != format) {
resetAudioDevices();
if (m_file) {
emit bufferLengthChanged(bufferLength());
emit dataLengthChanged(dataLength());
emit bufferChanged(0, 0, m_buffer);
setRecordPosition(bufferLength());
result = true;
} else {
m_bufferLength = audioLength(m_format, BufferDurationUs);
m_buffer.resize(m_bufferLength);
m_buffer.fill(0);
emit bufferLengthChanged(bufferLength());
if (m_generateTone) {
if (0 == m_tone.endFreq) {
const qreal nyquist = nyquistFrequency(m_format);
m_tone.endFreq = qMin(qreal(SpectrumHighFreq), nyquist);
}
// Call function defined in utils.h, at global scope
::generateTone(m_tone, m_format, m_buffer);
m_dataLength = m_bufferLength;
emit dataLengthChanged(dataLength());
emit bufferChanged(0, m_dataLength, m_buffer);
setRecordPosition(m_bufferLength);
result = true;
} else {
emit bufferChanged(0, 0, m_buffer);
m_audioInput = new QAudioInput(m_audioInputDevice, m_format, this);
m_audioInput->setNotifyInterval(NotifyIntervalMs);
result = true;
}
}
m_audioOutput = new QAudioOutput(m_audioOutputDevice, m_format, this);
m_audioOutput->setNotifyInterval(NotifyIntervalMs);
}
} else {
if (m_file)
emit errorMessage(tr("Audio format not supported"),
formatToString(m_format));
else if (m_generateTone)
emit errorMessage(tr("No suitable format found"), "");
else
emit errorMessage(tr("No common input / output format found"), "");
}
ENGINE_DEBUG << "Engine::initialize" << "m_bufferLength" << m_bufferLength;
ENGINE_DEBUG << "Engine::initialize" << "m_dataLength" << m_dataLength;
ENGINE_DEBUG << "Engine::initialize" << "format" << m_format;
return result;
}
bool Engine::selectFormat()
{
bool foundSupportedFormat = false;
if (m_file || QAudioFormat() != m_format) {
QAudioFormat format = m_format;
if (m_file)
// Header is read from the WAV file; just need to check whether
// it is supported by the audio output device
format = m_file->fileFormat();
if (m_audioOutputDevice.isFormatSupported(format)) {
setFormat(format);
foundSupportedFormat = true;
}
} else {
QList<int> frequenciesList;
#ifdef Q_OS_WIN
// The Windows audio backend does not correctly report format support
// (see QTBUG-9100). Furthermore, although the audio subsystem captures
// at 11025Hz, the resulting audio is corrupted.
frequenciesList += 8000;
#endif
if (!m_generateTone)
frequenciesList += m_audioInputDevice.supportedFrequencies();
frequenciesList += m_audioOutputDevice.supportedFrequencies();
frequenciesList = frequenciesList.toSet().toList(); // remove duplicates
qSort(frequenciesList);
ENGINE_DEBUG << "Engine::initialize frequenciesList" << frequenciesList;
QList<int> channelsList;
channelsList += m_audioInputDevice.supportedChannels();
channelsList += m_audioOutputDevice.supportedChannels();
channelsList = channelsList.toSet().toList();
qSort(channelsList);
ENGINE_DEBUG << "Engine::initialize channelsList" << channelsList;
QAudioFormat format;
format.setByteOrder(QAudioFormat::LittleEndian);
format.setCodec("audio/pcm");
format.setSampleSize(16);
format.setSampleType(QAudioFormat::SignedInt);
int frequency, channels;
foreach (frequency, frequenciesList) {
if (foundSupportedFormat)
break;
format.setFrequency(frequency);
foreach (channels, channelsList) {
format.setChannels(channels);
const bool inputSupport = m_generateTone ||
m_audioInputDevice.isFormatSupported(format);
const bool outputSupport = m_audioOutputDevice.isFormatSupported(format);
ENGINE_DEBUG << "Engine::initialize checking " << format
<< "input" << inputSupport
<< "output" << outputSupport;
if (inputSupport && outputSupport) {
foundSupportedFormat = true;
break;
}
}
}
if (!foundSupportedFormat)
format = QAudioFormat();
setFormat(format);
}
return foundSupportedFormat;
}
void Engine::stopRecording()
{
if (m_audioInput) {
m_audioInput->stop();
QCoreApplication::instance()->processEvents();
m_audioInput->disconnect();
}
m_audioInputIODevice = 0;
#ifdef DUMP_AUDIO
dumpData();
#endif
}
void Engine::stopPlayback()
{
if (m_audioOutput) {
m_audioOutput->stop();
QCoreApplication::instance()->processEvents();
m_audioOutput->disconnect();
setPlayPosition(0);
}
}
void Engine::setState(QAudio::State state)
{
const bool changed = (m_state != state);
m_state = state;
if (changed)
emit stateChanged(m_mode, m_state);
}
void Engine::setState(QAudio::Mode mode, QAudio::State state)
{
const bool changed = (m_mode != mode || m_state != state);
m_mode = mode;
m_state = state;
if (changed)
emit stateChanged(m_mode, m_state);
}
void Engine::setRecordPosition(qint64 position, bool forceEmit)
{
const bool changed = (m_recordPosition != position);
m_recordPosition = position;
if (changed || forceEmit)
emit recordPositionChanged(m_recordPosition);
}
void Engine::setPlayPosition(qint64 position, bool forceEmit)
{
const bool changed = (m_playPosition != position);
m_playPosition = position;
if (changed || forceEmit)
emit playPositionChanged(m_playPosition);
}
void Engine::calculateLevel(qint64 position, qint64 length)
{
#ifdef DISABLE_LEVEL
Q_UNUSED(position)
Q_UNUSED(length)
#else
Q_ASSERT(position + length <= m_bufferPosition + m_dataLength);
qreal peakLevel = 0.0;
qreal sum = 0.0;
const char *ptr = m_buffer.constData() + position - m_bufferPosition;
const char *const end = ptr + length;
while (ptr < end) {
const qint16 value = *reinterpret_cast<const qint16*>(ptr);
const qreal fracValue = pcmToReal(value);
peakLevel = qMax(peakLevel, fracValue);
sum += fracValue * fracValue;
ptr += 2;
}
const int numSamples = length / 2;
qreal rmsLevel = sqrt(sum / numSamples);
rmsLevel = qMax(qreal(0.0), rmsLevel);
rmsLevel = qMin(qreal(1.0), rmsLevel);
setLevel(rmsLevel, peakLevel, numSamples);
ENGINE_DEBUG << "Engine::calculateLevel" << "pos" << position << "len" << length
<< "rms" << rmsLevel << "peak" << peakLevel;
#endif
}
void Engine::calculateSpectrum(qint64 position)
{
#ifdef DISABLE_SPECTRUM
Q_UNUSED(position)
#else
Q_ASSERT(position + m_spectrumBufferLength <= m_bufferPosition + m_dataLength);
Q_ASSERT(0 == m_spectrumBufferLength % 2); // constraint of FFT algorithm
// QThread::currentThread is marked 'for internal use only', but
// we're only using it for debug output here, so it's probably OK :)
ENGINE_DEBUG << "Engine::calculateSpectrum" << QThread::currentThread()
<< "count" << m_count << "pos" << position << "len" << m_spectrumBufferLength
<< "spectrumAnalyser.isReady" << m_spectrumAnalyser.isReady();
if(m_spectrumAnalyser.isReady()) {
m_spectrumBuffer = QByteArray::fromRawData(m_buffer.constData() + position - m_bufferPosition,
m_spectrumBufferLength);
m_spectrumPosition = position;
m_spectrumAnalyser.calculate(m_spectrumBuffer, m_format);
}
#endif
}
void Engine::setFormat(const QAudioFormat &format)
{
const bool changed = (format != m_format);
m_format = format;
m_levelBufferLength = audioLength(m_format, LevelWindowUs);
m_spectrumBufferLength = SpectrumLengthSamples *
(m_format.sampleSize() / 8) * m_format.channels();
if (changed)
emit formatChanged(m_format);
}
void Engine::setLevel(qreal rmsLevel, qreal peakLevel, int numSamples)
{
m_rmsLevel = rmsLevel;
m_peakLevel = peakLevel;
emit levelChanged(m_rmsLevel, m_peakLevel, numSamples);
}
#ifdef DUMP_DATA
void Engine::createOutputDir()
{
m_outputDir.setPath("output");
// Ensure output directory exists and is empty
if (m_outputDir.exists()) {
const QStringList files = m_outputDir.entryList(QDir::Files);
QString file;
foreach (file, files)
m_outputDir.remove(file);
} else {
QDir::current().mkdir("output");
}
}
#endif // DUMP_DATA
#ifdef DUMP_AUDIO
void Engine::dumpData()
{
const QString txtFileName = m_outputDir.filePath("data.txt");
QFile txtFile(txtFileName);
txtFile.open(QFile::WriteOnly | QFile::Text);
QTextStream stream(&txtFile);
const qint16 *ptr = reinterpret_cast<const qint16*>(m_buffer.constData());
const int numSamples = m_dataLength / (2 * m_format.channels());
for (int i=0; i<numSamples; ++i) {
stream << i << "\t" << *ptr << "\n";
ptr += m_format.channels();
}
const QString pcmFileName = m_outputDir.filePath("data.pcm");
QFile pcmFile(pcmFileName);
pcmFile.open(QFile::WriteOnly);
pcmFile.write(m_buffer.constData(), m_dataLength);
}
#endif // DUMP_AUDIO

315
demos/spectrum/app/engine.h Normal file
View File

@@ -0,0 +1,315 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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 FrequencySpectrum;
QT_FORWARD_DECLARE_CLASS(QAudioInput)
QT_FORWARD_DECLARE_CLASS(QAudioOutput)
QT_FORWARD_DECLARE_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 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 bytes.
*/
qint64 recordPosition() const { return m_recordPosition; }
/**
* RMS level of the most recently processed set of audio samples.
* \return Level in range (0.0, 1.0)
*/
qreal rmsLevel() const { return m_rmsLevel; }
/**
* Peak level of the most recently processed set of audio samples.
* \return Level in range (0.0, 1.0)
*/
qreal peakLevel() const { return m_peakLevel; }
/**
* Position of the audio output device.
* \return Position in bytes.
*/
qint64 playPosition() const { return m_playPosition; }
/**
* Length of the internal engine buffer.
* \return Buffer length in bytes.
*/
qint64 bufferLength() const;
/**
* Amount of data held in the buffer.
* \return Data length in bytes.
*/
qint64 dataLength() const { return m_dataLength; }
/**
* Set window function applied to audio data before spectral analysis.
*/
void setWindowFunction(WindowFunction type);
public slots:
void startRecording();
void startPlayback();
void suspend();
void setAudioInputDevice(const QAudioDeviceInfo &device);
void setAudioOutputDevice(const QAudioDeviceInfo &device);
signals:
void stateChanged(QAudio::Mode mode, QAudio::State state);
/**
* 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 bufferLengthChanged(qint64 duration);
/**
* Amount of data in buffer has changed.
* \param Length of data in bytes
*/
void dataLengthChanged(qint64 duration);
/**
* Position of the audio input device has changed.
* \param position Position in bytes
*/
void recordPositionChanged(qint64 position);
/**
* Position of the audio output device has changed.
* \param position Position in bytes
*/
void playPositionChanged(qint64 position);
/**
* Level changed
* \param rmsLevel RMS level in range 0.0 - 1.0
* \param peakLevel Peak level in range 0.0 - 1.0
* \param numSamples Number of audio samples analyzed
*/
void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples);
/**
* Spectrum has changed.
* \param position Position of start of window in bytes
* \param length Length of window in bytes
* \param spectrum Resulting frequency spectrum
*/
void spectrumChanged(qint64 position, qint64 length, const FrequencySpectrum &spectrum);
/**
* Buffer containing audio data has changed.
* \param position Position of start of buffer in bytes
* \param buffer Buffer
*/
void bufferChanged(qint64 position, qint64 length, const QByteArray &buffer);
private slots:
void audioNotify();
void audioStateChanged(QAudio::State state);
void audioDataReady();
void spectrumChanged(const FrequencySpectrum &spectrum);
private:
void resetAudioDevices();
bool initialize();
bool selectFormat();
void stopRecording();
void stopPlayback();
void setState(QAudio::State state);
void setState(QAudio::Mode mode, QAudio::State state);
void setFormat(const QAudioFormat &format);
void setRecordPosition(qint64 position, bool forceEmit = false);
void setPlayPosition(qint64 position, bool forceEmit = false);
void calculateLevel(qint64 position, qint64 length);
void calculateSpectrum(qint64 position);
void setLevel(qreal rmsLevel, qreal peakLevel, int numSamples);
#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;
WavFile* m_file;
// We need a second file handle via which to read data into m_buffer
// for analysis
WavFile* m_analysisFile;
QAudioFormat m_format;
const QList<QAudioDeviceInfo> m_availableAudioInputDevices;
QAudioDeviceInfo m_audioInputDevice;
QAudioInput* m_audioInput;
QIODevice* m_audioInputIODevice;
qint64 m_recordPosition;
const QList<QAudioDeviceInfo> m_availableAudioOutputDevices;
QAudioDeviceInfo m_audioOutputDevice;
QAudioOutput* m_audioOutput;
qint64 m_playPosition;
QBuffer m_audioOutputIODevice;
QByteArray m_buffer;
qint64 m_bufferPosition;
qint64 m_bufferLength;
qint64 m_dataLength;
int m_levelBufferLength;
qreal m_rmsLevel;
qreal m_peakLevel;
int m_spectrumBufferLength;
QByteArray m_spectrumBuffer;
SpectrumAnalyser m_spectrumAnalyser;
qint64 m_spectrumPosition;
int m_count;
#ifdef DUMP_DATA
QDir m_outputDir;
#endif
};
#endif // ENGINE_H

View File

@@ -0,0 +1,89 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#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();
}

View File

@@ -0,0 +1,98 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -0,0 +1,142 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#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);
}

View File

@@ -0,0 +1,116 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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 analyzed
* 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

View File

@@ -0,0 +1,57 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#include <QtGui/QApplication>
#include "mainwidget.h"
int main(int argc, char **argv)
{
QApplication app(argc, argv);
app.setApplicationName("QtMultimedia spectrum analyzer");
MainWidget w;
#ifdef Q_OS_SYMBIAN
w.showMaximized();
#else
w.show();
#endif
return app.exec();
}

View File

@@ -0,0 +1,453 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#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(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::audioPositionChanged(qint64 position)
{
#ifndef DISABLE_WAVEFORM
m_waveform->audioPositionChanged(position);
#else
Q_UNUSED(position)
#endif
}
void MainWidget::bufferLengthChanged(qint64 length)
{
m_progressBar->bufferLengthChanged(length);
}
//-----------------------------------------------------------------------------
// Private slots
//-----------------------------------------------------------------------------
void MainWidget::showFileDialog()
{
const QString dir;
const QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open WAV file"), dir, "*.wav");
if (fileNames.count()) {
reset();
setMode(LoadFileMode);
m_engine->loadFile(fileNames.front());
updateButtonStates();
} else {
updateModeMenu();
}
}
void MainWidget::showSettingsDialog()
{
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()
{
m_toneGeneratorDialog->exec();
if (m_toneGeneratorDialog->result() == QDialog::Accepted) {
reset();
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();
}
} else {
updateModeMenu();
}
}
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->bufferLengthChanged(m_engine->bufferLength());
CHECKED_CONNECT(m_engine, SIGNAL(bufferLengthChanged(qint64)),
this, SLOT(bufferLengthChanged(qint64)));
CHECKED_CONNECT(m_engine, SIGNAL(dataLengthChanged(qint64)),
this, SLOT(updateButtonStates()));
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(audioPositionChanged(qint64)));
CHECKED_CONNECT(m_engine, SIGNAL(playPositionChanged(qint64)),
this, SLOT(audioPositionChanged(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)));
#ifndef DISABLE_WAVEFORM
CHECKED_CONNECT(m_engine, SIGNAL(bufferChanged(qint64, qint64, const QByteArray &)),
m_waveform, SLOT(bufferChanged(qint64, qint64, const QByteArray &)));
#endif
}
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->dataLength() &&*/
(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;
updateModeMenu();
}
void MainWidget::updateModeMenu()
{
m_loadFileAction->setChecked(LoadFileMode == m_mode);
m_generateToneAction->setChecked(GenerateToneMode == m_mode);
m_recordAction->setChecked(RecordMode == m_mode);
}

View File

@@ -0,0 +1,144 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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;
QT_FORWARD_DECLARE_CLASS(QAudioFormat)
QT_FORWARD_DECLARE_CLASS(QLabel)
QT_FORWARD_DECLARE_CLASS(QPushButton)
QT_FORWARD_DECLARE_CLASS(QMenu)
QT_FORWARD_DECLARE_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 audioPositionChanged(qint64 position);
void bufferLengthChanged(qint64 length);
private slots:
void showFileDialog();
void showSettingsDialog();
void showToneGeneratorDialog();
void initializeRecord();
void updateModeMenu();
void updateButtonStates();
private:
void createUi();
void createMenus();
void connectUi();
void reset();
enum Mode {
NoMode,
RecordMode,
GenerateToneMode,
LoadFileMode
};
void setMode(Mode mode);
private:
Mode m_mode;
Engine* m_engine;
#ifndef DISABLE_WAVEFORM
Waveform* m_waveform;
#endif
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

View File

@@ -0,0 +1,140 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#include "progressbar.h"
#include "spectrum.h"
#include <QPainter>
ProgressBar::ProgressBar(QWidget *parent)
: QWidget(parent)
, m_bufferLength(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_bufferLength = 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_bufferLength) {
QRect bar = rect();
const qreal play = qreal(m_playPosition) / m_bufferLength;
bar.setLeft(rect().left() + play * rect().width());
const qreal record = qreal(m_recordPosition) / m_bufferLength;
bar.setRight(rect().left() + record * rect().width());
painter.fillRect(bar, bufferColor);
QRect window = rect();
const qreal windowLeft = qreal(m_windowPosition) / m_bufferLength;
window.setLeft(rect().left() + windowLeft * rect().width());
const qreal windowWidth = qreal(m_windowLength) / m_bufferLength;
window.setWidth(windowWidth * rect().width());
painter.fillRect(window, windowColor);
}
}
void ProgressBar::bufferLengthChanged(qint64 bufferSize)
{
m_bufferLength = 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_bufferLength);
m_recordPosition = recordPosition;
repaint();
}
void ProgressBar::playPositionChanged(qint64 playPosition)
{
Q_ASSERT(playPosition >= 0);
Q_ASSERT(playPosition <= m_bufferLength);
m_playPosition = playPosition;
repaint();
}
void ProgressBar::windowChanged(qint64 position, qint64 length)
{
Q_ASSERT(position >= 0);
Q_ASSERT(position <= m_bufferLength);
Q_ASSERT(position + length <= m_bufferLength);
m_windowPosition = position;
m_windowLength = length;
repaint();
}

View File

@@ -0,0 +1,74 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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 bufferLengthChanged(qint64 length);
void recordPositionChanged(qint64 recordPosition);
void playPositionChanged(qint64 playPosition);
void windowChanged(qint64 position, qint64 length);
private:
qint64 m_bufferLength;
qint64 m_recordPosition;
qint64 m_playPosition;
qint64 m_windowPosition;
qint64 m_windowLength;
};
#endif // PROGRESSBAR_H

View File

@@ -0,0 +1,148 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#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(),
QVariant::fromValue(device));
foreach (device, availableOutputDevices)
m_outputDeviceComboBox->addItem(device.deviceName(),
QVariant::fromValue(device));
m_windowFunctionComboBox->addItem(tr("None"), QVariant::fromValue(int(NoWindow)));
m_windowFunctionComboBox->addItem("Hann", QVariant::fromValue(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>();
}

View File

@@ -0,0 +1,87 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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>
QT_FORWARD_DECLARE_CLASS(QComboBox)
QT_FORWARD_DECLARE_CLASS(QCheckBox)
QT_FORWARD_DECLARE_CLASS(QSlider)
QT_FORWARD_DECLARE_CLASS(QSpinBox)
QT_FORWARD_DECLARE_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

View File

@@ -0,0 +1,241 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#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();
}

View File

@@ -0,0 +1,99 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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"
QT_FORWARD_DECLARE_CLASS(QMouseEvent)
/**
* Widget which displays a spectrograph showing the frequency spectrum
* of the window of audio samples most recently analyzed 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

View File

@@ -0,0 +1,144 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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

View File

@@ -0,0 +1,7 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>images/record.png</file>
<file>images/settings.png</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,281 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#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() cannot be called on a QObject with a parent
setParent(0);
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());
// Analyze output to obtain amplitude and phase for each frequency
for (int i=2; i<=m_numSamples/2; ++i) {
// Calculate frequency of this complex sample
m_spectrum[i].frequency = qreal(i * inputFrequency) / (m_numSamples);
const qreal real = m_output[i];
qreal imag = 0.0;
if (i>0 && i<m_numSamples/2)
imag = m_output[m_numSamples/2 + i];
const qreal magnitude = sqrt(real*real + imag*imag);
qreal amplitude = SpectrumAnalyserMultiplier * log(magnitude);
// Bound amplitude to [0.0, 1.0]
m_spectrum[i].clipped = (amplitude > 1.0);
amplitude = qMax(qreal(0.0), amplitude);
amplitude = qMin(qreal(1.0), amplitude);
m_spectrum[i].amplitude = amplitude;
}
#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;
}

View File

@@ -0,0 +1,194 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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
QT_FORWARD_DECLARE_CLASS(QAudioFormat)
QT_FORWARD_DECLARE_CLASS(QThread)
class FFTRealWrapper;
class SpectrumAnalyserThreadPrivate;
/**
* Implementation of the spectrum analysis which can be run in a
* separate thread.
*/
class SpectrumAnalyserThread : public QObject
{
Q_OBJECT
public:
SpectrumAnalyserThread(QObject *parent);
~SpectrumAnalyserThread();
public slots:
void setWindowFunction(WindowFunction type);
void calculateSpectrum(const QByteArray &buffer,
int inputFrequency,
int bytesPerSample);
signals:
void calculationComplete(const FrequencySpectrum &spectrum);
private:
void calculateWindow();
private:
#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

View File

@@ -0,0 +1,91 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#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;
}
}

View File

@@ -0,0 +1,56 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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"
QT_FORWARD_DECLARE_CLASS(QAudioFormat)
QT_FORWARD_DECLARE_CLASS(QByteArray)
/**
* Generate a sine wave
*/
void generateTone(const SweptTone &tone, const QAudioFormat &format, QByteArray &buffer);
#endif // TONEGENERATOR_H

View File

@@ -0,0 +1,147 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#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);
}

View File

@@ -0,0 +1,81 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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>
QT_FORWARD_DECLARE_CLASS(QCheckBox)
QT_FORWARD_DECLARE_CLASS(QSlider)
QT_FORWARD_DECLARE_CLASS(QSpinBox)
QT_FORWARD_DECLARE_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

View File

@@ -0,0 +1,139 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#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)
{
qint64 result = (format.frequency() * format.channels() * (format.sampleSize() / 8))
* microSeconds / 1000000;
result -= result % (format.channelCount() * format.sampleSize());
return result;
}
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;
}

112
demos/spectrum/app/utils.h Normal file
View File

@@ -0,0 +1,112 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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>
QT_FORWARD_DECLARE_CLASS(QAudioFormat)
//-----------------------------------------------------------------------------
// Miscellaneous utility functions
//-----------------------------------------------------------------------------
qint64 audioDuration(const QAudioFormat &format, qint64 bytes);
qint64 audioLength(const QAudioFormat &format, qint64 microSeconds);
QString formatToString(const QAudioFormat &format);
qreal nyquistFrequency(const QAudioFormat &format);
// Scale PCM value to [-1.0, 1.0]
qreal pcmToReal(qint16 pcm);
// Scale real value in [-1.0, 1.0] to PCM
qint16 realToPcm(qreal real);
// Check whether the audio format is PCM
bool isPCM(const QAudioFormat &format);
// Check whether the audio format is signed, little-endian, 16-bit PCM
bool isPCMS16LE(const QAudioFormat &format);
// Compile-time calculation of powers of two
template<int N> class PowerOfTwo
{ public: static const int Result = PowerOfTwo<N-1>::Result * 2; };
template<> class PowerOfTwo<0>
{ public: static const int Result = 1; };
//-----------------------------------------------------------------------------
// Debug output
//-----------------------------------------------------------------------------
class NullDebug
{
public:
template <typename T>
NullDebug& operator<<(const T&) { return *this; }
};
inline NullDebug nullDebug() { return NullDebug(); }
#ifdef LOG_ENGINE
# define ENGINE_DEBUG qDebug()
#else
# define ENGINE_DEBUG nullDebug()
#endif
#ifdef LOG_SPECTRUMANALYSER
# define SPECTRUMANALYSER_DEBUG qDebug()
#else
# define SPECTRUMANALYSER_DEBUG nullDebug()
#endif
#ifdef LOG_WAVEFORM
# define WAVEFORM_DEBUG qDebug()
#else
# define WAVEFORM_DEBUG nullDebug()
#endif
#endif // UTILS_H

View File

@@ -0,0 +1,436 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#include "waveform.h"
#include "utils.h"
#include <QPainter>
#include <QResizeEvent>
#include <QDebug>
//#define PAINT_EVENT_TRACE
#ifdef PAINT_EVENT_TRACE
# define WAVEFORM_PAINT_DEBUG qDebug()
#else
# define WAVEFORM_PAINT_DEBUG nullDebug()
#endif
Waveform::Waveform(QWidget *parent)
: QWidget(parent)
, m_bufferPosition(0)
, m_bufferLength(0)
, m_audioPosition(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_PAINT_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_PAINT_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_PAINT_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_PAINT_DEBUG << "Waveform::paintEvent" << "pos ->" << pos;
} else {
// Reached end of tile array
WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "reached end of tile array";
break;
}
} else {
// Passed last tile which is painted
WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "tile" << point.index << "not painted";
break;
}
} else {
// pos is past end of tile array
WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "pos" << pos << "past end of tile array";
break;
}
}
WAVEFORM_PAINT_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
<< "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_bufferPosition = 0;
m_buffer = QByteArray();
m_audioPosition = 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::bufferChanged(qint64 position, qint64 length, const QByteArray &buffer)
{
WAVEFORM_DEBUG << "Waveform::bufferChanged"
<< "audioPosition" << m_audioPosition
<< "bufferPosition" << position
<< "bufferLength" << length;
m_bufferPosition = position;
m_bufferLength = length;
m_buffer = buffer;
paintTiles();
}
void Waveform::audioPositionChanged(qint64 position)
{
WAVEFORM_DEBUG << "Waveform::audioPositionChanged"
<< "audioPosition" << position
<< "bufferPosition" << m_bufferPosition
<< "bufferLength" << m_bufferLength;
if (position >= m_bufferPosition) {
if (position + m_windowLength > m_bufferPosition + m_bufferLength)
position = qMax(qint64(0), m_bufferPosition + m_bufferLength - m_windowLength);
m_audioPosition = 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;
}
}
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 tileStart = m_tileArrayStart + i * m_tileLength;
const qint64 tileEnd = tileStart + m_tileLength;
if (m_bufferPosition <= tileStart && m_bufferPosition + m_bufferLength >= tileEnd) {
paintTile(i);
updateRequired = true;
}
}
}
if (updateRequired)
update();
return updateRequired;
}
void Waveform::paintTile(int index)
{
const qint64 tileStart = m_tileArrayStart + index * m_tileLength;
WAVEFORM_DEBUG << "Waveform::paintTile"
<< "index" << index
<< "bufferPosition" << m_bufferPosition
<< "bufferLength" << m_bufferLength
<< "start" << tileStart
<< "end" << tileStart + m_tileLength;
Q_ASSERT(m_bufferPosition <= tileStart);
Q_ASSERT(m_bufferPosition + m_bufferLength >= 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 - m_bufferPosition) / 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 int offset = reinterpret_cast<const char*>(ptr) - m_buffer.constData();
Q_ASSERT(offset >= 0);
Q_ASSERT(offset < m_bufferLength);
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;
}

View File

@@ -0,0 +1,203 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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>
QT_FORWARD_DECLARE_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(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 bufferChanged(qint64 position, qint64 length, const QByteArray &buffer);
void audioPositionChanged(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:
qint64 m_bufferPosition;
qint64 m_bufferLength;
QByteArray m_buffer;
qint64 m_audioPosition;
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

View File

@@ -0,0 +1,151 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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$
**
****************************************************************************/
#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;
};
WavFile::WavFile(QObject *parent)
: QFile(parent)
, m_headerLength(0)
{
}
bool WavFile::open(const QString &fileName)
{
close();
setFileName(fileName);
return QFile::open(QIODevice::ReadOnly) && readHeader();
}
const QAudioFormat &WavFile::fileFormat() const
{
return m_fileFormat;
}
qint64 WavFile::headerLength() const
{
return m_headerLength;
}
bool WavFile::readHeader()
{
seek(0);
CombinedHeader header;
bool result = read(reinterpret_cast<char *>(&header), sizeof(CombinedHeader)) == sizeof(CombinedHeader);
if (result) {
if ((memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0
|| memcmp(&header.riff.descriptor.id, "RIFX", 4) == 0)
&& memcmp(&header.riff.type, "WAVE", 4) == 0
&& memcmp(&header.wave.descriptor.id, "fmt ", 4) == 0
&& (header.wave.audioFormat == 1 || header.wave.audioFormat == 0)) {
// Read off remaining header information
DATAHeader dataHeader;
if (qFromLittleEndian<quint32>(header.wave.descriptor.size) > sizeof(WAVEHeader)) {
// Extended data available
quint16 extraFormatBytes;
if (peek((char*)&extraFormatBytes, sizeof(quint16)) != sizeof(quint16))
return false;
const qint64 throwAwayBytes = sizeof(quint16) + qFromLittleEndian<quint16>(extraFormatBytes);
if (read(throwAwayBytes).size() != throwAwayBytes)
return false;
}
if (read((char*)&dataHeader, sizeof(DATAHeader)) != sizeof(DATAHeader))
return false;
// Establish format
if (memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0)
m_fileFormat.setByteOrder(QAudioFormat::LittleEndian);
else
m_fileFormat.setByteOrder(QAudioFormat::BigEndian);
int bps = qFromLittleEndian<quint16>(header.wave.bitsPerSample);
m_fileFormat.setChannels(qFromLittleEndian<quint16>(header.wave.numChannels));
m_fileFormat.setCodec("audio/pcm");
m_fileFormat.setFrequency(qFromLittleEndian<quint32>(header.wave.sampleRate));
m_fileFormat.setSampleSize(qFromLittleEndian<quint16>(header.wave.bitsPerSample));
m_fileFormat.setSampleType(bps == 8 ? QAudioFormat::UnSignedInt : QAudioFormat::SignedInt);
} else {
result = false;
}
}
m_headerLength = pos();
return result;
}

View File

@@ -0,0 +1,68 @@
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the 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>
class WavFile : public QFile
{
public:
WavFile(QObject *parent = 0);
bool open(const QString &fileName);
const QAudioFormat &fileFormat() const;
qint64 headerLength() const;
private:
bool readHeader();
private:
QAudioFormat m_fileFormat;
qint64 m_headerLength;
};
#endif