centralize and fixup example sources install targets

This follows suit with aeb036e in qtbase.

Change-Id: Ie8580d0a1f38ab9858b0e44c9f99bdc552a1752a
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@digia.com>
Reviewed-by: hjk <qthjk@ovi.com>
This commit is contained in:
Joerg Bornemann
2012-12-05 13:03:09 +01:00
committed by The Qt Project
parent 90c8ba233b
commit 6b4994c265
407 changed files with 216 additions and 271 deletions

View File

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

View File

@@ -0,0 +1,85 @@
include(../spectrum.pri)
static: error(This application cannot be statically linked to the fftreal library)
TEMPLATE = app
TARGET = spectrum
QT += multimedia widgets
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
# Dynamic linkage against FFTReal DLL
!contains(DEFINES, DISABLE_FFT) {
macx {
# Link to fftreal framework
LIBS += -F$${fftreal_dir}
LIBS += -framework fftreal
} else {
LIBS += -L..$${spectrum_build_dir}
LIBS += -lfftreal
}
}
target.path = $$[QT_INSTALL_EXAMPLES]/multimedia/spectrum
INSTALLS += target
# Deployment
DESTDIR = ..$${spectrum_build_dir}
macx {
!contains(DEFINES, DISABLE_FFT) {
# Relocate fftreal.framework into spectrum.app bundle
framework_dir = ../spectrum.app/Contents/Frameworks
framework_name = fftreal.framework/Versions/1/fftreal
QMAKE_POST_LINK = \
mkdir -p $${framework_dir} &&\
rm -rf $${framework_dir}/fftreal.framework &&\
cp -R $${fftreal_dir}/fftreal.framework $${framework_dir} &&\
install_name_tool -id @executable_path/../Frameworks/$${framework_name} \
$${framework_dir}/$${framework_name} &&\
install_name_tool -change $${framework_name} \
@executable_path/../Frameworks/$${framework_name} \
../spectrum.app/Contents/MacOS/spectrum
}
} else {
linux-g++*: {
# Provide relative path from application to fftreal library
QMAKE_LFLAGS += -Wl,--rpath=\\\$\$ORIGIN
}
}

View File

@@ -0,0 +1,754 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <QAudioInput>
#include <QAudioOutput>
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QMetaObject>
#include <QSet>
#include <QThread>
//-----------------------------------------------------------------------------
// Constants
//-----------------------------------------------------------------------------
const qint64 BufferDurationUs = 10 * 1000000;
const int NotifyIntervalMs = 100;
// Size of the level calculation window in microseconds
const int LevelWindowUs = 0.1 * 1000000;
//-----------------------------------------------------------------------------
// Constructor and destructor
//-----------------------------------------------------------------------------
Engine::Engine(QObject *parent)
: QObject(parent)
, m_mode(QAudio::AudioInput)
, m_state(QAudio::StoppedState)
, m_generateTone(false)
, m_file(0)
, m_analysisFile(0)
, m_availableAudioInputDevices
(QAudioDeviceInfo::availableDevices(QAudio::AudioInput))
, m_audioInputDevice(QAudioDeviceInfo::defaultInputDevice())
, m_audioInput(0)
, m_audioInputIODevice(0)
, m_recordPosition(0)
, m_availableAudioOutputDevices
(QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
, m_audioOutputDevice(QAudioDeviceInfo::defaultOutputDevice())
, m_audioOutput(0)
, m_playPosition(0)
, m_bufferPosition(0)
, m_bufferLength(0)
, m_dataLength(0)
, m_levelBufferLength(0)
, m_rmsLevel(0.0)
, m_peakLevel(0.0)
, m_spectrumBufferLength(0)
, m_spectrumAnalyser()
, m_spectrumPosition(0)
, m_count(0)
{
qRegisterMetaType<FrequencySpectrum>("FrequencySpectrum");
qRegisterMetaType<WindowFunction>("WindowFunction");
CHECKED_CONNECT(&m_spectrumAnalyser,
SIGNAL(spectrumChanged(FrequencySpectrum)),
this,
SLOT(spectrumChanged(FrequencySpectrum)));
initialize();
#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> sampleRatesList;
#ifdef Q_OS_WIN
// The Windows audio backend does not correctly report format support
// (see QTBUG-9100). Furthermore, although the audio subsystem captures
// at 11025Hz, the resulting audio is corrupted.
sampleRatesList += 8000;
#endif
if (!m_generateTone)
sampleRatesList += m_audioInputDevice.supportedSampleRates();
sampleRatesList += m_audioOutputDevice.supportedSampleRates();
sampleRatesList = sampleRatesList.toSet().toList(); // remove duplicates
qSort(sampleRatesList);
ENGINE_DEBUG << "Engine::initialize frequenciesList" << sampleRatesList;
QList<int> channelsList;
channelsList += m_audioInputDevice.supportedChannelCounts();
channelsList += m_audioOutputDevice.supportedChannelCounts();
channelsList = channelsList.toSet().toList();
qSort(channelsList);
ENGINE_DEBUG << "Engine::initialize channelsList" << channelsList;
QAudioFormat format;
format.setByteOrder(QAudioFormat::LittleEndian);
format.setCodec("audio/pcm");
format.setSampleSize(16);
format.setSampleType(QAudioFormat::SignedInt);
int sampleRate, channels;
foreach (sampleRate, sampleRatesList) {
if (foundSupportedFormat)
break;
format.setSampleRate(sampleRate);
foreach (channels, channelsList) {
format.setChannelCount(channels);
const bool inputSupport = m_generateTone ||
m_audioInputDevice.isFormatSupported(format);
const bool outputSupport = m_audioOutputDevice.isFormatSupported(format);
ENGINE_DEBUG << "Engine::initialize checking " << format
<< "input" << inputSupport
<< "output" << outputSupport;
if (inputSupport && outputSupport) {
foundSupportedFormat = true;
break;
}
}
}
if (!foundSupportedFormat)
format = QAudioFormat();
setFormat(format);
}
return foundSupportedFormat;
}
void Engine::stopRecording()
{
if (m_audioInput) {
m_audioInput->stop();
QCoreApplication::instance()->processEvents();
m_audioInput->disconnect();
}
m_audioInputIODevice = 0;
#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.channelCount();
if (changed)
emit formatChanged(m_format);
}
void Engine::setLevel(qreal rmsLevel, qreal peakLevel, int numSamples)
{
m_rmsLevel = rmsLevel;
m_peakLevel = peakLevel;
emit levelChanged(m_rmsLevel, m_peakLevel, numSamples);
}
#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

