1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "engine.h"
52#include "levelmeter.h"
53#include "mainwidget.h"
54#include "waveform.h"
55#include "progressbar.h"
56#include "settingsdialog.h"
57#include "spectrograph.h"
58#include "tonegeneratordialog.h"
59#include "utils.h"
60
61#include <QLabel>
62#include <QPushButton>
63#include <QHBoxLayout>
64#include <QVBoxLayout>
65#include <QStyle>
66#include <QMenu>
67#include <QFileDialog>
68#include <QTimerEvent>
69#include <QMessageBox>
70
71const int NullTimerId = -1;
72
73MainWidget::MainWidget(QWidget *parent)
74 : QWidget(parent)
75 , m_mode(NoMode)
76 , m_engine(new Engine(this))
77#ifndef DISABLE_WAVEFORM
78 , m_waveform(new Waveform(this))
79#endif
80 , m_progressBar(new ProgressBar(this))
81 , m_spectrograph(new Spectrograph(this))
82 , m_levelMeter(new LevelMeter(this))
83 , m_modeButton(new QPushButton(this))
84 , m_recordButton(new QPushButton(this))
85 , m_pauseButton(new QPushButton(this))
86 , m_playButton(new QPushButton(this))
87 , m_settingsButton(new QPushButton(this))
88 , m_infoMessage(new QLabel(tr(s: "Select a mode to begin"), this))
89 , m_infoMessageTimerId(NullTimerId)
90 , m_settingsDialog(new SettingsDialog(
91 m_engine->availableAudioInputDevices(),
92 m_engine->availableAudioOutputDevices(),
93 this))
94 , m_toneGeneratorDialog(new ToneGeneratorDialog(this))
95 , m_modeMenu(new QMenu(this))
96 , m_loadFileAction(0)
97 , m_generateToneAction(0)
98 , m_recordAction(0)
99{
100 m_spectrograph->setParams(numBars: SpectrumNumBands, lowFreq: SpectrumLowFreq, highFreq: SpectrumHighFreq);
101
102 createUi();
103 connectUi();
104}
105
106MainWidget::~MainWidget()
107{
108
109}
110
111
112//-----------------------------------------------------------------------------
113// Public slots
114//-----------------------------------------------------------------------------
115
116void MainWidget::stateChanged(QAudio::Mode mode, QAudio::State state)
117{
118 Q_UNUSED(mode);
119
120 updateButtonStates();
121
122 if (QAudio::ActiveState != state &&
123 QAudio::SuspendedState != state &&
124 QAudio::InterruptedState != state) {
125 m_levelMeter->reset();
126 m_spectrograph->reset();
127 }
128}
129
130void MainWidget::formatChanged(const QAudioFormat &format)
131{
132 infoMessage(message: formatToString(format), timeoutMs: NullMessageTimeout);
133
134#ifndef DISABLE_WAVEFORM
135 if (QAudioFormat() != format) {
136 m_waveform->initialize(format, audioBufferSize: WaveformTileLength,
137 windowDurationUs: WaveformWindowDuration);
138 }
139#endif
140}
141
142void MainWidget::spectrumChanged(qint64 position, qint64 length,
143 const FrequencySpectrum &spectrum)
144{
145 m_progressBar->windowChanged(position, length);
146 m_spectrograph->spectrumChanged(spectrum);
147}
148
149void MainWidget::infoMessage(const QString &message, int timeoutMs)
150{
151 m_infoMessage->setText(message);
152
153 if (NullTimerId != m_infoMessageTimerId) {
154 killTimer(id: m_infoMessageTimerId);
155 m_infoMessageTimerId = NullTimerId;
156 }
157
158 if (NullMessageTimeout != timeoutMs)
159 m_infoMessageTimerId = startTimer(interval: timeoutMs);
160}
161
162void MainWidget::errorMessage(const QString &heading, const QString &detail)
163{
164 QMessageBox::warning(parent: this, title: heading, text: detail, buttons: QMessageBox::Close);
165}
166
167void MainWidget::timerEvent(QTimerEvent *event)
168{
169 Q_ASSERT(event->timerId() == m_infoMessageTimerId);
170 Q_UNUSED(event) // suppress warnings in release builds
171 killTimer(id: m_infoMessageTimerId);
172 m_infoMessageTimerId = NullTimerId;
173 m_infoMessage->setText("");
174}
175
176void MainWidget::audioPositionChanged(qint64 position)
177{
178#ifndef DISABLE_WAVEFORM
179 m_waveform->audioPositionChanged(position);
180#else
181 Q_UNUSED(position)
182#endif
183}
184
185void MainWidget::bufferLengthChanged(qint64 length)
186{
187 m_progressBar->bufferLengthChanged(length);
188}
189
190
191//-----------------------------------------------------------------------------
192// Private slots
193//-----------------------------------------------------------------------------
194
195void MainWidget::showFileDialog()
196{
197 const QString dir;
198 const QStringList fileNames = QFileDialog::getOpenFileNames(parent: this, caption: tr(s: "Open WAV file"), dir, filter: "*.wav");
199 if (fileNames.count()) {
200 reset();
201 setMode(LoadFileMode);
202 m_engine->loadFile(fileName: fileNames.front());
203 updateButtonStates();
204 } else {
205 updateModeMenu();
206 }
207}
208
209void MainWidget::showSettingsDialog()
210{
211 m_settingsDialog->exec();
212 if (m_settingsDialog->result() == QDialog::Accepted) {
213 m_engine->setAudioInputDevice(m_settingsDialog->inputDevice());
214 m_engine->setAudioOutputDevice(m_settingsDialog->outputDevice());
215 m_engine->setWindowFunction(m_settingsDialog->windowFunction());
216 }
217}
218
219void MainWidget::showToneGeneratorDialog()
220{
221 m_toneGeneratorDialog->exec();
222 if (m_toneGeneratorDialog->result() == QDialog::Accepted) {
223 reset();
224 setMode(GenerateToneMode);
225 const qreal amplitude = m_toneGeneratorDialog->amplitude();
226 if (m_toneGeneratorDialog->isFrequencySweepEnabled()) {
227 m_engine->generateSweptTone(amplitude);
228 } else {
229 const qreal frequency = m_toneGeneratorDialog->frequency();
230 const Tone tone(frequency, amplitude);
231 m_engine->generateTone(tone);
232 updateButtonStates();
233 }
234 } else {
235 updateModeMenu();
236 }
237}
238
239void MainWidget::initializeRecord()
240{
241 reset();
242 setMode(RecordMode);
243 if (m_engine->initializeRecord())
244 updateButtonStates();
245}
246
247
248//-----------------------------------------------------------------------------
249// Private functions
250//-----------------------------------------------------------------------------
251
252void MainWidget::createUi()
253{
254 createMenus();
255
256 setWindowTitle(tr(s: "Spectrum Analyser"));
257
258 QVBoxLayout *windowLayout = new QVBoxLayout(this);
259
260 m_infoMessage->setSizePolicy(hor: QSizePolicy::Preferred, ver: QSizePolicy::Fixed);
261 m_infoMessage->setAlignment(Qt::AlignHCenter);
262 windowLayout->addWidget(m_infoMessage);
263
264#ifdef SUPERIMPOSE_PROGRESS_ON_WAVEFORM
265 QScopedPointer<QHBoxLayout> waveformLayout(new QHBoxLayout);
266 waveformLayout->addWidget(m_progressBar);
267 m_progressBar->setMinimumHeight(m_waveform->minimumHeight());
268 waveformLayout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
269 m_waveform->setLayout(waveformLayout.data());
270 waveformLayout.take();
271 windowLayout->addWidget(m_waveform);
272#else
273#ifndef DISABLE_WAVEFORM
274 windowLayout->addWidget(m_waveform);
275#endif // DISABLE_WAVEFORM
276 windowLayout->addWidget(m_progressBar);
277#endif // SUPERIMPOSE_PROGRESS_ON_WAVEFORM
278
279 // Spectrograph and level meter
280
281 QScopedPointer<QHBoxLayout> analysisLayout(new QHBoxLayout);
282 analysisLayout->addWidget(m_spectrograph);
283 analysisLayout->addWidget(m_levelMeter);
284 windowLayout->addLayout(layout: analysisLayout.data());
285 analysisLayout.take();
286
287 // Button panel
288
289 const QSize buttonSize(30, 30);
290
291 m_modeButton->setText(tr(s: "Mode"));
292
293 m_recordIcon = QIcon(":/images/record.png");
294 m_recordButton->setIcon(m_recordIcon);
295 m_recordButton->setEnabled(false);
296 m_recordButton->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Fixed);
297 m_recordButton->setMinimumSize(buttonSize);
298
299 m_pauseIcon = style()->standardIcon(standardIcon: QStyle::SP_MediaPause);
300 m_pauseButton->setIcon(m_pauseIcon);
301 m_pauseButton->setEnabled(false);
302 m_pauseButton->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Fixed);
303 m_pauseButton->setMinimumSize(buttonSize);
304
305 m_playIcon = style()->standardIcon(standardIcon: QStyle::SP_MediaPlay);
306 m_playButton->setIcon(m_playIcon);
307 m_playButton->setEnabled(false);
308 m_playButton->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Fixed);
309 m_playButton->setMinimumSize(buttonSize);
310
311 m_settingsIcon = QIcon(":/images/settings.png");
312 m_settingsButton->setIcon(m_settingsIcon);
313 m_settingsButton->setEnabled(true);
314 m_settingsButton->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Fixed);
315 m_settingsButton->setMinimumSize(buttonSize);
316
317 QScopedPointer<QHBoxLayout> buttonPanelLayout(new QHBoxLayout);
318 buttonPanelLayout->addStretch();
319 buttonPanelLayout->addWidget(m_modeButton);
320 buttonPanelLayout->addWidget(m_recordButton);
321 buttonPanelLayout->addWidget(m_pauseButton);
322 buttonPanelLayout->addWidget(m_playButton);
323 buttonPanelLayout->addWidget(m_settingsButton);
324
325 QWidget *buttonPanel = new QWidget(this);
326 buttonPanel->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Fixed);
327 buttonPanel->setLayout(buttonPanelLayout.data());
328 buttonPanelLayout.take(); // ownership transferred to buttonPanel
329
330 QScopedPointer<QHBoxLayout> bottomPaneLayout(new QHBoxLayout);
331 bottomPaneLayout->addWidget(buttonPanel);
332 windowLayout->addLayout(layout: bottomPaneLayout.data());
333 bottomPaneLayout.take(); // ownership transferred to windowLayout
334
335 // Apply layout
336
337 setLayout(windowLayout);
338}
339
340void MainWidget::connectUi()
341{
342 connect(sender: m_recordButton, signal: &QPushButton::clicked,
343 receiver: m_engine, slot: &Engine::startRecording);
344
345 connect(sender: m_pauseButton, signal: &QPushButton::clicked,
346 receiver: m_engine, slot: &Engine::suspend);
347
348 connect(sender: m_playButton, signal: &QPushButton::clicked,
349 receiver: m_engine, slot: &Engine::startPlayback);
350
351 connect(sender: m_settingsButton, signal: &QPushButton::clicked,
352 receiver: this, slot: &MainWidget::showSettingsDialog);
353
354 connect(sender: m_engine, signal: &Engine::stateChanged,
355 receiver: this, slot: &MainWidget::stateChanged);
356
357 connect(sender: m_engine, signal: &Engine::formatChanged,
358 receiver: this, slot: &MainWidget::formatChanged);
359
360 m_progressBar->bufferLengthChanged(length: m_engine->bufferLength());
361
362 connect(sender: m_engine, signal: &Engine::bufferLengthChanged,
363 receiver: this, slot: &MainWidget::bufferLengthChanged);
364
365 connect(sender: m_engine, signal: &Engine::dataLengthChanged,
366 receiver: this, slot: &MainWidget::updateButtonStates);
367
368 connect(sender: m_engine, signal: &Engine::recordPositionChanged,
369 receiver: m_progressBar, slot: &ProgressBar::recordPositionChanged);
370
371 connect(sender: m_engine, signal: &Engine::playPositionChanged,
372 receiver: m_progressBar, slot: &ProgressBar::playPositionChanged);
373
374 connect(sender: m_engine, signal: &Engine::recordPositionChanged,
375 receiver: this, slot: &MainWidget::audioPositionChanged);
376
377 connect(sender: m_engine, signal: &Engine::playPositionChanged,
378 receiver: this, slot: &MainWidget::audioPositionChanged);
379
380 connect(sender: m_engine, signal: &Engine::levelChanged,
381 receiver: m_levelMeter, slot: &LevelMeter::levelChanged);
382
383 connect(sender: m_engine, signal: QOverload<qint64, qint64, const FrequencySpectrum&>::of(ptr: &Engine::spectrumChanged),
384 receiver: this, slot: QOverload<qint64, qint64, const FrequencySpectrum&>::of(ptr: &MainWidget::spectrumChanged));
385
386 connect(sender: m_engine, signal: &Engine::infoMessage,
387 receiver: this, slot: &MainWidget::infoMessage);
388
389 connect(sender: m_engine, signal: &Engine::errorMessage,
390 receiver: this, slot: &MainWidget::errorMessage);
391
392 connect(sender: m_spectrograph, signal: &Spectrograph::infoMessage,
393 receiver: this, slot: &MainWidget::infoMessage);
394
395#ifndef DISABLE_WAVEFORM
396 connect(sender: m_engine, signal: &Engine::bufferChanged,
397 receiver: m_waveform, slot: &Waveform::bufferChanged);
398#endif
399}
400
401void MainWidget::createMenus()
402{
403 m_modeButton->setMenu(m_modeMenu);
404
405 m_generateToneAction = m_modeMenu->addAction(text: tr(s: "Play generated tone"));
406 m_recordAction = m_modeMenu->addAction(text: tr(s: "Record and play back"));
407 m_loadFileAction = m_modeMenu->addAction(text: tr(s: "Play file"));
408
409 m_loadFileAction->setCheckable(true);
410 m_generateToneAction->setCheckable(true);
411 m_recordAction->setCheckable(true);
412
413 connect(sender: m_loadFileAction, signal: &QAction::triggered, receiver: this, slot: &MainWidget::showFileDialog);
414 connect(sender: m_generateToneAction, signal: &QAction::triggered, receiver: this, slot: &MainWidget::showToneGeneratorDialog);
415 connect(sender: m_recordAction, signal: &QAction::triggered, receiver: this, slot: &MainWidget::initializeRecord);
416}
417
418void MainWidget::updateButtonStates()
419{
420 const bool recordEnabled = ((QAudio::AudioOutput == m_engine->mode() ||
421 (QAudio::ActiveState != m_engine->state() &&
422 QAudio::IdleState != m_engine->state())) &&
423 RecordMode == m_mode);
424 m_recordButton->setEnabled(recordEnabled);
425
426 const bool pauseEnabled = (QAudio::ActiveState == m_engine->state() ||
427 QAudio::IdleState == m_engine->state());
428 m_pauseButton->setEnabled(pauseEnabled);
429
430 const bool playEnabled = (/*m_engine->dataLength() &&*/
431 (QAudio::AudioOutput != m_engine->mode() ||
432 (QAudio::ActiveState != m_engine->state() &&
433 QAudio::IdleState != m_engine->state() &&
434 QAudio::InterruptedState != m_engine->state())));
435 m_playButton->setEnabled(playEnabled);
436}
437
438void MainWidget::reset()
439{
440#ifndef DISABLE_WAVEFORM
441 m_waveform->reset();
442#endif
443 m_engine->reset();
444 m_levelMeter->reset();
445 m_spectrograph->reset();
446 m_progressBar->reset();
447}
448
449void MainWidget::setMode(Mode mode)
450{
451 m_mode = mode;
452 updateModeMenu();
453}
454
455void MainWidget::updateModeMenu()
456{
457 m_loadFileAction->setChecked(LoadFileMode == m_mode);
458 m_generateToneAction->setChecked(GenerateToneMode == m_mode);
459 m_recordAction->setChecked(RecordMode == m_mode);
460}
461

source code of qtmultimedia/examples/multimedia/spectrum/app/mainwidget.cpp