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 | |
71 | const int NullTimerId = -1; |
72 | |
73 | MainWidget::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 | |
106 | MainWidget::~MainWidget() |
107 | { |
108 | |
109 | } |
110 | |
111 | |
112 | //----------------------------------------------------------------------------- |
113 | // Public slots |
114 | //----------------------------------------------------------------------------- |
115 | |
116 | void 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 | |
130 | void 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 | |
142 | void MainWidget::spectrumChanged(qint64 position, qint64 length, |
143 | const FrequencySpectrum &spectrum) |
144 | { |
145 | m_progressBar->windowChanged(position, length); |
146 | m_spectrograph->spectrumChanged(spectrum); |
147 | } |
148 | |
149 | void 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 | |
162 | void MainWidget::errorMessage(const QString &heading, const QString &detail) |
163 | { |
164 | QMessageBox::warning(parent: this, title: heading, text: detail, buttons: QMessageBox::Close); |
165 | } |
166 | |
167 | void 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 | |
176 | void MainWidget::audioPositionChanged(qint64 position) |
177 | { |
178 | #ifndef DISABLE_WAVEFORM |
179 | m_waveform->audioPositionChanged(position); |
180 | #else |
181 | Q_UNUSED(position) |
182 | #endif |
183 | } |
184 | |
185 | void MainWidget::bufferLengthChanged(qint64 length) |
186 | { |
187 | m_progressBar->bufferLengthChanged(length); |
188 | } |
189 | |
190 | |
191 | //----------------------------------------------------------------------------- |
192 | // Private slots |
193 | //----------------------------------------------------------------------------- |
194 | |
195 | void 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 | |
209 | void 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 | |
219 | void 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 | |
239 | void MainWidget::initializeRecord() |
240 | { |
241 | reset(); |
242 | setMode(RecordMode); |
243 | if (m_engine->initializeRecord()) |
244 | updateButtonStates(); |
245 | } |
246 | |
247 | |
248 | //----------------------------------------------------------------------------- |
249 | // Private functions |
250 | //----------------------------------------------------------------------------- |
251 | |
252 | void 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 | |
340 | void 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 | |
401 | void 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 | |
418 | void 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 | |
438 | void 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 | |
449 | void MainWidget::setMode(Mode mode) |
450 | { |
451 | m_mode = mode; |
452 | updateModeMenu(); |
453 | } |
454 | |
455 | void 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 | |