View File

@@ -0,0 +1,315 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <QAudioDeviceInfo>
#include <QAudioFormat>
#include <QBuffer>
#include <QByteArray>
#include <QDir>
#include <QObject>
#include <QVector>
#ifdef DUMP_CAPTURED_AUDIO
#define DUMP_DATA
#endif
#ifdef DUMP_SPECTRUM
#define DUMP_DATA
#endif
class FrequencySpectrum;
QT_BEGIN_NAMESPACE
class QAudioInput;
class QAudioOutput;
QT_END_NAMESPACE
/**
* This class interfaces with the QtMultimedia audio classes, and also with
* the SpectrumAnalyser class. Its role is to manage the capture and playback
* of audio data, meanwhile performing real-time analysis of the audio level
* and frequency spectrum.
*/
class Engine : public QObject
{
Q_OBJECT
public:
explicit Engine(QObject *parent = 0);
~Engine();
const QList<QAudioDeviceInfo> &availableAudioInputDevices() const
{ return m_availableAudioInputDevices; }
const QList<QAudioDeviceInfo> &availableAudioOutputDevices() const
{ return m_availableAudioOutputDevices; }
QAudio::Mode mode() const { return m_mode; }
QAudio::State state() const { return m_state; }
/**
* \return Current audio format
* \note May be QAudioFormat() if engine is not initialized
*/
const QAudioFormat& format() const { return m_format; }
/**
* Stop any ongoing recording or playback, and reset to ground state.
*/
void reset();
/**
* Load data from WAV file
*/
bool loadFile(const QString &fileName);
/**
* 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) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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,118 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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:
explicit 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,53 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 "mainwidget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setApplicationName("QtMultimedia spectrum analyzer");
MainWidget w;
w.show();
return app.exec();
}

View File

@@ -0,0 +1,447 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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)
{
QMessageBox::warning(this, heading, detail, QMessageBox::Close);
}
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,147 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <QAudio>
#include <QIcon>
#include <QWidget>
class Engine;
class FrequencySpectrum;
class LevelMeter;
class ProgressBar;
class SettingsDialog;
class Spectrograph;
class ToneGeneratorDialog;
class Waveform;
QT_BEGIN_NAMESPACE
class QAction;
class QAudioFormat;
class QLabel;
class QMenu;
class QPushButton;
QT_END_NAMESPACE
/**
* Main application widget, responsible for connecting the various UI
* elements to the Engine.
*/
class MainWidget : public QWidget
{
Q_OBJECT
public:
explicit 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) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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,75 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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:
explicit 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) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QLabel>
#include <QPushButton>
#include <QSlider>
#include <QSpinBox>
#include <QVBoxLayout>
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,89 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <QAudioDeviceInfo>
QT_BEGIN_NAMESPACE
class QComboBox;
class QCheckBox;
class QSlider;
class QSpinBox;
class QGridLayout;
QT_END_NAMESPACE
/**
* 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) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <QDebug>
#include <QMouseEvent>
#include <QPainter>
#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,98 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 "frequencyspectrum.h"
#include <QWidget>
/**
* 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:
explicit 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,146 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <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,276 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 "fftreal_wrapper.h"
#include <qmath.h>
#include <qmetatype.h>
#include <QAudioFormat>
#include <QThread>
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.channelCount() / 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.sampleRate()),
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,196 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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,90 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <QAudioFormat>
#include <qmath.h>
#include <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.channelCount() * 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.sampleRate();
// 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.channelCount(); ++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,58 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <qglobal.h>
#include "spectrum.h"
QT_BEGIN_NAMESPACE
class QAudioFormat;
class QByteArray;
QT_END_NAMESPACE
/**
* Generate a sine wave
*/
void generateTone(const SweptTone &tone, const QAudioFormat &format, QByteArray &buffer);
#endif // TONEGENERATOR_H

