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:
committed by
The Qt Project
parent
90c8ba233b
commit
6b4994c265
2
examples/multimedia/spectrum/app/.gitignore
vendored
Normal file
2
examples/multimedia/spectrum/app/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
spectrum
|
||||
spectrum.exe
|
||||
85
examples/multimedia/spectrum/app/app.pro
Normal file
85
examples/multimedia/spectrum/app/app.pro
Normal 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
|
||||
}
|
||||
}
|
||||
754
examples/multimedia/spectrum/app/engine.cpp
Normal file
754
examples/multimedia/spectrum/app/engine.cpp
Normal 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
|
||||
315
examples/multimedia/spectrum/app/engine.h
Normal file
315
examples/multimedia/spectrum/app/engine.h
Normal 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
|
||||
89
examples/multimedia/spectrum/app/frequencyspectrum.cpp
Normal file
89
examples/multimedia/spectrum/app/frequencyspectrum.cpp
Normal 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();
|
||||
}
|
||||
98
examples/multimedia/spectrum/app/frequencyspectrum.h
Normal file
98
examples/multimedia/spectrum/app/frequencyspectrum.h
Normal 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
|
||||
BIN
examples/multimedia/spectrum/app/images/record.png
Normal file
BIN
examples/multimedia/spectrum/app/images/record.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 670 B |
BIN
examples/multimedia/spectrum/app/images/settings.png
Normal file
BIN
examples/multimedia/spectrum/app/images/settings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
142
examples/multimedia/spectrum/app/levelmeter.cpp
Normal file
142
examples/multimedia/spectrum/app/levelmeter.cpp
Normal 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);
|
||||
}
|
||||
118
examples/multimedia/spectrum/app/levelmeter.h
Normal file
118
examples/multimedia/spectrum/app/levelmeter.h
Normal 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
|
||||
53
examples/multimedia/spectrum/app/main.cpp
Normal file
53
examples/multimedia/spectrum/app/main.cpp
Normal 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();
|
||||
}
|
||||
447
examples/multimedia/spectrum/app/mainwidget.cpp
Normal file
447
examples/multimedia/spectrum/app/mainwidget.cpp
Normal 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);
|
||||
}
|
||||
147
examples/multimedia/spectrum/app/mainwidget.h
Normal file
147
examples/multimedia/spectrum/app/mainwidget.h
Normal 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
|
||||
140
examples/multimedia/spectrum/app/progressbar.cpp
Normal file
140
examples/multimedia/spectrum/app/progressbar.cpp
Normal 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();
|
||||
}
|
||||
75
examples/multimedia/spectrum/app/progressbar.h
Normal file
75
examples/multimedia/spectrum/app/progressbar.h
Normal 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
|
||||
148
examples/multimedia/spectrum/app/settingsdialog.cpp
Normal file
148
examples/multimedia/spectrum/app/settingsdialog.cpp
Normal 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>();
|
||||
}
|
||||
|
||||
89
examples/multimedia/spectrum/app/settingsdialog.h
Normal file
89
examples/multimedia/spectrum/app/settingsdialog.h
Normal 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
|
||||
241
examples/multimedia/spectrum/app/spectrograph.cpp
Normal file
241
examples/multimedia/spectrum/app/spectrograph.cpp
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
98
examples/multimedia/spectrum/app/spectrograph.h
Normal file
98
examples/multimedia/spectrum/app/spectrograph.h
Normal 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
|
||||
146
examples/multimedia/spectrum/app/spectrum.h
Normal file
146
examples/multimedia/spectrum/app/spectrum.h
Normal 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
|
||||
|
||||
7
examples/multimedia/spectrum/app/spectrum.qrc
Normal file
7
examples/multimedia/spectrum/app/spectrum.qrc
Normal file
@@ -0,0 +1,7 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource>
|
||||
<file>images/record.png</file>
|
||||
<file>images/settings.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
276
examples/multimedia/spectrum/app/spectrumanalyser.cpp
Normal file
276
examples/multimedia/spectrum/app/spectrumanalyser.cpp
Normal 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;
|
||||
}
|
||||
196
examples/multimedia/spectrum/app/spectrumanalyser.h
Normal file
196
examples/multimedia/spectrum/app/spectrumanalyser.h
Normal 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
|
||||
|
||||
90
examples/multimedia/spectrum/app/tonegenerator.cpp
Normal file
90
examples/multimedia/spectrum/app/tonegenerator.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
58
examples/multimedia/spectrum/app/tonegenerator.h
Normal file
58
examples/multimedia/spectrum/app/tonegenerator.h
Normal 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
|
||||
|
||||
144
examples/multimedia/spectrum/app/tonegeneratordialog.cpp
Normal file
144
examples/multimedia/spectrum/app/tonegeneratordialog.cpp
Normal 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);
|
||||
}
|
||||
84
examples/multimedia/spectrum/app/tonegeneratordialog.h
Normal file
84
examples/multimedia/spectrum/app/tonegeneratordialog.h
Normal 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
|
||||
139
examples/multimedia/spectrum/app/utils.cpp
Normal file
139
examples/multimedia/spectrum/app/utils.cpp
Normal 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;
|
||||
}
|
||||
112
examples/multimedia/spectrum/app/utils.h
Normal file
112
examples/multimedia/spectrum/app/utils.h
Normal 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
|
||||
437
examples/multimedia/spectrum/app/waveform.cpp
Normal file
437
examples/multimedia/spectrum/app/waveform.cpp
Normal 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;
|
||||
}
|
||||
|
||||
202
examples/multimedia/spectrum/app/waveform.h
Normal file
202
examples/multimedia/spectrum/app/waveform.h
Normal 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
|
||||
151
examples/multimedia/spectrum/app/wavfile.cpp
Normal file
151
examples/multimedia/spectrum/app/wavfile.cpp
Normal 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;
|
||||
}
|
||||
66
examples/multimedia/spectrum/app/wavfile.h
Normal file
66
examples/multimedia/spectrum/app/wavfile.h
Normal 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
|
||||
Reference in New Issue
Block a user