diff options
Diffstat (limited to 'examples/multimedia/spectrum')
63 files changed, 2855 insertions, 4297 deletions
diff --git a/examples/multimedia/spectrum/3rdparty/fftreal/CMakeLists.txt b/examples/multimedia/spectrum/3rdparty/fftreal/CMakeLists.txt new file mode 100644 index 000000000..6c2af7a83 --- /dev/null +++ b/examples/multimedia/spectrum/3rdparty/fftreal/CMakeLists.txt @@ -0,0 +1,27 @@ +find_package(Qt6 REQUIRED COMPONENTS Core) + +add_library(fftreal STATIC + Array.h Array.hpp + DynArray.h DynArray.hpp + FFTRealFixLen.h FFTRealFixLen.hpp + FFTRealFixLenParam.h + FFTRealPassDirect.h FFTRealPassDirect.hpp + FFTRealPassInverse.h FFTRealPassInverse.hpp + FFTRealSelect.h FFTRealSelect.hpp + FFTRealUseTrigo.h FFTRealUseTrigo.hpp + OscSinCos.h OscSinCos.hpp + def.h + fftreal_wrapper.cpp fftreal_wrapper.h +) + +target_compile_definitions(fftreal PRIVATE + FFTREAL_LIBRARY + LOG_ENGINE + LOG_SPECTRUMANALYSER + SPECTRUM_ANALYSER_SEPARATE_THREAD + SUPERIMPOSE_PROGRESS_ON_WAVEFORM +) + +target_link_libraries(fftreal PRIVATE Qt6::Core) + +target_include_directories(fftreal INTERFACE .) diff --git a/examples/multimedia/spectrum/3rdparty/fftreal/fftreal.pro b/examples/multimedia/spectrum/3rdparty/fftreal/fftreal.pro index b2c96f96c..0b36ee7d1 100644 --- a/examples/multimedia/spectrum/3rdparty/fftreal/fftreal.pro +++ b/examples/multimedia/spectrum/3rdparty/fftreal/fftreal.pro @@ -1,8 +1,7 @@ include(../../spectrum.pri) -static: error(This library cannot be built for static linkage) - TEMPLATE = lib +CONFIG += static TARGET = fftreal # FFTReal @@ -24,18 +23,14 @@ HEADERS += Array.h \ OscSinCos.h \ OscSinCos.hpp \ def.h - + # Wrapper used to export the required instantiation of the FFTRealFixLen template HEADERS += fftreal_wrapper.h SOURCES += fftreal_wrapper.cpp DEFINES += FFTREAL_LIBRARY -macx { - CONFIG += lib_bundle -} else { - DESTDIR = ../..$${spectrum_build_dir} -} +DESTDIR = ../..$${spectrum_build_dir} EXAMPLE_FILES = bwins/fftreal.def eabi/fftreal.def readme.txt license.txt diff --git a/examples/multimedia/spectrum/3rdparty/fftreal/fftreal_wrapper.h b/examples/multimedia/spectrum/3rdparty/fftreal/fftreal_wrapper.h index 2da3147ac..1b92d8e65 100644 --- a/examples/multimedia/spectrum/3rdparty/fftreal/fftreal_wrapper.h +++ b/examples/multimedia/spectrum/3rdparty/fftreal/fftreal_wrapper.h @@ -22,12 +22,6 @@ #include <QtCore/QtGlobal> -#if defined(FFTREAL_LIBRARY) -# define FFTREAL_EXPORT Q_DECL_EXPORT -#else -# define FFTREAL_EXPORT Q_DECL_IMPORT -#endif - class FFTRealWrapperPrivate; // Each pass of the FFT processes 2^X samples, where X is the @@ -46,7 +40,7 @@ static const int FFTLengthPowerOfTwo = 12; * * See http://ldesoras.free.fr/prod.html */ -class FFTREAL_EXPORT FFTRealWrapper +class FFTRealWrapper { public: FFTRealWrapper(); diff --git a/examples/multimedia/spectrum/CMakeLists.txt b/examples/multimedia/spectrum/CMakeLists.txt new file mode 100644 index 000000000..faabe9c27 --- /dev/null +++ b/examples/multimedia/spectrum/CMakeLists.txt @@ -0,0 +1,67 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(spectrum LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/spectrum") + +find_package(Qt6 REQUIRED COMPONENTS Multimedia Widgets) + +add_subdirectory(3rdparty/fftreal) + +qt_add_executable(spectrum + engine.cpp engine.h + frequencyspectrum.cpp frequencyspectrum.h + levelmeter.cpp levelmeter.h + main.cpp + mainwidget.cpp mainwidget.h + progressbar.cpp progressbar.h + settingsdialog.cpp settingsdialog.h + spectrograph.cpp spectrograph.h + spectrum.h + spectrumanalyser.cpp spectrumanalyser.h + tonegenerator.cpp tonegenerator.h + tonegeneratordialog.cpp tonegeneratordialog.h + utils.cpp utils.h + waveform.cpp waveform.h +) + +set_target_properties(spectrum PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in +) + +target_compile_definitions(spectrum PRIVATE + LOG_ENGINE + LOG_SPECTRUMANALYSER + SPECTRUM_ANALYSER_SEPARATE_THREAD + SUPERIMPOSE_PROGRESS_ON_WAVEFORM +) + +target_link_libraries(spectrum PRIVATE + Qt6::Multimedia + Qt6::Widgets + fftreal +) + +qt_add_resources(spectrum "spectrum" + PREFIX + "/" + FILES + "images/record.png" + "images/settings.png" +) + +install(TARGETS spectrum + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/multimedia/spectrum/Info.plist.in b/examples/multimedia/spectrum/Info.plist.in new file mode 100644 index 000000000..d0b9ddd11 --- /dev/null +++ b/examples/multimedia/spectrum/Info.plist.in @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + + <key>CFBundleName</key> + <string>Qt ${MACOSX_BUNDLE_BUNDLE_NAME} Example</string> + <key>CFBundleIdentifier</key> + <string>Qt ${MACOSX_BUNDLE_GUI_IDENTIFIER} Example</string> + <key>CFBundleExecutable</key> + <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string> + + <key>CFBundleVersion</key> + <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string> + <key>CFBundleShortVersionString</key> + <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string> + <key>CFBundleLongVersionString</key> + <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string> + + <key>LSMinimumSystemVersion</key> + <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string> + + <key>CFBundleGetInfoString</key> + <string>${MACOSX_BUNDLE_INFO_STRING}</string> + <key>NSHumanReadableCopyright</key> + <string>${MACOSX_BUNDLE_COPYRIGHT}</string> + + <key>CFBundleIconFile</key> + <string>${MACOSX_BUNDLE_ICON_FILE}</string> + + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + + <key>NSPrincipalClass</key> + <string>NSApplication</string> + <key>NSMicrophoneUsageDescription</key> + <string>Qt Multimedia Example</string> +</dict> +</plist> diff --git a/examples/multimedia/spectrum/app.pro b/examples/multimedia/spectrum/app.pro new file mode 100644 index 000000000..9688940bb --- /dev/null +++ b/examples/multimedia/spectrum/app.pro @@ -0,0 +1,55 @@ +include(spectrum.pri) + +TEMPLATE = app + +TARGET = spectrum + +QT += multimedia widgets + +SOURCES += main.cpp \ + engine.cpp \ + frequencyspectrum.cpp \ + levelmeter.cpp \ + mainwidget.cpp \ + progressbar.cpp \ + settingsdialog.cpp \ + spectrograph.cpp \ + spectrumanalyser.cpp \ + tonegenerator.cpp \ + tonegeneratordialog.cpp \ + utils.cpp \ + waveform.cpp \ + +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 \ + +fftreal_dir = 3rdparty/fftreal + +INCLUDEPATH += $${fftreal_dir} + +RESOURCES = spectrum.qrc + +LIBS += -L./$${spectrum_build_dir} +android:LIBS += -lfftreal_$$QT_ARCH +else:LIBS += -lfftreal + +target.path = $$[QT_INSTALL_EXAMPLES]/multimedia/spectrum +INSTALLS += target + +CONFIG += install_ok # Do not cargo-cult this! + +# Deployment + +DESTDIR = ./$${spectrum_build_dir} +include(../shared/shared.pri) diff --git a/examples/multimedia/spectrum/app/.gitignore b/examples/multimedia/spectrum/app/.gitignore deleted file mode 100644 index 82cf2a28f..000000000 --- a/examples/multimedia/spectrum/app/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -spectrum -spectrum.exe diff --git a/examples/multimedia/spectrum/app/app.pro b/examples/multimedia/spectrum/app/app.pro deleted file mode 100644 index 726ce2d3f..000000000 --- a/examples/multimedia/spectrum/app/app.pro +++ /dev/null @@ -1,86 +0,0 @@ -include(../spectrum.pri) - -TEMPLATE = app - -TARGET = spectrum - -QT += multimedia widgets - -SOURCES += main.cpp \ - engine.cpp \ - frequencyspectrum.cpp \ - levelmeter.cpp \ - mainwidget.cpp \ - progressbar.cpp \ - settingsdialog.cpp \ - spectrograph.cpp \ - spectrumanalyser.cpp \ - tonegenerator.cpp \ - tonegeneratordialog.cpp \ - utils.cpp \ - waveform.cpp \ - wavfile.cpp - -HEADERS += engine.h \ - frequencyspectrum.h \ - levelmeter.h \ - mainwidget.h \ - progressbar.h \ - settingsdialog.h \ - spectrograph.h \ - spectrum.h \ - spectrumanalyser.h \ - tonegenerator.h \ - tonegeneratordialog.h \ - utils.h \ - waveform.h \ - wavfile.h - -fftreal_dir = ../3rdparty/fftreal - -INCLUDEPATH += $${fftreal_dir} - -RESOURCES = spectrum.qrc - -# Dynamic linkage against FFTReal DLL -!contains(DEFINES, DISABLE_FFT) { - macx { - # Link to fftreal framework - LIBS += -F$${fftreal_dir} - LIBS += -framework fftreal - } else { - LIBS += -L..$${spectrum_build_dir} - LIBS += -lfftreal - } -} - -target.path = $$[QT_INSTALL_EXAMPLES]/multimedia/spectrum -INSTALLS += target - -CONFIG += install_ok # Do not cargo-cult this! - -# Deployment - -DESTDIR = ..$${spectrum_build_dir} -macx { - !contains(DEFINES, DISABLE_FFT) { - # Relocate fftreal.framework into spectrum.app bundle - framework_dir = ../spectrum.app/Contents/Frameworks - framework_name = fftreal.framework/Versions/1/fftreal - QMAKE_POST_LINK = \ - mkdir -p $${framework_dir} &&\ - rm -rf $${framework_dir}/fftreal.framework &&\ - cp -R $${fftreal_dir}/fftreal.framework $${framework_dir} &&\ - install_name_tool -id @executable_path/../Frameworks/$${framework_name} \ - $${framework_dir}/$${framework_name} &&\ - install_name_tool -change $${framework_name} \ - @executable_path/../Frameworks/$${framework_name} \ - ../spectrum.app/Contents/MacOS/spectrum - } -} else { - linux-g++*: { - # Provide relative path from application to fftreal library - QMAKE_LFLAGS += -Wl,--rpath=\\\$\$ORIGIN - } -} -include(../../shared/shared.pri) diff --git a/examples/multimedia/spectrum/app/engine.cpp b/examples/multimedia/spectrum/app/engine.cpp deleted file mode 100644 index f934f8788..000000000 --- a/examples/multimedia/spectrum/app/engine.cpp +++ /dev/null @@ -1,783 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "engine.h" -#include "tonegenerator.h" -#include "utils.h" - -#include <math.h> - -#include <QAudioInput> -#include <QAudioOutput> -#include <QCoreApplication> -#include <QDebug> -#include <QFile> -#include <QMetaObject> -#include <QSet> -#include <QThread> - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- - -const qint64 BufferDurationUs = 10 * 1000000; -const int NotifyIntervalMs = 100; - -// Size of the level calculation window in microseconds -const int LevelWindowUs = 0.1 * 1000000; - -//----------------------------------------------------------------------------- -// Constructor and destructor -//----------------------------------------------------------------------------- - -Engine::Engine(QObject *parent) - : QObject(parent) - , m_mode(QAudio::AudioInput) - , m_state(QAudio::StoppedState) - , m_generateTone(false) - , m_file(0) - , m_analysisFile(0) - , m_availableAudioInputDevices - (QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) - , m_audioInputDevice(QAudioDeviceInfo::defaultInputDevice()) - , m_audioInput(0) - , m_audioInputIODevice(0) - , m_recordPosition(0) - , m_availableAudioOutputDevices - (QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) - , m_audioOutputDevice(QAudioDeviceInfo::defaultOutputDevice()) - , m_audioOutput(0) - , m_playPosition(0) - , m_bufferPosition(0) - , m_bufferLength(0) - , m_dataLength(0) - , m_levelBufferLength(0) - , m_rmsLevel(0.0) - , m_peakLevel(0.0) - , m_spectrumBufferLength(0) - , m_spectrumAnalyser() - , m_spectrumPosition(0) - , m_count(0) -{ - qRegisterMetaType<FrequencySpectrum>("FrequencySpectrum"); - qRegisterMetaType<WindowFunction>("WindowFunction"); - connect(&m_spectrumAnalyser, QOverload<const FrequencySpectrum&>::of(&SpectrumAnalyser::spectrumChanged), - this, QOverload<const FrequencySpectrum&>::of(&Engine::spectrumChanged)); - - // This code might misinterpret things like "-something -category". But - // it's unlikely that that needs to be supported so we'll let it go. - QStringList arguments = QCoreApplication::instance()->arguments(); - for (int i = 0; i < arguments.count(); ++i) { - if (arguments.at(i) == QStringLiteral("--")) - break; - - if (arguments.at(i) == QStringLiteral("-category") - || arguments.at(i) == QStringLiteral("--category")) { - ++i; - if (i < arguments.count()) - m_audioOutputCategory = arguments.at(i); - else - --i; - } - } - - 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; - connect(m_audioInput, &QAudioInput::stateChanged, - this, &Engine::audioStateChanged); - connect(m_audioInput, &QAudioInput::notify, - this, &Engine::audioNotify); - - m_count = 0; - m_dataLength = 0; - emit dataLengthChanged(0); - m_audioInputIODevice = m_audioInput->start(); - connect(m_audioInputIODevice, &QIODevice::readyRead, - this, &Engine::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; - connect(m_audioOutput, &QAudioOutput::stateChanged, - this, &Engine::audioStateChanged); - connect(m_audioOutput, &QAudioOutput::notify, - this, &Engine::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); - m_audioOutput->setCategory(m_audioOutputCategory); - } - } 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; - ENGINE_DEBUG << "Engine::initialize" << "m_audioOutputCategory" << m_audioOutputCategory; - - return result; -} - -bool Engine::selectFormat() -{ - bool foundSupportedFormat = false; - - if (m_file || QAudioFormat() != m_format) { - QAudioFormat format = m_format; - if (m_file) - // Header is read from the WAV file; just need to check whether - // it is supported by the audio output device - format = m_file->fileFormat(); - if (m_audioOutputDevice.isFormatSupported(format)) { - setFormat(format); - foundSupportedFormat = true; - } - } else { - - QList<int> sampleRatesList; - #ifdef Q_OS_WIN - // The Windows audio backend does not correctly report format support - // (see QTBUG-9100). Furthermore, although the audio subsystem captures - // at 11025Hz, the resulting audio is corrupted. - sampleRatesList += 8000; - #endif - - if (!m_generateTone) - sampleRatesList += m_audioInputDevice.supportedSampleRates(); - - sampleRatesList += m_audioOutputDevice.supportedSampleRates(); - std::sort(sampleRatesList.begin(), sampleRatesList.end()); - const auto uniqueRatesEnd = std::unique(sampleRatesList.begin(), sampleRatesList.end()); - sampleRatesList.erase(uniqueRatesEnd, sampleRatesList.end()); - ENGINE_DEBUG << "Engine::initialize frequenciesList" << sampleRatesList; - - QList<int> channelsList; - channelsList += m_audioInputDevice.supportedChannelCounts(); - channelsList += m_audioOutputDevice.supportedChannelCounts(); - std::sort(channelsList.begin(), channelsList.end()); - const auto uniqueChannelsEnd = std::unique(channelsList.begin(), channelsList.end()); - channelsList.erase(uniqueChannelsEnd, channelsList.end()); - ENGINE_DEBUG << "Engine::initialize channelsList" << channelsList; - - QAudioFormat format; - format.setByteOrder(QAudioFormat::LittleEndian); - format.setCodec("audio/pcm"); - format.setSampleSize(16); - format.setSampleType(QAudioFormat::SignedInt); - for (int sampleRate : qAsConst(sampleRatesList)) { - if (foundSupportedFormat) - break; - format.setSampleRate(sampleRate); - for (int channels : qAsConst(channelsList)) { - format.setChannelCount(channels); - const bool inputSupport = m_generateTone || - m_audioInputDevice.isFormatSupported(format); - const bool outputSupport = m_audioOutputDevice.isFormatSupported(format); - ENGINE_DEBUG << "Engine::initialize checking " << format - << "input" << inputSupport - << "output" << outputSupport; - if (inputSupport && outputSupport) { - foundSupportedFormat = true; - break; - } - } - } - - if (!foundSupportedFormat) - format = QAudioFormat(); - - setFormat(format); - } - - return foundSupportedFormat; -} - -void Engine::stopRecording() -{ - if (m_audioInput) { - m_audioInput->stop(); - QCoreApplication::instance()->processEvents(); - m_audioInput->disconnect(); - } - m_audioInputIODevice = 0; - -#ifdef DUMP_AUDIO - dumpData(); -#endif -} - -void Engine::stopPlayback() -{ - if (m_audioOutput) { - m_audioOutput->stop(); - QCoreApplication::instance()->processEvents(); - m_audioOutput->disconnect(); - setPlayPosition(0); - } -} - -void Engine::setState(QAudio::State state) -{ - const bool changed = (m_state != state); - m_state = state; - if (changed) - emit stateChanged(m_mode, m_state); -} - -void Engine::setState(QAudio::Mode mode, QAudio::State state) -{ - const bool changed = (m_mode != mode || m_state != state); - m_mode = mode; - m_state = state; - if (changed) - emit stateChanged(m_mode, m_state); -} - -void Engine::setRecordPosition(qint64 position, bool forceEmit) -{ - const bool changed = (m_recordPosition != position); - m_recordPosition = position; - if (changed || forceEmit) - emit recordPositionChanged(m_recordPosition); -} - -void Engine::setPlayPosition(qint64 position, bool forceEmit) -{ - const bool changed = (m_playPosition != position); - m_playPosition = position; - if (changed || forceEmit) - emit playPositionChanged(m_playPosition); -} - -void Engine::calculateLevel(qint64 position, qint64 length) -{ -#ifdef DISABLE_LEVEL - Q_UNUSED(position); - Q_UNUSED(length); -#else - Q_ASSERT(position + length <= m_bufferPosition + m_dataLength); - - qreal peakLevel = 0.0; - - qreal sum = 0.0; - const char *ptr = m_buffer.constData() + position - m_bufferPosition; - const char *const end = ptr + length; - while (ptr < end) { - const qint16 value = *reinterpret_cast<const qint16*>(ptr); - const qreal fracValue = pcmToReal(value); - peakLevel = qMax(peakLevel, fracValue); - sum += fracValue * fracValue; - ptr += 2; - } - const int numSamples = length / 2; - qreal rmsLevel = sqrt(sum / numSamples); - - rmsLevel = qMax(qreal(0.0), rmsLevel); - rmsLevel = qMin(qreal(1.0), rmsLevel); - setLevel(rmsLevel, peakLevel, numSamples); - - ENGINE_DEBUG << "Engine::calculateLevel" << "pos" << position << "len" << length - << "rms" << rmsLevel << "peak" << peakLevel; -#endif -} - -void Engine::calculateSpectrum(qint64 position) -{ -#ifdef DISABLE_SPECTRUM - Q_UNUSED(position); -#else - Q_ASSERT(position + m_spectrumBufferLength <= m_bufferPosition + m_dataLength); - Q_ASSERT(0 == m_spectrumBufferLength % 2); // constraint of FFT algorithm - - // QThread::currentThread is marked 'for internal use only', but - // we're only using it for debug output here, so it's probably OK :) - ENGINE_DEBUG << "Engine::calculateSpectrum" << QThread::currentThread() - << "count" << m_count << "pos" << position << "len" << m_spectrumBufferLength - << "spectrumAnalyser.isReady" << m_spectrumAnalyser.isReady(); - - if (m_spectrumAnalyser.isReady()) { - m_spectrumBuffer = QByteArray::fromRawData(m_buffer.constData() + position - m_bufferPosition, - m_spectrumBufferLength); - m_spectrumPosition = position; - m_spectrumAnalyser.calculate(m_spectrumBuffer, m_format); - } -#endif -} - -void Engine::setFormat(const QAudioFormat &format) -{ - const bool changed = (format != m_format); - m_format = format; - m_levelBufferLength = audioLength(m_format, LevelWindowUs); - m_spectrumBufferLength = SpectrumLengthSamples * - (m_format.sampleSize() / 8) * m_format.channelCount(); - if (changed) - emit formatChanged(m_format); -} - -void Engine::setLevel(qreal rmsLevel, qreal peakLevel, int numSamples) -{ - m_rmsLevel = rmsLevel; - m_peakLevel = peakLevel; - emit levelChanged(m_rmsLevel, m_peakLevel, numSamples); -} - -#ifdef DUMP_DATA -void Engine::createOutputDir() -{ - m_outputDir.setPath("output"); - - // Ensure output directory exists and is empty - if (m_outputDir.exists()) { - const QStringList files = m_outputDir.entryList(QDir::Files); - for (const QString &file : files) - m_outputDir.remove(file); - } else { - QDir::current().mkdir("output"); - } -} -#endif // DUMP_DATA - -#ifdef DUMP_AUDIO -void Engine::dumpData() -{ - const QString txtFileName = m_outputDir.filePath("data.txt"); - QFile txtFile(txtFileName); - txtFile.open(QFile::WriteOnly | QFile::Text); - QTextStream stream(&txtFile); - const qint16 *ptr = reinterpret_cast<const qint16*>(m_buffer.constData()); - const int numSamples = m_dataLength / (2 * m_format.channels()); - for (int i=0; i<numSamples; ++i) { - stream << i << "\t" << *ptr << "\n"; - ptr += m_format.channels(); - } - - const QString pcmFileName = m_outputDir.filePath("data.pcm"); - QFile pcmFile(pcmFileName); - pcmFile.open(QFile::WriteOnly); - pcmFile.write(m_buffer.constData(), m_dataLength); -} -#endif // DUMP_AUDIO diff --git a/examples/multimedia/spectrum/app/frequencyspectrum.cpp b/examples/multimedia/spectrum/app/frequencyspectrum.cpp deleted file mode 100644 index abec65d23..000000000 --- a/examples/multimedia/spectrum/app/frequencyspectrum.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd 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(); -} diff --git a/examples/multimedia/spectrum/app/frequencyspectrum.h b/examples/multimedia/spectrum/app/frequencyspectrum.h deleted file mode 100644 index 4b13cbb99..000000000 --- a/examples/multimedia/spectrum/app/frequencyspectrum.h +++ /dev/null @@ -1,107 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd 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/QList> - -/** - * 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 QList<Element>::iterator iterator; - typedef QList<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: - QList<Element> m_elements; -}; - -#endif // FREQUENCYSPECTRUM_H diff --git a/examples/multimedia/spectrum/app/levelmeter.cpp b/examples/multimedia/spectrum/app/levelmeter.cpp deleted file mode 100644 index f0c66eeb3..000000000 --- a/examples/multimedia/spectrum/app/levelmeter.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd 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, &QTimer::timeout, - this, &LevelMeter::redrawTimerExpired); - m_redrawTimer->start(RedrawInterval); -} - -LevelMeter::~LevelMeter() -{ - -} - -void LevelMeter::reset() -{ - m_rmsLevel = 0.0; - m_peakLevel = 0.0; - update(); -} - -void LevelMeter::levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples) -{ - // Smooth the RMS signal - const qreal smooth = pow(qreal(0.9), static_cast<qreal>(numSamples) / 256); // TODO: remove this magic number - m_rmsLevel = (m_rmsLevel * smooth) + (rmsLevel * (1.0 - smooth)); - - if (peakLevel > m_decayedPeakLevel) { - m_peakLevel = peakLevel; - m_decayedPeakLevel = peakLevel; - m_peakLevelChanged.start(); - } - - if (peakLevel > m_peakHoldLevel) { - m_peakHoldLevel = peakLevel; - m_peakHoldLevelChanged.start(); - } - - update(); -} - -void LevelMeter::redrawTimerExpired() -{ - // Decay the peak signal - const int elapsedMs = m_peakLevelChanged.elapsed(); - const qreal decayAmount = m_peakDecayRate * elapsedMs; - if (decayAmount < m_peakLevel) - m_decayedPeakLevel = m_peakLevel - decayAmount; - else - m_decayedPeakLevel = 0.0; - - // Check whether to clear the peak hold level - if (m_peakHoldLevelChanged.elapsed() > PeakHoldLevelDuration) - m_peakHoldLevel = 0.0; - - update(); -} - -void LevelMeter::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event); - - QPainter painter(this); - painter.fillRect(rect(), Qt::black); - - QRect bar = rect(); - - bar.setTop(rect().top() + (1.0 - m_peakHoldLevel) * rect().height()); - bar.setBottom(bar.top() + 5); - painter.fillRect(bar, m_rmsColor); - bar.setBottom(rect().bottom()); - - bar.setTop(rect().top() + (1.0 - m_decayedPeakLevel) * rect().height()); - painter.fillRect(bar, m_peakColor); - - bar.setTop(rect().top() + (1.0 - m_rmsLevel) * rect().height()); - painter.fillRect(bar, m_rmsColor); -} diff --git a/examples/multimedia/spectrum/app/levelmeter.h b/examples/multimedia/spectrum/app/levelmeter.h deleted file mode 100644 index 987f90e8f..000000000 --- a/examples/multimedia/spectrum/app/levelmeter.h +++ /dev/null @@ -1,128 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd 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 <QElapsedTimer> -#include <QWidget> - -/** - * Widget which displays a vertical audio level meter, indicating the - * RMS and peak levels of the window of audio samples most recently analyzed - * by the Engine. - */ -class LevelMeter : public QWidget -{ - Q_OBJECT - -public: - explicit LevelMeter(QWidget *parent = 0); - ~LevelMeter(); - - void paintEvent(QPaintEvent *event) override; - -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. - */ - QElapsedTimer 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. - */ - QElapsedTimer m_peakHoldLevelChanged; - - QTimer *m_redrawTimer; - - QColor m_rmsColor; - QColor m_peakColor; - -}; - -#endif // LEVELMETER_H diff --git a/examples/multimedia/spectrum/app/main.cpp b/examples/multimedia/spectrum/app/main.cpp deleted file mode 100644 index 7eeb94949..000000000 --- a/examples/multimedia/spectrum/app/main.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "mainwidget.h" -#include <QApplication> - -int main(int argc, char *argv[]) -{ - QApplication app(argc, argv); - app.setApplicationName("Qt Multimedia spectrum analyzer"); - - MainWidget w; - w.show(); - - return app.exec(); -} diff --git a/examples/multimedia/spectrum/app/mainwidget.h b/examples/multimedia/spectrum/app/mainwidget.h deleted file mode 100644 index 2ff863a13..000000000 --- a/examples/multimedia/spectrum/app/mainwidget.h +++ /dev/null @@ -1,157 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef MAINWIDGET_H -#define MAINWIDGET_H - -#include <QAudio> -#include <QIcon> -#include <QWidget> - -class Engine; -class FrequencySpectrum; -class LevelMeter; -class ProgressBar; -class SettingsDialog; -class Spectrograph; -class ToneGeneratorDialog; -class Waveform; - -QT_BEGIN_NAMESPACE -class QAction; -class QAudioFormat; -class QLabel; -class QMenu; -class QPushButton; -QT_END_NAMESPACE - -/** - * Main application widget, responsible for connecting the various UI - * elements to the Engine. - */ -class MainWidget : public QWidget -{ - Q_OBJECT - -public: - explicit MainWidget(QWidget *parent = 0); - ~MainWidget(); - - // QObject - void timerEvent(QTimerEvent *event) override; - -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 diff --git a/examples/multimedia/spectrum/app/progressbar.cpp b/examples/multimedia/spectrum/app/progressbar.cpp deleted file mode 100644 index b42816e1c..000000000 --- a/examples/multimedia/spectrum/app/progressbar.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd 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(); -} diff --git a/examples/multimedia/spectrum/app/progressbar.h b/examples/multimedia/spectrum/app/progressbar.h deleted file mode 100644 index 601900efb..000000000 --- a/examples/multimedia/spectrum/app/progressbar.h +++ /dev/null @@ -1,85 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef PROGRESSBAR_H -#define PROGRESSBAR_H - -#include <QWidget> - -/** - * Widget which displays a the current fill state of the Engine's internal - * buffer, and the current play/record position within that buffer. - */ -class ProgressBar : public QWidget -{ - Q_OBJECT - -public: - explicit ProgressBar(QWidget *parent = 0); - ~ProgressBar(); - - void reset(); - void paintEvent(QPaintEvent *event) override; - -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 diff --git a/examples/multimedia/spectrum/app/settingsdialog.cpp b/examples/multimedia/spectrum/app/settingsdialog.cpp deleted file mode 100644 index 889fcf639..000000000 --- a/examples/multimedia/spectrum/app/settingsdialog.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "settingsdialog.h" -#include <QCheckBox> -#include <QComboBox> -#include <QDialogButtonBox> -#include <QLabel> -#include <QPushButton> -#include <QSlider> -#include <QSpinBox> -#include <QVBoxLayout> - -SettingsDialog::SettingsDialog( - const QList<QAudioDeviceInfo> &availableInputDevices, - const QList<QAudioDeviceInfo> &availableOutputDevices, - QWidget *parent) - : QDialog(parent) - , m_windowFunction(DefaultWindowFunction) - , m_inputDeviceComboBox(new QComboBox(this)) - , m_outputDeviceComboBox(new QComboBox(this)) - , m_windowFunctionComboBox(new QComboBox(this)) -{ - QVBoxLayout *dialogLayout = new QVBoxLayout(this); - - // Populate combo boxes - - for (const QAudioDeviceInfo &device : availableInputDevices) - m_inputDeviceComboBox->addItem(device.deviceName(), - QVariant::fromValue(device)); - for (const QAudioDeviceInfo &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 - connect(m_inputDeviceComboBox, QOverload<int>::of(&QComboBox::activated), - this, &SettingsDialog::inputDeviceChanged); - connect(m_outputDeviceComboBox, QOverload<int>::of(&QComboBox::activated), - this, &SettingsDialog::outputDeviceChanged); - connect(m_windowFunctionComboBox, QOverload<int>::of(&QComboBox::activated), - this, &SettingsDialog::windowFunctionChanged); - - // Add standard buttons to layout - QDialogButtonBox *buttonBox = new QDialogButtonBox(this); - buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - dialogLayout->addWidget(buttonBox); - - // Connect standard buttons - connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, - this, &SettingsDialog::accept); - connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, - this, &SettingsDialog::reject); - - setLayout(dialogLayout); -} - -SettingsDialog::~SettingsDialog() -{ - -} - -void SettingsDialog::windowFunctionChanged(int index) -{ - m_windowFunction = static_cast<WindowFunction>( - m_windowFunctionComboBox->itemData(index).value<int>()); -} - -void SettingsDialog::inputDeviceChanged(int index) -{ - m_inputDevice = m_inputDeviceComboBox->itemData(index).value<QAudioDeviceInfo>(); -} - -void SettingsDialog::outputDeviceChanged(int index) -{ - m_outputDevice = m_outputDeviceComboBox->itemData(index).value<QAudioDeviceInfo>(); -} - diff --git a/examples/multimedia/spectrum/app/settingsdialog.h b/examples/multimedia/spectrum/app/settingsdialog.h deleted file mode 100644 index 3200ccf54..000000000 --- a/examples/multimedia/spectrum/app/settingsdialog.h +++ /dev/null @@ -1,99 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef SETTINGSDIALOG_H -#define SETTINGSDIALOG_H - -#include "spectrum.h" -#include <QDialog> -#include <QAudioDeviceInfo> - -QT_BEGIN_NAMESPACE -class QComboBox; -class QCheckBox; -class QSlider; -class QSpinBox; -class QGridLayout; -QT_END_NAMESPACE - -/** - * Dialog used to control settings such as the audio input / output device - * and the windowing function. - */ -class SettingsDialog : public QDialog -{ - Q_OBJECT - -public: - SettingsDialog(const QList<QAudioDeviceInfo> &availableInputDevices, - const QList<QAudioDeviceInfo> &availableOutputDevices, - QWidget *parent = 0); - ~SettingsDialog(); - - WindowFunction windowFunction() const { return m_windowFunction; } - const QAudioDeviceInfo &inputDevice() const { return m_inputDevice; } - const QAudioDeviceInfo &outputDevice() const { return m_outputDevice; } - -private slots: - void windowFunctionChanged(int index); - void inputDeviceChanged(int index); - void outputDeviceChanged(int index); - -private: - WindowFunction m_windowFunction; - QAudioDeviceInfo m_inputDevice; - QAudioDeviceInfo m_outputDevice; - - QComboBox *m_inputDeviceComboBox; - QComboBox *m_outputDeviceComboBox; - QComboBox *m_windowFunctionComboBox; -}; - -#endif // SETTINGSDIALOG_H diff --git a/examples/multimedia/spectrum/app/spectrograph.h b/examples/multimedia/spectrum/app/spectrograph.h deleted file mode 100644 index 0e7b11717..000000000 --- a/examples/multimedia/spectrum/app/spectrograph.h +++ /dev/null @@ -1,108 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef SPECTROGRAPH_H -#define SPECTROGRAPH_H - -#include "frequencyspectrum.h" - -#include <QWidget> - -/** - * Widget which displays a spectrograph showing the frequency spectrum - * of the window of audio samples most recently analyzed by the Engine. - */ -class Spectrograph : public QWidget -{ - Q_OBJECT - -public: - explicit Spectrograph(QWidget *parent = 0); - ~Spectrograph(); - - void setParams(int numBars, qreal lowFreq, qreal highFreq); - - // QObject - void timerEvent(QTimerEvent *event) override; - - // QWidget - void paintEvent(QPaintEvent *event) override; - void mousePressEvent(QMouseEvent *event) override; - -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; - }; - - QList<Bar> m_bars; - int m_barSelected; - int m_timerId; - qreal m_lowFreq; - qreal m_highFreq; - FrequencySpectrum m_spectrum; -}; - -#endif // SPECTROGRAPH_H diff --git a/examples/multimedia/spectrum/app/spectrum.h b/examples/multimedia/spectrum/app/spectrum.h deleted file mode 100644 index aea9ce7c5..000000000 --- a/examples/multimedia/spectrum/app/spectrum.h +++ /dev/null @@ -1,140 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef SPECTRUM_H -#define SPECTRUM_H - -#include <qglobal.h> -#include "utils.h" -#include "fftreal_wrapper.h" // For FFTLengthPowerOfTwo - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- - -// Number of audio samples used to calculate the frequency spectrum -const int SpectrumLengthSamples = PowerOfTwo<FFTLengthPowerOfTwo>::Result; - -// Number of bands in the frequency spectrum -const int SpectrumNumBands = 10; - -// Lower bound of first band in the spectrum -const qreal SpectrumLowFreq = 0.0; // Hz - -// Upper band of last band in the spectrum -const qreal SpectrumHighFreq = 1000.0; // Hz - -// Waveform window size in microseconds -const qint64 WaveformWindowDuration = 500 * 1000; - -// Length of waveform tiles in bytes -// Ideally, these would match the QAudio*::bufferSize(), but that isn't -// available until some time after QAudio*::start() has been called, and we -// need this value in order to initialize the waveform display. -// We therefore just choose a sensible value. -const int WaveformTileLength = 4096; - -// Fudge factor used to calculate the spectrum bar heights -const qreal SpectrumAnalyserMultiplier = 0.15; - -// Disable message timeout -const int NullMessageTimeout = -1; - - -//----------------------------------------------------------------------------- -// Types and data structures -//----------------------------------------------------------------------------- - -enum WindowFunction { - NoWindow, - HannWindow -}; - -const WindowFunction DefaultWindowFunction = HannWindow; - -struct Tone -{ - Tone(qreal freq = 0.0, qreal amp = 0.0) - : frequency(freq), amplitude(amp) - { } - - // Start and end frequencies for swept tone generation - qreal frequency; - - // Amplitude in range [0.0, 1.0] - qreal amplitude; -}; - -struct SweptTone -{ - SweptTone(qreal start = 0.0, qreal end = 0.0, qreal amp = 0.0) - : startFreq(start), endFreq(end), amplitude(amp) - { Q_ASSERT(end >= start); } - - SweptTone(const Tone &tone) - : startFreq(tone.frequency), endFreq(tone.frequency), amplitude(tone.amplitude) - { } - - // Start and end frequencies for swept tone generation - qreal startFreq; - qreal endFreq; - - // Amplitude in range [0.0, 1.0] - qreal amplitude; -}; - -// Handle some dependencies between macros defined in the .pro file - -#ifdef DISABLE_WAVEFORM -#undef SUPERIMPOSE_PROGRESS_ON_WAVEFORM -#endif - -#endif // SPECTRUM_H - diff --git a/examples/multimedia/spectrum/app/spectrumanalyser.cpp b/examples/multimedia/spectrum/app/spectrumanalyser.cpp deleted file mode 100644 index 6daa1c6aa..000000000 --- a/examples/multimedia/spectrum/app/spectrumanalyser.cpp +++ /dev/null @@ -1,286 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "spectrumanalyser.h" -#include "utils.h" -#include "fftreal_wrapper.h" - -#include <qmath.h> -#include <qmetatype.h> -#include <QAudioFormat> -#include <QThread> - -SpectrumAnalyserThread::SpectrumAnalyserThread(QObject *parent) - : QObject(parent) -#ifndef DISABLE_FFT - , m_fft(new FFTRealWrapper) -#endif - , m_numSamples(SpectrumLengthSamples) - , m_windowFunction(DefaultWindowFunction) - , m_window(SpectrumLengthSamples, 0.0) - , m_input(SpectrumLengthSamples, 0.0) - , m_output(SpectrumLengthSamples, 0.0) - , m_spectrum(SpectrumLengthSamples) -#ifdef SPECTRUM_ANALYSER_SEPARATE_THREAD - , m_thread(new QThread(this)) -#endif -{ -#ifdef SPECTRUM_ANALYSER_SEPARATE_THREAD - // moveToThread() cannot be called on a QObject with a parent - setParent(0); - moveToThread(m_thread); - m_thread->start(); -#endif - calculateWindow(); -} - -SpectrumAnalyserThread::~SpectrumAnalyserThread() -{ -#ifndef DISABLE_FFT - delete m_fft; -#endif -} - -void SpectrumAnalyserThread::setWindowFunction(WindowFunction type) -{ - m_windowFunction = type; - calculateWindow(); -} - -void SpectrumAnalyserThread::calculateWindow() -{ - for (int i=0; i<m_numSamples; ++i) { - DataType x = 0.0; - - switch (m_windowFunction) { - case NoWindow: - x = 1.0; - break; - case HannWindow: - x = 0.5 * (1 - qCos((2 * M_PI * i) / (m_numSamples - 1))); - break; - default: - Q_ASSERT(false); - } - - m_window[i] = x; - } -} - -void SpectrumAnalyserThread::calculateSpectrum(const QByteArray &buffer, - int inputFrequency, - int bytesPerSample) -{ -#ifndef DISABLE_FFT - Q_ASSERT(buffer.size() == m_numSamples * bytesPerSample); - - // Initialize data array - const char *ptr = buffer.constData(); - for (int i=0; i<m_numSamples; ++i) { - const qint16 pcmSample = *reinterpret_cast<const qint16*>(ptr); - // Scale down to range [-1.0, 1.0] - const DataType realSample = pcmToReal(pcmSample); - const DataType windowedSample = realSample * m_window[i]; - m_input[i] = windowedSample; - ptr += bytesPerSample; - } - - // Calculate the FFT - m_fft->calculateFFT(m_output.data(), m_input.data()); - - // Analyze output to obtain amplitude and phase for each frequency - for (int i=2; i<=m_numSamples/2; ++i) { - // Calculate frequency of this complex sample - m_spectrum[i].frequency = qreal(i * inputFrequency) / (m_numSamples); - - const qreal real = m_output[i]; - qreal imag = 0.0; - if (i>0 && i<m_numSamples/2) - imag = m_output[m_numSamples/2 + i]; - - const qreal magnitude = qSqrt(real*real + imag*imag); - qreal amplitude = SpectrumAnalyserMultiplier * qLn(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 -{ - connect(m_thread, &SpectrumAnalyserThread::calculationComplete, - this, &SpectrumAnalyser::calculationComplete); -} - -SpectrumAnalyser::~SpectrumAnalyser() -{ - -} - -#ifdef DUMP_SPECTRUMANALYSER -void SpectrumAnalyser::setOutputPath(const QString &outputDir) -{ - m_outputDir.setPath(outputDir); - m_textFile.setFileName(m_outputDir.filePath("spectrum.txt")); - m_textFile.open(QIODevice::WriteOnly | QIODevice::Text); - m_textStream.setDevice(&m_textFile); -} -#endif - -//----------------------------------------------------------------------------- -// Public functions -//----------------------------------------------------------------------------- - -void SpectrumAnalyser::setWindowFunction(WindowFunction type) -{ - const bool b = QMetaObject::invokeMethod(m_thread, "setWindowFunction", - Qt::AutoConnection, - Q_ARG(WindowFunction, type)); - Q_ASSERT(b); - Q_UNUSED(b); // suppress warnings in release builds -} - -void SpectrumAnalyser::calculate(const QByteArray &buffer, - const QAudioFormat &format) -{ - // QThread::currentThread is marked 'for internal use only', but - // we're only using it for debug output here, so it's probably OK :) - SPECTRUMANALYSER_DEBUG << "SpectrumAnalyser::calculate" - << QThread::currentThread() - << "state" << m_state; - - if (isReady()) { - Q_ASSERT(isPCMS16LE(format)); - - const int bytesPerSample = format.sampleSize() * format.channelCount() / 8; - -#ifdef DUMP_SPECTRUMANALYSER - m_count++; - const QString pcmFileName = m_outputDir.filePath(QString("spectrum_%1.pcm").arg(m_count, 4, 10, QChar('0'))); - QFile pcmFile(pcmFileName); - pcmFile.open(QIODevice::WriteOnly); - const int bufferLength = m_numSamples * bytesPerSample; - pcmFile.write(buffer, bufferLength); - - m_textStream << "TimeDomain " << m_count << "\n"; - const qint16* input = reinterpret_cast<const qint16*>(buffer); - for (int i=0; i<m_numSamples; ++i) { - m_textStream << i << "\t" << *input << "\n"; - input += format.channels(); - } -#endif - - m_state = Busy; - - // Invoke SpectrumAnalyserThread::calculateSpectrum using QMetaObject. If - // m_thread is in a different thread from the current thread, the - // calculation will be done in the child thread. - // Once the calculation is finished, a calculationChanged signal will be - // emitted by m_thread. - const bool b = QMetaObject::invokeMethod(m_thread, "calculateSpectrum", - Qt::AutoConnection, - Q_ARG(QByteArray, buffer), - Q_ARG(int, format.sampleRate()), - Q_ARG(int, bytesPerSample)); - Q_ASSERT(b); - Q_UNUSED(b); // suppress warnings in release builds - -#ifdef DUMP_SPECTRUMANALYSER - m_textStream << "FrequencySpectrum " << m_count << "\n"; - FrequencySpectrum::const_iterator x = m_spectrum.begin(); - for (int i=0; i<m_numSamples; ++i, ++x) - m_textStream << i << "\t" - << x->frequency << "\t" - << x->amplitude<< "\t" - << x->phase << "\n"; -#endif - } -} - -bool SpectrumAnalyser::isReady() const -{ - return (Idle == m_state); -} - -void SpectrumAnalyser::cancelCalculation() -{ - if (Busy == m_state) - m_state = Cancelled; -} - - -//----------------------------------------------------------------------------- -// Private slots -//----------------------------------------------------------------------------- - -void SpectrumAnalyser::calculationComplete(const FrequencySpectrum &spectrum) -{ - Q_ASSERT(Idle != m_state); - if (Busy == m_state) - emit spectrumChanged(spectrum); - m_state = Idle; -} diff --git a/examples/multimedia/spectrum/app/spectrumanalyser.h b/examples/multimedia/spectrum/app/spectrumanalyser.h deleted file mode 100644 index 799143489..000000000 --- a/examples/multimedia/spectrum/app/spectrumanalyser.h +++ /dev/null @@ -1,206 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd 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 <QList> - -#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 - QList<DataType> m_window; - - QList<DataType> m_input; - QList<DataType> m_output; - - FrequencySpectrum m_spectrum; - -#ifdef SPECTRUM_ANALYSER_SEPARATE_THREAD - QThread* m_thread; -#endif -}; - -/** - * Class which performs frequency spectrum analysis on a window of - * audio samples, provided to it by the Engine. - */ -class SpectrumAnalyser : public QObject -{ - Q_OBJECT - -public: - SpectrumAnalyser(QObject *parent = 0); - ~SpectrumAnalyser(); - -#ifdef DUMP_SPECTRUMANALYSER - void setOutputPath(const QString &outputPath); -#endif - -public: - /* - * Set the windowing function which is applied before calculating the FFT - */ - void setWindowFunction(WindowFunction type); - - /* - * Calculate a frequency spectrum - * - * \param buffer Audio data - * \param format Format of audio data - * - * Frequency spectrum is calculated asynchronously. The result is returned - * via the spectrumChanged signal. - * - * An ongoing calculation can be cancelled by calling cancelCalculation(). - * - */ - void calculate(const QByteArray &buffer, const QAudioFormat &format); - - /* - * Check whether the object is ready to perform another calculation - */ - bool isReady() const; - - /* - * Cancel an ongoing calculation - * - * Note that cancelling is asynchronous. - */ - void cancelCalculation(); - -signals: - void spectrumChanged(const FrequencySpectrum &spectrum); - -private slots: - void calculationComplete(const FrequencySpectrum &spectrum); - -private: - void calculateWindow(); - -private: - - SpectrumAnalyserThread* m_thread; - - enum State { - Idle, - Busy, - Cancelled - }; - - State m_state; - -#ifdef DUMP_SPECTRUMANALYSER - QDir m_outputDir; - int m_count; - QFile m_textFile; - QTextStream m_textStream; -#endif -}; - -#endif // SPECTRUMANALYSER_H - diff --git a/examples/multimedia/spectrum/app/tonegenerator.cpp b/examples/multimedia/spectrum/app/tonegenerator.cpp deleted file mode 100644 index 2e549ab4f..000000000 --- a/examples/multimedia/spectrum/app/tonegenerator.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "spectrum.h" -#include "utils.h" -#include <QByteArray> -#include <QAudioFormat> -#include <qmath.h> -#include <qendian.h> - -void generateTone(const SweptTone &tone, const QAudioFormat &format, QByteArray &buffer) -{ - Q_ASSERT(isPCMS16LE(format)); - - const int channelBytes = format.sampleSize() / 8; - const int sampleBytes = format.channelCount() * channelBytes; - int length = buffer.size(); - const int numSamples = buffer.size() / sampleBytes; - - Q_ASSERT(length % sampleBytes == 0); - Q_UNUSED(sampleBytes); // suppress warning in release builds - - unsigned char *ptr = reinterpret_cast<unsigned char *>(buffer.data()); - - qreal phase = 0.0; - - const qreal d = 2 * M_PI / format.sampleRate(); - - // We can't generate a zero-frequency sine wave - const qreal startFreq = tone.startFreq ? tone.startFreq : 1.0; - - // Amount by which phase increases on each sample - qreal phaseStep = d * startFreq; - - // Amount by which phaseStep increases on each sample - // If this is non-zero, the output is a frequency-swept tone - const qreal phaseStepStep = d * (tone.endFreq - startFreq) / numSamples; - - while (length) { - const qreal x = tone.amplitude * qSin(phase); - const qint16 value = realToPcm(x); - for (int i=0; i<format.channelCount(); ++i) { - qToLittleEndian<qint16>(value, ptr); - ptr += channelBytes; - length -= channelBytes; - } - - phase += phaseStep; - while (phase > 2 * M_PI) - phase -= 2 * M_PI; - phaseStep += phaseStepStep; - } -} diff --git a/examples/multimedia/spectrum/app/tonegenerator.h b/examples/multimedia/spectrum/app/tonegenerator.h deleted file mode 100644 index af6efade2..000000000 --- a/examples/multimedia/spectrum/app/tonegenerator.h +++ /dev/null @@ -1,68 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef TONEGENERATOR_H -#define TONEGENERATOR_H - -#include <qglobal.h> -#include "spectrum.h" - -QT_BEGIN_NAMESPACE -class QAudioFormat; -class QByteArray; -QT_END_NAMESPACE - -/** - * Generate a sine wave - */ -void generateTone(const SweptTone &tone, const QAudioFormat &format, QByteArray &buffer); - -#endif // TONEGENERATOR_H - diff --git a/examples/multimedia/spectrum/app/tonegeneratordialog.cpp b/examples/multimedia/spectrum/app/tonegeneratordialog.cpp deleted file mode 100644 index 76fe5d2e3..000000000 --- a/examples/multimedia/spectrum/app/tonegeneratordialog.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "tonegeneratordialog.h" -#include <QComboBox> -#include <QDialogButtonBox> -#include <QLabel> -#include <QPushButton> -#include <QVBoxLayout> -#include <QCheckBox> -#include <QSlider> -#include <QSpinBox> - -const int ToneGeneratorFreqMin = 1; -const int ToneGeneratorFreqMax = 1000; -const int ToneGeneratorFreqDefault = 440; -const int ToneGeneratorAmplitudeDefault = 75; - -ToneGeneratorDialog::ToneGeneratorDialog(QWidget *parent) - : QDialog(parent) - , m_toneGeneratorSweepCheckBox(new QCheckBox(tr("Frequency sweep"), this)) - , m_frequencySweepEnabled(true) - , m_toneGeneratorControl(new QWidget(this)) - , m_toneGeneratorFrequencyControl(new QWidget(this)) - , m_frequencySlider(new QSlider(Qt::Horizontal, this)) - , m_frequencySpinBox(new QSpinBox(this)) - , m_frequency(ToneGeneratorFreqDefault) - , m_amplitudeSlider(new QSlider(Qt::Horizontal, this)) -{ - QVBoxLayout *dialogLayout = new QVBoxLayout(this); - - m_toneGeneratorSweepCheckBox->setChecked(true); - - // Configure tone generator controls - m_frequencySlider->setRange(ToneGeneratorFreqMin, ToneGeneratorFreqMax); - m_frequencySlider->setValue(ToneGeneratorFreqDefault); - m_frequencySpinBox->setRange(ToneGeneratorFreqMin, ToneGeneratorFreqMax); - m_frequencySpinBox->setValue(ToneGeneratorFreqDefault); - m_amplitudeSlider->setRange(0, 100); - m_amplitudeSlider->setValue(ToneGeneratorAmplitudeDefault); - - // Add widgets to layout - QGridLayout *frequencyControlLayout = new QGridLayout; - QLabel *frequencyLabel = new QLabel(tr("Frequency (Hz)"), this); - frequencyControlLayout->addWidget(frequencyLabel, 0, 0, 2, 1); - frequencyControlLayout->addWidget(m_frequencySlider, 0, 1); - frequencyControlLayout->addWidget(m_frequencySpinBox, 1, 1); - m_toneGeneratorFrequencyControl->setLayout(frequencyControlLayout); - m_toneGeneratorFrequencyControl->setEnabled(false); - - QGridLayout *toneGeneratorLayout = new QGridLayout; - QLabel *amplitudeLabel = new QLabel(tr("Amplitude"), this); - toneGeneratorLayout->addWidget(m_toneGeneratorSweepCheckBox, 0, 1); - toneGeneratorLayout->addWidget(m_toneGeneratorFrequencyControl, 1, 0, 1, 2); - toneGeneratorLayout->addWidget(amplitudeLabel, 2, 0); - toneGeneratorLayout->addWidget(m_amplitudeSlider, 2, 1); - m_toneGeneratorControl->setLayout(toneGeneratorLayout); - m_toneGeneratorControl->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - dialogLayout->addWidget(m_toneGeneratorControl); - - // Connect - connect(m_toneGeneratorSweepCheckBox, &QCheckBox::toggled, - this, &ToneGeneratorDialog::frequencySweepEnabled); - connect(m_frequencySlider, &QSlider::valueChanged, - m_frequencySpinBox, &QSpinBox::setValue); - connect(m_frequencySpinBox, QOverload<int>::of(&QSpinBox::valueChanged), - m_frequencySlider, &QSlider::setValue); - - // Add standard buttons to layout - QDialogButtonBox *buttonBox = new QDialogButtonBox(this); - buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - dialogLayout->addWidget(buttonBox); - - // Connect standard buttons - connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, - this, &ToneGeneratorDialog::accept); - connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, - this, &ToneGeneratorDialog::reject); - - setLayout(dialogLayout); -} - -ToneGeneratorDialog::~ToneGeneratorDialog() -{ - -} - -bool ToneGeneratorDialog::isFrequencySweepEnabled() const -{ - return m_toneGeneratorSweepCheckBox->isChecked(); -} - -qreal ToneGeneratorDialog::frequency() const -{ - return qreal(m_frequencySlider->value()); -} - -qreal ToneGeneratorDialog::amplitude() const -{ - return qreal(m_amplitudeSlider->value()) / 100.0; -} - -void ToneGeneratorDialog::frequencySweepEnabled(bool enabled) -{ - m_frequencySweepEnabled = enabled; - m_toneGeneratorFrequencyControl->setEnabled(!enabled); -} diff --git a/examples/multimedia/spectrum/app/tonegeneratordialog.h b/examples/multimedia/spectrum/app/tonegeneratordialog.h deleted file mode 100644 index 48a44a152..000000000 --- a/examples/multimedia/spectrum/app/tonegeneratordialog.h +++ /dev/null @@ -1,94 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef TONEGENERATORDIALOG_H -#define TONEGENERATORDIALOG_H - -#include "spectrum.h" -#include <QAudioDeviceInfo> -#include <QDialog> - -QT_BEGIN_NAMESPACE -class QCheckBox; -class QSlider; -class QSpinBox; -class QGridLayout; -QT_END_NAMESPACE - -/** - * Dialog which controls the parameters of the tone generator. - */ -class ToneGeneratorDialog : public QDialog -{ - Q_OBJECT - -public: - explicit ToneGeneratorDialog(QWidget *parent = 0); - ~ToneGeneratorDialog(); - - bool isFrequencySweepEnabled() const; - qreal frequency() const; - qreal amplitude() const; - -private slots: - void frequencySweepEnabled(bool enabled); - -private: - QCheckBox *m_toneGeneratorSweepCheckBox; - bool m_frequencySweepEnabled; - QWidget *m_toneGeneratorControl; - QWidget *m_toneGeneratorFrequencyControl; - QSlider *m_frequencySlider; - QSpinBox *m_frequencySpinBox; - qreal m_frequency; - QSlider *m_amplitudeSlider; -}; - -#endif // TONEGENERATORDIALOG_H diff --git a/examples/multimedia/spectrum/app/utils.cpp b/examples/multimedia/spectrum/app/utils.cpp deleted file mode 100644 index fc8b9a448..000000000 --- a/examples/multimedia/spectrum/app/utils.cpp +++ /dev/null @@ -1,149 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QAudioFormat> -#include "utils.h" - -qint64 audioDuration(const QAudioFormat &format, qint64 bytes) -{ - return (bytes * 1000000) / - (format.sampleRate() * format.channelCount() * (format.sampleSize() / 8)); -} - -qint64 audioLength(const QAudioFormat &format, qint64 microSeconds) -{ - qint64 result = (format.sampleRate() * format.channelCount() * (format.sampleSize() / 8)) - * microSeconds / 1000000; - result -= result % (format.channelCount() * format.sampleSize()); - return result; -} - -qreal nyquistFrequency(const QAudioFormat &format) -{ - return format.sampleRate() / 2; -} - -QString formatToString(const QAudioFormat &format) -{ - QString result; - - if (QAudioFormat() != format) { - if (format.codec() == "audio/pcm") { - Q_ASSERT(format.sampleType() == QAudioFormat::SignedInt); - - const QString formatEndian = (format.byteOrder() == QAudioFormat::LittleEndian) - ? QString("LE") : QString("BE"); - - QString formatType; - switch (format.sampleType()) { - case QAudioFormat::SignedInt: - formatType = "signed"; - break; - case QAudioFormat::UnSignedInt: - formatType = "unsigned"; - break; - case QAudioFormat::Float: - formatType = "float"; - break; - case QAudioFormat::Unknown: - formatType = "unknown"; - break; - } - - QString formatChannels = QString("%1 channels").arg(format.channelCount()); - switch (format.channelCount()) { - case 1: - formatChannels = "mono"; - break; - case 2: - formatChannels = "stereo"; - break; - } - - result = QString("%1 Hz %2 bit %3 %4 %5") - .arg(format.sampleRate()) - .arg(format.sampleSize()) - .arg(formatType) - .arg(formatEndian) - .arg(formatChannels); - } else { - result = format.codec(); - } - } - - return result; -} - -bool isPCM(const QAudioFormat &format) -{ - return (format.codec() == "audio/pcm"); -} - - -bool isPCMS16LE(const QAudioFormat &format) -{ - return isPCM(format) && - format.sampleType() == QAudioFormat::SignedInt && - format.sampleSize() == 16 && - format.byteOrder() == QAudioFormat::LittleEndian; -} - -const qint16 PCMS16MaxValue = 32767; -const quint16 PCMS16MaxAmplitude = 32768; // because minimum is -32768 - -qreal pcmToReal(qint16 pcm) -{ - return qreal(pcm) / PCMS16MaxAmplitude; -} - -qint16 realToPcm(qreal real) -{ - return real * PCMS16MaxValue; -} diff --git a/examples/multimedia/spectrum/app/utils.h b/examples/multimedia/spectrum/app/utils.h deleted file mode 100644 index cd6357182..000000000 --- a/examples/multimedia/spectrum/app/utils.h +++ /dev/null @@ -1,122 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd 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 diff --git a/examples/multimedia/spectrum/app/wavfile.cpp b/examples/multimedia/spectrum/app/wavfile.cpp deleted file mode 100644 index 475200db5..000000000 --- a/examples/multimedia/spectrum/app/wavfile.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <qendian.h> -#include "wavfile.h" - -struct chunk -{ - char id[4]; - quint32 size; -}; - -struct RIFFHeader -{ - chunk descriptor; // "RIFF" - char type[4]; // "WAVE" -}; - -struct WAVEHeader -{ - chunk descriptor; - quint16 audioFormat; - quint16 numChannels; - quint32 sampleRate; - quint32 byteRate; - quint16 blockAlign; - quint16 bitsPerSample; -}; - -struct DATAHeader -{ - chunk descriptor; -}; - -struct CombinedHeader -{ - RIFFHeader riff; - WAVEHeader wave; -}; - -WavFile::WavFile(QObject *parent) - : QFile(parent) - , m_headerLength(0) -{ - -} - -bool WavFile::open(const QString &fileName) -{ - close(); - setFileName(fileName); - return QFile::open(QIODevice::ReadOnly) && readHeader(); -} - -const QAudioFormat &WavFile::fileFormat() const -{ - return m_fileFormat; -} - -qint64 WavFile::headerLength() const -{ - return m_headerLength; -} - -bool WavFile::readHeader() -{ - seek(0); - CombinedHeader header; - bool result = read(reinterpret_cast<char *>(&header), sizeof(CombinedHeader)) == sizeof(CombinedHeader); - if (result) { - if ((memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0 - || memcmp(&header.riff.descriptor.id, "RIFX", 4) == 0) - && memcmp(&header.riff.type, "WAVE", 4) == 0 - && memcmp(&header.wave.descriptor.id, "fmt ", 4) == 0 - && (header.wave.audioFormat == 1 || header.wave.audioFormat == 0)) { - - // Read off remaining header information - DATAHeader dataHeader; - - if (qFromLittleEndian<quint32>(header.wave.descriptor.size) > sizeof(WAVEHeader)) { - // Extended data available - quint16 extraFormatBytes; - if (peek((char*)&extraFormatBytes, sizeof(quint16)) != sizeof(quint16)) - return false; - const qint64 throwAwayBytes = sizeof(quint16) + qFromLittleEndian<quint16>(extraFormatBytes); - if (read(throwAwayBytes).size() != throwAwayBytes) - return false; - } - - if (read((char*)&dataHeader, sizeof(DATAHeader)) != sizeof(DATAHeader)) - return false; - - // Establish format - if (memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0) - m_fileFormat.setByteOrder(QAudioFormat::LittleEndian); - else - m_fileFormat.setByteOrder(QAudioFormat::BigEndian); - - int bps = qFromLittleEndian<quint16>(header.wave.bitsPerSample); - m_fileFormat.setChannelCount(qFromLittleEndian<quint16>(header.wave.numChannels)); - m_fileFormat.setCodec("audio/pcm"); - m_fileFormat.setSampleRate(qFromLittleEndian<quint32>(header.wave.sampleRate)); - m_fileFormat.setSampleSize(qFromLittleEndian<quint16>(header.wave.bitsPerSample)); - m_fileFormat.setSampleType(bps == 8 ? QAudioFormat::UnSignedInt : QAudioFormat::SignedInt); - } else { - result = false; - } - } - m_headerLength = pos(); - return result; -} diff --git a/examples/multimedia/spectrum/app/wavfile.h b/examples/multimedia/spectrum/app/wavfile.h deleted file mode 100644 index a63bc5c93..000000000 --- a/examples/multimedia/spectrum/app/wavfile.h +++ /dev/null @@ -1,76 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef WAVFILE_H -#define WAVFILE_H - -#include <QObject> -#include <QFile> -#include <QAudioFormat> - -class WavFile : public QFile -{ -public: - WavFile(QObject *parent = 0); - - using QFile::open; - bool open(const QString &fileName); - const QAudioFormat &fileFormat() const; - qint64 headerLength() const; - -private: - bool readHeader(); - -private: - QAudioFormat m_fileFormat; - qint64 m_headerLength; -}; - -#endif // WAVFILE_H diff --git a/examples/multimedia/spectrum/doc/src/spectrum.qdoc b/examples/multimedia/spectrum/doc/src/spectrum.qdoc index ad0ec984f..25e0760a4 100644 --- a/examples/multimedia/spectrum/doc/src/spectrum.qdoc +++ b/examples/multimedia/spectrum/doc/src/spectrum.qdoc @@ -1,34 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2015 The Qt Company Ltd. -** Contact: http://www.qt.io/licensing/ -** -** This file is part of the documentation of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:FDL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Free Documentation License Usage -** Alternatively, this file may be used under the terms of the GNU Free -** Documentation License version 1.3 as published by the Free Software -** Foundation and appearing in the file included in the packaging of -** this file. Please review the following information to ensure -** the GNU Free Documentation License version 1.3 requirements -** will be met: https://www.gnu.org/licenses/fdl-1.3.html. -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2015 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only /*! - \example multimedia/spectrum + \example spectrum \title Spectrum Example \ingroup multimedia_examples + \examplecategory {Multimedia} \brief Analyzing a raw audio stream using the FFTReal library. \e Spectrum demonstrates how the \l{Qt Multimedia} module can be used to @@ -58,8 +35,9 @@ Spectrum analysis is performed by calculating the Fast Fourier Transform (FFT) of a segment of audio data. An open-source library, - \l{http://ldesoras.free.fr/prod.html}{FFTReal}, against which the - application is dynamically linked, is used to compute the transform. + \l{http://ldesoras.free.fr/prod.html}{FFTReal} is used to compute the + transform. FFTReal is available under the GNU Library General Public License + 2.0 or later. \include examples-run.qdocinc */ diff --git a/examples/multimedia/spectrum/engine.cpp b/examples/multimedia/spectrum/engine.cpp new file mode 100644 index 000000000..cb7aeadcb --- /dev/null +++ b/examples/multimedia/spectrum/engine.cpp @@ -0,0 +1,750 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "engine.h" +#include "tonegenerator.h" +#include "utils.h" + +#include <QAudioSink> +#include <QAudioSource> +#include <QCoreApplication> +#include <QDebug> +#include <QFile> +#include <QMetaObject> +#include <QSet> +#include <QThread> + +#if QT_CONFIG(permissions) + #include <QPermission> +#endif + +#include <math.h> + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- + +const qint64 BufferDurationUs = 10 * 1000000; + +// Size of the level calculation window in microseconds +const int LevelWindowUs = 0.1 * 1000000; + +//----------------------------------------------------------------------------- +// Constructor and destructor +//----------------------------------------------------------------------------- + +Engine::Engine(QObject *parent) + : QObject(parent), + m_mode(QAudioDevice::Input), + m_state(QAudio::StoppedState), + m_devices(new QMediaDevices(this)), + m_generateTone(false), + m_file(nullptr), + m_analysisFile(nullptr), + m_audioInput(nullptr), + m_audioInputIODevice(nullptr), + m_recordPosition(0), + m_audioOutput(nullptr), + 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_spectrumPosition(0), + m_count(0) +{ + connect(&m_spectrumAnalyser, + QOverload<const FrequencySpectrum &>::of(&SpectrumAnalyser::spectrumChanged), this, + QOverload<const FrequencySpectrum &>::of(&Engine::spectrumChanged)); + + // This code might misinterpret things like "-something -category". But + // it's unlikely that that needs to be supported so we'll let it go. + QStringList arguments = QCoreApplication::instance()->arguments(); + for (int i = 0; i < arguments.count(); ++i) { + if (arguments.at(i) == QStringLiteral("--")) + break; + } + + initAudioDevices(); + + initialize(); + +#ifdef DUMP_DATA + createOutputDir(); +#endif + +#ifdef DUMP_SPECTRUM + m_spectrumAnalyser.setOutputPath(outputPath()); +#endif + + m_notifyTimer = new QTimer(this); + m_notifyTimer->setInterval(1000); + connect(m_notifyTimer, &QTimer::timeout, this, &Engine::audioNotify); +} + +Engine::~Engine() = default; + +//----------------------------------------------------------------------------- +// Public functions +//----------------------------------------------------------------------------- + +bool Engine::loadFile(const QString &fileName) +{ + reset(); + bool result = false; + Q_ASSERT(!m_generateTone); + Q_ASSERT(!m_file); + Q_ASSERT(!fileName.isEmpty()); + QIODevice *file = new QFile(fileName); + if (file->open(QIODevice::ReadOnly)) { + m_file = new QWaveDecoder(file, this); + if (m_file->open(QIODevice::ReadOnly)) { + if (m_file->audioFormat().sampleFormat() == QAudioFormat::Int16) { + result = initialize(); + } else { + emit errorMessage(tr("Audio format not supported"), + formatToString(m_file->audioFormat())); + } + } else + emit errorMessage(tr("Could not open WAV decoder for file"), fileName); + } else { + emit errorMessage(tr("Could not open file"), fileName); + } + if (result) { + file->close(); + file->open(QIODevice::ReadOnly); + m_analysisFile = new QWaveDecoder(file, this); + m_analysisFile->open(QIODevice::ReadOnly); + } + 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->getDevice()->size() : m_bufferLength; +} + +void Engine::setWindowFunction(WindowFunction type) +{ + m_spectrumAnalyser.setWindowFunction(type); +} + +//----------------------------------------------------------------------------- +// Public slots +//----------------------------------------------------------------------------- + +void Engine::startRecording() +{ + if (m_audioInput) { + if (QAudioDevice::Input == m_mode && QAudio::SuspendedState == m_state) { + m_audioInput->resume(); + } else { + m_spectrumAnalyser.cancelCalculation(); + emit spectrumChanged(0, 0, FrequencySpectrum()); + + m_buffer.fill(0); + setRecordPosition(0, true); + stopPlayback(); + m_mode = QAudioDevice::Input; + connect(m_audioInput, &QAudioSource::stateChanged, this, &Engine::audioStateChanged); + + m_count = 0; + m_dataLength = 0; + emit dataLengthChanged(0); + m_audioInputIODevice = m_audioInput->start(); + connect(m_audioInputIODevice, &QIODevice::readyRead, this, &Engine::audioDataReady); + } + m_notifyTimer->start(); + } +} + +void Engine::startPlayback() +{ + if (!m_audioOutput) + initialize(); + + if (m_audioOutput) { + if (QAudioDevice::Output == 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(); + emit spectrumChanged(0, 0, FrequencySpectrum()); + setPlayPosition(0, true); + stopRecording(); + m_mode = QAudioDevice::Output; + connect(m_audioOutput, &QAudioSink::stateChanged, this, &Engine::audioStateChanged); + + m_count = 0; + if (m_file) { + m_file->seek(0); + m_bufferPosition = 0; + m_dataLength = 0; + m_audioOutput->start(m_file->getDevice()); + } else { + m_audioOutputIODevice.close(); + m_audioOutputIODevice.setBuffer(&m_buffer); + m_audioOutputIODevice.open(QIODevice::ReadOnly); + m_audioOutput->start(&m_audioOutputIODevice); + } + } + m_notifyTimer->start(); + } +} + +void Engine::suspend() +{ + if (QAudio::ActiveState == m_state || QAudio::IdleState == m_state) { + switch (m_mode) { + case QAudioDevice::Input: + m_audioInput->suspend(); + break; + case QAudioDevice::Output: + m_audioOutput->suspend(); + break; + default: + break; + } + m_notifyTimer->stop(); + } +} + +void Engine::setAudioInputDevice(const QAudioDevice &device) +{ + if (device.id() != m_audioInputDevice.id()) { + m_audioInputDevice = device; + initialize(); + } +} + +void Engine::setAudioOutputDevice(const QAudioDevice &device) +{ + if (device.id() != m_audioOutputDevice.id()) { + m_audioOutputDevice = device; + initialize(); + } +} + +//----------------------------------------------------------------------------- +// Private slots +//----------------------------------------------------------------------------- + +void Engine::initAudioDevices() +{ +#if QT_CONFIG(permissions) + QMicrophonePermission microphonePermission; + switch (qApp->checkPermission(microphonePermission)) { + case Qt::PermissionStatus::Undetermined: + qApp->requestPermission(microphonePermission, this, &Engine::initAudioDevices); + return; + case Qt::PermissionStatus::Denied: + qWarning("Microphone permission is not granted!"); + return; + case Qt::PermissionStatus::Granted: + break; + } +#endif + m_availableAudioInputDevices = m_devices->audioInputs(); + m_audioInputDevice = m_devices->defaultAudioInput(); + m_availableAudioOutputDevices = m_devices->audioOutputs(); + m_audioOutputDevice = m_devices->defaultAudioOutput(); +} + +void Engine::audioNotify() +{ + switch (m_mode) { + case QAudioDevice::Input: { + const qint64 recordPosition = + qMin(m_bufferLength, m_format.bytesForDuration(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 QAudioDevice::Output: { + const qint64 playPosition = m_format.bytesForDuration(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->getDevice()->size(), + qMax(levelPosition + m_levelBufferLength, + spectrumPosition + m_spectrumBufferLength)); + const qint64 readLen = + readEnd - readPos + m_format.bytesForDuration(WaveformWindowDuration); + qDebug() << "Engine::audioNotify [1]" + << "analysisFileSize" << m_analysisFile->getDevice()->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; + default: + 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->getDevice()->size()) { + stopPlayback(); + } else { + if (QAudio::StoppedState == state) { + // Check error + QAudio::Error error = QAudio::NoError; + switch (m_mode) { + case QAudioDevice::Input: + error = m_audioInput->error(); + break; + case QAudioDevice::Output: + error = m_audioOutput->error(); + break; + default: + break; + } + if (QAudio::NoError != error) { + emitError(error); + reset(); + return; + } + } + setState(state); + } +} + +void Engine::audioDataReady() +{ + Q_ASSERT(0 == m_bufferPosition); + const qint64 bytesReady = m_audioInput->bytesAvailable(); + 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 = nullptr; + m_audioInputIODevice = nullptr; + setRecordPosition(0); + delete m_audioOutput; + m_audioOutput = nullptr; + setPlayPosition(0); + m_spectrumPosition = 0; + setLevel(0.0, 0.0, 0); +} + +void Engine::reset() +{ + stopRecording(); + stopPlayback(); + setState(QAudioDevice::Input, QAudio::StoppedState); + m_generateTone = false; + setFormat(QAudioFormat()); + delete m_file; + m_file = nullptr; + delete m_analysisFile; + m_analysisFile = nullptr; + 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 = m_format.bytesForDuration(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 QAudioSource(m_audioInputDevice, m_format, this); + result = true; + } + } + m_audioOutput = new QAudioSink(m_audioOutputDevice, m_format, this); + } + } 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->audioFormat(); + if (m_audioOutputDevice.isFormatSupported(format)) { + setFormat(format); + foundSupportedFormat = true; + } + } else { + + int minSampleRate = qMin(m_audioInputDevice.minimumSampleRate(), + m_audioOutputDevice.minimumSampleRate()); + int maxSampleRate = qMin(m_audioInputDevice.maximumSampleRate(), + m_audioOutputDevice.maximumSampleRate()); + int minChannelCount = qMin(m_audioInputDevice.minimumChannelCount(), + m_audioOutputDevice.minimumChannelCount()); + int maxChannelCount = qMin(m_audioInputDevice.maximumChannelCount(), + m_audioOutputDevice.maximumChannelCount()); + + QAudioFormat format; + format.setSampleFormat(QAudioFormat::Int16); + format.setSampleRate(qBound(minSampleRate, 48000, maxSampleRate)); + format.setChannelCount(qBound(minChannelCount, 2, maxChannelCount)); + + const bool inputSupport = m_audioInputDevice.isFormatSupported(format); + const bool outputSupport = m_audioOutputDevice.isFormatSupported(format); + if (inputSupport && outputSupport) + foundSupportedFormat = true; + + setFormat(format); + } + + return foundSupportedFormat; +} + +void Engine::stopRecording() +{ + if (m_audioInput) { + m_audioInput->stop(); + QCoreApplication::instance()->processEvents(); + m_audioInput->disconnect(); + } + m_audioInputIODevice = nullptr; + m_notifyTimer->stop(); + +#ifdef DUMP_AUDIO + dumpData(); +#endif +} + +void Engine::stopPlayback() +{ + if (m_audioOutput) { + m_audioOutput->stop(); + QCoreApplication::instance()->processEvents(); + m_audioOutput->disconnect(); + setPlayPosition(0); + } + m_notifyTimer->stop(); +} + +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(QAudioDevice::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 = m_format.bytesForDuration(LevelWindowUs); + m_spectrumBufferLength = SpectrumLengthSamples * format.bytesPerFrame(); + 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); +} + +void Engine::emitError(QAudio::Error error) +{ + QString errorString; + switch (error) { + case QAudio::NoError: + errorString = tr("NoError"); + break; + case QAudio::OpenError: + errorString = tr("OpenError: An error occurred opening the audio device."); + break; + case QAudio::IOError: + errorString = tr("IOError: An error occurred during read/write of audio device."); + break; + case QAudio::UnderrunError: + errorString = tr("UnderrunError: Audio data is not being fed" + "to the audio device at a fast enough rate."); + break; + case QAudio::FatalError: + errorString = tr("FatalError: A non-recoverable error has occurred," + "the audio device is not usable at this time."); + break; + } + + emit errorMessage(tr("Audio Device"), errorString); +} + +#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); + for (const QString &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 + +#include "moc_engine.cpp" diff --git a/examples/multimedia/spectrum/app/engine.h b/examples/multimedia/spectrum/engine.h index 714af4d9d..9e8ba51d0 100644 --- a/examples/multimedia/spectrum/app/engine.h +++ b/examples/multimedia/spectrum/engine.h @@ -1,80 +1,35 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd 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$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #ifndef ENGINE_H #define ENGINE_H #include "spectrum.h" #include "spectrumanalyser.h" -#include "wavfile.h" -#include <QAudioDeviceInfo> +#include <QAudioDevice> #include <QAudioFormat> #include <QBuffer> #include <QByteArray> #include <QDir> #include <QList> +#include <QMediaDevices> #include <QObject> +#include <QTimer> +#include <QWaveDecoder> #ifdef DUMP_CAPTURED_AUDIO -#define DUMP_DATA +# define DUMP_DATA #endif #ifdef DUMP_SPECTRUM -#define DUMP_DATA +# define DUMP_DATA #endif class FrequencySpectrum; QT_BEGIN_NAMESPACE -class QAudioInput; -class QAudioOutput; +class QAudioSource; +class QAudioSink; QT_END_NAMESPACE /** @@ -88,29 +43,32 @@ class Engine : public QObject Q_OBJECT public: - explicit Engine(QObject *parent = 0); + explicit Engine(QObject *parent = nullptr); ~Engine(); - const QList<QAudioDeviceInfo> &availableAudioInputDevices() const - { return m_availableAudioInputDevices; } + const QList<QAudioDevice> &availableAudioInputDevices() const + { + return m_availableAudioInputDevices; + } - const QList<QAudioDeviceInfo> &availableAudioOutputDevices() const - { return m_availableAudioOutputDevices; } + const QList<QAudioDevice> &availableAudioOutputDevices() const + { + return m_availableAudioOutputDevices; + } - QAudio::Mode mode() const { return m_mode; } + QAudioDevice::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; } + 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 */ @@ -176,11 +134,11 @@ public slots: void startRecording(); void startPlayback(); void suspend(); - void setAudioInputDevice(const QAudioDeviceInfo &device); - void setAudioOutputDevice(const QAudioDeviceInfo &device); + void setAudioInputDevice(const QAudioDevice &device); + void setAudioOutputDevice(const QAudioDevice &device); signals: - void stateChanged(QAudio::Mode mode, QAudio::State state); + void stateChanged(QAudioDevice::Mode mode, QAudio::State state); /** * Informational message for non-modal display @@ -245,6 +203,7 @@ signals: void bufferChanged(qint64 position, qint64 length, const QByteArray &buffer); private slots: + void initAudioDevices(); void audioNotify(); void audioStateChanged(QAudio::State state); void audioDataReady(); @@ -257,17 +216,21 @@ private: void stopRecording(); void stopPlayback(); void setState(QAudio::State state); - void setState(QAudio::Mode mode, QAudio::State state); + void setState(QAudioDevice::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); + void emitError(QAudio::Error error); #ifdef DUMP_DATA void createOutputDir(); - QString outputPath() const { return m_outputDir.path(); } + QString outputPath() const + { + return m_outputDir.path(); + } #endif #ifdef DUMP_CAPTURED_AUDIO @@ -275,52 +238,52 @@ private: #endif private: - QAudio::Mode m_mode; - QAudio::State m_state; + QAudioDevice::Mode m_mode; + QAudio::State m_state; + QMediaDevices *m_devices; - bool m_generateTone; - SweptTone m_tone; + bool m_generateTone; + SweptTone m_tone; - WavFile* m_file; + QWaveDecoder *m_file; // We need a second file handle via which to read data into m_buffer // for analysis - WavFile* m_analysisFile; + QWaveDecoder *m_analysisFile; - QAudioFormat m_format; + QAudioFormat m_format; - const QList<QAudioDeviceInfo> m_availableAudioInputDevices; - QAudioDeviceInfo m_audioInputDevice; - QAudioInput* m_audioInput; - QIODevice* m_audioInputIODevice; - qint64 m_recordPosition; + QList<QAudioDevice> m_availableAudioInputDevices; + QAudioDevice m_audioInputDevice; + QAudioSource *m_audioInput; + QIODevice *m_audioInputIODevice; + qint64 m_recordPosition; - const QList<QAudioDeviceInfo> m_availableAudioOutputDevices; - QAudioDeviceInfo m_audioOutputDevice; - QAudioOutput* m_audioOutput; - QString m_audioOutputCategory; - qint64 m_playPosition; - QBuffer m_audioOutputIODevice; + QList<QAudioDevice> m_availableAudioOutputDevices; + QAudioDevice m_audioOutputDevice; + QAudioSink *m_audioOutput; + qint64 m_playPosition; + QBuffer m_audioOutputIODevice; - QByteArray m_buffer; - qint64 m_bufferPosition; - qint64 m_bufferLength; - qint64 m_dataLength; + QByteArray m_buffer; + qint64 m_bufferPosition; + qint64 m_bufferLength; + qint64 m_dataLength; - int m_levelBufferLength; - qreal m_rmsLevel; - qreal m_peakLevel; + int m_levelBufferLength; + qreal m_rmsLevel; + qreal m_peakLevel; - int m_spectrumBufferLength; - QByteArray m_spectrumBuffer; - SpectrumAnalyser m_spectrumAnalyser; - qint64 m_spectrumPosition; + int m_spectrumBufferLength; + QByteArray m_spectrumBuffer; + SpectrumAnalyser m_spectrumAnalyser; + qint64 m_spectrumPosition; - int m_count; + int m_count; + QTimer *m_notifyTimer = nullptr; #ifdef DUMP_DATA - QDir m_outputDir; + QDir m_outputDir; #endif - }; #endif // ENGINE_H diff --git a/examples/multimedia/spectrum/frequencyspectrum.cpp b/examples/multimedia/spectrum/frequencyspectrum.cpp new file mode 100644 index 000000000..f271a04aa --- /dev/null +++ b/examples/multimedia/spectrum/frequencyspectrum.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "frequencyspectrum.h" + +FrequencySpectrum::FrequencySpectrum(int numPoints) : m_elements(numPoints) { } + +void FrequencySpectrum::reset() +{ + iterator i = begin(); + for (; i != end(); ++i) + *i = Element(); +} + +int FrequencySpectrum::count() const +{ + return m_elements.count(); +} + +FrequencySpectrum::Element &FrequencySpectrum::operator[](int index) +{ + return m_elements[index]; +} + +const FrequencySpectrum::Element &FrequencySpectrum::operator[](int index) const +{ + return m_elements[index]; +} + +FrequencySpectrum::iterator FrequencySpectrum::begin() +{ + return m_elements.begin(); +} + +FrequencySpectrum::iterator FrequencySpectrum::end() +{ + return m_elements.end(); +} + +FrequencySpectrum::const_iterator FrequencySpectrum::begin() const +{ + return m_elements.begin(); +} + +FrequencySpectrum::const_iterator FrequencySpectrum::end() const +{ + return m_elements.end(); +} diff --git a/examples/multimedia/spectrum/frequencyspectrum.h b/examples/multimedia/spectrum/frequencyspectrum.h new file mode 100644 index 000000000..9f3771571 --- /dev/null +++ b/examples/multimedia/spectrum/frequencyspectrum.h @@ -0,0 +1,60 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef FREQUENCYSPECTRUM_H +#define FREQUENCYSPECTRUM_H + +#include <QList> + +/** + * 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 QList<Element>::iterator iterator; + typedef QList<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: + QList<Element> m_elements; +}; + +#endif // FREQUENCYSPECTRUM_H diff --git a/examples/multimedia/spectrum/app/images/record.png b/examples/multimedia/spectrum/images/record.png Binary files differindex 184fce809..184fce809 100644 --- a/examples/multimedia/spectrum/app/images/record.png +++ b/examples/multimedia/spectrum/images/record.png diff --git a/examples/multimedia/spectrum/app/images/settings.png b/examples/multimedia/spectrum/images/settings.png Binary files differindex 12179dc9a..12179dc9a 100644 --- a/examples/multimedia/spectrum/app/images/settings.png +++ b/examples/multimedia/spectrum/images/settings.png diff --git a/examples/multimedia/spectrum/levelmeter.cpp b/examples/multimedia/spectrum/levelmeter.cpp new file mode 100644 index 000000000..2c889c34d --- /dev/null +++ b/examples/multimedia/spectrum/levelmeter.cpp @@ -0,0 +1,103 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "levelmeter.h" + +#include <QDebug> +#include <QPainter> +#include <QTimer> + +#include <math.h> + +// 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, &QTimer::timeout, this, &LevelMeter::redrawTimerExpired); + m_redrawTimer->start(RedrawInterval); +} + +LevelMeter::~LevelMeter() = default; + +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); +} + +#include "moc_levelmeter.cpp" diff --git a/examples/multimedia/spectrum/levelmeter.h b/examples/multimedia/spectrum/levelmeter.h new file mode 100644 index 000000000..e131bc904 --- /dev/null +++ b/examples/multimedia/spectrum/levelmeter.h @@ -0,0 +1,80 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef LEVELMETER_H +#define LEVELMETER_H + +#include <QElapsedTimer> +#include <QWidget> + +/** + * Widget which displays a vertical audio level meter, indicating the + * RMS and peak levels of the window of audio samples most recently analyzed + * by the Engine. + */ +class LevelMeter : public QWidget +{ + Q_OBJECT + +public: + explicit LevelMeter(QWidget *parent = nullptr); + ~LevelMeter(); + + void paintEvent(QPaintEvent *event) override; + +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. + */ + QElapsedTimer 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. + */ + QElapsedTimer m_peakHoldLevelChanged; + + QTimer *m_redrawTimer; + + QColor m_rmsColor; + QColor m_peakColor; +}; + +#endif // LEVELMETER_H diff --git a/examples/multimedia/spectrum/main.cpp b/examples/multimedia/spectrum/main.cpp new file mode 100644 index 000000000..31913b2f2 --- /dev/null +++ b/examples/multimedia/spectrum/main.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwidget.h" + +#include <QApplication> + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + app.setApplicationName("Qt Multimedia spectrum analyzer"); + + MainWidget w; + w.show(); + + return app.exec(); +} diff --git a/examples/multimedia/spectrum/app/mainwidget.cpp b/examples/multimedia/spectrum/mainwidget.cpp index d6163f7f9..214d317d9 100644 --- a/examples/multimedia/spectrum/app/mainwidget.cpp +++ b/examples/multimedia/spectrum/mainwidget.cpp @@ -1,101 +1,55 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd 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$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#include "mainwidget.h" #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 "waveform.h" +#include <QFileDialog> +#include <QHBoxLayout> #include <QLabel> +#include <QMenu> +#include <QMessageBox> #include <QPushButton> -#include <QHBoxLayout> -#include <QVBoxLayout> #include <QStyle> -#include <QMenu> -#include <QFileDialog> #include <QTimerEvent> -#include <QMessageBox> +#include <QVBoxLayout> const int NullTimerId = -1; MainWidget::MainWidget(QWidget *parent) - : QWidget(parent) - , m_mode(NoMode) - , m_engine(new Engine(this)) + : QWidget(parent), + m_mode(NoMode), + m_engine(new Engine(this)) #ifndef DISABLE_WAVEFORM - , m_waveform(new Waveform(this)) + , + 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_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(nullptr), + m_generateToneAction(nullptr), + m_recordAction(nullptr), + m_errorOccurred(false) { m_spectrograph->setParams(SpectrumNumBands, SpectrumLowFreq, SpectrumHighFreq); @@ -103,25 +57,19 @@ MainWidget::MainWidget(QWidget *parent) connectUi(); } -MainWidget::~MainWidget() -{ - -} - +MainWidget::~MainWidget() = default; //----------------------------------------------------------------------------- // Public slots //----------------------------------------------------------------------------- -void MainWidget::stateChanged(QAudio::Mode mode, QAudio::State state) +void MainWidget::stateChanged(QAudioDevice::Mode mode, QAudio::State state) { Q_UNUSED(mode); updateButtonStates(); - if (QAudio::ActiveState != state && - QAudio::SuspendedState != state && - QAudio::InterruptedState != state) { + if (QAudio::ActiveState != state && QAudio::SuspendedState != state) { m_levelMeter->reset(); m_spectrograph->reset(); } @@ -129,18 +77,16 @@ void MainWidget::stateChanged(QAudio::Mode mode, QAudio::State state) void MainWidget::formatChanged(const QAudioFormat &format) { - infoMessage(formatToString(format), NullMessageTimeout); + infoMessage(formatToString(format), NullMessageTimeout); #ifndef DISABLE_WAVEFORM if (QAudioFormat() != format) { - m_waveform->initialize(format, WaveformTileLength, - WaveformWindowDuration); + m_waveform->initialize(format, WaveformTileLength, WaveformWindowDuration); } #endif } -void MainWidget::spectrumChanged(qint64 position, qint64 length, - const FrequencySpectrum &spectrum) +void MainWidget::spectrumChanged(qint64 position, qint64 length, const FrequencySpectrum &spectrum) { m_progressBar->windowChanged(position, length); m_spectrograph->spectrumChanged(spectrum); @@ -162,6 +108,8 @@ void MainWidget::infoMessage(const QString &message, int timeoutMs) void MainWidget::errorMessage(const QString &heading, const QString &detail) { QMessageBox::warning(this, heading, detail, QMessageBox::Close); + m_errorOccurred = true; + reset(); } void MainWidget::timerEvent(QTimerEvent *event) @@ -187,15 +135,16 @@ void MainWidget::bufferLengthChanged(qint64 length) m_progressBar->bufferLengthChanged(length); } - //----------------------------------------------------------------------------- // Private slots //----------------------------------------------------------------------------- void MainWidget::showFileDialog() { + m_errorOccurred = false; const QString dir; - const QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open WAV file"), dir, "*.wav"); + const QStringList fileNames = + QFileDialog::getOpenFileNames(this, tr("Open WAV file"), dir, "*.wav"); if (fileNames.count()) { reset(); setMode(LoadFileMode); @@ -218,6 +167,7 @@ void MainWidget::showSettingsDialog() void MainWidget::showToneGeneratorDialog() { + m_errorOccurred = false; m_toneGeneratorDialog->exec(); if (m_toneGeneratorDialog->result() == QDialog::Accepted) { reset(); @@ -232,19 +182,20 @@ void MainWidget::showToneGeneratorDialog() updateButtonStates(); } } else { + setMode(NoMode); updateModeMenu(); } } void MainWidget::initializeRecord() { + m_errorOccurred = false; reset(); setMode(RecordMode); if (m_engine->initializeRecord()) updateButtonStates(); } - //----------------------------------------------------------------------------- // Private functions //----------------------------------------------------------------------------- @@ -262,27 +213,25 @@ void MainWidget::createUi() windowLayout->addWidget(m_infoMessage); #ifdef SUPERIMPOSE_PROGRESS_ON_WAVEFORM - QScopedPointer<QHBoxLayout> waveformLayout(new QHBoxLayout); + std::unique_ptr<QHBoxLayout> waveformLayout(new QHBoxLayout); waveformLayout->addWidget(m_progressBar); m_progressBar->setMinimumHeight(m_waveform->minimumHeight()); waveformLayout->setContentsMargins(0, 0, 0, 0); - m_waveform->setLayout(waveformLayout.data()); - waveformLayout.take(); + m_waveform->setLayout(waveformLayout.release()); windowLayout->addWidget(m_waveform); #else -#ifndef DISABLE_WAVEFORM +# ifndef DISABLE_WAVEFORM windowLayout->addWidget(m_waveform); -#endif // DISABLE_WAVEFORM +# endif // DISABLE_WAVEFORM windowLayout->addWidget(m_progressBar); #endif // SUPERIMPOSE_PROGRESS_ON_WAVEFORM // Spectrograph and level meter - QScopedPointer<QHBoxLayout> analysisLayout(new QHBoxLayout); + std::unique_ptr<QHBoxLayout> analysisLayout(new QHBoxLayout); analysisLayout->addWidget(m_spectrograph); analysisLayout->addWidget(m_levelMeter); - windowLayout->addLayout(analysisLayout.data()); - analysisLayout.take(); + windowLayout->addLayout(analysisLayout.release()); // Button panel @@ -314,7 +263,7 @@ void MainWidget::createUi() m_settingsButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_settingsButton->setMinimumSize(buttonSize); - QScopedPointer<QHBoxLayout> buttonPanelLayout(new QHBoxLayout); + std::unique_ptr<QHBoxLayout> buttonPanelLayout(new QHBoxLayout); buttonPanelLayout->addStretch(); buttonPanelLayout->addWidget(m_modeButton); buttonPanelLayout->addWidget(m_recordButton); @@ -324,13 +273,11 @@ void MainWidget::createUi() QWidget *buttonPanel = new QWidget(this); buttonPanel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - buttonPanel->setLayout(buttonPanelLayout.data()); - buttonPanelLayout.take(); // ownership transferred to buttonPanel + buttonPanel->setLayout(buttonPanelLayout.release()); - QScopedPointer<QHBoxLayout> bottomPaneLayout(new QHBoxLayout); + std::unique_ptr<QHBoxLayout> bottomPaneLayout(new QHBoxLayout); bottomPaneLayout->addWidget(buttonPanel); - windowLayout->addLayout(bottomPaneLayout.data()); - bottomPaneLayout.take(); // ownership transferred to windowLayout + windowLayout->addLayout(bottomPaneLayout.release()); // Apply layout @@ -339,62 +286,49 @@ void MainWidget::createUi() void MainWidget::connectUi() { - connect(m_recordButton, &QPushButton::clicked, - m_engine, &Engine::startRecording); + connect(m_recordButton, &QPushButton::clicked, m_engine, &Engine::startRecording); - connect(m_pauseButton, &QPushButton::clicked, - m_engine, &Engine::suspend); + connect(m_pauseButton, &QPushButton::clicked, m_engine, &Engine::suspend); - connect(m_playButton, &QPushButton::clicked, - m_engine, &Engine::startPlayback); + connect(m_playButton, &QPushButton::clicked, m_engine, &Engine::startPlayback); - connect(m_settingsButton, &QPushButton::clicked, - this, &MainWidget::showSettingsDialog); + connect(m_settingsButton, &QPushButton::clicked, this, &MainWidget::showSettingsDialog); - connect(m_engine, &Engine::stateChanged, - this, &MainWidget::stateChanged); + connect(m_engine, &Engine::stateChanged, this, &MainWidget::stateChanged); - connect(m_engine, &Engine::formatChanged, - this, &MainWidget::formatChanged); + connect(m_engine, &Engine::formatChanged, this, &MainWidget::formatChanged); m_progressBar->bufferLengthChanged(m_engine->bufferLength()); - connect(m_engine, &Engine::bufferLengthChanged, - this, &MainWidget::bufferLengthChanged); + connect(m_engine, &Engine::bufferLengthChanged, this, &MainWidget::bufferLengthChanged); - connect(m_engine, &Engine::dataLengthChanged, - this, &MainWidget::updateButtonStates); + connect(m_engine, &Engine::dataLengthChanged, this, &MainWidget::updateButtonStates); - connect(m_engine, &Engine::recordPositionChanged, - m_progressBar, &ProgressBar::recordPositionChanged); + connect(m_engine, &Engine::recordPositionChanged, m_progressBar, + &ProgressBar::recordPositionChanged); - connect(m_engine, &Engine::playPositionChanged, - m_progressBar, &ProgressBar::playPositionChanged); + connect(m_engine, &Engine::playPositionChanged, m_progressBar, + &ProgressBar::playPositionChanged); - connect(m_engine, &Engine::recordPositionChanged, - this, &MainWidget::audioPositionChanged); + connect(m_engine, &Engine::recordPositionChanged, this, &MainWidget::audioPositionChanged); - connect(m_engine, &Engine::playPositionChanged, - this, &MainWidget::audioPositionChanged); + connect(m_engine, &Engine::playPositionChanged, this, &MainWidget::audioPositionChanged); - connect(m_engine, &Engine::levelChanged, - m_levelMeter, &LevelMeter::levelChanged); + connect(m_engine, &Engine::levelChanged, m_levelMeter, &LevelMeter::levelChanged); - connect(m_engine, QOverload<qint64, qint64, const FrequencySpectrum&>::of(&Engine::spectrumChanged), - this, QOverload<qint64, qint64, const FrequencySpectrum&>::of(&MainWidget::spectrumChanged)); + connect(m_engine, + QOverload<qint64, qint64, const FrequencySpectrum &>::of(&Engine::spectrumChanged), + this, + QOverload<qint64, qint64, const FrequencySpectrum &>::of(&MainWidget::spectrumChanged)); - connect(m_engine, &Engine::infoMessage, - this, &MainWidget::infoMessage); + connect(m_engine, &Engine::infoMessage, this, &MainWidget::infoMessage); - connect(m_engine, &Engine::errorMessage, - this, &MainWidget::errorMessage); + connect(m_engine, &Engine::errorMessage, this, &MainWidget::errorMessage); - connect(m_spectrograph, &Spectrograph::infoMessage, - this, &MainWidget::infoMessage); + connect(m_spectrograph, &Spectrograph::infoMessage, this, &MainWidget::infoMessage); #ifndef DISABLE_WAVEFORM - connect(m_engine, &Engine::bufferChanged, - m_waveform, &Waveform::bufferChanged); + connect(m_engine, &Engine::bufferChanged, m_waveform, &Waveform::bufferChanged); #endif } @@ -417,22 +351,22 @@ void MainWidget::createMenus() 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 recordEnabled = ((QAudioDevice::Output == m_engine->mode() + || (QAudio::ActiveState != m_engine->state() + && QAudio::IdleState != m_engine->state())) + && RecordMode == m_mode); + m_recordButton->setEnabled(m_errorOccurred ? false : recordEnabled); - const bool pauseEnabled = (QAudio::ActiveState == m_engine->state() || - QAudio::IdleState == m_engine->state()); - m_pauseButton->setEnabled(pauseEnabled); + const bool pauseEnabled = + (QAudio::ActiveState == m_engine->state() || QAudio::IdleState == m_engine->state()); + m_pauseButton->setEnabled(m_errorOccurred ? false : pauseEnabled); const bool playEnabled = (/*m_engine->dataLength() &&*/ - (QAudio::AudioOutput != m_engine->mode() || - (QAudio::ActiveState != m_engine->state() && - QAudio::IdleState != m_engine->state() && - QAudio::InterruptedState != m_engine->state()))); - m_playButton->setEnabled(playEnabled); + (QAudioDevice::Output != m_engine->mode() + || (QAudio::ActiveState != m_engine->state() + && QAudio::IdleState != m_engine->state()))); + + m_playButton->setEnabled(m_errorOccurred ? false : playEnabled); } void MainWidget::reset() @@ -444,6 +378,10 @@ void MainWidget::reset() m_levelMeter->reset(); m_spectrograph->reset(); m_progressBar->reset(); + if (m_errorOccurred) { + setMode(Mode::NoMode); + updateButtonStates(); + } } void MainWidget::setMode(Mode mode) @@ -458,3 +396,5 @@ void MainWidget::updateModeMenu() m_generateToneAction->setChecked(GenerateToneMode == m_mode); m_recordAction->setChecked(RecordMode == m_mode); } + +#include "moc_mainwidget.cpp" diff --git a/examples/multimedia/spectrum/mainwidget.h b/examples/multimedia/spectrum/mainwidget.h new file mode 100644 index 000000000..789e312f5 --- /dev/null +++ b/examples/multimedia/spectrum/mainwidget.h @@ -0,0 +1,105 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MAINWIDGET_H +#define MAINWIDGET_H + +#include <QAudioDevice> +#include <QIcon> +#include <QWidget> + +class Engine; +class FrequencySpectrum; +class LevelMeter; +class ProgressBar; +class SettingsDialog; +class Spectrograph; +class ToneGeneratorDialog; +class Waveform; + +QT_BEGIN_NAMESPACE +class QAction; +class QAudioFormat; +class QLabel; +class QMenu; +class QPushButton; +QT_END_NAMESPACE + +/** + * Main application widget, responsible for connecting the various UI + * elements to the Engine. + */ +class MainWidget : public QWidget +{ + Q_OBJECT + +public: + explicit MainWidget(QWidget *parent = nullptr); + ~MainWidget(); + + // QObject + void timerEvent(QTimerEvent *event) override; + +public slots: + void stateChanged(QAudioDevice::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; + bool m_errorOccurred; +}; + +#endif // MAINWIDGET_H diff --git a/examples/multimedia/spectrum/progressbar.cpp b/examples/multimedia/spectrum/progressbar.cpp new file mode 100644 index 000000000..1806097e2 --- /dev/null +++ b/examples/multimedia/spectrum/progressbar.cpp @@ -0,0 +1,101 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "progressbar.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() = default; + +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(); +} + +#include "moc_progressbar.cpp" diff --git a/examples/multimedia/spectrum/progressbar.h b/examples/multimedia/spectrum/progressbar.h new file mode 100644 index 000000000..8b117c7e6 --- /dev/null +++ b/examples/multimedia/spectrum/progressbar.h @@ -0,0 +1,38 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef PROGRESSBAR_H +#define PROGRESSBAR_H + +#include <QWidget> + +/** + * Widget which displays a the current fill state of the Engine's internal + * buffer, and the current play/record position within that buffer. + */ +class ProgressBar : public QWidget +{ + Q_OBJECT + +public: + explicit ProgressBar(QWidget *parent = nullptr); + ~ProgressBar(); + + void reset(); + void paintEvent(QPaintEvent *event) override; + +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 diff --git a/examples/multimedia/spectrum/settingsdialog.cpp b/examples/multimedia/spectrum/settingsdialog.cpp new file mode 100644 index 000000000..bdd52e123 --- /dev/null +++ b/examples/multimedia/spectrum/settingsdialog.cpp @@ -0,0 +1,102 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "settingsdialog.h" + +#include <QCheckBox> +#include <QComboBox> +#include <QDialogButtonBox> +#include <QLabel> +#include <QPushButton> +#include <QSlider> +#include <QSpinBox> +#include <QVBoxLayout> + +SettingsDialog::SettingsDialog(const QList<QAudioDevice> &availableInputDevices, + const QList<QAudioDevice> &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 + + for (const QAudioDevice &device : availableInputDevices) + m_inputDeviceComboBox->addItem(device.description(), QVariant::fromValue(device)); + for (const QAudioDevice &device : availableOutputDevices) + m_outputDeviceComboBox->addItem(device.description(), 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 + + std::unique_ptr<QHBoxLayout> inputDeviceLayout(new QHBoxLayout); + QLabel *inputDeviceLabel = new QLabel(tr("Input device"), this); + inputDeviceLayout->addWidget(inputDeviceLabel); + inputDeviceLayout->addWidget(m_inputDeviceComboBox); + dialogLayout->addLayout(inputDeviceLayout.release()); + + std::unique_ptr<QHBoxLayout> outputDeviceLayout(new QHBoxLayout); + QLabel *outputDeviceLabel = new QLabel(tr("Output device"), this); + outputDeviceLayout->addWidget(outputDeviceLabel); + outputDeviceLayout->addWidget(m_outputDeviceComboBox); + dialogLayout->addLayout(outputDeviceLayout.release()); + + std::unique_ptr<QHBoxLayout> windowFunctionLayout(new QHBoxLayout); + QLabel *windowFunctionLabel = new QLabel(tr("Window function"), this); + windowFunctionLayout->addWidget(windowFunctionLabel); + windowFunctionLayout->addWidget(m_windowFunctionComboBox); + dialogLayout->addLayout(windowFunctionLayout.release()); + + // Connect + connect(m_inputDeviceComboBox, QOverload<int>::of(&QComboBox::activated), this, + &SettingsDialog::inputDeviceChanged); + connect(m_outputDeviceComboBox, QOverload<int>::of(&QComboBox::activated), this, + &SettingsDialog::outputDeviceChanged); + connect(m_windowFunctionComboBox, QOverload<int>::of(&QComboBox::activated), this, + &SettingsDialog::windowFunctionChanged); + + // Add standard buttons to layout + QDialogButtonBox *buttonBox = new QDialogButtonBox(this); + buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + dialogLayout->addWidget(buttonBox); + + // Connect standard buttons + connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, + &SettingsDialog::accept); + connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, + &SettingsDialog::reject); + + setLayout(dialogLayout); +} + +SettingsDialog::~SettingsDialog() = default; + +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<QAudioDevice>(); +} + +void SettingsDialog::outputDeviceChanged(int index) +{ + m_outputDevice = m_outputDeviceComboBox->itemData(index).value<QAudioDevice>(); +} + +#include "moc_settingsdialog.cpp" diff --git a/examples/multimedia/spectrum/settingsdialog.h b/examples/multimedia/spectrum/settingsdialog.h new file mode 100644 index 000000000..ac877d975 --- /dev/null +++ b/examples/multimedia/spectrum/settingsdialog.h @@ -0,0 +1,52 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SETTINGSDIALOG_H +#define SETTINGSDIALOG_H + +#include "spectrum.h" + +#include <QAudioDevice> +#include <QDialog> + +QT_BEGIN_NAMESPACE +class QComboBox; +class QCheckBox; +class QSlider; +class QSpinBox; +class QGridLayout; +QT_END_NAMESPACE + +/** + * Dialog used to control settings such as the audio input / output device + * and the windowing function. + */ +class SettingsDialog : public QDialog +{ + Q_OBJECT + +public: + SettingsDialog(const QList<QAudioDevice> &availableInputDevices, + const QList<QAudioDevice> &availableOutputDevices, QWidget *parent = nullptr); + ~SettingsDialog(); + + WindowFunction windowFunction() const { return m_windowFunction; } + const QAudioDevice &inputDevice() const { return m_inputDevice; } + const QAudioDevice &outputDevice() const { return m_outputDevice; } + +private slots: + void windowFunctionChanged(int index); + void inputDeviceChanged(int index); + void outputDeviceChanged(int index); + +private: + WindowFunction m_windowFunction; + QAudioDevice m_inputDevice; + QAudioDevice m_outputDevice; + + QComboBox *m_inputDeviceComboBox; + QComboBox *m_outputDeviceComboBox; + QComboBox *m_windowFunctionComboBox; +}; + +#endif // SETTINGSDIALOG_H diff --git a/examples/multimedia/spectrum/app/spectrograph.cpp b/examples/multimedia/spectrum/spectrograph.cpp index 6eaa097fc..96274b50b 100644 --- a/examples/multimedia/spectrum/app/spectrograph.cpp +++ b/examples/multimedia/spectrum/spectrograph.cpp @@ -1,54 +1,8 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd 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$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "spectrograph.h" + #include <QDebug> #include <QMouseEvent> #include <QPainter> @@ -59,19 +13,16 @@ 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) + : QWidget(parent), + m_barSelected(NullIndex), + m_timerId(NullTimerId), + m_lowFreq(0.0), + m_highFreq(0.0) { setMinimumHeight(100); } -Spectrograph::~Spectrograph() -{ - -} +Spectrograph::~Spectrograph() = default; void Spectrograph::setParams(int numBars, qreal lowFreq, qreal highFreq) { @@ -134,8 +85,8 @@ void Spectrograph::paintEvent(QPaintEvent *event) 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); + for (int i = 1; i < numHorizontalSections; ++i) { + line.translate(rect().width() / numHorizontalSections, 0); painter.drawLine(line); } } @@ -143,8 +94,8 @@ void Spectrograph::paintEvent(QPaintEvent *event) // 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); + for (int i = 1; i < numVerticalSections; ++i) { + line.translate(0, rect().height() / numVerticalSections); painter.drawLine(line); } @@ -163,7 +114,7 @@ void Spectrograph::paintEvent(QPaintEvent *event) const int leftPaddingWidth = (paddingWidth + gapWidth) / 2; const int barHeight = rect().height() - 2 * gapWidth; - for (int i=0; i<numBars; ++i) { + 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(); @@ -205,7 +156,7 @@ 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()) + if (index < 0 || index >= m_bars.count()) Q_ASSERT(false); return index; } @@ -214,7 +165,7 @@ 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); + return QPair<qreal, qreal>(index * bandWidth, (index + 1) * bandWidth); } void Spectrograph::updateBars() @@ -222,7 +173,7 @@ 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) { + 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)]; @@ -233,11 +184,11 @@ void Spectrograph::updateBars() update(); } -void Spectrograph::selectBar(int index) { +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); + const QString message = + QStringLiteral("%1 - %2 Hz").arg(frequencyRange.first).arg(frequencyRange.second); emit infoMessage(message, BarSelectionInterval); if (NullTimerId != m_timerId) @@ -248,4 +199,4 @@ void Spectrograph::selectBar(int index) { update(); } - +#include "moc_spectrograph.cpp" diff --git a/examples/multimedia/spectrum/spectrograph.h b/examples/multimedia/spectrum/spectrograph.h new file mode 100644 index 000000000..1344d3257 --- /dev/null +++ b/examples/multimedia/spectrum/spectrograph.h @@ -0,0 +1,62 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SPECTROGRAPH_H +#define SPECTROGRAPH_H + +#include "frequencyspectrum.h" + +#include <QWidget> + +/** + * Widget which displays a spectrograph showing the frequency spectrum + * of the window of audio samples most recently analyzed by the Engine. + */ +class Spectrograph : public QWidget +{ + Q_OBJECT + +public: + explicit Spectrograph(QWidget *parent = nullptr); + ~Spectrograph(); + + void setParams(int numBars, qreal lowFreq, qreal highFreq); + + // QObject + void timerEvent(QTimerEvent *event) override; + + // QWidget + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + +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; + }; + + QList<Bar> m_bars; + int m_barSelected; + int m_timerId; + qreal m_lowFreq; + qreal m_highFreq; + FrequencySpectrum m_spectrum; +}; + +#endif // SPECTROGRAPH_H diff --git a/examples/multimedia/spectrum/spectrum.h b/examples/multimedia/spectrum/spectrum.h new file mode 100644 index 000000000..e416e82bd --- /dev/null +++ b/examples/multimedia/spectrum/spectrum.h @@ -0,0 +1,91 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SPECTRUM_H +#define SPECTRUM_H + +#include "fftreal_wrapper.h" // For FFTLengthPowerOfTwo +#include "utils.h" + +#include <QtGlobal> + +//----------------------------------------------------------------------------- +// 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 }; +Q_DECLARE_METATYPE(WindowFunction) + +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; +}; + +// Handle some dependencies between macros defined in the .pro file + +#ifdef DISABLE_WAVEFORM +# undef SUPERIMPOSE_PROGRESS_ON_WAVEFORM +#endif + +#endif // SPECTRUM_H diff --git a/examples/multimedia/spectrum/spectrum.pri b/examples/multimedia/spectrum/spectrum.pri index 38b3f1501..431d728ec 100644 --- a/examples/multimedia/spectrum/spectrum.pri +++ b/examples/multimedia/spectrum/spectrum.pri @@ -23,8 +23,6 @@ DEFINES += LOG_ENGINE # If this macro is defined, the FFTReal DLL will not be built #DEFINES += DISABLE_FFT -static: DEFINES += DISABLE_FFT - # Disables rendering of the waveform #DEFINES += DISABLE_WAVEFORM @@ -48,4 +46,3 @@ win32 { CONFIG(debug, release|debug): spectrum_build_dir = /debug } } - diff --git a/examples/multimedia/spectrum/spectrum.pro b/examples/multimedia/spectrum/spectrum.pro index 0ca2ee554..782c00698 100644 --- a/examples/multimedia/spectrum/spectrum.pro +++ b/examples/multimedia/spectrum/spectrum.pro @@ -2,10 +2,10 @@ include(spectrum.pri) TEMPLATE = subdirs -# Ensure that library is built before application -CONFIG += ordered +SUBDIRS += 3rdparty/fftreal -!contains(DEFINES, DISABLE_FFT): SUBDIRS += 3rdparty/fftreal +app.file = app.pro +app.depends = 3rdparty/fftreal SUBDIRS += app TARGET = spectrum diff --git a/examples/multimedia/spectrum/app/spectrum.qrc b/examples/multimedia/spectrum/spectrum.qrc index 61004791b..61004791b 100644 --- a/examples/multimedia/spectrum/app/spectrum.qrc +++ b/examples/multimedia/spectrum/spectrum.qrc diff --git a/examples/multimedia/spectrum/spectrumanalyser.cpp b/examples/multimedia/spectrum/spectrumanalyser.cpp new file mode 100644 index 000000000..c309c6a73 --- /dev/null +++ b/examples/multimedia/spectrum/spectrumanalyser.cpp @@ -0,0 +1,233 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "spectrumanalyser.h" +#include "fftreal_wrapper.h" +#include "utils.h" + +#include <QAudioFormat> +#include <QMetaType> +#include <QThread> +#include <QtMath> + +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(nullptr); + 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 bytesPerFrame) +{ +#ifndef DISABLE_FFT + Q_ASSERT(buffer.size() == m_numSamples * bytesPerFrame); + + // 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 += bytesPerFrame; + } + + // 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 = qSqrt(real * real + imag * imag); + qreal amplitude = SpectrumAnalyserMultiplier * qLn(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 +{ + connect(m_thread, &SpectrumAnalyserThread::calculationComplete, this, + &SpectrumAnalyser::calculationComplete); +} + +SpectrumAnalyser::~SpectrumAnalyser() = default; + +#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(format.sampleFormat() == QAudioFormat::Int16); + + const int bytesPerFrame = format.bytesPerFrame(); + +#ifdef DUMP_SPECTRUMANALYSER + m_count++; + const QString pcmFileName = + m_outputDir.filePath(QStringLiteral("spectrum_%1.pcm").arg(m_count, 4, 10, QChar('0'))); + QFile pcmFile(pcmFileName); + pcmFile.open(QIODevice::WriteOnly); + const int bufferLength = m_numSamples * bytesPerFrame; + pcmFile.write(buffer, bufferLength); + + m_textStream << "TimeDomain " << m_count << "\n"; + const qint16 *input = reinterpret_cast<const qint16 *>(buffer); + for (int i = 0; i < m_numSamples; ++i) { + m_textStream << i << "\t" << *input << "\n"; + input += format.channels(); + } +#endif + + m_state = Busy; + + // Invoke SpectrumAnalyserThread::calculateSpectrum using QMetaObject. If + // m_thread is in a different thread from the current thread, the + // calculation will be done in the child thread. + // Once the calculation is finished, a calculationChanged signal will be + // emitted by m_thread. + const bool b = QMetaObject::invokeMethod( + m_thread, "calculateSpectrum", Qt::AutoConnection, Q_ARG(QByteArray, buffer), + Q_ARG(int, format.sampleRate()), Q_ARG(int, bytesPerFrame)); + 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; +} + +#include "moc_spectrumanalyser.cpp" diff --git a/examples/multimedia/spectrum/spectrumanalyser.h b/examples/multimedia/spectrum/spectrumanalyser.h new file mode 100644 index 000000000..202e602fe --- /dev/null +++ b/examples/multimedia/spectrum/spectrumanalyser.h @@ -0,0 +1,151 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SPECTRUMANALYSER_H +#define SPECTRUMANALYSER_H + +#include "frequencyspectrum.h" +#include "spectrum.h" + +#include <QByteArray> +#include <QList> +#include <QObject> + +#ifdef DUMP_SPECTRUMANALYSER +# include <QDir> +# include <QFile> +# include <QTextStream> +#endif + +#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 + QList<DataType> m_window; + + QList<DataType> m_input; + QList<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 = nullptr); + ~SpectrumAnalyser(); + +#ifdef DUMP_SPECTRUMANALYSER + void setOutputPath(const QString &outputPath); +#endif + +public: + /* + * Set the windowing function which is applied before calculating the FFT + */ + void setWindowFunction(WindowFunction type); + + /* + * Calculate a frequency spectrum + * + * \param buffer Audio data + * \param format Format of audio data + * + * Frequency spectrum is calculated asynchronously. The result is returned + * via the spectrumChanged signal. + * + * An ongoing calculation can be cancelled by calling cancelCalculation(). + * + */ + void calculate(const QByteArray &buffer, const QAudioFormat &format); + + /* + * Check whether the object is ready to perform another calculation + */ + bool isReady() const; + + /* + * Cancel an ongoing calculation + * + * Note that cancelling is asynchronous. + */ + void cancelCalculation(); + +signals: + void spectrumChanged(const FrequencySpectrum &spectrum); + +private slots: + void calculationComplete(const FrequencySpectrum &spectrum); + +private: + void calculateWindow(); + +private: + SpectrumAnalyserThread *m_thread; + + enum State { Idle, Busy, Cancelled }; + + State m_state; + +#ifdef DUMP_SPECTRUMANALYSER + QDir m_outputDir; + int m_count; + QFile m_textFile; + QTextStream m_textStream; +#endif +}; + +#endif // SPECTRUMANALYSER_H diff --git a/examples/multimedia/spectrum/tonegenerator.cpp b/examples/multimedia/spectrum/tonegenerator.cpp new file mode 100644 index 000000000..5df1df44d --- /dev/null +++ b/examples/multimedia/spectrum/tonegenerator.cpp @@ -0,0 +1,54 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "spectrum.h" +#include "utils.h" + +#include <QAudioFormat> +#include <QByteArray> +#include <QtEndian> +#include <QtMath> + +void generateTone(const SweptTone &tone, const QAudioFormat &format, QByteArray &buffer) +{ + Q_ASSERT(format.sampleFormat() == QAudioFormat::Int16); + + const int channelBytes = format.bytesPerSample(); + const int sampleBytes = format.channelCount() * channelBytes; + int length = buffer.size(); + const int numSamples = buffer.size() / sampleBytes; + + Q_ASSERT(length % sampleBytes == 0); + Q_UNUSED(sampleBytes); // suppress warning in release builds + + unsigned char *ptr = reinterpret_cast<unsigned char *>(buffer.data()); + + qreal phase = 0.0; + + const qreal d = 2 * M_PI / format.sampleRate(); + + // We can't generate a zero-frequency sine wave + const qreal startFreq = tone.startFreq ? tone.startFreq : 1.0; + + // Amount by which phase increases on each sample + qreal phaseStep = d * startFreq; + + // Amount by which phaseStep increases on each sample + // If this is non-zero, the output is a frequency-swept tone + const qreal phaseStepStep = d * (tone.endFreq - startFreq) / numSamples; + + while (length) { + const qreal x = tone.amplitude * qSin(phase); + const qint16 value = realToPcm(x); + for (int i = 0; i < format.channelCount(); ++i) { + qToLittleEndian<qint16>(value, ptr); + ptr += channelBytes; + length -= channelBytes; + } + + phase += phaseStep; + while (phase > 2 * M_PI) + phase -= 2 * M_PI; + phaseStep += phaseStepStep; + } +} diff --git a/examples/multimedia/spectrum/tonegenerator.h b/examples/multimedia/spectrum/tonegenerator.h new file mode 100644 index 000000000..2c331fb3c --- /dev/null +++ b/examples/multimedia/spectrum/tonegenerator.h @@ -0,0 +1,21 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef TONEGENERATOR_H +#define TONEGENERATOR_H + +#include "spectrum.h" + +#include <QtGlobal> + +QT_BEGIN_NAMESPACE +class QAudioFormat; +class QByteArray; +QT_END_NAMESPACE + +/** + * Generate a sine wave + */ +void generateTone(const SweptTone &tone, const QAudioFormat &format, QByteArray &buffer); + +#endif // TONEGENERATOR_H diff --git a/examples/multimedia/spectrum/tonegeneratordialog.cpp b/examples/multimedia/spectrum/tonegeneratordialog.cpp new file mode 100644 index 000000000..1c98c2b86 --- /dev/null +++ b/examples/multimedia/spectrum/tonegeneratordialog.cpp @@ -0,0 +1,105 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "tonegeneratordialog.h" + +#include <QCheckBox> +#include <QComboBox> +#include <QDialogButtonBox> +#include <QLabel> +#include <QPushButton> +#include <QSlider> +#include <QSpinBox> +#include <QVBoxLayout> + +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_amplitudeSlider(new QSlider(Qt::Horizontal, this)) +{ + QVBoxLayout *dialogLayout = new QVBoxLayout(this); + + m_toneGeneratorSweepCheckBox->setChecked(true); + + // Configure tone generator controls + m_frequencySlider->setRange(ToneGeneratorFreqMin, ToneGeneratorFreqMax); + m_frequencySlider->setValue(ToneGeneratorFreqDefault); + m_frequencySpinBox->setRange(ToneGeneratorFreqMin, ToneGeneratorFreqMax); + m_frequencySpinBox->setValue(ToneGeneratorFreqDefault); + m_amplitudeSlider->setRange(0, 100); + m_amplitudeSlider->setValue(ToneGeneratorAmplitudeDefault); + + // Add widgets to layout + QGridLayout *frequencyControlLayout = new QGridLayout; + QLabel *frequencyLabel = new QLabel(tr("Frequency (Hz)"), this); + frequencyControlLayout->addWidget(frequencyLabel, 0, 0, 2, 1); + frequencyControlLayout->addWidget(m_frequencySlider, 0, 1); + frequencyControlLayout->addWidget(m_frequencySpinBox, 1, 1); + m_toneGeneratorFrequencyControl->setLayout(frequencyControlLayout); + m_toneGeneratorFrequencyControl->setEnabled(false); + + QGridLayout *toneGeneratorLayout = new QGridLayout; + QLabel *amplitudeLabel = new QLabel(tr("Amplitude"), this); + toneGeneratorLayout->addWidget(m_toneGeneratorSweepCheckBox, 0, 1); + toneGeneratorLayout->addWidget(m_toneGeneratorFrequencyControl, 1, 0, 1, 2); + toneGeneratorLayout->addWidget(amplitudeLabel, 2, 0); + toneGeneratorLayout->addWidget(m_amplitudeSlider, 2, 1); + m_toneGeneratorControl->setLayout(toneGeneratorLayout); + m_toneGeneratorControl->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + dialogLayout->addWidget(m_toneGeneratorControl); + + // Connect + connect(m_toneGeneratorSweepCheckBox, &QCheckBox::toggled, this, + &ToneGeneratorDialog::frequencySweepEnabled); + connect(m_frequencySlider, &QSlider::valueChanged, m_frequencySpinBox, &QSpinBox::setValue); + connect(m_frequencySpinBox, QOverload<int>::of(&QSpinBox::valueChanged), m_frequencySlider, + &QSlider::setValue); + + // Add standard buttons to layout + QDialogButtonBox *buttonBox = new QDialogButtonBox(this); + buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + dialogLayout->addWidget(buttonBox); + + // Connect standard buttons + connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, + &ToneGeneratorDialog::accept); + connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, + &ToneGeneratorDialog::reject); + + setLayout(dialogLayout); +} + +ToneGeneratorDialog::~ToneGeneratorDialog() = default; + +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); +} + +#include "moc_tonegeneratordialog.cpp" diff --git a/examples/multimedia/spectrum/tonegeneratordialog.h b/examples/multimedia/spectrum/tonegeneratordialog.h new file mode 100644 index 000000000..2b0aed501 --- /dev/null +++ b/examples/multimedia/spectrum/tonegeneratordialog.h @@ -0,0 +1,47 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef TONEGENERATORDIALOG_H +#define TONEGENERATORDIALOG_H + +#include "spectrum.h" + +#include <QAudioDevice> +#include <QDialog> + +QT_BEGIN_NAMESPACE +class QCheckBox; +class QSlider; +class QSpinBox; +class QGridLayout; +QT_END_NAMESPACE + +/** + * Dialog which controls the parameters of the tone generator. + */ +class ToneGeneratorDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ToneGeneratorDialog(QWidget *parent = nullptr); + ~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; + QSlider *m_amplitudeSlider; +}; + +#endif // TONEGENERATORDIALOG_H diff --git a/examples/multimedia/spectrum/utils.cpp b/examples/multimedia/spectrum/utils.cpp new file mode 100644 index 000000000..3b4146a51 --- /dev/null +++ b/examples/multimedia/spectrum/utils.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "utils.h" +#include <QAudioFormat> + +qreal nyquistFrequency(const QAudioFormat &format) +{ + return format.sampleRate() / 2; +} + +QString formatToString(const QAudioFormat &format) +{ + QString result; + + if (QAudioFormat() != format) { + + QString formatType; + switch (format.sampleFormat()) { + case QAudioFormat::UInt8: + formatType = "Unsigned8"; + break; + case QAudioFormat::Int16: + formatType = "Signed16"; + break; + case QAudioFormat::Int32: + formatType = "Signed32"; + break; + case QAudioFormat::Float: + formatType = "Float"; + break; + default: + formatType = "Unknown"; + break; + } + + QString formatChannels = QStringLiteral("%1 channels").arg(format.channelCount()); + switch (format.channelCount()) { + case 1: + formatChannels = "mono"; + break; + case 2: + formatChannels = "stereo"; + break; + } + + result = QStringLiteral("%1 Hz %2 bit %3 %4") + .arg(format.sampleRate()) + .arg(format.bytesPerSample() * 8) + .arg(formatType) + .arg(formatChannels); + } + + return result; +} + +const qint16 PCMS16MaxValue = 32767; +const quint16 PCMS16MaxAmplitude = 32768; // because minimum is -32768 + +qreal pcmToReal(qint16 pcm) +{ + return qreal(pcm) / PCMS16MaxAmplitude; +} + +qint16 realToPcm(qreal real) +{ + return real * PCMS16MaxValue; +} diff --git a/examples/multimedia/spectrum/utils.h b/examples/multimedia/spectrum/utils.h new file mode 100644 index 000000000..da0711c66 --- /dev/null +++ b/examples/multimedia/spectrum/utils.h @@ -0,0 +1,79 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef UTILS_H +#define UTILS_H + +#include <QDebug> +#include <QtGlobal> + +QT_FORWARD_DECLARE_CLASS(QAudioFormat) + +//----------------------------------------------------------------------------- +// Miscellaneous utility functions +//----------------------------------------------------------------------------- + +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); + +// Compile-time calculation of powers of two + +template<int N> +class PowerOfTwo +{ +public: + static const int Result = PowerOfTwo<N - 1>::Result * 2; +}; + +template<> +class PowerOfTwo<0> +{ +public: + static const int Result = 1; +}; + +//----------------------------------------------------------------------------- +// Debug output +//----------------------------------------------------------------------------- + +class NullDebug +{ +public: + template<typename T> + NullDebug &operator<<(const T &) + { + return *this; + } +}; + +inline NullDebug nullDebug() +{ + return NullDebug(); +} + +#ifdef LOG_ENGINE +# define ENGINE_DEBUG qDebug() +#else +# define ENGINE_DEBUG nullDebug() +#endif + +#ifdef LOG_SPECTRUMANALYSER +# define SPECTRUMANALYSER_DEBUG qDebug() +#else +# define SPECTRUMANALYSER_DEBUG nullDebug() +#endif + +#ifdef LOG_WAVEFORM +# define WAVEFORM_DEBUG qDebug() +#else +# define WAVEFORM_DEBUG nullDebug() +#endif + +#endif // UTILS_H diff --git a/examples/multimedia/spectrum/app/waveform.cpp b/examples/multimedia/spectrum/waveform.cpp index 3e7462157..a3727a2a6 100644 --- a/examples/multimedia/spectrum/app/waveform.cpp +++ b/examples/multimedia/spectrum/waveform.cpp @@ -1,76 +1,30 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd 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$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "waveform.h" #include "utils.h" + +#include <QDebug> #include <QPainter> #include <QResizeEvent> -#include <QDebug> //#define PAINT_EVENT_TRACE #ifdef PAINT_EVENT_TRACE -# define WAVEFORM_PAINT_DEBUG qDebug() +# define WAVEFORM_PAINT_DEBUG qDebug() #else -# define WAVEFORM_PAINT_DEBUG nullDebug() +# 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) + : 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); @@ -89,24 +43,23 @@ void Waveform::paintEvent(QPaintEvent * /*event*/) if (m_active) { WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" - << "windowPosition" << m_windowPosition - << "windowLength" << m_windowLength; + << "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; + 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)); + const qint64 sectionLength = + qMin((m_tileLength - point.positionOffset), (windowEnd - pos)); Q_ASSERT(sectionLength > 0); const int sourceRight = tilePixelOffset(point.positionOffset + sectionLength); @@ -120,9 +73,10 @@ void Waveform::paintEvent(QPaintEvent * /*event*/) sourceRect.setLeft(point.pixelOffset); sourceRect.setRight(sourceRight); - WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "tileIndex" << point.index - << "source" << point.pixelOffset << sourceRight - << "dest" << destLeft << destRight; + WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" + << "tileIndex" << point.index << "source" + << point.pixelOffset << sourceRight << "dest" << destLeft + << destRight; painter.drawPixmap(destRect, *tile.pixmap, sourceRect); @@ -130,25 +84,30 @@ void Waveform::paintEvent(QPaintEvent * /*event*/) if (point.index < m_tiles.count()) { pos = tilePosition(point.index + 1); - WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "pos ->" << pos; + WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" + << "pos ->" << pos; } else { // Reached end of tile array - WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "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"; + 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"; + WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" + << "pos" << pos << "past end of tile array"; break; } } - WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" << "final pos" << pos << "final x" << destRight; + WAVEFORM_PAINT_DEBUG << "Waveform::paintEvent" + << "final pos" << pos << "final x" << destRight; } } @@ -158,11 +117,12 @@ void Waveform::resizeEvent(QResizeEvent *event) createPixmaps(event->size()); } -void Waveform::initialize(const QAudioFormat &format, qint64 audioBufferSize, qint64 windowDurationUs) +void Waveform::initialize(const QAudioFormat &format, qint64 audioBufferSize, + qint64 windowDurationUs) { WAVEFORM_DEBUG << "Waveform::initialize" - << "audioBufferSize" << audioBufferSize - << "windowDurationUs" << windowDurationUs; + << "audioBufferSize" << audioBufferSize << "windowDurationUs" + << windowDurationUs; reset(); @@ -172,7 +132,7 @@ void Waveform::initialize(const QAudioFormat &format, qint64 audioBufferSize, qi m_tileLength = audioBufferSize; // Calculate window size - m_windowLength = audioLength(m_format, windowDurationUs); + m_windowLength = m_format.bytesForDuration(windowDurationUs); // Calculate number of tiles required int nTiles; @@ -185,11 +145,10 @@ void Waveform::initialize(const QAudioFormat &format, qint64 audioBufferSize, qi } WAVEFORM_DEBUG << "Waveform::initialize" - << "tileLength" << m_tileLength - << "windowLength" << m_windowLength - << "nTiles" << nTiles; + << "tileLength" << m_tileLength << "windowLength" << m_windowLength << "nTiles" + << nTiles; - m_pixmaps.fill(0, nTiles); + m_pixmaps.fill(nullptr, nTiles); m_tiles.resize(nTiles); createPixmaps(rect().size()); @@ -217,8 +176,7 @@ void Waveform::reset() void Waveform::bufferChanged(qint64 position, qint64 length, const QByteArray &buffer) { WAVEFORM_DEBUG << "Waveform::bufferChanged" - << "audioPosition" << m_audioPosition - << "bufferPosition" << position + << "audioPosition" << m_audioPosition << "bufferPosition" << position << "bufferLength" << length; m_bufferPosition = position; m_bufferLength = length; @@ -229,8 +187,7 @@ void Waveform::bufferChanged(qint64 position, qint64 length, const QByteArray &b void Waveform::audioPositionChanged(qint64 position) { WAVEFORM_DEBUG << "Waveform::audioPositionChanged" - << "audioPosition" << position - << "bufferPosition" << m_bufferPosition + << "audioPosition" << position << "bufferPosition" << m_bufferPosition << "bufferLength" << m_bufferLength; if (position >= m_bufferPosition) { @@ -243,7 +200,7 @@ void Waveform::audioPositionChanged(qint64 position) void Waveform::deletePixmaps() { - qDeleteAll(qExchange(m_pixmaps, {})); + qDeleteAll(std::exchange(m_pixmaps, {})); } void Waveform::createPixmaps(const QSize &widgetSize) @@ -252,20 +209,18 @@ void Waveform::createPixmaps(const QSize &widgetSize) m_pixmapSize.setWidth(qreal(widgetSize.width()) * m_tileLength / m_windowLength); WAVEFORM_DEBUG << "Waveform::createPixmaps" - << "widgetSize" << widgetSize - << "pixmapSize" << m_pixmapSize; + << "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); + for (auto &pixmap : m_pixmaps) { + delete pixmap; + pixmap = new QPixmap(m_pixmapSize); } // Update tile pixmap pointers, and mark for repainting - for (int i=0; i<m_tiles.count(); ++i) { + for (int i = 0; i < m_tiles.count(); ++i) { m_tiles[i].pixmap = m_pixmaps[i]; m_tiles[i].painted = false; } @@ -274,14 +229,14 @@ void Waveform::createPixmaps(const QSize &widgetSize) void Waveform::setWindowPosition(qint64 position) { WAVEFORM_DEBUG << "Waveform::setWindowPosition" - << "old" << m_windowPosition << "new" << position - << "tileArrayStart" << m_tileArrayStart; + << "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))) { + 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; @@ -336,7 +291,7 @@ bool Waveform::paintTiles() WAVEFORM_DEBUG << "Waveform::paintTiles"; bool updateRequired = false; - for (int i=0; i<m_tiles.count(); ++i) { + 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; @@ -359,11 +314,8 @@ 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; + << "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); @@ -371,8 +323,8 @@ void Waveform::paintTile(int index) 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 qint16 *base = reinterpret_cast<const qint16 *>(m_buffer.constData()); + const qint16 *buffer = base + ((tileStart - m_bufferPosition) / 2); const int numSamples = m_tileLength / (2 * m_format.channelCount()); QPainter painter(tile.pixmap); @@ -394,10 +346,10 @@ void Waveform::paintTile(int index) QLine line(origin, origin); - for (int i=0; i<numSamples; ++i) { - const qint16* ptr = buffer + i * m_format.channelCount(); + for (int i = 0; i < numSamples; ++i) { + const qint16 *ptr = buffer + i * m_format.channelCount(); - const int offset = reinterpret_cast<const char*>(ptr) - m_buffer.constData(); + const int offset = reinterpret_cast<const char *>(ptr) - m_buffer.constData(); Q_ASSERT(offset >= 0); Q_ASSERT(offset < m_bufferLength); Q_UNUSED(offset); @@ -418,7 +370,8 @@ void Waveform::paintTile(int index) void Waveform::shuffleTiles(int n) { - WAVEFORM_DEBUG << "Waveform::shuffleTiles" << "n" << n; + WAVEFORM_DEBUG << "Waveform::shuffleTiles" + << "n" << n; while (n--) { Tile tile = m_tiles.first(); @@ -428,17 +381,20 @@ void Waveform::shuffleTiles(int n) m_tileArrayStart += m_tileLength; } - WAVEFORM_DEBUG << "Waveform::shuffleTiles" << "tileArrayStart" << m_tileArrayStart; + WAVEFORM_DEBUG << "Waveform::shuffleTiles" + << "tileArrayStart" << m_tileArrayStart; } void Waveform::resetTiles(qint64 newStartPos) { - WAVEFORM_DEBUG << "Waveform::resetTiles" << "newStartPos" << newStartPos; + WAVEFORM_DEBUG << "Waveform::resetTiles" + << "newStartPos" << newStartPos; QList<Tile>::iterator i = m_tiles.begin(); - for ( ; i != m_tiles.end(); ++i) + for (; i != m_tiles.end(); ++i) i->painted = false; m_tileArrayStart = newStartPos; } +#include "moc_waveform.cpp" diff --git a/examples/multimedia/spectrum/app/waveform.h b/examples/multimedia/spectrum/waveform.h index 8c26e99b2..86fda9962 100644 --- a/examples/multimedia/spectrum/app/waveform.h +++ b/examples/multimedia/spectrum/waveform.h @@ -1,52 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, 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 The Qt Company Ltd 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$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #ifndef WAVEFORM_H #define WAVEFORM_H @@ -70,7 +23,7 @@ class Waveform : public QWidget Q_OBJECT public: - explicit Waveform(QWidget *parent = 0); + explicit Waveform(QWidget *parent = nullptr); ~Waveform(); // QWidget @@ -115,17 +68,18 @@ private: struct TilePoint { TilePoint(int idx = 0, qint64 pos = 0, qint64 pix = 0) - : index(idx), positionOffset(pos), pixelOffset(pix) - { } + : index(idx), positionOffset(pos), pixelOffset(pix) + { + } // Index of tile - int index; + int index; // Number of bytes from start of tile - qint64 positionOffset; + qint64 positionOffset; // Number of pixels from left of corresponding pixmap - int pixelOffset; + int pixelOffset; }; /* @@ -177,36 +131,37 @@ private: void resetTiles(qint64 newStartPos); private: - qint64 m_bufferPosition; - qint64 m_bufferLength; - QByteArray m_buffer; + qint64 m_bufferPosition; + qint64 m_bufferLength; + QByteArray m_buffer; - qint64 m_audioPosition; - QAudioFormat m_format; + qint64 m_audioPosition; + QAudioFormat m_format; - bool m_active; + bool m_active; - QSize m_pixmapSize; - QList<QPixmap*> m_pixmaps; + QSize m_pixmapSize; + QList<QPixmap *> m_pixmaps; - struct Tile { + struct Tile + { // Pointer into parent m_pixmaps array - QPixmap* pixmap; + QPixmap *pixmap; // Flag indicating whether this tile has been painted - bool painted; + bool painted; }; - QList<Tile> m_tiles; + QList<Tile> m_tiles; // Length of audio data in bytes depicted by each tile - qint64 m_tileLength; + qint64 m_tileLength; // Position in bytes of the first tile, relative to m_buffer - qint64 m_tileArrayStart; + qint64 m_tileArrayStart; - qint64 m_windowPosition; - qint64 m_windowLength; + qint64 m_windowPosition; + qint64 m_windowLength; }; #endif // WAVEFORM_H |