View File

@@ -0,0 +1,144 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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
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);
m_toneGeneratorFrequencyControl->setEnabled(false);
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);
m_toneGeneratorControl->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
dialogLayout->addWidget(m_toneGeneratorControl);
// 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,84 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <QAudioDeviceInfo>
#include <QDialog>
QT_BEGIN_NAMESPACE
class QCheckBox;
class QSlider;
class QSpinBox;
class QGridLayout;
QT_END_NAMESPACE
/**
* Dialog which controls the parameters of the tone generator.
*/
class ToneGeneratorDialog : public QDialog
{
Q_OBJECT
public:
explicit 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) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <QAudioFormat>
#include "utils.h"
qint64 audioDuration(const QAudioFormat &format, qint64 bytes)
{
return (bytes * 1000000) /
(format.sampleRate() * format.channelCount() * (format.sampleSize() / 8));
}
qint64 audioLength(const QAudioFormat &format, qint64 microSeconds)
{
qint64 result = (format.sampleRate() * format.channelCount() * (format.sampleSize() / 8))
* microSeconds / 1000000;
result -= result % (format.channelCount() * format.sampleSize());
return result;
}
qreal nyquistFrequency(const QAudioFormat &format)
{
return format.sampleRate() / 2;
}
QString formatToString(const QAudioFormat &format)
{
QString result;
if (QAudioFormat() != format) {
if (format.codec() == "audio/pcm") {
Q_ASSERT(format.sampleType() == QAudioFormat::SignedInt);
const QString formatEndian = (format.byteOrder() == QAudioFormat::LittleEndian)
? QString("LE") : QString("BE");
QString formatType;
switch (format.sampleType()) {
case QAudioFormat::SignedInt:
formatType = "signed";
break;
case QAudioFormat::UnSignedInt:
formatType = "unsigned";
break;
case QAudioFormat::Float:
formatType = "float";
break;
case QAudioFormat::Unknown:
formatType = "unknown";
break;
}
QString formatChannels = QString("%1 channels").arg(format.channelCount());
switch (format.channelCount()) {
case 1:
formatChannels = "mono";
break;
case 2:
formatChannels = "stereo";
break;
}
result = QString("%1 Hz %2 bit %3 %4 %5")
.arg(format.sampleRate())
.arg(format.sampleSize())
.arg(formatType)
.arg(formatEndian)
.arg(formatChannels);
} else {
result = format.codec();
}
}
return result;
}
bool isPCM(const QAudioFormat &format)
{
return (format.codec() == "audio/pcm");
}
bool isPCMS16LE(const QAudioFormat &format)
{
return isPCM(format) &&
format.sampleType() == QAudioFormat::SignedInt &&
format.sampleSize() == 16 &&
format.byteOrder() == QAudioFormat::LittleEndian;
}
const qint16 PCMS16MaxValue = 32767;
const quint16 PCMS16MaxAmplitude = 32768; // because minimum is -32768
qreal pcmToReal(qint16 pcm)
{
return qreal(pcm) / PCMS16MaxAmplitude;
}
qint16 realToPcm(qreal real)
{
return real * PCMS16MaxValue;
}

