Initial import from the monolithic Qt.
This is the beginning of revision history for this module. If you want to look at revision history older than this, please refer to the Qt Git wiki for how to use Git history grafting. At the time of writing, this wiki is located here: http://qt.gitorious.org/qt/pages/GitIntroductionWithQt If you have already performed the grafting and you don't see any history beyond this commit, try running "git log" with the "--follow" argument. Branched from the monolithic repo, Qt master branch, at commit 896db169ea224deb96c59ce8af800d019de63f12
This commit is contained in:
2
demos/spectrum/app/.gitignore
vendored
Normal file
2
demos/spectrum/app/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
spectrum
|
||||
spectrum.exe
|
||||
118
demos/spectrum/app/app.pro
Normal file
118
demos/spectrum/app/app.pro
Normal file
@@ -0,0 +1,118 @@
|
||||
include(../spectrum.pri)
|
||||
|
||||
static: error(This application cannot be statically linked to the fftreal library)
|
||||
|
||||
TEMPLATE = app
|
||||
|
||||
TARGET = spectrum
|
||||
|
||||
QT += multimedia
|
||||
|
||||
SOURCES += main.cpp \
|
||||
engine.cpp \
|
||||
frequencyspectrum.cpp \
|
||||
levelmeter.cpp \
|
||||
mainwidget.cpp \
|
||||
progressbar.cpp \
|
||||
settingsdialog.cpp \
|
||||
spectrograph.cpp \
|
||||
spectrumanalyser.cpp \
|
||||
tonegenerator.cpp \
|
||||
tonegeneratordialog.cpp \
|
||||
utils.cpp \
|
||||
waveform.cpp \
|
||||
wavfile.cpp
|
||||
|
||||
HEADERS += engine.h \
|
||||
frequencyspectrum.h \
|
||||
levelmeter.h \
|
||||
mainwidget.h \
|
||||
progressbar.h \
|
||||
settingsdialog.h \
|
||||
spectrograph.h \
|
||||
spectrum.h \
|
||||
spectrumanalyser.h \
|
||||
tonegenerator.h \
|
||||
tonegeneratordialog.h \
|
||||
utils.h \
|
||||
waveform.h \
|
||||
wavfile.h
|
||||
|
||||
fftreal_dir = ../3rdparty/fftreal
|
||||
|
||||
INCLUDEPATH += $${fftreal_dir}
|
||||
|
||||
RESOURCES = spectrum.qrc
|
||||
|
||||
symbian {
|
||||
# Platform security capability required to record audio on Symbian
|
||||
TARGET.CAPABILITY = UserEnvironment
|
||||
|
||||
# Provide unique ID for the generated binary, required by Symbian OS
|
||||
TARGET.UID3 = 0xA000E402
|
||||
}
|
||||
|
||||
|
||||
# Dynamic linkage against FFTReal DLL
|
||||
!contains(DEFINES, DISABLE_FFT) {
|
||||
symbian {
|
||||
# Must explicitly add the .dll suffix to ensure dynamic linkage
|
||||
LIBS += -lfftreal.dll
|
||||
QMAKE_LIBDIR += $${fftreal_dir}
|
||||
} else {
|
||||
macx {
|
||||
# Link to fftreal framework
|
||||
LIBS += -F$${fftreal_dir}
|
||||
LIBS += -framework fftreal
|
||||
} else {
|
||||
LIBS += -L..$${spectrum_build_dir}
|
||||
LIBS += -lfftreal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Install
|
||||
|
||||
sources.files = $$SOURCES $$HEADERS $$RESOURCES app.pro
|
||||
sources.path = $$[QT_INSTALL_DEMOS]/qtmultimedia/spectrum/app
|
||||
images.files += images/record.png images/settings.png
|
||||
images.path = $$[QT_INSTALL_DEMOS]/qtmultimedia/spectrum/app/images
|
||||
INSTALLS += sources images
|
||||
|
||||
# Deployment
|
||||
|
||||
symbian {
|
||||
include($$QT_SOURCE_TREE/demos/symbianpkgrules.pri)
|
||||
|
||||
!contains(DEFINES, DISABLE_FFT) {
|
||||
# Include FFTReal DLL in the SIS file
|
||||
fftreal.files = ../fftreal.dll
|
||||
fftreal.path = !:/sys/bin
|
||||
DEPLOYMENT += fftreal
|
||||
}
|
||||
} else {
|
||||
DESTDIR = ..$${spectrum_build_dir}
|
||||
macx {
|
||||
!contains(DEFINES, DISABLE_FFT) {
|
||||
# Relocate fftreal.framework into spectrum.app bundle
|
||||
framework_dir = ../spectrum.app/Contents/Frameworks
|
||||
framework_name = fftreal.framework/Versions/1/fftreal
|
||||
QMAKE_POST_LINK = \
|
||||
mkdir -p $${framework_dir} &&\
|
||||
rm -rf $${framework_dir}/fftreal.framework &&\
|
||||
cp -R $${fftreal_dir}/fftreal.framework $${framework_dir} &&\
|
||||
install_name_tool -id @executable_path/../Frameworks/$${framework_name} \
|
||||
$${framework_dir}/$${framework_name} &&\
|
||||
install_name_tool -change $${framework_name} \
|
||||
@executable_path/../Frameworks/$${framework_name} \
|
||||
../spectrum.app/Contents/MacOS/spectrum
|
||||
}
|
||||
} else {
|
||||
linux-g++*: {
|
||||
# Provide relative path from application to fftreal library
|
||||
QMAKE_LFLAGS += -Wl,--rpath=\\\$\$ORIGIN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
766
demos/spectrum/app/engine.cpp
Normal file
766
demos/spectrum/app/engine.cpp
Normal file
@@ -0,0 +1,766 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "engine.h"
|
||||
#include "tonegenerator.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QMetaObject>
|
||||
#include <QSet>
|
||||
#include <QtMultimedia/QAudioInput>
|
||||
#include <QtMultimedia/QAudioOutput>
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
#include <QFile>
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Constants
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const qint64 BufferDurationUs = 10 * 1000000;
|
||||
const int NotifyIntervalMs = 100;
|
||||
|
||||
// Size of the level calculation window in microseconds
|
||||
const int LevelWindowUs = 0.1 * 1000000;
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helper functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
QDebug& operator<<(QDebug &debug, const QAudioFormat &format)
|
||||
{
|
||||
debug << format.frequency() << "Hz"
|
||||
<< format.channels() << "channels";
|
||||
return debug;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Constructor and destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
Engine::Engine(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_mode(QAudio::AudioInput)
|
||||
, m_state(QAudio::StoppedState)
|
||||
, m_generateTone(false)
|
||||
, m_file(0)
|
||||
, m_analysisFile(0)
|
||||
, m_availableAudioInputDevices
|
||||
(QAudioDeviceInfo::availableDevices(QAudio::AudioInput))
|
||||
, m_audioInputDevice(QAudioDeviceInfo::defaultInputDevice())
|
||||
, m_audioInput(0)
|
||||
, m_audioInputIODevice(0)
|
||||
, m_recordPosition(0)
|
||||
, m_availableAudioOutputDevices
|
||||
(QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
|
||||
, m_audioOutputDevice(QAudioDeviceInfo::defaultOutputDevice())
|
||||
, m_audioOutput(0)
|
||||
, m_playPosition(0)
|
||||
, m_bufferPosition(0)
|
||||
, m_bufferLength(0)
|
||||
, m_dataLength(0)
|
||||
, m_levelBufferLength(0)
|
||||
, m_rmsLevel(0.0)
|
||||
, m_peakLevel(0.0)
|
||||
, m_spectrumBufferLength(0)
|
||||
, m_spectrumAnalyser()
|
||||
, m_spectrumPosition(0)
|
||||
, m_count(0)
|
||||
{
|
||||
qRegisterMetaType<FrequencySpectrum>("FrequencySpectrum");
|
||||
qRegisterMetaType<WindowFunction>("WindowFunction");
|
||||
CHECKED_CONNECT(&m_spectrumAnalyser,
|
||||
SIGNAL(spectrumChanged(FrequencySpectrum)),
|
||||
this,
|
||||
SLOT(spectrumChanged(FrequencySpectrum)));
|
||||
|
||||
initialize();
|
||||
|
||||
#ifdef DUMP_DATA
|
||||
createOutputDir();
|
||||
#endif
|
||||
|
||||
#ifdef DUMP_SPECTRUM
|
||||
m_spectrumAnalyser.setOutputPath(outputPath());
|
||||
#endif
|
||||
}
|
||||
|
||||
Engine::~Engine()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool Engine::loadFile(const QString &fileName)
|
||||
{
|
||||
reset();
|
||||
bool result = false;
|
||||
Q_ASSERT(!m_generateTone);
|
||||
Q_ASSERT(!m_file);
|
||||
Q_ASSERT(!fileName.isEmpty());
|
||||
m_file = new WavFile(this);
|
||||
if (m_file->open(fileName)) {
|
||||
if (isPCMS16LE(m_file->fileFormat())) {
|
||||
result = initialize();
|
||||
} else {
|
||||
emit errorMessage(tr("Audio format not supported"),
|
||||
formatToString(m_file->fileFormat()));
|
||||
}
|
||||
} else {
|
||||
emit errorMessage(tr("Could not open file"), fileName);
|
||||
}
|
||||
if (result) {
|
||||
m_analysisFile = new WavFile(this);
|
||||
m_analysisFile->open(fileName);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Engine::generateTone(const Tone &tone)
|
||||
{
|
||||
reset();
|
||||
Q_ASSERT(!m_generateTone);
|
||||
Q_ASSERT(!m_file);
|
||||
m_generateTone = true;
|
||||
m_tone = tone;
|
||||
ENGINE_DEBUG << "Engine::generateTone"
|
||||
<< "startFreq" << m_tone.startFreq
|
||||
<< "endFreq" << m_tone.endFreq
|
||||
<< "amp" << m_tone.amplitude;
|
||||
return initialize();
|
||||
}
|
||||
|
||||
bool Engine::generateSweptTone(qreal amplitude)
|
||||
{
|
||||
Q_ASSERT(!m_generateTone);
|
||||
Q_ASSERT(!m_file);
|
||||
m_generateTone = true;
|
||||
m_tone.startFreq = 1;
|
||||
m_tone.endFreq = 0;
|
||||
m_tone.amplitude = amplitude;
|
||||
ENGINE_DEBUG << "Engine::generateSweptTone"
|
||||
<< "startFreq" << m_tone.startFreq
|
||||
<< "amp" << m_tone.amplitude;
|
||||
return initialize();
|
||||
}
|
||||
|
||||
bool Engine::initializeRecord()
|
||||
{
|
||||
reset();
|
||||
ENGINE_DEBUG << "Engine::initializeRecord";
|
||||
Q_ASSERT(!m_generateTone);
|
||||
Q_ASSERT(!m_file);
|
||||
m_generateTone = false;
|
||||
m_tone = SweptTone();
|
||||
return initialize();
|
||||
}
|
||||
|
||||
qint64 Engine::bufferLength() const
|
||||
{
|
||||
return m_file ? m_file->size() : m_bufferLength;
|
||||
}
|
||||
|
||||
void Engine::setWindowFunction(WindowFunction type)
|
||||
{
|
||||
m_spectrumAnalyser.setWindowFunction(type);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public slots
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void Engine::startRecording()
|
||||
{
|
||||
if (m_audioInput) {
|
||||
if (QAudio::AudioInput == m_mode &&
|
||||
QAudio::SuspendedState == m_state) {
|
||||
m_audioInput->resume();
|
||||
} else {
|
||||
m_spectrumAnalyser.cancelCalculation();
|
||||
spectrumChanged(0, 0, FrequencySpectrum());
|
||||
|
||||
m_buffer.fill(0);
|
||||
setRecordPosition(0, true);
|
||||
stopPlayback();
|
||||
m_mode = QAudio::AudioInput;
|
||||
CHECKED_CONNECT(m_audioInput, SIGNAL(stateChanged(QAudio::State)),
|
||||
this, SLOT(audioStateChanged(QAudio::State)));
|
||||
CHECKED_CONNECT(m_audioInput, SIGNAL(notify()),
|
||||
this, SLOT(audioNotify()));
|
||||
m_count = 0;
|
||||
m_dataLength = 0;
|
||||
emit dataLengthChanged(0);
|
||||
m_audioInputIODevice = m_audioInput->start();
|
||||
CHECKED_CONNECT(m_audioInputIODevice, SIGNAL(readyRead()),
|
||||
this, SLOT(audioDataReady()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::startPlayback()
|
||||
{
|
||||
if (m_audioOutput) {
|
||||
if (QAudio::AudioOutput == m_mode &&
|
||||
QAudio::SuspendedState == m_state) {
|
||||
#ifdef Q_OS_WIN
|
||||
// The Windows backend seems to internally go back into ActiveState
|
||||
// while still returning SuspendedState, so to ensure that it doesn't
|
||||
// ignore the resume() call, we first re-suspend
|
||||
m_audioOutput->suspend();
|
||||
#endif
|
||||
m_audioOutput->resume();
|
||||
} else {
|
||||
m_spectrumAnalyser.cancelCalculation();
|
||||
spectrumChanged(0, 0, FrequencySpectrum());
|
||||
setPlayPosition(0, true);
|
||||
stopRecording();
|
||||
m_mode = QAudio::AudioOutput;
|
||||
CHECKED_CONNECT(m_audioOutput, SIGNAL(stateChanged(QAudio::State)),
|
||||
this, SLOT(audioStateChanged(QAudio::State)));
|
||||
CHECKED_CONNECT(m_audioOutput, SIGNAL(notify()),
|
||||
this, SLOT(audioNotify()));
|
||||
m_count = 0;
|
||||
if (m_file) {
|
||||
m_file->seek(0);
|
||||
m_bufferPosition = 0;
|
||||
m_dataLength = 0;
|
||||
m_audioOutput->start(m_file);
|
||||
} else {
|
||||
m_audioOutputIODevice.close();
|
||||
m_audioOutputIODevice.setBuffer(&m_buffer);
|
||||
m_audioOutputIODevice.open(QIODevice::ReadOnly);
|
||||
m_audioOutput->start(&m_audioOutputIODevice);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::suspend()
|
||||
{
|
||||
if (QAudio::ActiveState == m_state ||
|
||||
QAudio::IdleState == m_state) {
|
||||
switch (m_mode) {
|
||||
case QAudio::AudioInput:
|
||||
m_audioInput->suspend();
|
||||
break;
|
||||
case QAudio::AudioOutput:
|
||||
m_audioOutput->suspend();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::setAudioInputDevice(const QAudioDeviceInfo &device)
|
||||
{
|
||||
if (device.deviceName() != m_audioInputDevice.deviceName()) {
|
||||
m_audioInputDevice = device;
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::setAudioOutputDevice(const QAudioDeviceInfo &device)
|
||||
{
|
||||
if (device.deviceName() != m_audioOutputDevice.deviceName()) {
|
||||
m_audioOutputDevice = device;
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Private slots
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void Engine::audioNotify()
|
||||
{
|
||||
switch (m_mode) {
|
||||
case QAudio::AudioInput: {
|
||||
const qint64 recordPosition = qMin(m_bufferLength, audioLength(m_format, m_audioInput->processedUSecs()));
|
||||
setRecordPosition(recordPosition);
|
||||
const qint64 levelPosition = m_dataLength - m_levelBufferLength;
|
||||
if (levelPosition >= 0)
|
||||
calculateLevel(levelPosition, m_levelBufferLength);
|
||||
if (m_dataLength >= m_spectrumBufferLength) {
|
||||
const qint64 spectrumPosition = m_dataLength - m_spectrumBufferLength;
|
||||
calculateSpectrum(spectrumPosition);
|
||||
}
|
||||
emit bufferChanged(0, m_dataLength, m_buffer);
|
||||
}
|
||||
break;
|
||||
case QAudio::AudioOutput: {
|
||||
const qint64 playPosition = audioLength(m_format, m_audioOutput->processedUSecs());
|
||||
setPlayPosition(qMin(bufferLength(), playPosition));
|
||||
const qint64 levelPosition = playPosition - m_levelBufferLength;
|
||||
const qint64 spectrumPosition = playPosition - m_spectrumBufferLength;
|
||||
if (m_file) {
|
||||
if (levelPosition > m_bufferPosition ||
|
||||
spectrumPosition > m_bufferPosition ||
|
||||
qMax(m_levelBufferLength, m_spectrumBufferLength) > m_dataLength) {
|
||||
m_bufferPosition = 0;
|
||||
m_dataLength = 0;
|
||||
// Data needs to be read into m_buffer in order to be analysed
|
||||
const qint64 readPos = qMax(qint64(0), qMin(levelPosition, spectrumPosition));
|
||||
const qint64 readEnd = qMin(m_analysisFile->size(), qMax(levelPosition + m_levelBufferLength, spectrumPosition + m_spectrumBufferLength));
|
||||
const qint64 readLen = readEnd - readPos + audioLength(m_format, WaveformWindowDuration);
|
||||
qDebug() << "Engine::audioNotify [1]"
|
||||
<< "analysisFileSize" << m_analysisFile->size()
|
||||
<< "readPos" << readPos
|
||||
<< "readLen" << readLen;
|
||||
if (m_analysisFile->seek(readPos + m_analysisFile->headerLength())) {
|
||||
m_buffer.resize(readLen);
|
||||
m_bufferPosition = readPos;
|
||||
m_dataLength = m_analysisFile->read(m_buffer.data(), readLen);
|
||||
qDebug() << "Engine::audioNotify [2]" << "bufferPosition" << m_bufferPosition << "dataLength" << m_dataLength;
|
||||
} else {
|
||||
qDebug() << "Engine::audioNotify [2]" << "file seek error";
|
||||
}
|
||||
emit bufferChanged(m_bufferPosition, m_dataLength, m_buffer);
|
||||
}
|
||||
} else {
|
||||
if (playPosition >= m_dataLength)
|
||||
stopPlayback();
|
||||
}
|
||||
if (levelPosition >= 0 && levelPosition + m_levelBufferLength < m_bufferPosition + m_dataLength)
|
||||
calculateLevel(levelPosition, m_levelBufferLength);
|
||||
if (spectrumPosition >= 0 && spectrumPosition + m_spectrumBufferLength < m_bufferPosition + m_dataLength)
|
||||
calculateSpectrum(spectrumPosition);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::audioStateChanged(QAudio::State state)
|
||||
{
|
||||
ENGINE_DEBUG << "Engine::audioStateChanged from" << m_state
|
||||
<< "to" << state;
|
||||
|
||||
if (QAudio::IdleState == state && m_file && m_file->pos() == m_file->size()) {
|
||||
stopPlayback();
|
||||
} else {
|
||||
if (QAudio::StoppedState == state) {
|
||||
// Check error
|
||||
QAudio::Error error = QAudio::NoError;
|
||||
switch (m_mode) {
|
||||
case QAudio::AudioInput:
|
||||
error = m_audioInput->error();
|
||||
break;
|
||||
case QAudio::AudioOutput:
|
||||
error = m_audioOutput->error();
|
||||
break;
|
||||
}
|
||||
if (QAudio::NoError != error) {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
setState(state);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::audioDataReady()
|
||||
{
|
||||
Q_ASSERT(0 == m_bufferPosition);
|
||||
const qint64 bytesReady = m_audioInput->bytesReady();
|
||||
const qint64 bytesSpace = m_buffer.size() - m_dataLength;
|
||||
const qint64 bytesToRead = qMin(bytesReady, bytesSpace);
|
||||
|
||||
const qint64 bytesRead = m_audioInputIODevice->read(
|
||||
m_buffer.data() + m_dataLength,
|
||||
bytesToRead);
|
||||
|
||||
if (bytesRead) {
|
||||
m_dataLength += bytesRead;
|
||||
emit dataLengthChanged(dataLength());
|
||||
}
|
||||
|
||||
if (m_buffer.size() == m_dataLength)
|
||||
stopRecording();
|
||||
}
|
||||
|
||||
void Engine::spectrumChanged(const FrequencySpectrum &spectrum)
|
||||
{
|
||||
ENGINE_DEBUG << "Engine::spectrumChanged" << "pos" << m_spectrumPosition;
|
||||
emit spectrumChanged(m_spectrumPosition, m_spectrumBufferLength, spectrum);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Private functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void Engine::resetAudioDevices()
|
||||
{
|
||||
delete m_audioInput;
|
||||
m_audioInput = 0;
|
||||
m_audioInputIODevice = 0;
|
||||
setRecordPosition(0);
|
||||
delete m_audioOutput;
|
||||
m_audioOutput = 0;
|
||||
setPlayPosition(0);
|
||||
m_spectrumPosition = 0;
|
||||
setLevel(0.0, 0.0, 0);
|
||||
}
|
||||
|
||||
void Engine::reset()
|
||||
{
|
||||
stopRecording();
|
||||
stopPlayback();
|
||||
setState(QAudio::AudioInput, QAudio::StoppedState);
|
||||
setFormat(QAudioFormat());
|
||||
m_generateTone = false;
|
||||
delete m_file;
|
||||
m_file = 0;
|
||||
delete m_analysisFile;
|
||||
m_analysisFile = 0;
|
||||
m_buffer.clear();
|
||||
m_bufferPosition = 0;
|
||||
m_bufferLength = 0;
|
||||
m_dataLength = 0;
|
||||
emit dataLengthChanged(0);
|
||||
resetAudioDevices();
|
||||
}
|
||||
|
||||
bool Engine::initialize()
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
QAudioFormat format = m_format;
|
||||
|
||||
if (selectFormat()) {
|
||||
if (m_format != format) {
|
||||
resetAudioDevices();
|
||||
if (m_file) {
|
||||
emit bufferLengthChanged(bufferLength());
|
||||
emit dataLengthChanged(dataLength());
|
||||
emit bufferChanged(0, 0, m_buffer);
|
||||
setRecordPosition(bufferLength());
|
||||
result = true;
|
||||
} else {
|
||||
m_bufferLength = audioLength(m_format, BufferDurationUs);
|
||||
m_buffer.resize(m_bufferLength);
|
||||
m_buffer.fill(0);
|
||||
emit bufferLengthChanged(bufferLength());
|
||||
if (m_generateTone) {
|
||||
if (0 == m_tone.endFreq) {
|
||||
const qreal nyquist = nyquistFrequency(m_format);
|
||||
m_tone.endFreq = qMin(qreal(SpectrumHighFreq), nyquist);
|
||||
}
|
||||
// Call function defined in utils.h, at global scope
|
||||
::generateTone(m_tone, m_format, m_buffer);
|
||||
m_dataLength = m_bufferLength;
|
||||
emit dataLengthChanged(dataLength());
|
||||
emit bufferChanged(0, m_dataLength, m_buffer);
|
||||
setRecordPosition(m_bufferLength);
|
||||
result = true;
|
||||
} else {
|
||||
emit bufferChanged(0, 0, m_buffer);
|
||||
m_audioInput = new QAudioInput(m_audioInputDevice, m_format, this);
|
||||
m_audioInput->setNotifyInterval(NotifyIntervalMs);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
m_audioOutput = new QAudioOutput(m_audioOutputDevice, m_format, this);
|
||||
m_audioOutput->setNotifyInterval(NotifyIntervalMs);
|
||||
}
|
||||
} else {
|
||||
if (m_file)
|
||||
emit errorMessage(tr("Audio format not supported"),
|
||||
formatToString(m_format));
|
||||
else if (m_generateTone)
|
||||
emit errorMessage(tr("No suitable format found"), "");
|
||||
else
|
||||
emit errorMessage(tr("No common input / output format found"), "");
|
||||
}
|
||||
|
||||
ENGINE_DEBUG << "Engine::initialize" << "m_bufferLength" << m_bufferLength;
|
||||
ENGINE_DEBUG << "Engine::initialize" << "m_dataLength" << m_dataLength;
|
||||
ENGINE_DEBUG << "Engine::initialize" << "format" << m_format;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Engine::selectFormat()
|
||||
{
|
||||
bool foundSupportedFormat = false;
|
||||
|
||||
if (m_file || QAudioFormat() != m_format) {
|
||||
QAudioFormat format = m_format;
|
||||
if (m_file)
|
||||
// Header is read from the WAV file; just need to check whether
|
||||
// it is supported by the audio output device
|
||||
format = m_file->fileFormat();
|
||||
if (m_audioOutputDevice.isFormatSupported(format)) {
|
||||
setFormat(format);
|
||||
foundSupportedFormat = true;
|
||||
}
|
||||
} else {
|
||||
|
||||
QList<int> frequenciesList;
|
||||
#ifdef Q_OS_WIN
|
||||
// The Windows audio backend does not correctly report format support
|
||||
// (see QTBUG-9100). Furthermore, although the audio subsystem captures
|
||||
// at 11025Hz, the resulting audio is corrupted.
|
||||
frequenciesList += 8000;
|
||||
#endif
|
||||
|
||||
if (!m_generateTone)
|
||||
frequenciesList += m_audioInputDevice.supportedFrequencies();
|
||||
|
||||
frequenciesList += m_audioOutputDevice.supportedFrequencies();
|
||||
frequenciesList = frequenciesList.toSet().toList(); // remove duplicates
|
||||
qSort(frequenciesList);
|
||||
ENGINE_DEBUG << "Engine::initialize frequenciesList" << frequenciesList;
|
||||
|
||||
QList<int> channelsList;
|
||||
channelsList += m_audioInputDevice.supportedChannels();
|
||||
channelsList += m_audioOutputDevice.supportedChannels();
|
||||
channelsList = channelsList.toSet().toList();
|
||||
qSort(channelsList);
|
||||
ENGINE_DEBUG << "Engine::initialize channelsList" << channelsList;
|
||||
|
||||
QAudioFormat format;
|
||||
format.setByteOrder(QAudioFormat::LittleEndian);
|
||||
format.setCodec("audio/pcm");
|
||||
format.setSampleSize(16);
|
||||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
int frequency, channels;
|
||||
foreach (frequency, frequenciesList) {
|
||||
if (foundSupportedFormat)
|
||||
break;
|
||||
format.setFrequency(frequency);
|
||||
foreach (channels, channelsList) {
|
||||
format.setChannels(channels);
|
||||
const bool inputSupport = m_generateTone ||
|
||||
m_audioInputDevice.isFormatSupported(format);
|
||||
const bool outputSupport = m_audioOutputDevice.isFormatSupported(format);
|
||||
ENGINE_DEBUG << "Engine::initialize checking " << format
|
||||
<< "input" << inputSupport
|
||||
<< "output" << outputSupport;
|
||||
if (inputSupport && outputSupport) {
|
||||
foundSupportedFormat = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundSupportedFormat)
|
||||
format = QAudioFormat();
|
||||
|
||||
setFormat(format);
|
||||
}
|
||||
|
||||
return foundSupportedFormat;
|
||||
}
|
||||
|
||||
void Engine::stopRecording()
|
||||
{
|
||||
if (m_audioInput) {
|
||||
m_audioInput->stop();
|
||||
QCoreApplication::instance()->processEvents();
|
||||
m_audioInput->disconnect();
|
||||
}
|
||||
m_audioInputIODevice = 0;
|
||||
|
||||
#ifdef DUMP_AUDIO
|
||||
dumpData();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Engine::stopPlayback()
|
||||
{
|
||||
if (m_audioOutput) {
|
||||
m_audioOutput->stop();
|
||||
QCoreApplication::instance()->processEvents();
|
||||
m_audioOutput->disconnect();
|
||||
setPlayPosition(0);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::setState(QAudio::State state)
|
||||
{
|
||||
const bool changed = (m_state != state);
|
||||
m_state = state;
|
||||
if (changed)
|
||||
emit stateChanged(m_mode, m_state);
|
||||
}
|
||||
|
||||
void Engine::setState(QAudio::Mode mode, QAudio::State state)
|
||||
{
|
||||
const bool changed = (m_mode != mode || m_state != state);
|
||||
m_mode = mode;
|
||||
m_state = state;
|
||||
if (changed)
|
||||
emit stateChanged(m_mode, m_state);
|
||||
}
|
||||
|
||||
void Engine::setRecordPosition(qint64 position, bool forceEmit)
|
||||
{
|
||||
const bool changed = (m_recordPosition != position);
|
||||
m_recordPosition = position;
|
||||
if (changed || forceEmit)
|
||||
emit recordPositionChanged(m_recordPosition);
|
||||
}
|
||||
|
||||
void Engine::setPlayPosition(qint64 position, bool forceEmit)
|
||||
{
|
||||
const bool changed = (m_playPosition != position);
|
||||
m_playPosition = position;
|
||||
if (changed || forceEmit)
|
||||
emit playPositionChanged(m_playPosition);
|
||||
}
|
||||
|
||||
void Engine::calculateLevel(qint64 position, qint64 length)
|
||||
{
|
||||
#ifdef DISABLE_LEVEL
|
||||
Q_UNUSED(position)
|
||||
Q_UNUSED(length)
|
||||
#else
|
||||
Q_ASSERT(position + length <= m_bufferPosition + m_dataLength);
|
||||
|
||||
qreal peakLevel = 0.0;
|
||||
|
||||
qreal sum = 0.0;
|
||||
const char *ptr = m_buffer.constData() + position - m_bufferPosition;
|
||||
const char *const end = ptr + length;
|
||||
while (ptr < end) {
|
||||
const qint16 value = *reinterpret_cast<const qint16*>(ptr);
|
||||
const qreal fracValue = pcmToReal(value);
|
||||
peakLevel = qMax(peakLevel, fracValue);
|
||||
sum += fracValue * fracValue;
|
||||
ptr += 2;
|
||||
}
|
||||
const int numSamples = length / 2;
|
||||
qreal rmsLevel = sqrt(sum / numSamples);
|
||||
|
||||
rmsLevel = qMax(qreal(0.0), rmsLevel);
|
||||
rmsLevel = qMin(qreal(1.0), rmsLevel);
|
||||
setLevel(rmsLevel, peakLevel, numSamples);
|
||||
|
||||
ENGINE_DEBUG << "Engine::calculateLevel" << "pos" << position << "len" << length
|
||||
<< "rms" << rmsLevel << "peak" << peakLevel;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Engine::calculateSpectrum(qint64 position)
|
||||
{
|
||||
#ifdef DISABLE_SPECTRUM
|
||||
Q_UNUSED(position)
|
||||
#else
|
||||
Q_ASSERT(position + m_spectrumBufferLength <= m_bufferPosition + m_dataLength);
|
||||
Q_ASSERT(0 == m_spectrumBufferLength % 2); // constraint of FFT algorithm
|
||||
|
||||
// QThread::currentThread is marked 'for internal use only', but
|
||||
// we're only using it for debug output here, so it's probably OK :)
|
||||
ENGINE_DEBUG << "Engine::calculateSpectrum" << QThread::currentThread()
|
||||
<< "count" << m_count << "pos" << position << "len" << m_spectrumBufferLength
|
||||
<< "spectrumAnalyser.isReady" << m_spectrumAnalyser.isReady();
|
||||
|
||||
if(m_spectrumAnalyser.isReady()) {
|
||||
m_spectrumBuffer = QByteArray::fromRawData(m_buffer.constData() + position - m_bufferPosition,
|
||||
m_spectrumBufferLength);
|
||||
m_spectrumPosition = position;
|
||||
m_spectrumAnalyser.calculate(m_spectrumBuffer, m_format);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Engine::setFormat(const QAudioFormat &format)
|
||||
{
|
||||
const bool changed = (format != m_format);
|
||||
m_format = format;
|
||||
m_levelBufferLength = audioLength(m_format, LevelWindowUs);
|
||||
m_spectrumBufferLength = SpectrumLengthSamples *
|
||||
(m_format.sampleSize() / 8) * m_format.channels();
|
||||
if (changed)
|
||||
emit formatChanged(m_format);
|
||||
}
|
||||
|
||||
void Engine::setLevel(qreal rmsLevel, qreal peakLevel, int numSamples)
|
||||
{
|
||||
m_rmsLevel = rmsLevel;
|
||||
m_peakLevel = peakLevel;
|
||||
emit levelChanged(m_rmsLevel, m_peakLevel, numSamples);
|
||||
}
|
||||
|
||||
#ifdef DUMP_DATA
|
||||
void Engine::createOutputDir()
|
||||
{
|
||||
m_outputDir.setPath("output");
|
||||
|
||||
// Ensure output directory exists and is empty
|
||||
if (m_outputDir.exists()) {
|
||||
const QStringList files = m_outputDir.entryList(QDir::Files);
|
||||
QString file;
|
||||
foreach (file, files)
|
||||
m_outputDir.remove(file);
|
||||
} else {
|
||||
QDir::current().mkdir("output");
|
||||
}
|
||||
}
|
||||
#endif // DUMP_DATA
|
||||
|
||||
#ifdef DUMP_AUDIO
|
||||
void Engine::dumpData()
|
||||
{
|
||||
const QString txtFileName = m_outputDir.filePath("data.txt");
|
||||
QFile txtFile(txtFileName);
|
||||
txtFile.open(QFile::WriteOnly | QFile::Text);
|
||||
QTextStream stream(&txtFile);
|
||||
const qint16 *ptr = reinterpret_cast<const qint16*>(m_buffer.constData());
|
||||
const int numSamples = m_dataLength / (2 * m_format.channels());
|
||||
for (int i=0; i<numSamples; ++i) {
|
||||
stream << i << "\t" << *ptr << "\n";
|
||||
ptr += m_format.channels();
|
||||
}
|
||||
|
||||
const QString pcmFileName = m_outputDir.filePath("data.pcm");
|
||||
QFile pcmFile(pcmFileName);
|
||||
pcmFile.open(QFile::WriteOnly);
|
||||
pcmFile.write(m_buffer.constData(), m_dataLength);
|
||||
}
|
||||
#endif // DUMP_AUDIO
|
||||
315
demos/spectrum/app/engine.h
Normal file
315
demos/spectrum/app/engine.h
Normal file
@@ -0,0 +1,315 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef ENGINE_H
|
||||
#define ENGINE_H
|
||||
|
||||
#include "spectrum.h"
|
||||
#include "spectrumanalyser.h"
|
||||
#include "wavfile.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QBuffer>
|
||||
#include <QVector>
|
||||
#include <QtMultimedia/QAudioDeviceInfo>
|
||||
#include <QtMultimedia/QAudioFormat>
|
||||
|
||||
#ifdef DUMP_CAPTURED_AUDIO
|
||||
#define DUMP_DATA
|
||||
#endif
|
||||
|
||||
#ifdef DUMP_SPECTRUM
|
||||
#define DUMP_DATA
|
||||
#endif
|
||||
|
||||
#ifdef DUMP_DATA
|
||||
#include <QDir>
|
||||
#endif
|
||||
|
||||
class FrequencySpectrum;
|
||||
QT_FORWARD_DECLARE_CLASS(QAudioInput)
|
||||
QT_FORWARD_DECLARE_CLASS(QAudioOutput)
|
||||
QT_FORWARD_DECLARE_CLASS(QFile)
|
||||
|
||||
/**
|
||||
* This class interfaces with the QtMultimedia audio classes, and also with
|
||||
* the SpectrumAnalyser class. Its role is to manage the capture and playback
|
||||
* of audio data, meanwhile performing real-time analysis of the audio level
|
||||
* and frequency spectrum.
|
||||
*/
|
||||
class Engine : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Engine(QObject *parent = 0);
|
||||
~Engine();
|
||||
|
||||
const QList<QAudioDeviceInfo>& availableAudioInputDevices() const
|
||||
{ return m_availableAudioInputDevices; }
|
||||
|
||||
const QList<QAudioDeviceInfo>& availableAudioOutputDevices() const
|
||||
{ return m_availableAudioOutputDevices; }
|
||||
|
||||
QAudio::Mode mode() const { return m_mode; }
|
||||
QAudio::State state() const { return m_state; }
|
||||
|
||||
/**
|
||||
* \return Current audio format
|
||||
* \note May be QAudioFormat() if engine is not initialized
|
||||
*/
|
||||
const QAudioFormat& format() const { return m_format; }
|
||||
|
||||
/**
|
||||
* Stop any ongoing recording or playback, and reset to ground state.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Load data from WAV file
|
||||
*/
|
||||
bool loadFile(const QString &fileName);
|
||||
|
||||
/**
|
||||
* Generate tone
|
||||
*/
|
||||
bool generateTone(const Tone &tone);
|
||||
|
||||
/**
|
||||
* Generate tone
|
||||
*/
|
||||
bool generateSweptTone(qreal amplitude);
|
||||
|
||||
/**
|
||||
* Initialize for recording
|
||||
*/
|
||||
bool initializeRecord();
|
||||
|
||||
/**
|
||||
* Position of the audio input device.
|
||||
* \return Position in bytes.
|
||||
*/
|
||||
qint64 recordPosition() const { return m_recordPosition; }
|
||||
|
||||
/**
|
||||
* RMS level of the most recently processed set of audio samples.
|
||||
* \return Level in range (0.0, 1.0)
|
||||
*/
|
||||
qreal rmsLevel() const { return m_rmsLevel; }
|
||||
|
||||
/**
|
||||
* Peak level of the most recently processed set of audio samples.
|
||||
* \return Level in range (0.0, 1.0)
|
||||
*/
|
||||
qreal peakLevel() const { return m_peakLevel; }
|
||||
|
||||
/**
|
||||
* Position of the audio output device.
|
||||
* \return Position in bytes.
|
||||
*/
|
||||
qint64 playPosition() const { return m_playPosition; }
|
||||
|
||||
/**
|
||||
* Length of the internal engine buffer.
|
||||
* \return Buffer length in bytes.
|
||||
*/
|
||||
qint64 bufferLength() const;
|
||||
|
||||
/**
|
||||
* Amount of data held in the buffer.
|
||||
* \return Data length in bytes.
|
||||
*/
|
||||
qint64 dataLength() const { return m_dataLength; }
|
||||
|
||||
/**
|
||||
* Set window function applied to audio data before spectral analysis.
|
||||
*/
|
||||
void setWindowFunction(WindowFunction type);
|
||||
|
||||
public slots:
|
||||
void startRecording();
|
||||
void startPlayback();
|
||||
void suspend();
|
||||
void setAudioInputDevice(const QAudioDeviceInfo &device);
|
||||
void setAudioOutputDevice(const QAudioDeviceInfo &device);
|
||||
|
||||
signals:
|
||||
void stateChanged(QAudio::Mode mode, QAudio::State state);
|
||||
|
||||
/**
|
||||
* Informational message for non-modal display
|
||||
*/
|
||||
void infoMessage(const QString &message, int durationMs);
|
||||
|
||||
/**
|
||||
* Error message for modal display
|
||||
*/
|
||||
void errorMessage(const QString &heading, const QString &detail);
|
||||
|
||||
/**
|
||||
* Format of audio data has changed
|
||||
*/
|
||||
void formatChanged(const QAudioFormat &format);
|
||||
|
||||
/**
|
||||
* Length of buffer has changed.
|
||||
* \param duration Duration in microseconds
|
||||
*/
|
||||
void bufferLengthChanged(qint64 duration);
|
||||
|
||||
/**
|
||||
* Amount of data in buffer has changed.
|
||||
* \param Length of data in bytes
|
||||
*/
|
||||
void dataLengthChanged(qint64 duration);
|
||||
|
||||
/**
|
||||
* Position of the audio input device has changed.
|
||||
* \param position Position in bytes
|
||||
*/
|
||||
void recordPositionChanged(qint64 position);
|
||||
|
||||
/**
|
||||
* Position of the audio output device has changed.
|
||||
* \param position Position in bytes
|
||||
*/
|
||||
void playPositionChanged(qint64 position);
|
||||
|
||||
/**
|
||||
* Level changed
|
||||
* \param rmsLevel RMS level in range 0.0 - 1.0
|
||||
* \param peakLevel Peak level in range 0.0 - 1.0
|
||||
* \param numSamples Number of audio samples analyzed
|
||||
*/
|
||||
void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples);
|
||||
|
||||
/**
|
||||
* Spectrum has changed.
|
||||
* \param position Position of start of window in bytes
|
||||
* \param length Length of window in bytes
|
||||
* \param spectrum Resulting frequency spectrum
|
||||
*/
|
||||
void spectrumChanged(qint64 position, qint64 length, const FrequencySpectrum &spectrum);
|
||||
|
||||
/**
|
||||
* Buffer containing audio data has changed.
|
||||
* \param position Position of start of buffer in bytes
|
||||
* \param buffer Buffer
|
||||
*/
|
||||
void bufferChanged(qint64 position, qint64 length, const QByteArray &buffer);
|
||||
|
||||
private slots:
|
||||
void audioNotify();
|
||||
void audioStateChanged(QAudio::State state);
|
||||
void audioDataReady();
|
||||
void spectrumChanged(const FrequencySpectrum &spectrum);
|
||||
|
||||
private:
|
||||
void resetAudioDevices();
|
||||
bool initialize();
|
||||
bool selectFormat();
|
||||
void stopRecording();
|
||||
void stopPlayback();
|
||||
void setState(QAudio::State state);
|
||||
void setState(QAudio::Mode mode, QAudio::State state);
|
||||
void setFormat(const QAudioFormat &format);
|
||||
void setRecordPosition(qint64 position, bool forceEmit = false);
|
||||
void setPlayPosition(qint64 position, bool forceEmit = false);
|
||||
void calculateLevel(qint64 position, qint64 length);
|
||||
void calculateSpectrum(qint64 position);
|
||||
void setLevel(qreal rmsLevel, qreal peakLevel, int numSamples);
|
||||
|
||||
#ifdef DUMP_DATA
|
||||
void createOutputDir();
|
||||
QString outputPath() const { return m_outputDir.path(); }
|
||||
#endif
|
||||
|
||||
#ifdef DUMP_CAPTURED_AUDIO
|
||||
void dumpData();
|
||||
#endif
|
||||
|
||||
private:
|
||||
QAudio::Mode m_mode;
|
||||
QAudio::State m_state;
|
||||
|
||||
bool m_generateTone;
|
||||
SweptTone m_tone;
|
||||
|
||||
WavFile* m_file;
|
||||
// We need a second file handle via which to read data into m_buffer
|
||||
// for analysis
|
||||
WavFile* m_analysisFile;
|
||||
|
||||
QAudioFormat m_format;
|
||||
|
||||
const QList<QAudioDeviceInfo> m_availableAudioInputDevices;
|
||||
QAudioDeviceInfo m_audioInputDevice;
|
||||
QAudioInput* m_audioInput;
|
||||
QIODevice* m_audioInputIODevice;
|
||||
qint64 m_recordPosition;
|
||||
|
||||
const QList<QAudioDeviceInfo> m_availableAudioOutputDevices;
|
||||
QAudioDeviceInfo m_audioOutputDevice;
|
||||
QAudioOutput* m_audioOutput;
|
||||
qint64 m_playPosition;
|
||||
QBuffer m_audioOutputIODevice;
|
||||
|
||||
QByteArray m_buffer;
|
||||
qint64 m_bufferPosition;
|
||||
qint64 m_bufferLength;
|
||||
qint64 m_dataLength;
|
||||
|
||||
int m_levelBufferLength;
|
||||
qreal m_rmsLevel;
|
||||
qreal m_peakLevel;
|
||||
|
||||
int m_spectrumBufferLength;
|
||||
QByteArray m_spectrumBuffer;
|
||||
SpectrumAnalyser m_spectrumAnalyser;
|
||||
qint64 m_spectrumPosition;
|
||||
|
||||
int m_count;
|
||||
|
||||
#ifdef DUMP_DATA
|
||||
QDir m_outputDir;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
#endif // ENGINE_H
|
||||
89
demos/spectrum/app/frequencyspectrum.cpp
Normal file
89
demos/spectrum/app/frequencyspectrum.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "frequencyspectrum.h"
|
||||
|
||||
FrequencySpectrum::FrequencySpectrum(int numPoints)
|
||||
: m_elements(numPoints)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void FrequencySpectrum::reset()
|
||||
{
|
||||
iterator i = begin();
|
||||
for ( ; i != end(); ++i)
|
||||
*i = Element();
|
||||
}
|
||||
|
||||
int FrequencySpectrum::count() const
|
||||
{
|
||||
return m_elements.count();
|
||||
}
|
||||
|
||||
FrequencySpectrum::Element& FrequencySpectrum::operator[](int index)
|
||||
{
|
||||
return m_elements[index];
|
||||
}
|
||||
|
||||
const FrequencySpectrum::Element& FrequencySpectrum::operator[](int index) const
|
||||
{
|
||||
return m_elements[index];
|
||||
}
|
||||
|
||||
FrequencySpectrum::iterator FrequencySpectrum::begin()
|
||||
{
|
||||
return m_elements.begin();
|
||||
}
|
||||
|
||||
FrequencySpectrum::iterator FrequencySpectrum::end()
|
||||
{
|
||||
return m_elements.end();
|
||||
}
|
||||
|
||||
FrequencySpectrum::const_iterator FrequencySpectrum::begin() const
|
||||
{
|
||||
return m_elements.begin();
|
||||
}
|
||||
|
||||
FrequencySpectrum::const_iterator FrequencySpectrum::end() const
|
||||
{
|
||||
return m_elements.end();
|
||||
}
|
||||
98
demos/spectrum/app/frequencyspectrum.h
Normal file
98
demos/spectrum/app/frequencyspectrum.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef FREQUENCYSPECTRUM_H
|
||||
#define FREQUENCYSPECTRUM_H
|
||||
|
||||
#include <QtCore/QVector>
|
||||
|
||||
/**
|
||||
* Represents a frequency spectrum as a series of elements, each of which
|
||||
* consists of a frequency, an amplitude and a phase.
|
||||
*/
|
||||
class FrequencySpectrum {
|
||||
public:
|
||||
FrequencySpectrum(int numPoints = 0);
|
||||
|
||||
struct Element {
|
||||
Element()
|
||||
: frequency(0.0), amplitude(0.0), phase(0.0), clipped(false)
|
||||
{ }
|
||||
|
||||
/**
|
||||
* Frequency in Hertz
|
||||
*/
|
||||
qreal frequency;
|
||||
|
||||
/**
|
||||
* Amplitude in range [0.0, 1.0]
|
||||
*/
|
||||
qreal amplitude;
|
||||
|
||||
/**
|
||||
* Phase in range [0.0, 2*PI]
|
||||
*/
|
||||
qreal phase;
|
||||
|
||||
/**
|
||||
* Indicates whether value has been clipped during spectrum analysis
|
||||
*/
|
||||
bool clipped;
|
||||
};
|
||||
|
||||
typedef QVector<Element>::iterator iterator;
|
||||
typedef QVector<Element>::const_iterator const_iterator;
|
||||
|
||||
void reset();
|
||||
|
||||
int count() const;
|
||||
Element& operator[](int index);
|
||||
const Element& operator[](int index) const;
|
||||
iterator begin();
|
||||
iterator end();
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
|
||||
private:
|
||||
QVector<Element> m_elements;
|
||||
|
||||
};
|
||||
|
||||
#endif // FREQUENCYSPECTRUM_H
|
||||
BIN
demos/spectrum/app/images/record.png
Normal file
BIN
demos/spectrum/app/images/record.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 670 B |
BIN
demos/spectrum/app/images/settings.png
Normal file
BIN
demos/spectrum/app/images/settings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
142
demos/spectrum/app/levelmeter.cpp
Normal file
142
demos/spectrum/app/levelmeter.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "levelmeter.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <QPainter>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
// Constants
|
||||
const int RedrawInterval = 100; // ms
|
||||
const qreal PeakDecayRate = 0.001;
|
||||
const int PeakHoldLevelDuration = 2000; // ms
|
||||
|
||||
|
||||
LevelMeter::LevelMeter(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_rmsLevel(0.0)
|
||||
, m_peakLevel(0.0)
|
||||
, m_decayedPeakLevel(0.0)
|
||||
, m_peakDecayRate(PeakDecayRate)
|
||||
, m_peakHoldLevel(0.0)
|
||||
, m_redrawTimer(new QTimer(this))
|
||||
, m_rmsColor(Qt::red)
|
||||
, m_peakColor(255, 200, 200, 255)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
setMinimumWidth(30);
|
||||
|
||||
connect(m_redrawTimer, SIGNAL(timeout()), this, SLOT(redrawTimerExpired()));
|
||||
m_redrawTimer->start(RedrawInterval);
|
||||
}
|
||||
|
||||
LevelMeter::~LevelMeter()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void LevelMeter::reset()
|
||||
{
|
||||
m_rmsLevel = 0.0;
|
||||
m_peakLevel = 0.0;
|
||||
update();
|
||||
}
|
||||
|
||||
void LevelMeter::levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples)
|
||||
{
|
||||
// Smooth the RMS signal
|
||||
const qreal smooth = pow(qreal(0.9), static_cast<qreal>(numSamples) / 256); // TODO: remove this magic number
|
||||
m_rmsLevel = (m_rmsLevel * smooth) + (rmsLevel * (1.0 - smooth));
|
||||
|
||||
if (peakLevel > m_decayedPeakLevel) {
|
||||
m_peakLevel = peakLevel;
|
||||
m_decayedPeakLevel = peakLevel;
|
||||
m_peakLevelChanged.start();
|
||||
}
|
||||
|
||||
if (peakLevel > m_peakHoldLevel) {
|
||||
m_peakHoldLevel = peakLevel;
|
||||
m_peakHoldLevelChanged.start();
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void LevelMeter::redrawTimerExpired()
|
||||
{
|
||||
// Decay the peak signal
|
||||
const int elapsedMs = m_peakLevelChanged.elapsed();
|
||||
const qreal decayAmount = m_peakDecayRate * elapsedMs;
|
||||
if (decayAmount < m_peakLevel)
|
||||
m_decayedPeakLevel = m_peakLevel - decayAmount;
|
||||
else
|
||||
m_decayedPeakLevel = 0.0;
|
||||
|
||||
// Check whether to clear the peak hold level
|
||||
if (m_peakHoldLevelChanged.elapsed() > PeakHoldLevelDuration)
|
||||
m_peakHoldLevel = 0.0;
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void LevelMeter::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
|
||||
QPainter painter(this);
|
||||
painter.fillRect(rect(), Qt::black);
|
||||
|
||||
QRect bar = rect();
|
||||
|
||||
bar.setTop(rect().top() + (1.0 - m_peakHoldLevel) * rect().height());
|
||||
bar.setBottom(bar.top() + 5);
|
||||
painter.fillRect(bar, m_rmsColor);
|
||||
bar.setBottom(rect().bottom());
|
||||
|
||||
bar.setTop(rect().top() + (1.0 - m_decayedPeakLevel) * rect().height());
|
||||
painter.fillRect(bar, m_peakColor);
|
||||
|
||||
bar.setTop(rect().top() + (1.0 - m_rmsLevel) * rect().height());
|
||||
painter.fillRect(bar, m_rmsColor);
|
||||
}
|
||||
116
demos/spectrum/app/levelmeter.h
Normal file
116
demos/spectrum/app/levelmeter.h
Normal file
@@ -0,0 +1,116 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef LEVELMETER_H
|
||||
#define LEVELMETER_H
|
||||
|
||||
#include <QTime>
|
||||
#include <QWidget>
|
||||
|
||||
/**
|
||||
* Widget which displays a vertical audio level meter, indicating the
|
||||
* RMS and peak levels of the window of audio samples most recently analyzed
|
||||
* by the Engine.
|
||||
*/
|
||||
class LevelMeter : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
LevelMeter(QWidget *parent = 0);
|
||||
~LevelMeter();
|
||||
|
||||
void paintEvent(QPaintEvent *event);
|
||||
|
||||
public slots:
|
||||
void reset();
|
||||
void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples);
|
||||
|
||||
private slots:
|
||||
void redrawTimerExpired();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Height of RMS level bar.
|
||||
* Range 0.0 - 1.0.
|
||||
*/
|
||||
qreal m_rmsLevel;
|
||||
|
||||
/**
|
||||
* Most recent peak level.
|
||||
* Range 0.0 - 1.0.
|
||||
*/
|
||||
qreal m_peakLevel;
|
||||
|
||||
/**
|
||||
* Height of peak level bar.
|
||||
* This is calculated by decaying m_peakLevel depending on the
|
||||
* elapsed time since m_peakLevelChanged, and the value of m_decayRate.
|
||||
*/
|
||||
qreal m_decayedPeakLevel;
|
||||
|
||||
/**
|
||||
* Time at which m_peakLevel was last changed.
|
||||
*/
|
||||
QTime m_peakLevelChanged;
|
||||
|
||||
/**
|
||||
* Rate at which peak level bar decays.
|
||||
* Expressed in level units / millisecond.
|
||||
*/
|
||||
qreal m_peakDecayRate;
|
||||
|
||||
/**
|
||||
* High watermark of peak level.
|
||||
* Range 0.0 - 1.0.
|
||||
*/
|
||||
qreal m_peakHoldLevel;
|
||||
|
||||
/**
|
||||
* Time at which m_peakHoldLevel was last changed.
|
||||
*/
|
||||
QTime m_peakHoldLevelChanged;
|
||||
|
||||
QTimer *m_redrawTimer;
|
||||
|
||||
QColor m_rmsColor;
|
||||
QColor m_peakColor;
|
||||
|
||||
};
|
||||
|
||||
#endif // LEVELMETER_H
|
||||
57
demos/spectrum/app/main.cpp
Normal file
57
demos/spectrum/app/main.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <QtGui/QApplication>
|
||||
#include "mainwidget.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
app.setApplicationName("QtMultimedia spectrum analyzer");
|
||||
MainWidget w;
|
||||
|
||||
#ifdef Q_OS_SYMBIAN
|
||||
w.showMaximized();
|
||||
#else
|
||||
w.show();
|
||||
#endif
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
453
demos/spectrum/app/mainwidget.cpp
Normal file
453
demos/spectrum/app/mainwidget.cpp
Normal file
@@ -0,0 +1,453 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "engine.h"
|
||||
#include "levelmeter.h"
|
||||
#include "mainwidget.h"
|
||||
#include "waveform.h"
|
||||
#include "progressbar.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "spectrograph.h"
|
||||
#include "tonegeneratordialog.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QStyle>
|
||||
#include <QMenu>
|
||||
#include <QFileDialog>
|
||||
#include <QTimerEvent>
|
||||
#include <QMessageBox>
|
||||
|
||||
const int NullTimerId = -1;
|
||||
|
||||
MainWidget::MainWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_mode(NoMode)
|
||||
, m_engine(new Engine(this))
|
||||
#ifndef DISABLE_WAVEFORM
|
||||
, m_waveform(new Waveform(this))
|
||||
#endif
|
||||
, m_progressBar(new ProgressBar(this))
|
||||
, m_spectrograph(new Spectrograph(this))
|
||||
, m_levelMeter(new LevelMeter(this))
|
||||
, m_modeButton(new QPushButton(this))
|
||||
, m_recordButton(new QPushButton(this))
|
||||
, m_pauseButton(new QPushButton(this))
|
||||
, m_playButton(new QPushButton(this))
|
||||
, m_settingsButton(new QPushButton(this))
|
||||
, m_infoMessage(new QLabel(tr("Select a mode to begin"), this))
|
||||
, m_infoMessageTimerId(NullTimerId)
|
||||
, m_settingsDialog(new SettingsDialog(
|
||||
m_engine->availableAudioInputDevices(),
|
||||
m_engine->availableAudioOutputDevices(),
|
||||
this))
|
||||
, m_toneGeneratorDialog(new ToneGeneratorDialog(this))
|
||||
, m_modeMenu(new QMenu(this))
|
||||
, m_loadFileAction(0)
|
||||
, m_generateToneAction(0)
|
||||
, m_recordAction(0)
|
||||
{
|
||||
m_spectrograph->setParams(SpectrumNumBands, SpectrumLowFreq, SpectrumHighFreq);
|
||||
|
||||
createUi();
|
||||
connectUi();
|
||||
}
|
||||
|
||||
MainWidget::~MainWidget()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public slots
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void MainWidget::stateChanged(QAudio::Mode mode, QAudio::State state)
|
||||
{
|
||||
Q_UNUSED(mode);
|
||||
|
||||
updateButtonStates();
|
||||
|
||||
if (QAudio::ActiveState != state && QAudio::SuspendedState != state) {
|
||||
m_levelMeter->reset();
|
||||
m_spectrograph->reset();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWidget::formatChanged(const QAudioFormat &format)
|
||||
{
|
||||
infoMessage(formatToString(format), NullMessageTimeout);
|
||||
|
||||
#ifndef DISABLE_WAVEFORM
|
||||
if (QAudioFormat() != format) {
|
||||
m_waveform->initialize(format, WaveformTileLength,
|
||||
WaveformWindowDuration);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWidget::spectrumChanged(qint64 position, qint64 length,
|
||||
const FrequencySpectrum &spectrum)
|
||||
{
|
||||
m_progressBar->windowChanged(position, length);
|
||||
m_spectrograph->spectrumChanged(spectrum);
|
||||
}
|
||||
|
||||
void MainWidget::infoMessage(const QString &message, int timeoutMs)
|
||||
{
|
||||
m_infoMessage->setText(message);
|
||||
|
||||
if (NullTimerId != m_infoMessageTimerId) {
|
||||
killTimer(m_infoMessageTimerId);
|
||||
m_infoMessageTimerId = NullTimerId;
|
||||
}
|
||||
|
||||
if (NullMessageTimeout != timeoutMs)
|
||||
m_infoMessageTimerId = startTimer(timeoutMs);
|
||||
}
|
||||
|
||||
void MainWidget::errorMessage(const QString &heading, const QString &detail)
|
||||
{
|
||||
#ifdef Q_OS_SYMBIAN
|
||||
const QString message = heading + "\n" + detail;
|
||||
QMessageBox::warning(this, "", message, QMessageBox::Close);
|
||||
#else
|
||||
QMessageBox::warning(this, heading, detail, QMessageBox::Close);
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWidget::timerEvent(QTimerEvent *event)
|
||||
{
|
||||
Q_ASSERT(event->timerId() == m_infoMessageTimerId);
|
||||
Q_UNUSED(event) // suppress warnings in release builds
|
||||
killTimer(m_infoMessageTimerId);
|
||||
m_infoMessageTimerId = NullTimerId;
|
||||
m_infoMessage->setText("");
|
||||
}
|
||||
|
||||
void MainWidget::audioPositionChanged(qint64 position)
|
||||
{
|
||||
#ifndef DISABLE_WAVEFORM
|
||||
m_waveform->audioPositionChanged(position);
|
||||
#else
|
||||
Q_UNUSED(position)
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWidget::bufferLengthChanged(qint64 length)
|
||||
{
|
||||
m_progressBar->bufferLengthChanged(length);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Private slots
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void MainWidget::showFileDialog()
|
||||
{
|
||||
const QString dir;
|
||||
const QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open WAV file"), dir, "*.wav");
|
||||
if (fileNames.count()) {
|
||||
reset();
|
||||
setMode(LoadFileMode);
|
||||
m_engine->loadFile(fileNames.front());
|
||||
updateButtonStates();
|
||||
} else {
|
||||
updateModeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWidget::showSettingsDialog()
|
||||
{
|
||||
m_settingsDialog->exec();
|
||||
if (m_settingsDialog->result() == QDialog::Accepted) {
|
||||
m_engine->setAudioInputDevice(m_settingsDialog->inputDevice());
|
||||
m_engine->setAudioOutputDevice(m_settingsDialog->outputDevice());
|
||||
m_engine->setWindowFunction(m_settingsDialog->windowFunction());
|
||||
}
|
||||
}
|
||||
|
||||
void MainWidget::showToneGeneratorDialog()
|
||||
{
|
||||
m_toneGeneratorDialog->exec();
|
||||
if (m_toneGeneratorDialog->result() == QDialog::Accepted) {
|
||||
reset();
|
||||
setMode(GenerateToneMode);
|
||||
const qreal amplitude = m_toneGeneratorDialog->amplitude();
|
||||
if (m_toneGeneratorDialog->isFrequencySweepEnabled()) {
|
||||
m_engine->generateSweptTone(amplitude);
|
||||
} else {
|
||||
const qreal frequency = m_toneGeneratorDialog->frequency();
|
||||
const Tone tone(frequency, amplitude);
|
||||
m_engine->generateTone(tone);
|
||||
updateButtonStates();
|
||||
}
|
||||
} else {
|
||||
updateModeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWidget::initializeRecord()
|
||||
{
|
||||
reset();
|
||||
setMode(RecordMode);
|
||||
if (m_engine->initializeRecord())
|
||||
updateButtonStates();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Private functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void MainWidget::createUi()
|
||||
{
|
||||
createMenus();
|
||||
|
||||
setWindowTitle(tr("Spectrum Analyser"));
|
||||
|
||||
QVBoxLayout *windowLayout = new QVBoxLayout(this);
|
||||
|
||||
m_infoMessage->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
m_infoMessage->setAlignment(Qt::AlignHCenter);
|
||||
windowLayout->addWidget(m_infoMessage);
|
||||
|
||||
#ifdef SUPERIMPOSE_PROGRESS_ON_WAVEFORM
|
||||
QScopedPointer<QHBoxLayout> waveformLayout(new QHBoxLayout);
|
||||
waveformLayout->addWidget(m_progressBar);
|
||||
m_progressBar->setMinimumHeight(m_waveform->minimumHeight());
|
||||
waveformLayout->setMargin(0);
|
||||
m_waveform->setLayout(waveformLayout.data());
|
||||
waveformLayout.take();
|
||||
windowLayout->addWidget(m_waveform);
|
||||
#else
|
||||
#ifndef DISABLE_WAVEFORM
|
||||
windowLayout->addWidget(m_waveform);
|
||||
#endif // DISABLE_WAVEFORM
|
||||
windowLayout->addWidget(m_progressBar);
|
||||
#endif // SUPERIMPOSE_PROGRESS_ON_WAVEFORM
|
||||
|
||||
// Spectrograph and level meter
|
||||
|
||||
QScopedPointer<QHBoxLayout> analysisLayout(new QHBoxLayout);
|
||||
analysisLayout->addWidget(m_spectrograph);
|
||||
analysisLayout->addWidget(m_levelMeter);
|
||||
windowLayout->addLayout(analysisLayout.data());
|
||||
analysisLayout.take();
|
||||
|
||||
// Button panel
|
||||
|
||||
const QSize buttonSize(30, 30);
|
||||
|
||||
m_modeButton->setText(tr("Mode"));
|
||||
|
||||
m_recordIcon = QIcon(":/images/record.png");
|
||||
m_recordButton->setIcon(m_recordIcon);
|
||||
m_recordButton->setEnabled(false);
|
||||
m_recordButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
m_recordButton->setMinimumSize(buttonSize);
|
||||
|
||||
m_pauseIcon = style()->standardIcon(QStyle::SP_MediaPause);
|
||||
m_pauseButton->setIcon(m_pauseIcon);
|
||||
m_pauseButton->setEnabled(false);
|
||||
m_pauseButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
m_pauseButton->setMinimumSize(buttonSize);
|
||||
|
||||
m_playIcon = style()->standardIcon(QStyle::SP_MediaPlay);
|
||||
m_playButton->setIcon(m_playIcon);
|
||||
m_playButton->setEnabled(false);
|
||||
m_playButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
m_playButton->setMinimumSize(buttonSize);
|
||||
|
||||
m_settingsIcon = QIcon(":/images/settings.png");
|
||||
m_settingsButton->setIcon(m_settingsIcon);
|
||||
m_settingsButton->setEnabled(true);
|
||||
m_settingsButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
m_settingsButton->setMinimumSize(buttonSize);
|
||||
|
||||
QScopedPointer<QHBoxLayout> buttonPanelLayout(new QHBoxLayout);
|
||||
buttonPanelLayout->addStretch();
|
||||
buttonPanelLayout->addWidget(m_modeButton);
|
||||
buttonPanelLayout->addWidget(m_recordButton);
|
||||
buttonPanelLayout->addWidget(m_pauseButton);
|
||||
buttonPanelLayout->addWidget(m_playButton);
|
||||
buttonPanelLayout->addWidget(m_settingsButton);
|
||||
|
||||
QWidget *buttonPanel = new QWidget(this);
|
||||
buttonPanel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
buttonPanel->setLayout(buttonPanelLayout.data());
|
||||
buttonPanelLayout.take(); // ownership transferred to buttonPanel
|
||||
|
||||
QScopedPointer<QHBoxLayout> bottomPaneLayout(new QHBoxLayout);
|
||||
bottomPaneLayout->addWidget(buttonPanel);
|
||||
windowLayout->addLayout(bottomPaneLayout.data());
|
||||
bottomPaneLayout.take(); // ownership transferred to windowLayout
|
||||
|
||||
// Apply layout
|
||||
|
||||
setLayout(windowLayout);
|
||||
}
|
||||
|
||||
void MainWidget::connectUi()
|
||||
{
|
||||
CHECKED_CONNECT(m_recordButton, SIGNAL(clicked()),
|
||||
m_engine, SLOT(startRecording()));
|
||||
|
||||
CHECKED_CONNECT(m_pauseButton, SIGNAL(clicked()),
|
||||
m_engine, SLOT(suspend()));
|
||||
|
||||
CHECKED_CONNECT(m_playButton, SIGNAL(clicked()),
|
||||
m_engine, SLOT(startPlayback()));
|
||||
|
||||
CHECKED_CONNECT(m_settingsButton, SIGNAL(clicked()),
|
||||
this, SLOT(showSettingsDialog()));
|
||||
|
||||
CHECKED_CONNECT(m_engine, SIGNAL(stateChanged(QAudio::Mode,QAudio::State)),
|
||||
this, SLOT(stateChanged(QAudio::Mode,QAudio::State)));
|
||||
|
||||
CHECKED_CONNECT(m_engine, SIGNAL(formatChanged(const QAudioFormat &)),
|
||||
this, SLOT(formatChanged(const QAudioFormat &)));
|
||||
|
||||
m_progressBar->bufferLengthChanged(m_engine->bufferLength());
|
||||
|
||||
CHECKED_CONNECT(m_engine, SIGNAL(bufferLengthChanged(qint64)),
|
||||
this, SLOT(bufferLengthChanged(qint64)));
|
||||
|
||||
CHECKED_CONNECT(m_engine, SIGNAL(dataLengthChanged(qint64)),
|
||||
this, SLOT(updateButtonStates()));
|
||||
|
||||
CHECKED_CONNECT(m_engine, SIGNAL(recordPositionChanged(qint64)),
|
||||
m_progressBar, SLOT(recordPositionChanged(qint64)));
|
||||
|
||||
CHECKED_CONNECT(m_engine, SIGNAL(playPositionChanged(qint64)),
|
||||
m_progressBar, SLOT(playPositionChanged(qint64)));
|
||||
|
||||
CHECKED_CONNECT(m_engine, SIGNAL(recordPositionChanged(qint64)),
|
||||
this, SLOT(audioPositionChanged(qint64)));
|
||||
|
||||
CHECKED_CONNECT(m_engine, SIGNAL(playPositionChanged(qint64)),
|
||||
this, SLOT(audioPositionChanged(qint64)));
|
||||
|
||||
CHECKED_CONNECT(m_engine, SIGNAL(levelChanged(qreal, qreal, int)),
|
||||
m_levelMeter, SLOT(levelChanged(qreal, qreal, int)));
|
||||
|
||||
CHECKED_CONNECT(m_engine, SIGNAL(spectrumChanged(qint64, qint64, const FrequencySpectrum &)),
|
||||
this, SLOT(spectrumChanged(qint64, qint64, const FrequencySpectrum &)));
|
||||
|
||||
CHECKED_CONNECT(m_engine, SIGNAL(infoMessage(QString, int)),
|
||||
this, SLOT(infoMessage(QString, int)));
|
||||
|
||||
CHECKED_CONNECT(m_engine, SIGNAL(errorMessage(QString, QString)),
|
||||
this, SLOT(errorMessage(QString, QString)));
|
||||
|
||||
CHECKED_CONNECT(m_spectrograph, SIGNAL(infoMessage(QString, int)),
|
||||
this, SLOT(infoMessage(QString, int)));
|
||||
|
||||
#ifndef DISABLE_WAVEFORM
|
||||
CHECKED_CONNECT(m_engine, SIGNAL(bufferChanged(qint64, qint64, const QByteArray &)),
|
||||
m_waveform, SLOT(bufferChanged(qint64, qint64, const QByteArray &)));
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWidget::createMenus()
|
||||
{
|
||||
m_modeButton->setMenu(m_modeMenu);
|
||||
|
||||
m_generateToneAction = m_modeMenu->addAction(tr("Play generated tone"));
|
||||
m_recordAction = m_modeMenu->addAction(tr("Record and play back"));
|
||||
m_loadFileAction = m_modeMenu->addAction(tr("Play file"));
|
||||
|
||||
m_loadFileAction->setCheckable(true);
|
||||
m_generateToneAction->setCheckable(true);
|
||||
m_recordAction->setCheckable(true);
|
||||
|
||||
connect(m_loadFileAction, SIGNAL(triggered(bool)), this, SLOT(showFileDialog()));
|
||||
connect(m_generateToneAction, SIGNAL(triggered(bool)), this, SLOT(showToneGeneratorDialog()));
|
||||
connect(m_recordAction, SIGNAL(triggered(bool)), this, SLOT(initializeRecord()));
|
||||
}
|
||||
|
||||
void MainWidget::updateButtonStates()
|
||||
{
|
||||
const bool recordEnabled = ((QAudio::AudioOutput == m_engine->mode() ||
|
||||
(QAudio::ActiveState != m_engine->state() &&
|
||||
QAudio::IdleState != m_engine->state())) &&
|
||||
RecordMode == m_mode);
|
||||
m_recordButton->setEnabled(recordEnabled);
|
||||
|
||||
const bool pauseEnabled = (QAudio::ActiveState == m_engine->state() ||
|
||||
QAudio::IdleState == m_engine->state());
|
||||
m_pauseButton->setEnabled(pauseEnabled);
|
||||
|
||||
const bool playEnabled = (/*m_engine->dataLength() &&*/
|
||||
(QAudio::AudioOutput != m_engine->mode() ||
|
||||
(QAudio::ActiveState != m_engine->state() &&
|
||||
QAudio::IdleState != m_engine->state())));
|
||||
m_playButton->setEnabled(playEnabled);
|
||||
}
|
||||
|
||||
void MainWidget::reset()
|
||||
{
|
||||
#ifndef DISABLE_WAVEFORM
|
||||
m_waveform->reset();
|
||||
#endif
|
||||
m_engine->reset();
|
||||
m_levelMeter->reset();
|
||||
m_spectrograph->reset();
|
||||
m_progressBar->reset();
|
||||
}
|
||||
|
||||
void MainWidget::setMode(Mode mode)
|
||||
{
|
||||
m_mode = mode;
|
||||
updateModeMenu();
|
||||
}
|
||||
|
||||
void MainWidget::updateModeMenu()
|
||||
{
|
||||
m_loadFileAction->setChecked(LoadFileMode == m_mode);
|
||||
m_generateToneAction->setChecked(GenerateToneMode == m_mode);
|
||||
m_recordAction->setChecked(RecordMode == m_mode);
|
||||
}
|
||||
|
||||
144
demos/spectrum/app/mainwidget.h
Normal file
144
demos/spectrum/app/mainwidget.h
Normal file
@@ -0,0 +1,144 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef MAINWIDGET_H
|
||||
#define MAINWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QIcon>
|
||||
#include <QtMultimedia/qaudio.h>
|
||||
|
||||
class Engine;
|
||||
class FrequencySpectrum;
|
||||
class ProgressBar;
|
||||
class Spectrograph;
|
||||
class Waveform;
|
||||
class LevelMeter;
|
||||
class SettingsDialog;
|
||||
class ToneGeneratorDialog;
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QAudioFormat)
|
||||
QT_FORWARD_DECLARE_CLASS(QLabel)
|
||||
QT_FORWARD_DECLARE_CLASS(QPushButton)
|
||||
QT_FORWARD_DECLARE_CLASS(QMenu)
|
||||
QT_FORWARD_DECLARE_CLASS(QAction)
|
||||
|
||||
/**
|
||||
* Main application widget, responsible for connecting the various UI
|
||||
* elements to the Engine.
|
||||
*/
|
||||
class MainWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MainWidget(QWidget *parent = 0);
|
||||
~MainWidget();
|
||||
|
||||
// QObject
|
||||
void timerEvent(QTimerEvent *event);
|
||||
|
||||
public slots:
|
||||
void stateChanged(QAudio::Mode mode, QAudio::State state);
|
||||
void formatChanged(const QAudioFormat &format);
|
||||
void spectrumChanged(qint64 position, qint64 length,
|
||||
const FrequencySpectrum &spectrum);
|
||||
void infoMessage(const QString &message, int timeoutMs);
|
||||
void errorMessage(const QString &heading, const QString &detail);
|
||||
void audioPositionChanged(qint64 position);
|
||||
void bufferLengthChanged(qint64 length);
|
||||
|
||||
private slots:
|
||||
void showFileDialog();
|
||||
void showSettingsDialog();
|
||||
void showToneGeneratorDialog();
|
||||
void initializeRecord();
|
||||
void updateModeMenu();
|
||||
void updateButtonStates();
|
||||
|
||||
private:
|
||||
void createUi();
|
||||
void createMenus();
|
||||
void connectUi();
|
||||
void reset();
|
||||
|
||||
enum Mode {
|
||||
NoMode,
|
||||
RecordMode,
|
||||
GenerateToneMode,
|
||||
LoadFileMode
|
||||
};
|
||||
|
||||
void setMode(Mode mode);
|
||||
|
||||
private:
|
||||
Mode m_mode;
|
||||
|
||||
Engine* m_engine;
|
||||
|
||||
#ifndef DISABLE_WAVEFORM
|
||||
Waveform* m_waveform;
|
||||
#endif
|
||||
ProgressBar* m_progressBar;
|
||||
Spectrograph* m_spectrograph;
|
||||
LevelMeter* m_levelMeter;
|
||||
|
||||
QPushButton* m_modeButton;
|
||||
QPushButton* m_recordButton;
|
||||
QIcon m_recordIcon;
|
||||
QPushButton* m_pauseButton;
|
||||
QIcon m_pauseIcon;
|
||||
QPushButton* m_playButton;
|
||||
QIcon m_playIcon;
|
||||
QPushButton* m_settingsButton;
|
||||
QIcon m_settingsIcon;
|
||||
|
||||
QLabel* m_infoMessage;
|
||||
int m_infoMessageTimerId;
|
||||
|
||||
SettingsDialog* m_settingsDialog;
|
||||
ToneGeneratorDialog* m_toneGeneratorDialog;
|
||||
|
||||
QMenu* m_modeMenu;
|
||||
QAction* m_loadFileAction;
|
||||
QAction* m_generateToneAction;
|
||||
QAction* m_recordAction;
|
||||
|
||||
};
|
||||
|
||||
#endif // MAINWIDGET_H
|
||||
140
demos/spectrum/app/progressbar.cpp
Normal file
140
demos/spectrum/app/progressbar.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "progressbar.h"
|
||||
#include "spectrum.h"
|
||||
#include <QPainter>
|
||||
|
||||
ProgressBar::ProgressBar(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_bufferLength(0)
|
||||
, m_recordPosition(0)
|
||||
, m_playPosition(0)
|
||||
, m_windowPosition(0)
|
||||
, m_windowLength(0)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
setMinimumHeight(30);
|
||||
#ifdef SUPERIMPOSE_PROGRESS_ON_WAVEFORM
|
||||
setAutoFillBackground(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
ProgressBar::~ProgressBar()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ProgressBar::reset()
|
||||
{
|
||||
m_bufferLength = 0;
|
||||
m_recordPosition = 0;
|
||||
m_playPosition = 0;
|
||||
m_windowPosition = 0;
|
||||
m_windowLength = 0;
|
||||
update();
|
||||
}
|
||||
|
||||
void ProgressBar::paintEvent(QPaintEvent * /*event*/)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
QColor bufferColor(0, 0, 255);
|
||||
QColor windowColor(0, 255, 0);
|
||||
|
||||
#ifdef SUPERIMPOSE_PROGRESS_ON_WAVEFORM
|
||||
bufferColor.setAlphaF(0.5);
|
||||
windowColor.setAlphaF(0.5);
|
||||
#else
|
||||
painter.fillRect(rect(), Qt::black);
|
||||
#endif
|
||||
|
||||
if (m_bufferLength) {
|
||||
QRect bar = rect();
|
||||
const qreal play = qreal(m_playPosition) / m_bufferLength;
|
||||
bar.setLeft(rect().left() + play * rect().width());
|
||||
const qreal record = qreal(m_recordPosition) / m_bufferLength;
|
||||
bar.setRight(rect().left() + record * rect().width());
|
||||
painter.fillRect(bar, bufferColor);
|
||||
|
||||
QRect window = rect();
|
||||
const qreal windowLeft = qreal(m_windowPosition) / m_bufferLength;
|
||||
window.setLeft(rect().left() + windowLeft * rect().width());
|
||||
const qreal windowWidth = qreal(m_windowLength) / m_bufferLength;
|
||||
window.setWidth(windowWidth * rect().width());
|
||||
painter.fillRect(window, windowColor);
|
||||
}
|
||||
}
|
||||
|
||||
void ProgressBar::bufferLengthChanged(qint64 bufferSize)
|
||||
{
|
||||
m_bufferLength = bufferSize;
|
||||
m_recordPosition = 0;
|
||||
m_playPosition = 0;
|
||||
m_windowPosition = 0;
|
||||
m_windowLength = 0;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void ProgressBar::recordPositionChanged(qint64 recordPosition)
|
||||
{
|
||||
Q_ASSERT(recordPosition >= 0);
|
||||
Q_ASSERT(recordPosition <= m_bufferLength);
|
||||
m_recordPosition = recordPosition;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void ProgressBar::playPositionChanged(qint64 playPosition)
|
||||
{
|
||||
Q_ASSERT(playPosition >= 0);
|
||||
Q_ASSERT(playPosition <= m_bufferLength);
|
||||
m_playPosition = playPosition;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void ProgressBar::windowChanged(qint64 position, qint64 length)
|
||||
{
|
||||
Q_ASSERT(position >= 0);
|
||||
Q_ASSERT(position <= m_bufferLength);
|
||||
Q_ASSERT(position + length <= m_bufferLength);
|
||||
m_windowPosition = position;
|
||||
m_windowLength = length;
|
||||
repaint();
|
||||
}
|
||||
74
demos/spectrum/app/progressbar.h
Normal file
74
demos/spectrum/app/progressbar.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef PROGRESSBAR_H
|
||||
#define PROGRESSBAR_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
/**
|
||||
* Widget which displays a the current fill state of the Engine's internal
|
||||
* buffer, and the current play/record position within that buffer.
|
||||
*/
|
||||
class ProgressBar : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProgressBar(QWidget *parent = 0);
|
||||
~ProgressBar();
|
||||
|
||||
void reset();
|
||||
void paintEvent(QPaintEvent *event);
|
||||
|
||||
public slots:
|
||||
void bufferLengthChanged(qint64 length);
|
||||
void recordPositionChanged(qint64 recordPosition);
|
||||
void playPositionChanged(qint64 playPosition);
|
||||
void windowChanged(qint64 position, qint64 length);
|
||||
|
||||
private:
|
||||
qint64 m_bufferLength;
|
||||
qint64 m_recordPosition;
|
||||
qint64 m_playPosition;
|
||||
qint64 m_windowPosition;
|
||||
qint64 m_windowLength;
|
||||
|
||||
};
|
||||
|
||||
#endif // PROGRESSBAR_H
|
||||
148
demos/spectrum/app/settingsdialog.cpp
Normal file
148
demos/spectrum/app/settingsdialog.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "settingsdialog.h"
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QCheckBox>
|
||||
#include <QSlider>
|
||||
#include <QSpinBox>
|
||||
|
||||
SettingsDialog::SettingsDialog(
|
||||
const QList<QAudioDeviceInfo> &availableInputDevices,
|
||||
const QList<QAudioDeviceInfo> &availableOutputDevices,
|
||||
QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, m_windowFunction(DefaultWindowFunction)
|
||||
, m_inputDeviceComboBox(new QComboBox(this))
|
||||
, m_outputDeviceComboBox(new QComboBox(this))
|
||||
, m_windowFunctionComboBox(new QComboBox(this))
|
||||
{
|
||||
QVBoxLayout *dialogLayout = new QVBoxLayout(this);
|
||||
|
||||
// Populate combo boxes
|
||||
|
||||
QAudioDeviceInfo device;
|
||||
foreach (device, availableInputDevices)
|
||||
m_inputDeviceComboBox->addItem(device.deviceName(),
|
||||
QVariant::fromValue(device));
|
||||
foreach (device, availableOutputDevices)
|
||||
m_outputDeviceComboBox->addItem(device.deviceName(),
|
||||
QVariant::fromValue(device));
|
||||
|
||||
m_windowFunctionComboBox->addItem(tr("None"), QVariant::fromValue(int(NoWindow)));
|
||||
m_windowFunctionComboBox->addItem("Hann", QVariant::fromValue(int(HannWindow)));
|
||||
m_windowFunctionComboBox->setCurrentIndex(m_windowFunction);
|
||||
|
||||
// Initialize default devices
|
||||
if (!availableInputDevices.empty())
|
||||
m_inputDevice = availableInputDevices.front();
|
||||
if (!availableOutputDevices.empty())
|
||||
m_outputDevice = availableOutputDevices.front();
|
||||
|
||||
// Add widgets to layout
|
||||
|
||||
QScopedPointer<QHBoxLayout> inputDeviceLayout(new QHBoxLayout);
|
||||
QLabel *inputDeviceLabel = new QLabel(tr("Input device"), this);
|
||||
inputDeviceLayout->addWidget(inputDeviceLabel);
|
||||
inputDeviceLayout->addWidget(m_inputDeviceComboBox);
|
||||
dialogLayout->addLayout(inputDeviceLayout.data());
|
||||
inputDeviceLayout.take(); // ownership transferred to dialogLayout
|
||||
|
||||
QScopedPointer<QHBoxLayout> outputDeviceLayout(new QHBoxLayout);
|
||||
QLabel *outputDeviceLabel = new QLabel(tr("Output device"), this);
|
||||
outputDeviceLayout->addWidget(outputDeviceLabel);
|
||||
outputDeviceLayout->addWidget(m_outputDeviceComboBox);
|
||||
dialogLayout->addLayout(outputDeviceLayout.data());
|
||||
outputDeviceLayout.take(); // ownership transferred to dialogLayout
|
||||
|
||||
QScopedPointer<QHBoxLayout> windowFunctionLayout(new QHBoxLayout);
|
||||
QLabel *windowFunctionLabel = new QLabel(tr("Window function"), this);
|
||||
windowFunctionLayout->addWidget(windowFunctionLabel);
|
||||
windowFunctionLayout->addWidget(m_windowFunctionComboBox);
|
||||
dialogLayout->addLayout(windowFunctionLayout.data());
|
||||
windowFunctionLayout.take(); // ownership transferred to dialogLayout
|
||||
|
||||
// Connect
|
||||
CHECKED_CONNECT(m_inputDeviceComboBox, SIGNAL(activated(int)),
|
||||
this, SLOT(inputDeviceChanged(int)));
|
||||
CHECKED_CONNECT(m_outputDeviceComboBox, SIGNAL(activated(int)),
|
||||
this, SLOT(outputDeviceChanged(int)));
|
||||
CHECKED_CONNECT(m_windowFunctionComboBox, SIGNAL(activated(int)),
|
||||
this, SLOT(windowFunctionChanged(int)));
|
||||
|
||||
// Add standard buttons to layout
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
|
||||
buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
dialogLayout->addWidget(buttonBox);
|
||||
|
||||
// Connect standard buttons
|
||||
CHECKED_CONNECT(buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()),
|
||||
this, SLOT(accept()));
|
||||
CHECKED_CONNECT(buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()),
|
||||
this, SLOT(reject()));
|
||||
|
||||
setLayout(dialogLayout);
|
||||
}
|
||||
|
||||
SettingsDialog::~SettingsDialog()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SettingsDialog::windowFunctionChanged(int index)
|
||||
{
|
||||
m_windowFunction = static_cast<WindowFunction>(
|
||||
m_windowFunctionComboBox->itemData(index).value<int>());
|
||||
}
|
||||
|
||||
void SettingsDialog::inputDeviceChanged(int index)
|
||||
{
|
||||
m_inputDevice = m_inputDeviceComboBox->itemData(index).value<QAudioDeviceInfo>();
|
||||
}
|
||||
|
||||
void SettingsDialog::outputDeviceChanged(int index)
|
||||
{
|
||||
m_outputDevice = m_outputDeviceComboBox->itemData(index).value<QAudioDeviceInfo>();
|
||||
}
|
||||
|
||||
87
demos/spectrum/app/settingsdialog.h
Normal file
87
demos/spectrum/app/settingsdialog.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef SETTINGSDIALOG_H
|
||||
#define SETTINGSDIALOG_H
|
||||
|
||||
#include "spectrum.h"
|
||||
#include <QDialog>
|
||||
#include <QtMultimedia/QAudioDeviceInfo>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QComboBox)
|
||||
QT_FORWARD_DECLARE_CLASS(QCheckBox)
|
||||
QT_FORWARD_DECLARE_CLASS(QSlider)
|
||||
QT_FORWARD_DECLARE_CLASS(QSpinBox)
|
||||
QT_FORWARD_DECLARE_CLASS(QGridLayout)
|
||||
|
||||
/**
|
||||
* Dialog used to control settings such as the audio input / output device
|
||||
* and the windowing function.
|
||||
*/
|
||||
class SettingsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SettingsDialog(const QList<QAudioDeviceInfo> &availableInputDevices,
|
||||
const QList<QAudioDeviceInfo> &availableOutputDevices,
|
||||
QWidget *parent = 0);
|
||||
~SettingsDialog();
|
||||
|
||||
WindowFunction windowFunction() const { return m_windowFunction; }
|
||||
const QAudioDeviceInfo& inputDevice() const { return m_inputDevice; }
|
||||
const QAudioDeviceInfo& outputDevice() const { return m_outputDevice; }
|
||||
|
||||
private slots:
|
||||
void windowFunctionChanged(int index);
|
||||
void inputDeviceChanged(int index);
|
||||
void outputDeviceChanged(int index);
|
||||
|
||||
private:
|
||||
WindowFunction m_windowFunction;
|
||||
QAudioDeviceInfo m_inputDevice;
|
||||
QAudioDeviceInfo m_outputDevice;
|
||||
|
||||
QComboBox* m_inputDeviceComboBox;
|
||||
QComboBox* m_outputDeviceComboBox;
|
||||
|
||||
QComboBox* m_windowFunctionComboBox;
|
||||
|
||||
};
|
||||
|
||||
#endif // SETTINGSDIALOG_H
|
||||
241
demos/spectrum/app/spectrograph.cpp
Normal file
241
demos/spectrum/app/spectrograph.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "spectrograph.h"
|
||||
#include <QPainter>
|
||||
#include <QMouseEvent>
|
||||
#include <QDebug>
|
||||
#include <QTimerEvent>
|
||||
|
||||
const int NullTimerId = -1;
|
||||
const int NullIndex = -1;
|
||||
const int BarSelectionInterval = 2000;
|
||||
|
||||
Spectrograph::Spectrograph(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_barSelected(NullIndex)
|
||||
, m_timerId(NullTimerId)
|
||||
, m_lowFreq(0.0)
|
||||
, m_highFreq(0.0)
|
||||
{
|
||||
setMinimumHeight(100);
|
||||
}
|
||||
|
||||
Spectrograph::~Spectrograph()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Spectrograph::setParams(int numBars, qreal lowFreq, qreal highFreq)
|
||||
{
|
||||
Q_ASSERT(numBars > 0);
|
||||
Q_ASSERT(highFreq > lowFreq);
|
||||
m_bars.resize(numBars);
|
||||
m_lowFreq = lowFreq;
|
||||
m_highFreq = highFreq;
|
||||
updateBars();
|
||||
}
|
||||
|
||||
void Spectrograph::timerEvent(QTimerEvent *event)
|
||||
{
|
||||
Q_ASSERT(event->timerId() == m_timerId);
|
||||
Q_UNUSED(event) // suppress warnings in release builds
|
||||
killTimer(m_timerId);
|
||||
m_timerId = NullTimerId;
|
||||
m_barSelected = NullIndex;
|
||||
update();
|
||||
}
|
||||
|
||||
void Spectrograph::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
|
||||
QPainter painter(this);
|
||||
painter.fillRect(rect(), Qt::black);
|
||||
|
||||
const int numBars = m_bars.count();
|
||||
|
||||
// Highlight region of selected bar
|
||||
if (m_barSelected != NullIndex && numBars) {
|
||||
QRect regionRect = rect();
|
||||
regionRect.setLeft(m_barSelected * rect().width() / numBars);
|
||||
regionRect.setWidth(rect().width() / numBars);
|
||||
QColor regionColor(202, 202, 64);
|
||||
painter.setBrush(Qt::DiagCrossPattern);
|
||||
painter.fillRect(regionRect, regionColor);
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
}
|
||||
|
||||
QColor barColor(51, 204, 102);
|
||||
QColor clipColor(255, 255, 0);
|
||||
|
||||
// Draw the outline
|
||||
const QColor gridColor = barColor.darker();
|
||||
QPen gridPen(gridColor);
|
||||
painter.setPen(gridPen);
|
||||
painter.drawLine(rect().topLeft(), rect().topRight());
|
||||
painter.drawLine(rect().topRight(), rect().bottomRight());
|
||||
painter.drawLine(rect().bottomRight(), rect().bottomLeft());
|
||||
painter.drawLine(rect().bottomLeft(), rect().topLeft());
|
||||
|
||||
QVector<qreal> dashes;
|
||||
dashes << 2 << 2;
|
||||
gridPen.setDashPattern(dashes);
|
||||
painter.setPen(gridPen);
|
||||
|
||||
// Draw vertical lines between bars
|
||||
if (numBars) {
|
||||
const int numHorizontalSections = numBars;
|
||||
QLine line(rect().topLeft(), rect().bottomLeft());
|
||||
for (int i=1; i<numHorizontalSections; ++i) {
|
||||
line.translate(rect().width()/numHorizontalSections, 0);
|
||||
painter.drawLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw horizontal lines
|
||||
const int numVerticalSections = 10;
|
||||
QLine line(rect().topLeft(), rect().topRight());
|
||||
for (int i=1; i<numVerticalSections; ++i) {
|
||||
line.translate(0, rect().height()/numVerticalSections);
|
||||
painter.drawLine(line);
|
||||
}
|
||||
|
||||
barColor = barColor.lighter();
|
||||
barColor.setAlphaF(0.75);
|
||||
clipColor.setAlphaF(0.75);
|
||||
|
||||
// Draw the bars
|
||||
if (numBars) {
|
||||
// Calculate width of bars and gaps
|
||||
const int widgetWidth = rect().width();
|
||||
const int barPlusGapWidth = widgetWidth / numBars;
|
||||
const int barWidth = 0.8 * barPlusGapWidth;
|
||||
const int gapWidth = barPlusGapWidth - barWidth;
|
||||
const int paddingWidth = widgetWidth - numBars * (barWidth + gapWidth);
|
||||
const int leftPaddingWidth = (paddingWidth + gapWidth) / 2;
|
||||
const int barHeight = rect().height() - 2 * gapWidth;
|
||||
|
||||
for (int i=0; i<numBars; ++i) {
|
||||
const qreal value = m_bars[i].value;
|
||||
Q_ASSERT(value >= 0.0 && value <= 1.0);
|
||||
QRect bar = rect();
|
||||
bar.setLeft(rect().left() + leftPaddingWidth + (i * (gapWidth + barWidth)));
|
||||
bar.setWidth(barWidth);
|
||||
bar.setTop(rect().top() + gapWidth + (1.0 - value) * barHeight);
|
||||
bar.setBottom(rect().bottom() - gapWidth);
|
||||
|
||||
QColor color = barColor;
|
||||
if (m_bars[i].clipped)
|
||||
color = clipColor;
|
||||
|
||||
painter.fillRect(bar, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Spectrograph::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
const QPoint pos = event->pos();
|
||||
const int index = m_bars.count() * (pos.x() - rect().left()) / rect().width();
|
||||
selectBar(index);
|
||||
}
|
||||
|
||||
void Spectrograph::reset()
|
||||
{
|
||||
m_spectrum.reset();
|
||||
spectrumChanged(m_spectrum);
|
||||
}
|
||||
|
||||
void Spectrograph::spectrumChanged(const FrequencySpectrum &spectrum)
|
||||
{
|
||||
m_spectrum = spectrum;
|
||||
updateBars();
|
||||
}
|
||||
|
||||
int Spectrograph::barIndex(qreal frequency) const
|
||||
{
|
||||
Q_ASSERT(frequency >= m_lowFreq && frequency < m_highFreq);
|
||||
const qreal bandWidth = (m_highFreq - m_lowFreq) / m_bars.count();
|
||||
const int index = (frequency - m_lowFreq) / bandWidth;
|
||||
if(index <0 || index >= m_bars.count())
|
||||
Q_ASSERT(false);
|
||||
return index;
|
||||
}
|
||||
|
||||
QPair<qreal, qreal> Spectrograph::barRange(int index) const
|
||||
{
|
||||
Q_ASSERT(index >= 0 && index < m_bars.count());
|
||||
const qreal bandWidth = (m_highFreq - m_lowFreq) / m_bars.count();
|
||||
return QPair<qreal, qreal>(index * bandWidth, (index+1) * bandWidth);
|
||||
}
|
||||
|
||||
void Spectrograph::updateBars()
|
||||
{
|
||||
m_bars.fill(Bar());
|
||||
FrequencySpectrum::const_iterator i = m_spectrum.begin();
|
||||
const FrequencySpectrum::const_iterator end = m_spectrum.end();
|
||||
for ( ; i != end; ++i) {
|
||||
const FrequencySpectrum::Element e = *i;
|
||||
if (e.frequency >= m_lowFreq && e.frequency < m_highFreq) {
|
||||
Bar &bar = m_bars[barIndex(e.frequency)];
|
||||
bar.value = qMax(bar.value, e.amplitude);
|
||||
bar.clipped |= e.clipped;
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void Spectrograph::selectBar(int index) {
|
||||
const QPair<qreal, qreal> frequencyRange = barRange(index);
|
||||
const QString message = QString("%1 - %2 Hz")
|
||||
.arg(frequencyRange.first)
|
||||
.arg(frequencyRange.second);
|
||||
emit infoMessage(message, BarSelectionInterval);
|
||||
|
||||
if (NullTimerId != m_timerId)
|
||||
killTimer(m_timerId);
|
||||
m_timerId = startTimer(BarSelectionInterval);
|
||||
|
||||
m_barSelected = index;
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
99
demos/spectrum/app/spectrograph.h
Normal file
99
demos/spectrum/app/spectrograph.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef SPECTROGRAPH_H
|
||||
#define SPECTROGRAPH_H
|
||||
|
||||
#include <QWidget>
|
||||
#include "frequencyspectrum.h"
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QMouseEvent)
|
||||
|
||||
/**
|
||||
* Widget which displays a spectrograph showing the frequency spectrum
|
||||
* of the window of audio samples most recently analyzed by the Engine.
|
||||
*/
|
||||
class Spectrograph : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Spectrograph(QWidget *parent = 0);
|
||||
~Spectrograph();
|
||||
|
||||
void setParams(int numBars, qreal lowFreq, qreal highFreq);
|
||||
|
||||
// QObject
|
||||
void timerEvent(QTimerEvent *event);
|
||||
|
||||
// QWidget
|
||||
void paintEvent(QPaintEvent *event);
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
|
||||
signals:
|
||||
void infoMessage(const QString &message, int intervalMs);
|
||||
|
||||
public slots:
|
||||
void reset();
|
||||
void spectrumChanged(const FrequencySpectrum &spectrum);
|
||||
|
||||
private:
|
||||
int barIndex(qreal frequency) const;
|
||||
QPair<qreal, qreal> barRange(int barIndex) const;
|
||||
void updateBars();
|
||||
|
||||
void selectBar(int index);
|
||||
|
||||
private:
|
||||
struct Bar {
|
||||
Bar() : value(0.0), clipped(false) { }
|
||||
qreal value;
|
||||
bool clipped;
|
||||
};
|
||||
|
||||
QVector<Bar> m_bars;
|
||||
int m_barSelected;
|
||||
int m_timerId;
|
||||
qreal m_lowFreq;
|
||||
qreal m_highFreq;
|
||||
FrequencySpectrum m_spectrum;
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // SPECTROGRAPH_H
|
||||
144
demos/spectrum/app/spectrum.h
Normal file
144
demos/spectrum/app/spectrum.h
Normal file
@@ -0,0 +1,144 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef SPECTRUM_H
|
||||
#define SPECTRUM_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#include "utils.h"
|
||||
#include "fftreal_wrapper.h" // For FFTLengthPowerOfTwo
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Constants
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Number of audio samples used to calculate the frequency spectrum
|
||||
const int SpectrumLengthSamples = PowerOfTwo<FFTLengthPowerOfTwo>::Result;
|
||||
|
||||
// Number of bands in the frequency spectrum
|
||||
const int SpectrumNumBands = 10;
|
||||
|
||||
// Lower bound of first band in the spectrum
|
||||
const qreal SpectrumLowFreq = 0.0; // Hz
|
||||
|
||||
// Upper band of last band in the spectrum
|
||||
const qreal SpectrumHighFreq = 1000.0; // Hz
|
||||
|
||||
// Waveform window size in microseconds
|
||||
const qint64 WaveformWindowDuration = 500 * 1000;
|
||||
|
||||
// Length of waveform tiles in bytes
|
||||
// Ideally, these would match the QAudio*::bufferSize(), but that isn't
|
||||
// available until some time after QAudio*::start() has been called, and we
|
||||
// need this value in order to initialize the waveform display.
|
||||
// We therefore just choose a sensible value.
|
||||
const int WaveformTileLength = 4096;
|
||||
|
||||
// Fudge factor used to calculate the spectrum bar heights
|
||||
const qreal SpectrumAnalyserMultiplier = 0.15;
|
||||
|
||||
// Disable message timeout
|
||||
const int NullMessageTimeout = -1;
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Types and data structures
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
enum WindowFunction {
|
||||
NoWindow,
|
||||
HannWindow
|
||||
};
|
||||
|
||||
const WindowFunction DefaultWindowFunction = HannWindow;
|
||||
|
||||
struct Tone {
|
||||
Tone(qreal freq = 0.0, qreal amp = 0.0)
|
||||
: frequency(freq), amplitude(amp)
|
||||
{ }
|
||||
|
||||
// Start and end frequencies for swept tone generation
|
||||
qreal frequency;
|
||||
|
||||
// Amplitude in range [0.0, 1.0]
|
||||
qreal amplitude;
|
||||
};
|
||||
|
||||
struct SweptTone {
|
||||
SweptTone(qreal start = 0.0, qreal end = 0.0, qreal amp = 0.0)
|
||||
: startFreq(start), endFreq(end), amplitude(amp)
|
||||
{ Q_ASSERT(end >= start); }
|
||||
|
||||
SweptTone(const Tone &tone)
|
||||
: startFreq(tone.frequency), endFreq(tone.frequency), amplitude(tone.amplitude)
|
||||
{ }
|
||||
|
||||
// Start and end frequencies for swept tone generation
|
||||
qreal startFreq;
|
||||
qreal endFreq;
|
||||
|
||||
// Amplitude in range [0.0, 1.0]
|
||||
qreal amplitude;
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Macros
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Macro which connects a signal to a slot, and which causes application to
|
||||
// abort if the connection fails. This is intended to catch programming errors
|
||||
// such as mis-typing a signal or slot name. It is necessary to write our own
|
||||
// macro to do this - the following idiom
|
||||
// Q_ASSERT(connect(source, signal, receiver, slot));
|
||||
// will not work because Q_ASSERT compiles to a no-op in release builds.
|
||||
|
||||
#define CHECKED_CONNECT(source, signal, receiver, slot) \
|
||||
if(!connect(source, signal, receiver, slot)) \
|
||||
qt_assert_x(Q_FUNC_INFO, "CHECKED_CONNECT failed", __FILE__, __LINE__);
|
||||
|
||||
// Handle some dependencies between macros defined in the .pro file
|
||||
|
||||
#ifdef DISABLE_WAVEFORM
|
||||
#undef SUPERIMPOSE_PROGRESS_ON_WAVEFORM
|
||||
#endif
|
||||
|
||||
#endif // SPECTRUM_H
|
||||
|
||||
7
demos/spectrum/app/spectrum.qrc
Normal file
7
demos/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>
|
||||
|
||||
281
demos/spectrum/app/spectrumanalyser.cpp
Normal file
281
demos/spectrum/app/spectrumanalyser.cpp
Normal file
@@ -0,0 +1,281 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "spectrumanalyser.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QtCore/qmath.h>
|
||||
#include <QtCore/qmetatype.h>
|
||||
#include <QtMultimedia/QAudioFormat>
|
||||
#include <QThread>
|
||||
|
||||
#include "fftreal_wrapper.h"
|
||||
|
||||
SpectrumAnalyserThread::SpectrumAnalyserThread(QObject *parent)
|
||||
: QObject(parent)
|
||||
#ifndef DISABLE_FFT
|
||||
, m_fft(new FFTRealWrapper)
|
||||
#endif
|
||||
, m_numSamples(SpectrumLengthSamples)
|
||||
, m_windowFunction(DefaultWindowFunction)
|
||||
, m_window(SpectrumLengthSamples, 0.0)
|
||||
, m_input(SpectrumLengthSamples, 0.0)
|
||||
, m_output(SpectrumLengthSamples, 0.0)
|
||||
, m_spectrum(SpectrumLengthSamples)
|
||||
#ifdef SPECTRUM_ANALYSER_SEPARATE_THREAD
|
||||
, m_thread(new QThread(this))
|
||||
#endif
|
||||
{
|
||||
#ifdef SPECTRUM_ANALYSER_SEPARATE_THREAD
|
||||
// moveToThread() cannot be called on a QObject with a parent
|
||||
setParent(0);
|
||||
moveToThread(m_thread);
|
||||
m_thread->start();
|
||||
#endif
|
||||
calculateWindow();
|
||||
}
|
||||
|
||||
SpectrumAnalyserThread::~SpectrumAnalyserThread()
|
||||
{
|
||||
#ifndef DISABLE_FFT
|
||||
delete m_fft;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SpectrumAnalyserThread::setWindowFunction(WindowFunction type)
|
||||
{
|
||||
m_windowFunction = type;
|
||||
calculateWindow();
|
||||
}
|
||||
|
||||
void SpectrumAnalyserThread::calculateWindow()
|
||||
{
|
||||
for (int i=0; i<m_numSamples; ++i) {
|
||||
DataType x = 0.0;
|
||||
|
||||
switch (m_windowFunction) {
|
||||
case NoWindow:
|
||||
x = 1.0;
|
||||
break;
|
||||
case HannWindow:
|
||||
x = 0.5 * (1 - qCos((2 * M_PI * i) / (m_numSamples - 1)));
|
||||
break;
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
}
|
||||
|
||||
m_window[i] = x;
|
||||
}
|
||||
}
|
||||
|
||||
void SpectrumAnalyserThread::calculateSpectrum(const QByteArray &buffer,
|
||||
int inputFrequency,
|
||||
int bytesPerSample)
|
||||
{
|
||||
#ifndef DISABLE_FFT
|
||||
Q_ASSERT(buffer.size() == m_numSamples * bytesPerSample);
|
||||
|
||||
// Initialize data array
|
||||
const char *ptr = buffer.constData();
|
||||
for (int i=0; i<m_numSamples; ++i) {
|
||||
const qint16 pcmSample = *reinterpret_cast<const qint16*>(ptr);
|
||||
// Scale down to range [-1.0, 1.0]
|
||||
const DataType realSample = pcmToReal(pcmSample);
|
||||
const DataType windowedSample = realSample * m_window[i];
|
||||
m_input[i] = windowedSample;
|
||||
ptr += bytesPerSample;
|
||||
}
|
||||
|
||||
// Calculate the FFT
|
||||
m_fft->calculateFFT(m_output.data(), m_input.data());
|
||||
|
||||
// Analyze output to obtain amplitude and phase for each frequency
|
||||
for (int i=2; i<=m_numSamples/2; ++i) {
|
||||
// Calculate frequency of this complex sample
|
||||
m_spectrum[i].frequency = qreal(i * inputFrequency) / (m_numSamples);
|
||||
|
||||
const qreal real = m_output[i];
|
||||
qreal imag = 0.0;
|
||||
if (i>0 && i<m_numSamples/2)
|
||||
imag = m_output[m_numSamples/2 + i];
|
||||
|
||||
const qreal magnitude = sqrt(real*real + imag*imag);
|
||||
qreal amplitude = SpectrumAnalyserMultiplier * log(magnitude);
|
||||
|
||||
// Bound amplitude to [0.0, 1.0]
|
||||
m_spectrum[i].clipped = (amplitude > 1.0);
|
||||
amplitude = qMax(qreal(0.0), amplitude);
|
||||
amplitude = qMin(qreal(1.0), amplitude);
|
||||
m_spectrum[i].amplitude = amplitude;
|
||||
}
|
||||
#endif
|
||||
|
||||
emit calculationComplete(m_spectrum);
|
||||
}
|
||||
|
||||
|
||||
//=============================================================================
|
||||
// SpectrumAnalyser
|
||||
//=============================================================================
|
||||
|
||||
SpectrumAnalyser::SpectrumAnalyser(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_thread(new SpectrumAnalyserThread(this))
|
||||
, m_state(Idle)
|
||||
#ifdef DUMP_SPECTRUMANALYSER
|
||||
, m_count(0)
|
||||
#endif
|
||||
{
|
||||
CHECKED_CONNECT(m_thread, SIGNAL(calculationComplete(FrequencySpectrum)),
|
||||
this, SLOT(calculationComplete(FrequencySpectrum)));
|
||||
}
|
||||
|
||||
SpectrumAnalyser::~SpectrumAnalyser()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#ifdef DUMP_SPECTRUMANALYSER
|
||||
void SpectrumAnalyser::setOutputPath(const QString &outputDir)
|
||||
{
|
||||
m_outputDir.setPath(outputDir);
|
||||
m_textFile.setFileName(m_outputDir.filePath("spectrum.txt"));
|
||||
m_textFile.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
m_textStream.setDevice(&m_textFile);
|
||||
}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SpectrumAnalyser::setWindowFunction(WindowFunction type)
|
||||
{
|
||||
const bool b = QMetaObject::invokeMethod(m_thread, "setWindowFunction",
|
||||
Qt::AutoConnection,
|
||||
Q_ARG(WindowFunction, type));
|
||||
Q_ASSERT(b);
|
||||
Q_UNUSED(b) // suppress warnings in release builds
|
||||
}
|
||||
|
||||
void SpectrumAnalyser::calculate(const QByteArray &buffer,
|
||||
const QAudioFormat &format)
|
||||
{
|
||||
// QThread::currentThread is marked 'for internal use only', but
|
||||
// we're only using it for debug output here, so it's probably OK :)
|
||||
SPECTRUMANALYSER_DEBUG << "SpectrumAnalyser::calculate"
|
||||
<< QThread::currentThread()
|
||||
<< "state" << m_state;
|
||||
|
||||
if (isReady()) {
|
||||
Q_ASSERT(isPCMS16LE(format));
|
||||
|
||||
const int bytesPerSample = format.sampleSize() * format.channels() / 8;
|
||||
|
||||
#ifdef DUMP_SPECTRUMANALYSER
|
||||
m_count++;
|
||||
const QString pcmFileName = m_outputDir.filePath(QString("spectrum_%1.pcm").arg(m_count, 4, 10, QChar('0')));
|
||||
QFile pcmFile(pcmFileName);
|
||||
pcmFile.open(QIODevice::WriteOnly);
|
||||
const int bufferLength = m_numSamples * bytesPerSample;
|
||||
pcmFile.write(buffer, bufferLength);
|
||||
|
||||
m_textStream << "TimeDomain " << m_count << "\n";
|
||||
const qint16* input = reinterpret_cast<const qint16*>(buffer);
|
||||
for (int i=0; i<m_numSamples; ++i) {
|
||||
m_textStream << i << "\t" << *input << "\n";
|
||||
input += format.channels();
|
||||
}
|
||||
#endif
|
||||
|
||||
m_state = Busy;
|
||||
|
||||
// Invoke SpectrumAnalyserThread::calculateSpectrum using QMetaObject. If
|
||||
// m_thread is in a different thread from the current thread, the
|
||||
// calculation will be done in the child thread.
|
||||
// Once the calculation is finished, a calculationChanged signal will be
|
||||
// emitted by m_thread.
|
||||
const bool b = QMetaObject::invokeMethod(m_thread, "calculateSpectrum",
|
||||
Qt::AutoConnection,
|
||||
Q_ARG(QByteArray, buffer),
|
||||
Q_ARG(int, format.frequency()),
|
||||
Q_ARG(int, bytesPerSample));
|
||||
Q_ASSERT(b);
|
||||
Q_UNUSED(b) // suppress warnings in release builds
|
||||
|
||||
#ifdef DUMP_SPECTRUMANALYSER
|
||||
m_textStream << "FrequencySpectrum " << m_count << "\n";
|
||||
FrequencySpectrum::const_iterator x = m_spectrum.begin();
|
||||
for (int i=0; i<m_numSamples; ++i, ++x)
|
||||
m_textStream << i << "\t"
|
||||
<< x->frequency << "\t"
|
||||
<< x->amplitude<< "\t"
|
||||
<< x->phase << "\n";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool SpectrumAnalyser::isReady() const
|
||||
{
|
||||
return (Idle == m_state);
|
||||
}
|
||||
|
||||
void SpectrumAnalyser::cancelCalculation()
|
||||
{
|
||||
if (Busy == m_state)
|
||||
m_state = Cancelled;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Private slots
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SpectrumAnalyser::calculationComplete(const FrequencySpectrum &spectrum)
|
||||
{
|
||||
Q_ASSERT(Idle != m_state);
|
||||
if (Busy == m_state)
|
||||
emit spectrumChanged(spectrum);
|
||||
m_state = Idle;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
194
demos/spectrum/app/spectrumanalyser.h
Normal file
194
demos/spectrum/app/spectrumanalyser.h
Normal file
@@ -0,0 +1,194 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef SPECTRUMANALYSER_H
|
||||
#define SPECTRUMANALYSER_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
|
||||
#ifdef DUMP_SPECTRUMANALYSER
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#endif
|
||||
|
||||
#include "frequencyspectrum.h"
|
||||
#include "spectrum.h"
|
||||
|
||||
#ifndef DISABLE_FFT
|
||||
#include "FFTRealFixLenParam.h"
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QAudioFormat)
|
||||
QT_FORWARD_DECLARE_CLASS(QThread)
|
||||
|
||||
class FFTRealWrapper;
|
||||
|
||||
class SpectrumAnalyserThreadPrivate;
|
||||
|
||||
/**
|
||||
* Implementation of the spectrum analysis which can be run in a
|
||||
* separate thread.
|
||||
*/
|
||||
class SpectrumAnalyserThread : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpectrumAnalyserThread(QObject *parent);
|
||||
~SpectrumAnalyserThread();
|
||||
|
||||
public slots:
|
||||
void setWindowFunction(WindowFunction type);
|
||||
void calculateSpectrum(const QByteArray &buffer,
|
||||
int inputFrequency,
|
||||
int bytesPerSample);
|
||||
|
||||
signals:
|
||||
void calculationComplete(const FrequencySpectrum &spectrum);
|
||||
|
||||
private:
|
||||
void calculateWindow();
|
||||
|
||||
private:
|
||||
#ifndef DISABLE_FFT
|
||||
FFTRealWrapper* m_fft;
|
||||
#endif
|
||||
|
||||
const int m_numSamples;
|
||||
|
||||
WindowFunction m_windowFunction;
|
||||
|
||||
#ifdef DISABLE_FFT
|
||||
typedef qreal DataType;
|
||||
#else
|
||||
typedef FFTRealFixLenParam::DataType DataType;
|
||||
#endif
|
||||
QVector<DataType> m_window;
|
||||
|
||||
QVector<DataType> m_input;
|
||||
QVector<DataType> m_output;
|
||||
|
||||
FrequencySpectrum m_spectrum;
|
||||
|
||||
#ifdef SPECTRUM_ANALYSER_SEPARATE_THREAD
|
||||
QThread* m_thread;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Class which performs frequency spectrum analysis on a window of
|
||||
* audio samples, provided to it by the Engine.
|
||||
*/
|
||||
class SpectrumAnalyser : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpectrumAnalyser(QObject *parent = 0);
|
||||
~SpectrumAnalyser();
|
||||
|
||||
#ifdef DUMP_SPECTRUMANALYSER
|
||||
void setOutputPath(const QString &outputPath);
|
||||
#endif
|
||||
|
||||
public:
|
||||
/*
|
||||
* Set the windowing function which is applied before calculating the FFT
|
||||
*/
|
||||
void setWindowFunction(WindowFunction type);
|
||||
|
||||
/*
|
||||
* Calculate a frequency spectrum
|
||||
*
|
||||
* \param buffer Audio data
|
||||
* \param format Format of audio data
|
||||
*
|
||||
* Frequency spectrum is calculated asynchronously. The result is returned
|
||||
* via the spectrumChanged signal.
|
||||
*
|
||||
* An ongoing calculation can be cancelled by calling cancelCalculation().
|
||||
*
|
||||
*/
|
||||
void calculate(const QByteArray &buffer, const QAudioFormat &format);
|
||||
|
||||
/*
|
||||
* Check whether the object is ready to perform another calculation
|
||||
*/
|
||||
bool isReady() const;
|
||||
|
||||
/*
|
||||
* Cancel an ongoing calculation
|
||||
*
|
||||
* Note that cancelling is asynchronous.
|
||||
*/
|
||||
void cancelCalculation();
|
||||
|
||||
signals:
|
||||
void spectrumChanged(const FrequencySpectrum &spectrum);
|
||||
|
||||
private slots:
|
||||
void calculationComplete(const FrequencySpectrum &spectrum);
|
||||
|
||||
private:
|
||||
void calculateWindow();
|
||||
|
||||
private:
|
||||
|
||||
SpectrumAnalyserThread* m_thread;
|
||||
|
||||
enum State {
|
||||
Idle,
|
||||
Busy,
|
||||
Cancelled
|
||||
};
|
||||
|
||||
State m_state;
|
||||
|
||||
#ifdef DUMP_SPECTRUMANALYSER
|
||||
QDir m_outputDir;
|
||||
int m_count;
|
||||
QFile m_textFile;
|
||||
QTextStream m_textStream;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // SPECTRUMANALYSER_H
|
||||
|
||||
91
demos/spectrum/app/tonegenerator.cpp
Normal file
91
demos/spectrum/app/tonegenerator.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "spectrum.h"
|
||||
#include "utils.h"
|
||||
#include <QByteArray>
|
||||
#include <QtMultimedia/QAudioFormat>
|
||||
#include <QtCore/qmath.h>
|
||||
#include <QtCore/qendian.h>
|
||||
|
||||
void generateTone(const SweptTone &tone, const QAudioFormat &format, QByteArray &buffer)
|
||||
{
|
||||
Q_ASSERT(isPCMS16LE(format));
|
||||
|
||||
const int channelBytes = format.sampleSize() / 8;
|
||||
const int sampleBytes = format.channels() * channelBytes;
|
||||
int length = buffer.size();
|
||||
const int numSamples = buffer.size() / sampleBytes;
|
||||
|
||||
Q_ASSERT(length % sampleBytes == 0);
|
||||
Q_UNUSED(sampleBytes) // suppress warning in release builds
|
||||
|
||||
unsigned char *ptr = reinterpret_cast<unsigned char *>(buffer.data());
|
||||
|
||||
qreal phase = 0.0;
|
||||
|
||||
const qreal d = 2 * M_PI / format.frequency();
|
||||
|
||||
// We can't generate a zero-frequency sine wave
|
||||
const qreal startFreq = tone.startFreq ? tone.startFreq : 1.0;
|
||||
|
||||
// Amount by which phase increases on each sample
|
||||
qreal phaseStep = d * startFreq;
|
||||
|
||||
// Amount by which phaseStep increases on each sample
|
||||
// If this is non-zero, the output is a frequency-swept tone
|
||||
const qreal phaseStepStep = d * (tone.endFreq - startFreq) / numSamples;
|
||||
|
||||
while (length) {
|
||||
const qreal x = tone.amplitude * qSin(phase);
|
||||
const qint16 value = realToPcm(x);
|
||||
for (int i=0; i<format.channels(); ++i) {
|
||||
qToLittleEndian<qint16>(value, ptr);
|
||||
ptr += channelBytes;
|
||||
length -= channelBytes;
|
||||
}
|
||||
|
||||
phase += phaseStep;
|
||||
while (phase > 2 * M_PI)
|
||||
phase -= 2 * M_PI;
|
||||
phaseStep += phaseStepStep;
|
||||
}
|
||||
}
|
||||
|
||||
56
demos/spectrum/app/tonegenerator.h
Normal file
56
demos/spectrum/app/tonegenerator.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef TONEGENERATOR_H
|
||||
#define TONEGENERATOR_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#include "spectrum.h"
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QAudioFormat)
|
||||
QT_FORWARD_DECLARE_CLASS(QByteArray)
|
||||
|
||||
/**
|
||||
* Generate a sine wave
|
||||
*/
|
||||
void generateTone(const SweptTone &tone, const QAudioFormat &format, QByteArray &buffer);
|
||||
|
||||
#endif // TONEGENERATOR_H
|
||||
|
||||
147
demos/spectrum/app/tonegeneratordialog.cpp
Normal file
147
demos/spectrum/app/tonegeneratordialog.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "tonegeneratordialog.h"
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QCheckBox>
|
||||
#include <QSlider>
|
||||
#include <QSpinBox>
|
||||
|
||||
const int ToneGeneratorFreqMin = 1;
|
||||
const int ToneGeneratorFreqMax = 1000;
|
||||
const int ToneGeneratorFreqDefault = 440;
|
||||
const int ToneGeneratorAmplitudeDefault = 75;
|
||||
|
||||
ToneGeneratorDialog::ToneGeneratorDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, m_toneGeneratorSweepCheckBox(new QCheckBox(tr("Frequency sweep"), this))
|
||||
, m_frequencySweepEnabled(true)
|
||||
, m_toneGeneratorControl(new QWidget(this))
|
||||
, m_toneGeneratorFrequencyControl(new QWidget(this))
|
||||
, m_frequencySlider(new QSlider(Qt::Horizontal, this))
|
||||
, m_frequencySpinBox(new QSpinBox(this))
|
||||
, m_frequency(ToneGeneratorFreqDefault)
|
||||
, m_amplitudeSlider(new QSlider(Qt::Horizontal, this))
|
||||
{
|
||||
QVBoxLayout *dialogLayout = new QVBoxLayout(this);
|
||||
|
||||
m_toneGeneratorSweepCheckBox->setChecked(true);
|
||||
|
||||
// Configure tone generator controls
|
||||
m_frequencySlider->setRange(ToneGeneratorFreqMin, ToneGeneratorFreqMax);
|
||||
m_frequencySlider->setValue(ToneGeneratorFreqDefault);
|
||||
m_frequencySpinBox->setRange(ToneGeneratorFreqMin, ToneGeneratorFreqMax);
|
||||
m_frequencySpinBox->setValue(ToneGeneratorFreqDefault);
|
||||
m_amplitudeSlider->setRange(0, 100);
|
||||
m_amplitudeSlider->setValue(ToneGeneratorAmplitudeDefault);
|
||||
|
||||
// Add widgets to layout
|
||||
|
||||
QScopedPointer<QGridLayout> frequencyControlLayout(new QGridLayout);
|
||||
QLabel *frequencyLabel = new QLabel(tr("Frequency (Hz)"), this);
|
||||
frequencyControlLayout->addWidget(frequencyLabel, 0, 0, 2, 1);
|
||||
frequencyControlLayout->addWidget(m_frequencySlider, 0, 1);
|
||||
frequencyControlLayout->addWidget(m_frequencySpinBox, 1, 1);
|
||||
m_toneGeneratorFrequencyControl->setLayout(frequencyControlLayout.data());
|
||||
frequencyControlLayout.take(); // ownership transferred to m_toneGeneratorFrequencyControl
|
||||
m_toneGeneratorFrequencyControl->setEnabled(false);
|
||||
|
||||
QScopedPointer<QGridLayout> toneGeneratorLayout(new QGridLayout);
|
||||
QLabel *amplitudeLabel = new QLabel(tr("Amplitude"), this);
|
||||
toneGeneratorLayout->addWidget(m_toneGeneratorSweepCheckBox, 0, 1);
|
||||
toneGeneratorLayout->addWidget(m_toneGeneratorFrequencyControl, 1, 0, 1, 2);
|
||||
toneGeneratorLayout->addWidget(amplitudeLabel, 2, 0);
|
||||
toneGeneratorLayout->addWidget(m_amplitudeSlider, 2, 1);
|
||||
m_toneGeneratorControl->setLayout(toneGeneratorLayout.data());
|
||||
m_toneGeneratorControl->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
dialogLayout->addWidget(m_toneGeneratorControl);
|
||||
toneGeneratorLayout.take(); // ownership transferred
|
||||
|
||||
// Connect
|
||||
CHECKED_CONNECT(m_toneGeneratorSweepCheckBox, SIGNAL(toggled(bool)),
|
||||
this, SLOT(frequencySweepEnabled(bool)));
|
||||
CHECKED_CONNECT(m_frequencySlider, SIGNAL(valueChanged(int)),
|
||||
m_frequencySpinBox, SLOT(setValue(int)));
|
||||
CHECKED_CONNECT(m_frequencySpinBox, SIGNAL(valueChanged(int)),
|
||||
m_frequencySlider, SLOT(setValue(int)));
|
||||
|
||||
// Add standard buttons to layout
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
|
||||
buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
dialogLayout->addWidget(buttonBox);
|
||||
|
||||
// Connect standard buttons
|
||||
CHECKED_CONNECT(buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()),
|
||||
this, SLOT(accept()));
|
||||
CHECKED_CONNECT(buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()),
|
||||
this, SLOT(reject()));
|
||||
|
||||
setLayout(dialogLayout);
|
||||
}
|
||||
|
||||
ToneGeneratorDialog::~ToneGeneratorDialog()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ToneGeneratorDialog::isFrequencySweepEnabled() const
|
||||
{
|
||||
return m_toneGeneratorSweepCheckBox->isChecked();
|
||||
}
|
||||
|
||||
qreal ToneGeneratorDialog::frequency() const
|
||||
{
|
||||
return qreal(m_frequencySlider->value());
|
||||
}
|
||||
|
||||
qreal ToneGeneratorDialog::amplitude() const
|
||||
{
|
||||
return qreal(m_amplitudeSlider->value()) / 100.0;
|
||||
}
|
||||
|
||||
void ToneGeneratorDialog::frequencySweepEnabled(bool enabled)
|
||||
{
|
||||
m_frequencySweepEnabled = enabled;
|
||||
m_toneGeneratorFrequencyControl->setEnabled(!enabled);
|
||||
}
|
||||
81
demos/spectrum/app/tonegeneratordialog.h
Normal file
81
demos/spectrum/app/tonegeneratordialog.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef TONEGENERATORDIALOG_H
|
||||
#define TONEGENERATORDIALOG_H
|
||||
|
||||
#include "spectrum.h"
|
||||
#include <QDialog>
|
||||
#include <QtMultimedia/QAudioDeviceInfo>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QCheckBox)
|
||||
QT_FORWARD_DECLARE_CLASS(QSlider)
|
||||
QT_FORWARD_DECLARE_CLASS(QSpinBox)
|
||||
QT_FORWARD_DECLARE_CLASS(QGridLayout)
|
||||
|
||||
/**
|
||||
* Dialog which controls the parameters of the tone generator.
|
||||
*/
|
||||
class ToneGeneratorDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ToneGeneratorDialog(QWidget *parent = 0);
|
||||
~ToneGeneratorDialog();
|
||||
|
||||
bool isFrequencySweepEnabled() const;
|
||||
qreal frequency() const;
|
||||
qreal amplitude() const;
|
||||
|
||||
private slots:
|
||||
void frequencySweepEnabled(bool enabled);
|
||||
|
||||
private:
|
||||
QCheckBox* m_toneGeneratorSweepCheckBox;
|
||||
bool m_frequencySweepEnabled;
|
||||
QWidget* m_toneGeneratorControl;
|
||||
QWidget* m_toneGeneratorFrequencyControl;
|
||||
QSlider* m_frequencySlider;
|
||||
QSpinBox* m_frequencySpinBox;
|
||||
qreal m_frequency;
|
||||
QSlider* m_amplitudeSlider;
|
||||
|
||||
};
|
||||
|
||||
#endif // TONEGENERATORDIALOG_H
|
||||
139
demos/spectrum/app/utils.cpp
Normal file
139
demos/spectrum/app/utils.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <QtMultimedia/QAudioFormat>
|
||||
#include "utils.h"
|
||||
|
||||
qint64 audioDuration(const QAudioFormat &format, qint64 bytes)
|
||||
{
|
||||
return (bytes * 1000000) /
|
||||
(format.frequency() * format.channels() * (format.sampleSize() / 8));
|
||||
}
|
||||
|
||||
qint64 audioLength(const QAudioFormat &format, qint64 microSeconds)
|
||||
{
|
||||
qint64 result = (format.frequency() * format.channels() * (format.sampleSize() / 8))
|
||||
* microSeconds / 1000000;
|
||||
result -= result % (format.channelCount() * format.sampleSize());
|
||||
return result;
|
||||
}
|
||||
|
||||
qreal nyquistFrequency(const QAudioFormat &format)
|
||||
{
|
||||
return format.frequency() / 2;
|
||||
}
|
||||
|
||||
QString formatToString(const QAudioFormat &format)
|
||||
{
|
||||
QString result;
|
||||
|
||||
if (QAudioFormat() != format) {
|
||||
if (format.codec() == "audio/pcm") {
|
||||
Q_ASSERT(format.sampleType() == QAudioFormat::SignedInt);
|
||||
|
||||
const QString formatEndian = (format.byteOrder() == QAudioFormat::LittleEndian)
|
||||
? QString("LE") : QString("BE");
|
||||
|
||||
QString formatType;
|
||||
switch(format.sampleType()) {
|
||||
case QAudioFormat::SignedInt:
|
||||
formatType = "signed";
|
||||
break;
|
||||
case QAudioFormat::UnSignedInt:
|
||||
formatType = "unsigned";
|
||||
break;
|
||||
case QAudioFormat::Float:
|
||||
formatType = "float";
|
||||
break;
|
||||
case QAudioFormat::Unknown:
|
||||
formatType = "unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
QString formatChannels = QString("%1 channels").arg(format.channels());
|
||||
switch (format.channels()) {
|
||||
case 1:
|
||||
formatChannels = "mono";
|
||||
break;
|
||||
case 2:
|
||||
formatChannels = "stereo";
|
||||
break;
|
||||
}
|
||||
|
||||
result = QString("%1 Hz %2 bit %3 %4 %5")
|
||||
.arg(format.frequency())
|
||||
.arg(format.sampleSize())
|
||||
.arg(formatType)
|
||||
.arg(formatEndian)
|
||||
.arg(formatChannels);
|
||||
} else {
|
||||
result = format.codec();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool isPCM(const QAudioFormat &format)
|
||||
{
|
||||
return (format.codec() == "audio/pcm");
|
||||
}
|
||||
|
||||
|
||||
bool isPCMS16LE(const QAudioFormat &format)
|
||||
{
|
||||
return (isPCM(format) &&
|
||||
format.sampleType() == QAudioFormat::SignedInt &&
|
||||
format.sampleSize() == 16 &&
|
||||
format.byteOrder() == QAudioFormat::LittleEndian);
|
||||
}
|
||||
|
||||
const qint16 PCMS16MaxValue = 32767;
|
||||
const quint16 PCMS16MaxAmplitude = 32768; // because minimum is -32768
|
||||
|
||||
qreal pcmToReal(qint16 pcm)
|
||||
{
|
||||
return qreal(pcm) / PCMS16MaxAmplitude;
|
||||
}
|
||||
|
||||
qint16 realToPcm(qreal real)
|
||||
{
|
||||
return real * PCMS16MaxValue;
|
||||
}
|
||||
112
demos/spectrum/app/utils.h
Normal file
112
demos/spectrum/app/utils.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <QDebug>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QAudioFormat)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Miscellaneous utility functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
qint64 audioDuration(const QAudioFormat &format, qint64 bytes);
|
||||
qint64 audioLength(const QAudioFormat &format, qint64 microSeconds);
|
||||
|
||||
QString formatToString(const QAudioFormat &format);
|
||||
|
||||
qreal nyquistFrequency(const QAudioFormat &format);
|
||||
|
||||
// Scale PCM value to [-1.0, 1.0]
|
||||
qreal pcmToReal(qint16 pcm);
|
||||
|
||||
// Scale real value in [-1.0, 1.0] to PCM
|
||||
qint16 realToPcm(qreal real);
|
||||
|
||||
// Check whether the audio format is PCM
|
||||
bool isPCM(const QAudioFormat &format);
|
||||
|
||||
// Check whether the audio format is signed, little-endian, 16-bit PCM
|
||||
bool isPCMS16LE(const QAudioFormat &format);
|
||||
|
||||
// Compile-time calculation of powers of two
|
||||
|
||||
template<int N> class PowerOfTwo
|
||||
{ public: static const int Result = PowerOfTwo<N-1>::Result * 2; };
|
||||
|
||||
template<> class PowerOfTwo<0>
|
||||
{ public: static const int Result = 1; };
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Debug output
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class NullDebug
|
||||
{
|
||||
public:
|
||||
template <typename T>
|
||||
NullDebug& operator<<(const T&) { return *this; }
|
||||
};
|
||||
|
||||
inline NullDebug nullDebug() { return NullDebug(); }
|
||||
|
||||
#ifdef LOG_ENGINE
|
||||
# define ENGINE_DEBUG qDebug()
|
||||
#else
|
||||
# define ENGINE_DEBUG nullDebug()
|
||||
#endif
|
||||
|
||||
#ifdef LOG_SPECTRUMANALYSER
|
||||
# define SPECTRUMANALYSER_DEBUG qDebug()
|
||||
#else
|
||||
# define SPECTRUMANALYSER_DEBUG nullDebug()
|
||||
#endif
|
||||
|
||||
#ifdef LOG_WAVEFORM
|
||||
# define WAVEFORM_DEBUG qDebug()
|
||||
#else
|
||||
# define WAVEFORM_DEBUG nullDebug()
|
||||
#endif
|
||||
|
||||
#endif // UTILS_H
|
||||
436
demos/spectrum/app/waveform.cpp
Normal file
436
demos/spectrum/app/waveform.cpp
Normal file
@@ -0,0 +1,436 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "waveform.h"
|
||||
#include "utils.h"
|
||||
#include <QPainter>
|
||||
#include <QResizeEvent>
|
||||
#include <QDebug>
|
||||
|
||||
//#define PAINT_EVENT_TRACE
|
||||
#ifdef PAINT_EVENT_TRACE
|
||||
# define WAVEFORM_PAINT_DEBUG qDebug()
|
||||
#else
|
||||
# define WAVEFORM_PAINT_DEBUG nullDebug()
|
||||
#endif
|
||||
|
||||
Waveform::Waveform(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_bufferPosition(0)
|
||||
, m_bufferLength(0)
|
||||
, m_audioPosition(0)
|
||||
, m_active(false)
|
||||
, m_tileLength(0)
|
||||
, m_tileArrayStart(0)
|
||||
, m_windowPosition(0)
|
||||
, m_windowLength(0)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
setMinimumHeight(50);
|
||||
}
|
||||
|
||||
Waveform::~Waveform()
|
||||
{
|
||||
deletePixmaps();
|
||||
}
|
||||
|
||||
void Waveform::paintEvent(QPaintEvent * /*event*/)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
painter.fillRect(rect(), Qt::black);
|
||||
|
||||
if (m_active) {
|
||||
WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent"
|
||||
<< "windowPosition" << m_windowPosition
|
||||
<< "windowLength" << m_windowLength;
|
||||
qint64 pos = m_windowPosition;
|
||||
const qint64 windowEnd = m_windowPosition + m_windowLength;
|
||||
int destLeft = 0;
|
||||
int destRight = 0;
|
||||
while (pos < windowEnd) {
|
||||
const TilePoint point = tilePoint(pos);
|
||||
WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "pos" << pos
|
||||
<< "tileIndex" << point.index
|
||||
<< "positionOffset" << point.positionOffset
|
||||
<< "pixelOffset" << point.pixelOffset;
|
||||
|
||||
if (point.index != NullIndex) {
|
||||
const Tile &tile = m_tiles[point.index];
|
||||
if (tile.painted) {
|
||||
const qint64 sectionLength = qMin((m_tileLength - point.positionOffset),
|
||||
(windowEnd - pos));
|
||||
Q_ASSERT(sectionLength > 0);
|
||||
|
||||
const int sourceRight = tilePixelOffset(point.positionOffset + sectionLength);
|
||||
destRight = windowPixelOffset(pos - m_windowPosition + sectionLength);
|
||||
|
||||
QRect destRect = rect();
|
||||
destRect.setLeft(destLeft);
|
||||
destRect.setRight(destRight);
|
||||
|
||||
QRect sourceRect(QPoint(), m_pixmapSize);
|
||||
sourceRect.setLeft(point.pixelOffset);
|
||||
sourceRect.setRight(sourceRight);
|
||||
|
||||
WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "tileIndex" << point.index
|
||||
<< "source" << point.pixelOffset << sourceRight
|
||||
<< "dest" << destLeft << destRight;
|
||||
|
||||
painter.drawPixmap(destRect, *tile.pixmap, sourceRect);
|
||||
|
||||
destLeft = destRight;
|
||||
|
||||
if (point.index < m_tiles.count()) {
|
||||
pos = tilePosition(point.index + 1);
|
||||
WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "pos ->" << pos;
|
||||
} else {
|
||||
// Reached end of tile array
|
||||
WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "reached end of tile array";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Passed last tile which is painted
|
||||
WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "tile" << point.index << "not painted";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// pos is past end of tile array
|
||||
WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "pos" << pos << "past end of tile array";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "final pos" << pos << "final x" << destRight;
|
||||
}
|
||||
}
|
||||
|
||||
void Waveform::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
if (event->size() != event->oldSize())
|
||||
createPixmaps(event->size());
|
||||
}
|
||||
|
||||
void Waveform::initialize(const QAudioFormat &format, qint64 audioBufferSize, qint64 windowDurationUs)
|
||||
{
|
||||
WAVEFORM_DEBUG << "Waveform::initialize"
|
||||
<< "audioBufferSize" << audioBufferSize
|
||||
<< "windowDurationUs" << windowDurationUs;
|
||||
|
||||
reset();
|
||||
|
||||
m_format = format;
|
||||
|
||||
// Calculate tile size
|
||||
m_tileLength = audioBufferSize;
|
||||
|
||||
// Calculate window size
|
||||
m_windowLength = audioLength(m_format, windowDurationUs);
|
||||
|
||||
// Calculate number of tiles required
|
||||
int nTiles;
|
||||
if (m_tileLength > m_windowLength) {
|
||||
nTiles = 2;
|
||||
} else {
|
||||
nTiles = m_windowLength / m_tileLength + 1;
|
||||
if (m_windowLength % m_tileLength)
|
||||
++nTiles;
|
||||
}
|
||||
|
||||
WAVEFORM_DEBUG << "Waveform::initialize"
|
||||
<< "tileLength" << m_tileLength
|
||||
<< "windowLength" << m_windowLength
|
||||
<< "nTiles" << nTiles;
|
||||
|
||||
m_pixmaps.fill(0, nTiles);
|
||||
m_tiles.resize(nTiles);
|
||||
|
||||
createPixmaps(rect().size());
|
||||
|
||||
m_active = true;
|
||||
}
|
||||
|
||||
void Waveform::reset()
|
||||
{
|
||||
WAVEFORM_DEBUG << "Waveform::reset";
|
||||
|
||||
m_bufferPosition = 0;
|
||||
m_buffer = QByteArray();
|
||||
m_audioPosition = 0;
|
||||
m_format = QAudioFormat();
|
||||
m_active = false;
|
||||
deletePixmaps();
|
||||
m_tiles.clear();
|
||||
m_tileLength = 0;
|
||||
m_tileArrayStart = 0;
|
||||
m_windowPosition = 0;
|
||||
m_windowLength = 0;
|
||||
}
|
||||
|
||||
void Waveform::bufferChanged(qint64 position, qint64 length, const QByteArray &buffer)
|
||||
{
|
||||
WAVEFORM_DEBUG << "Waveform::bufferChanged"
|
||||
<< "audioPosition" << m_audioPosition
|
||||
<< "bufferPosition" << position
|
||||
<< "bufferLength" << length;
|
||||
m_bufferPosition = position;
|
||||
m_bufferLength = length;
|
||||
m_buffer = buffer;
|
||||
paintTiles();
|
||||
}
|
||||
|
||||
void Waveform::audioPositionChanged(qint64 position)
|
||||
{
|
||||
WAVEFORM_DEBUG << "Waveform::audioPositionChanged"
|
||||
<< "audioPosition" << position
|
||||
<< "bufferPosition" << m_bufferPosition
|
||||
<< "bufferLength" << m_bufferLength;
|
||||
|
||||
if (position >= m_bufferPosition) {
|
||||
if (position + m_windowLength > m_bufferPosition + m_bufferLength)
|
||||
position = qMax(qint64(0), m_bufferPosition + m_bufferLength - m_windowLength);
|
||||
m_audioPosition = position;
|
||||
setWindowPosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
void Waveform::deletePixmaps()
|
||||
{
|
||||
QPixmap *pixmap;
|
||||
foreach (pixmap, m_pixmaps)
|
||||
delete pixmap;
|
||||
m_pixmaps.clear();
|
||||
}
|
||||
|
||||
void Waveform::createPixmaps(const QSize &widgetSize)
|
||||
{
|
||||
m_pixmapSize = widgetSize;
|
||||
m_pixmapSize.setWidth(qreal(widgetSize.width()) * m_tileLength / m_windowLength);
|
||||
|
||||
WAVEFORM_DEBUG << "Waveform::createPixmaps"
|
||||
<< "widgetSize" << widgetSize
|
||||
<< "pixmapSize" << m_pixmapSize;
|
||||
|
||||
Q_ASSERT(m_tiles.count() == m_pixmaps.count());
|
||||
|
||||
// (Re)create pixmaps
|
||||
for (int i=0; i<m_pixmaps.size(); ++i) {
|
||||
delete m_pixmaps[i];
|
||||
m_pixmaps[i] = 0;
|
||||
m_pixmaps[i] = new QPixmap(m_pixmapSize);
|
||||
}
|
||||
|
||||
// Update tile pixmap pointers, and mark for repainting
|
||||
for (int i=0; i<m_tiles.count(); ++i) {
|
||||
m_tiles[i].pixmap = m_pixmaps[i];
|
||||
m_tiles[i].painted = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Waveform::setWindowPosition(qint64 position)
|
||||
{
|
||||
WAVEFORM_DEBUG << "Waveform::setWindowPosition"
|
||||
<< "old" << m_windowPosition << "new" << position
|
||||
<< "tileArrayStart" << m_tileArrayStart;
|
||||
|
||||
const qint64 oldPosition = m_windowPosition;
|
||||
m_windowPosition = position;
|
||||
|
||||
if((m_windowPosition >= oldPosition) &&
|
||||
(m_windowPosition - m_tileArrayStart < (m_tiles.count() * m_tileLength))) {
|
||||
// Work out how many tiles need to be shuffled
|
||||
const qint64 offset = m_windowPosition - m_tileArrayStart;
|
||||
const int nTiles = offset / m_tileLength;
|
||||
shuffleTiles(nTiles);
|
||||
} else {
|
||||
resetTiles(m_windowPosition);
|
||||
}
|
||||
|
||||
if(!paintTiles() && m_windowPosition != oldPosition)
|
||||
update();
|
||||
}
|
||||
|
||||
qint64 Waveform::tilePosition(int index) const
|
||||
{
|
||||
return m_tileArrayStart + index * m_tileLength;
|
||||
}
|
||||
|
||||
Waveform::TilePoint Waveform::tilePoint(qint64 position) const
|
||||
{
|
||||
TilePoint result;
|
||||
if (position >= m_tileArrayStart) {
|
||||
const qint64 tileArrayEnd = m_tileArrayStart + m_tiles.count() * m_tileLength;
|
||||
if (position < tileArrayEnd) {
|
||||
const qint64 offsetIntoTileArray = position - m_tileArrayStart;
|
||||
result.index = offsetIntoTileArray / m_tileLength;
|
||||
Q_ASSERT(result.index >= 0 && result.index <= m_tiles.count());
|
||||
result.positionOffset = offsetIntoTileArray % m_tileLength;
|
||||
result.pixelOffset = tilePixelOffset(result.positionOffset);
|
||||
Q_ASSERT(result.pixelOffset >= 0 && result.pixelOffset <= m_pixmapSize.width());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int Waveform::tilePixelOffset(qint64 positionOffset) const
|
||||
{
|
||||
Q_ASSERT(positionOffset >= 0 && positionOffset <= m_tileLength);
|
||||
const int result = (qreal(positionOffset) / m_tileLength) * m_pixmapSize.width();
|
||||
return result;
|
||||
}
|
||||
|
||||
int Waveform::windowPixelOffset(qint64 positionOffset) const
|
||||
{
|
||||
Q_ASSERT(positionOffset >= 0 && positionOffset <= m_windowLength);
|
||||
const int result = (qreal(positionOffset) / m_windowLength) * rect().width();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Waveform::paintTiles()
|
||||
{
|
||||
WAVEFORM_DEBUG << "Waveform::paintTiles";
|
||||
bool updateRequired = false;
|
||||
|
||||
for (int i=0; i<m_tiles.count(); ++i) {
|
||||
const Tile &tile = m_tiles[i];
|
||||
if (!tile.painted) {
|
||||
const qint64 tileStart = m_tileArrayStart + i * m_tileLength;
|
||||
const qint64 tileEnd = tileStart + m_tileLength;
|
||||
if (m_bufferPosition <= tileStart && m_bufferPosition + m_bufferLength >= tileEnd) {
|
||||
paintTile(i);
|
||||
updateRequired = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (updateRequired)
|
||||
update();
|
||||
|
||||
return updateRequired;
|
||||
}
|
||||
|
||||
void Waveform::paintTile(int index)
|
||||
{
|
||||
const qint64 tileStart = m_tileArrayStart + index * m_tileLength;
|
||||
|
||||
WAVEFORM_DEBUG << "Waveform::paintTile"
|
||||
<< "index" << index
|
||||
<< "bufferPosition" << m_bufferPosition
|
||||
<< "bufferLength" << m_bufferLength
|
||||
<< "start" << tileStart
|
||||
<< "end" << tileStart + m_tileLength;
|
||||
|
||||
Q_ASSERT(m_bufferPosition <= tileStart);
|
||||
Q_ASSERT(m_bufferPosition + m_bufferLength >= tileStart + m_tileLength);
|
||||
|
||||
Tile &tile = m_tiles[index];
|
||||
Q_ASSERT(!tile.painted);
|
||||
|
||||
const qint16* base = reinterpret_cast<const qint16*>(m_buffer.constData());
|
||||
const qint16* buffer = base + ((tileStart - m_bufferPosition) / 2);
|
||||
const int numSamples = m_tileLength / (2 * m_format.channels());
|
||||
|
||||
QPainter painter(tile.pixmap);
|
||||
|
||||
painter.fillRect(tile.pixmap->rect(), Qt::black);
|
||||
|
||||
QPen pen(Qt::white);
|
||||
painter.setPen(pen);
|
||||
|
||||
// Calculate initial PCM value
|
||||
qint16 previousPcmValue = 0;
|
||||
if (buffer > base)
|
||||
previousPcmValue = *(buffer - m_format.channels());
|
||||
|
||||
// Calculate initial point
|
||||
const qreal previousRealValue = pcmToReal(previousPcmValue);
|
||||
const int originY = ((previousRealValue + 1.0) / 2) * m_pixmapSize.height();
|
||||
const QPoint origin(0, originY);
|
||||
|
||||
QLine line(origin, origin);
|
||||
|
||||
for (int i=0; i<numSamples; ++i) {
|
||||
const qint16* ptr = buffer + i * m_format.channels();
|
||||
|
||||
const int offset = reinterpret_cast<const char*>(ptr) - m_buffer.constData();
|
||||
Q_ASSERT(offset >= 0);
|
||||
Q_ASSERT(offset < m_bufferLength);
|
||||
|
||||
const qint16 pcmValue = *ptr;
|
||||
const qreal realValue = pcmToReal(pcmValue);
|
||||
|
||||
const int x = tilePixelOffset(i * 2 * m_format.channels());
|
||||
const int y = ((realValue + 1.0) / 2) * m_pixmapSize.height();
|
||||
|
||||
line.setP2(QPoint(x, y));
|
||||
painter.drawLine(line);
|
||||
line.setP1(line.p2());
|
||||
}
|
||||
|
||||
tile.painted = true;
|
||||
}
|
||||
|
||||
void Waveform::shuffleTiles(int n)
|
||||
{
|
||||
WAVEFORM_DEBUG << "Waveform::shuffleTiles" << "n" << n;
|
||||
|
||||
while (n--) {
|
||||
Tile tile = m_tiles.first();
|
||||
tile.painted = false;
|
||||
m_tiles.erase(m_tiles.begin());
|
||||
m_tiles += tile;
|
||||
m_tileArrayStart += m_tileLength;
|
||||
}
|
||||
|
||||
WAVEFORM_DEBUG << "Waveform::shuffleTiles" << "tileArrayStart" << m_tileArrayStart;
|
||||
}
|
||||
|
||||
void Waveform::resetTiles(qint64 newStartPos)
|
||||
{
|
||||
WAVEFORM_DEBUG << "Waveform::resetTiles" << "newStartPos" << newStartPos;
|
||||
|
||||
QVector<Tile>::iterator i = m_tiles.begin();
|
||||
for ( ; i != m_tiles.end(); ++i)
|
||||
i->painted = false;
|
||||
|
||||
m_tileArrayStart = newStartPos;
|
||||
}
|
||||
|
||||
203
demos/spectrum/app/waveform.h
Normal file
203
demos/spectrum/app/waveform.h
Normal file
@@ -0,0 +1,203 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef WAVEFORM_H
|
||||
#define WAVEFORM_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QtMultimedia/QAudioFormat>
|
||||
#include <QPixmap>
|
||||
#include <QScopedPointer>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QByteArray)
|
||||
|
||||
/**
|
||||
* Widget which displays a section of the audio waveform.
|
||||
*
|
||||
* The waveform is rendered on a set of QPixmaps which form a group of tiles
|
||||
* whose extent covers the widget. As the audio position is updated, these
|
||||
* tiles are scrolled from left to right; when the left-most tile scrolls
|
||||
* outside the widget, it is moved to the right end of the tile array and
|
||||
* painted with the next section of the waveform.
|
||||
*/
|
||||
class Waveform : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Waveform(QWidget *parent = 0);
|
||||
~Waveform();
|
||||
|
||||
// QWidget
|
||||
void paintEvent(QPaintEvent *event);
|
||||
void resizeEvent(QResizeEvent *event);
|
||||
|
||||
void initialize(const QAudioFormat &format, qint64 audioBufferSize, qint64 windowDurationUs);
|
||||
void reset();
|
||||
|
||||
void setAutoUpdatePosition(bool enabled);
|
||||
|
||||
public slots:
|
||||
void bufferChanged(qint64 position, qint64 length, const QByteArray &buffer);
|
||||
void audioPositionChanged(qint64 position);
|
||||
|
||||
private:
|
||||
static const int NullIndex = -1;
|
||||
|
||||
void deletePixmaps();
|
||||
|
||||
/*
|
||||
* (Re)create all pixmaps, repaint and update the display.
|
||||
* Triggers an update();
|
||||
*/
|
||||
void createPixmaps(const QSize &newSize);
|
||||
|
||||
/*
|
||||
* Update window position.
|
||||
* Triggers an update().
|
||||
*/
|
||||
void setWindowPosition(qint64 position);
|
||||
|
||||
/*
|
||||
* Base position of tile
|
||||
*/
|
||||
qint64 tilePosition(int index) const;
|
||||
|
||||
/*
|
||||
* Structure which identifies a point within a given
|
||||
* tile.
|
||||
*/
|
||||
struct TilePoint
|
||||
{
|
||||
TilePoint(int idx = 0, qint64 pos = 0, qint64 pix = 0)
|
||||
: index(idx), positionOffset(pos), pixelOffset(pix)
|
||||
{ }
|
||||
|
||||
// Index of tile
|
||||
int index;
|
||||
|
||||
// Number of bytes from start of tile
|
||||
qint64 positionOffset;
|
||||
|
||||
// Number of pixels from left of corresponding pixmap
|
||||
int pixelOffset;
|
||||
};
|
||||
|
||||
/*
|
||||
* Convert position in m_buffer into a tile index and an offset in pixels
|
||||
* into the corresponding pixmap.
|
||||
*
|
||||
* \param position Offset into m_buffer, in bytes
|
||||
|
||||
* If position is outside the tile array, index is NullIndex and
|
||||
* offset is zero.
|
||||
*/
|
||||
TilePoint tilePoint(qint64 position) const;
|
||||
|
||||
/*
|
||||
* Convert offset in bytes into a tile into an offset in pixels
|
||||
* within that tile.
|
||||
*/
|
||||
int tilePixelOffset(qint64 positionOffset) const;
|
||||
|
||||
/*
|
||||
* Convert offset in bytes into the window into an offset in pixels
|
||||
* within the widget rect().
|
||||
*/
|
||||
int windowPixelOffset(qint64 positionOffset) const;
|
||||
|
||||
/*
|
||||
* Paint all tiles which can be painted.
|
||||
* \return true iff update() was called
|
||||
*/
|
||||
bool paintTiles();
|
||||
|
||||
/*
|
||||
* Paint the specified tile
|
||||
*
|
||||
* \pre Sufficient data is available to completely paint the tile, i.e.
|
||||
* m_dataLength is greater than the upper bound of the tile.
|
||||
*/
|
||||
void paintTile(int index);
|
||||
|
||||
/*
|
||||
* Move the first n tiles to the end of the array, and mark them as not
|
||||
* painted.
|
||||
*/
|
||||
void shuffleTiles(int n);
|
||||
|
||||
/*
|
||||
* Reset tile array
|
||||
*/
|
||||
void resetTiles(qint64 newStartPos);
|
||||
|
||||
private:
|
||||
qint64 m_bufferPosition;
|
||||
qint64 m_bufferLength;
|
||||
QByteArray m_buffer;
|
||||
|
||||
qint64 m_audioPosition;
|
||||
QAudioFormat m_format;
|
||||
|
||||
bool m_active;
|
||||
|
||||
QSize m_pixmapSize;
|
||||
QVector<QPixmap*> m_pixmaps;
|
||||
|
||||
struct Tile {
|
||||
// Pointer into parent m_pixmaps array
|
||||
QPixmap* pixmap;
|
||||
|
||||
// Flag indicating whether this tile has been painted
|
||||
bool painted;
|
||||
};
|
||||
|
||||
QVector<Tile> m_tiles;
|
||||
|
||||
// Length of audio data in bytes depicted by each tile
|
||||
qint64 m_tileLength;
|
||||
|
||||
// Position in bytes of the first tile, relative to m_buffer
|
||||
qint64 m_tileArrayStart;
|
||||
|
||||
qint64 m_windowPosition;
|
||||
qint64 m_windowLength;
|
||||
|
||||
};
|
||||
|
||||
#endif // WAVEFORM_H
|
||||
151
demos/spectrum/app/wavfile.cpp
Normal file
151
demos/spectrum/app/wavfile.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <QtCore/qendian.h>
|
||||
#include <QVector>
|
||||
#include <QDebug>
|
||||
#include "utils.h"
|
||||
#include "wavfile.h"
|
||||
|
||||
struct chunk
|
||||
{
|
||||
char id[4];
|
||||
quint32 size;
|
||||
};
|
||||
|
||||
struct RIFFHeader
|
||||
{
|
||||
chunk descriptor; // "RIFF"
|
||||
char type[4]; // "WAVE"
|
||||
};
|
||||
|
||||
struct WAVEHeader
|
||||
{
|
||||
chunk descriptor;
|
||||
quint16 audioFormat;
|
||||
quint16 numChannels;
|
||||
quint32 sampleRate;
|
||||
quint32 byteRate;
|
||||
quint16 blockAlign;
|
||||
quint16 bitsPerSample;
|
||||
};
|
||||
|
||||
struct DATAHeader
|
||||
{
|
||||
chunk descriptor;
|
||||
};
|
||||
|
||||
struct CombinedHeader
|
||||
{
|
||||
RIFFHeader riff;
|
||||
WAVEHeader wave;
|
||||
};
|
||||
|
||||
WavFile::WavFile(QObject *parent)
|
||||
: QFile(parent)
|
||||
, m_headerLength(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool WavFile::open(const QString &fileName)
|
||||
{
|
||||
close();
|
||||
setFileName(fileName);
|
||||
return QFile::open(QIODevice::ReadOnly) && readHeader();
|
||||
}
|
||||
|
||||
const QAudioFormat &WavFile::fileFormat() const
|
||||
{
|
||||
return m_fileFormat;
|
||||
}
|
||||
|
||||
qint64 WavFile::headerLength() const
|
||||
{
|
||||
return m_headerLength;
|
||||
}
|
||||
|
||||
bool WavFile::readHeader()
|
||||
{
|
||||
seek(0);
|
||||
CombinedHeader header;
|
||||
bool result = read(reinterpret_cast<char *>(&header), sizeof(CombinedHeader)) == sizeof(CombinedHeader);
|
||||
if (result) {
|
||||
if ((memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0
|
||||
|| memcmp(&header.riff.descriptor.id, "RIFX", 4) == 0)
|
||||
&& memcmp(&header.riff.type, "WAVE", 4) == 0
|
||||
&& memcmp(&header.wave.descriptor.id, "fmt ", 4) == 0
|
||||
&& (header.wave.audioFormat == 1 || header.wave.audioFormat == 0)) {
|
||||
|
||||
// Read off remaining header information
|
||||
DATAHeader dataHeader;
|
||||
|
||||
if (qFromLittleEndian<quint32>(header.wave.descriptor.size) > sizeof(WAVEHeader)) {
|
||||
// Extended data available
|
||||
quint16 extraFormatBytes;
|
||||
if (peek((char*)&extraFormatBytes, sizeof(quint16)) != sizeof(quint16))
|
||||
return false;
|
||||
const qint64 throwAwayBytes = sizeof(quint16) + qFromLittleEndian<quint16>(extraFormatBytes);
|
||||
if (read(throwAwayBytes).size() != throwAwayBytes)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (read((char*)&dataHeader, sizeof(DATAHeader)) != sizeof(DATAHeader))
|
||||
return false;
|
||||
|
||||
// Establish format
|
||||
if (memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0)
|
||||
m_fileFormat.setByteOrder(QAudioFormat::LittleEndian);
|
||||
else
|
||||
m_fileFormat.setByteOrder(QAudioFormat::BigEndian);
|
||||
|
||||
int bps = qFromLittleEndian<quint16>(header.wave.bitsPerSample);
|
||||
m_fileFormat.setChannels(qFromLittleEndian<quint16>(header.wave.numChannels));
|
||||
m_fileFormat.setCodec("audio/pcm");
|
||||
m_fileFormat.setFrequency(qFromLittleEndian<quint32>(header.wave.sampleRate));
|
||||
m_fileFormat.setSampleSize(qFromLittleEndian<quint16>(header.wave.bitsPerSample));
|
||||
m_fileFormat.setSampleType(bps == 8 ? QAudioFormat::UnSignedInt : QAudioFormat::SignedInt);
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
m_headerLength = pos();
|
||||
return result;
|
||||
}
|
||||
68
demos/spectrum/app/wavfile.h
Normal file
68
demos/spectrum/app/wavfile.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
|
||||
** the names of its contributors may be used to endorse or promote
|
||||
** products derived from this software without specific prior written
|
||||
** permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#ifndef WAVFILE_H
|
||||
#define WAVFILE_H
|
||||
|
||||
#include <QtCore/qobject.h>
|
||||
#include <QtCore/qfile.h>
|
||||
#include <QtMultimedia/qaudioformat.h>
|
||||
|
||||
class WavFile : public QFile
|
||||
{
|
||||
public:
|
||||
WavFile(QObject *parent = 0);
|
||||
|
||||
bool open(const QString &fileName);
|
||||
const QAudioFormat &fileFormat() const;
|
||||
qint64 headerLength() const;
|
||||
|
||||
private:
|
||||
bool readHeader();
|
||||
|
||||
private:
|
||||
QAudioFormat m_fileFormat;
|
||||
qint64 m_headerLength;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user