View File

@@ -0,0 +1,112 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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,437 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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.channelCount());
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.channelCount());
// 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.channelCount();
const int offset = reinterpret_cast<const char*>(ptr) - m_buffer.constData();
Q_ASSERT(offset >= 0);
Q_ASSERT(offset < m_bufferLength);
Q_UNUSED(offset);
const qint16 pcmValue = *ptr;
const qreal realValue = pcmToReal(pcmValue);
const int x = tilePixelOffset(i * 2 * m_format.channelCount());
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,202 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <QAudioFormat>
#include <QPixmap>
#include <QScopedPointer>
#include <QWidget>
/**
* 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:
explicit 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) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <qendian.h>
#include <QVector>
#include <QDebug>
#include "utils.h"
#include "wavfile.h"
struct chunk
{
char id[4];
quint32 size;
};
struct RIFFHeader
{
chunk descriptor; // "RIFF"
char type[4]; // "WAVE"
};
struct WAVEHeader
{
chunk descriptor;
quint16 audioFormat;
quint16 numChannels;
quint32 sampleRate;
quint32 byteRate;
quint16 blockAlign;
quint16 bitsPerSample;
};
struct DATAHeader
{
chunk descriptor;
};
struct CombinedHeader
{
RIFFHeader riff;
WAVEHeader wave;
};
WavFile::WavFile(QObject *parent)
: QFile(parent)
, m_headerLength(0)
{
}
bool WavFile::open(const QString &fileName)
{
close();
setFileName(fileName);
return QFile::open(QIODevice::ReadOnly) && readHeader();
}
const QAudioFormat &WavFile::fileFormat() const
{
return m_fileFormat;
}
qint64 WavFile::headerLength() const
{
return m_headerLength;
}
bool WavFile::readHeader()
{
seek(0);
CombinedHeader header;
bool result = read(reinterpret_cast<char *>(&header), sizeof(CombinedHeader)) == sizeof(CombinedHeader);
if (result) {
if ((memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0
|| memcmp(&header.riff.descriptor.id, "RIFX", 4) == 0)
&& memcmp(&header.riff.type, "WAVE", 4) == 0
&& memcmp(&header.wave.descriptor.id, "fmt ", 4) == 0
&& (header.wave.audioFormat == 1 || header.wave.audioFormat == 0)) {
// Read off remaining header information
DATAHeader dataHeader;
if (qFromLittleEndian<quint32>(header.wave.descriptor.size) > sizeof(WAVEHeader)) {
// Extended data available
quint16 extraFormatBytes;
if (peek((char*)&extraFormatBytes, sizeof(quint16)) != sizeof(quint16))
return false;
const qint64 throwAwayBytes = sizeof(quint16) + qFromLittleEndian<quint16>(extraFormatBytes);
if (read(throwAwayBytes).size() != throwAwayBytes)
return false;
}
if (read((char*)&dataHeader, sizeof(DATAHeader)) != sizeof(DATAHeader))
return false;
// Establish format
if (memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0)
m_fileFormat.setByteOrder(QAudioFormat::LittleEndian);
else
m_fileFormat.setByteOrder(QAudioFormat::BigEndian);
int bps = qFromLittleEndian<quint16>(header.wave.bitsPerSample);
m_fileFormat.setChannelCount(qFromLittleEndian<quint16>(header.wave.numChannels));
m_fileFormat.setCodec("audio/pcm");
m_fileFormat.setSampleRate(qFromLittleEndian<quint32>(header.wave.sampleRate));
m_fileFormat.setSampleSize(qFromLittleEndian<quint16>(header.wave.bitsPerSample));
m_fileFormat.setSampleType(bps == 8 ? QAudioFormat::UnSignedInt : QAudioFormat::SignedInt);
} else {
result = false;
}
}
m_headerLength = pos();
return result;
}

View File

@@ -0,0 +1,66 @@
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia Plc 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 <QObject>
#include <QFile>
#include <QAudioFormat>
class WavFile : public QFile
{
public:
WavFile(QObject *parent = 0);
using QFile::open;
bool open(const QString &fileName);
const QAudioFormat &fileFormat() const;
qint64 headerLength() const;
private:
bool readHeader();
private:
QAudioFormat m_fileFormat;
qint64 m_headerLength;
};
#endif // WAVFILE_H