| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  | #include <iostream>
 | 
					
						
							|  |  |  | #include <exception>
 | 
					
						
							|  |  |  | #include <stdexcept>
 | 
					
						
							|  |  |  | #include <string>
 | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  | #include <memory>
 | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include <locale.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <QCoreApplication>
 | 
					
						
							|  |  |  | #include <QTextStream>
 | 
					
						
							|  |  |  | #include <QCommandLineParser>
 | 
					
						
							|  |  |  | #include <QCommandLineOption>
 | 
					
						
							|  |  |  | #include <QStringList>
 | 
					
						
							|  |  |  | #include <QFileInfo>
 | 
					
						
							|  |  |  | #include <QAudioFormat>
 | 
					
						
							|  |  |  | #include <QAudioDeviceInfo>
 | 
					
						
							|  |  |  | #include <QAudioInput>
 | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  | #include <QAudioOutput>
 | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  | #include <QTimer>
 | 
					
						
							|  |  |  | #include <QDateTime>
 | 
					
						
							| 
									
										
										
										
											2019-05-10 20:31:16 +01:00
										 |  |  | #include <QDebug>
 | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include "revision_utils.hpp"
 | 
					
						
							|  |  |  | #include "Audio/BWFFile.hpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   QTextStream qtout {stdout}; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  | class Record final | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |   : public QObject | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   Q_OBJECT; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | public: | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |   Record (int start, int duration, QAudioDeviceInfo const& source_device, BWFFile * output, int notify_interval, int buffer_size) | 
					
						
							|  |  |  |     : source_ {source_device, output->format ()} | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |     , notify_interval_ {notify_interval} | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |     , output_ {output} | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |     , duration_ {duration} | 
					
						
							|  |  |  |   { | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |     if (buffer_size) source_.setBufferSize (output_->format ().bytesForFrames (buffer_size)); | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |     if (notify_interval_) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         source_.setNotifyInterval (notify_interval); | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |         connect (&source_, &QAudioInput::notify, this, &Record::notify); | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |     if (start == -1) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         start_recording (); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       { | 
					
						
							| 
									
										
										
										
											2019-05-10 20:31:16 +01:00
										 |  |  |         auto now = QDateTime::currentDateTimeUtc (); | 
					
						
							|  |  |  |         auto time = now.time (); | 
					
						
							|  |  |  |         auto then = now; | 
					
						
							|  |  |  |         then.setTime (QTime {time.hour (), time.minute (), start}); | 
					
						
							|  |  |  |         auto delta_ms = (now.msecsTo (then) + (60 * 1000)) % (60 * 1000); | 
					
						
							|  |  |  |         QTimer::singleShot (int (delta_ms), Qt::PreciseTimer, this, &Record::start_recording); | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Q_SIGNAL void done (); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | private: | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |   Q_SLOT void start_recording () | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |   { | 
					
						
							| 
									
										
										
										
											2020-06-13 16:04:41 +01:00
										 |  |  |     qtout << "started recording at " << QDateTime::currentDateTimeUtc ().toString ("hh:mm:ss.zzz UTC") | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
 | 
					
						
							|  |  |  |           << Qt::endl | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |           << endl | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |       ; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |     source_.start (output_); | 
					
						
							|  |  |  |     if (!notify_interval_) QTimer::singleShot (duration_ * 1000, Qt::PreciseTimer, this, &Record::stop_recording); | 
					
						
							| 
									
										
										
										
											2020-06-13 16:04:41 +01:00
										 |  |  |     qtout << QString {"buffer size used is: %1"}.arg (source_.bufferSize ()) | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
 | 
					
						
							|  |  |  |                                                    << Qt::endl | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |                                                    << endl | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |                                                    ; | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Q_SLOT void notify () | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     auto length = source_.elapsedUSecs (); | 
					
						
							| 
									
										
										
										
											2020-06-13 16:04:41 +01:00
										 |  |  |     qtout << QString {"%1 μs recorded\r"}.arg (length) | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
 | 
					
						
							|  |  |  |                                              << Qt::flush | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |                                              << flush | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |                                              ; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |     if (length >= duration_ * 1000 * 1000) stop_recording (); | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |   Q_SLOT void stop_recording () | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |   { | 
					
						
							|  |  |  |     auto length = source_.elapsedUSecs (); | 
					
						
							|  |  |  |     source_.stop (); | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |     qtout << QString {"%1 μs recorded "}.arg (length) << '(' << source_.format ().framesForBytes (output_->size ()) << " frames recorded)\n"; | 
					
						
							| 
									
										
										
										
											2020-06-13 16:04:41 +01:00
										 |  |  |     qtout << "stopped recording at " << QDateTime::currentDateTimeUtc ().toString ("hh:mm:ss.zzz UTC") | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
 | 
					
						
							|  |  |  |           << Qt::endl | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |           << endl | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |       ; | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |     Q_EMIT done (); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QAudioInput source_; | 
					
						
							|  |  |  |   int notify_interval_; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |   BWFFile * output_; | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |   int duration_; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  | class Playback final | 
					
						
							|  |  |  |   : public QObject | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   Q_OBJECT; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | public: | 
					
						
							| 
									
										
										
										
											2019-05-11 16:43:48 +01:00
										 |  |  |   Playback (int start, BWFFile * input, QAudioDeviceInfo const& sink_device, int notify_interval, int buffer_size, QString const& category) | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |     : input_ {input} | 
					
						
							|  |  |  |     , sink_ {sink_device, input->format ()} | 
					
						
							|  |  |  |     , notify_interval_ {notify_interval} | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (buffer_size) sink_.setBufferSize (input_->format ().bytesForFrames (buffer_size)); | 
					
						
							| 
									
										
										
										
											2019-05-11 16:43:48 +01:00
										 |  |  |     if (category.size ()) sink_.setCategory (category); | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |     if (notify_interval_) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         sink_.setNotifyInterval (notify_interval); | 
					
						
							|  |  |  |         connect (&sink_, &QAudioOutput::notify, this, &Playback::notify); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-05-11 01:57:56 +01:00
										 |  |  |     connect (&sink_, &QAudioOutput::stateChanged, this, &Playback::sink_state_changed); | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |     if (start == -1) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         start_playback (); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       { | 
					
						
							| 
									
										
										
										
											2019-05-10 20:31:16 +01:00
										 |  |  |         auto now = QDateTime::currentDateTimeUtc (); | 
					
						
							|  |  |  |         auto time = now.time (); | 
					
						
							|  |  |  |         auto then = now; | 
					
						
							|  |  |  |         then.setTime (QTime {time.hour (), time.minute (), start}); | 
					
						
							|  |  |  |         auto delta_ms = (now.msecsTo (then) + (60 * 1000)) % (60 * 1000); | 
					
						
							|  |  |  |         QTimer::singleShot (int (delta_ms), Qt::PreciseTimer, this, &Playback::start_playback); | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |       } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   Q_SIGNAL void done (); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | private: | 
					
						
							|  |  |  |   Q_SLOT void start_playback () | 
					
						
							|  |  |  |   { | 
					
						
							| 
									
										
										
										
											2020-06-13 16:04:41 +01:00
										 |  |  |     qtout << "started playback at " << QDateTime::currentDateTimeUtc ().toString ("hh:mm:ss.zzz UTC") | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
 | 
					
						
							|  |  |  |           << Qt::endl | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |           << endl | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |       ; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |     sink_.start (input_); | 
					
						
							| 
									
										
										
										
											2020-06-13 16:04:41 +01:00
										 |  |  |     qtout << QString {"buffer size used is: %1 (%2 frames)"}.arg (sink_.bufferSize ()).arg (sink_.format ().framesForBytes (sink_.bufferSize ())) | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
 | 
					
						
							|  |  |  |                                                                << Qt::endl | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |                                                                << endl | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |                                                                ; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Q_SLOT void notify () | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     auto length = sink_.elapsedUSecs (); | 
					
						
							| 
									
										
										
										
											2020-06-13 16:04:41 +01:00
										 |  |  |     qtout << QString {"%1 μs rendered\r"}.arg (length) << | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
 | 
					
						
							|  |  |  |                                              Qt::flush | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |                                              flush | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |                                              ; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Q_SLOT void sink_state_changed (QAudio::State state) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     switch (state) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |       case QAudio::ActiveState: | 
					
						
							|  |  |  |         qtout << "\naudio output state changed to active\n"; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case QAudio::SuspendedState: | 
					
						
							|  |  |  |         qtout << "\naudio output state changed to suspended\n"; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case QAudio::StoppedState: | 
					
						
							|  |  |  |         qtout << "\naudio output state changed to stopped\n"; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case QAudio::IdleState: | 
					
						
							|  |  |  |         stop_playback (); | 
					
						
							| 
									
										
										
										
											2019-05-11 01:57:56 +01:00
										 |  |  |         qtout << "\naudio output state changed to idle\n"; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |         break; | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK (5, 10, 0)
 | 
					
						
							|  |  |  |       case QAudio::InterruptedState: | 
					
						
							|  |  |  |         qtout << "\naudio output state changed to interrupted\n"; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Q_SLOT void stop_playback () | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     auto length = sink_.elapsedUSecs (); | 
					
						
							|  |  |  |     sink_.stop (); | 
					
						
							|  |  |  |     qtout << QString {"%1 μs rendered "}.arg (length) << '(' << sink_.format ().framesForBytes (input_->size ()) << " frames rendered)\n"; | 
					
						
							| 
									
										
										
										
											2020-06-13 16:04:41 +01:00
										 |  |  |     qtout << "stopped playback at " << QDateTime::currentDateTimeUtc ().toString ("hh:mm:ss.zzz UTC") | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
 | 
					
						
							|  |  |  |           << Qt::endl | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |           << endl | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |       ; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |     Q_EMIT done (); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   BWFFile * input_; | 
					
						
							|  |  |  |   QAudioOutput sink_; | 
					
						
							|  |  |  |   int notify_interval_; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  | #include "record_time_signal.moc"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int main(int argc, char *argv[]) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   QCoreApplication app {argc, argv}; | 
					
						
							|  |  |  |   try | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       ::setlocale (LC_NUMERIC, "C"); // ensure number forms are in
 | 
					
						
							|  |  |  |                                      // consistent format, do this
 | 
					
						
							|  |  |  |                                      // after instantiating
 | 
					
						
							|  |  |  |                                      // QApplication so that Qt has
 | 
					
						
							|  |  |  |                                      // correct l18n
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Override programs executable basename as application name.
 | 
					
						
							|  |  |  |       app.setApplicationName ("WSJT-X Record Time Signal"); | 
					
						
							|  |  |  |       app.setApplicationVersion (version ()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       QCommandLineParser parser; | 
					
						
							|  |  |  |       parser.setApplicationDescription ( | 
					
						
							|  |  |  |                                         "\nTool to determine and experiment with QAudioInput latencies\n\n" | 
					
						
							|  |  |  |                                         "\tUse the -I option to list available recording device numbers\n" | 
					
						
							|  |  |  |                                         ); | 
					
						
							|  |  |  |       auto help_option = parser.addHelpOption (); | 
					
						
							|  |  |  |       auto version_option = parser.addVersionOption (); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       parser.addOptions ({ | 
					
						
							|  |  |  |           {{"I", "list-audio-inputs"}, | 
					
						
							|  |  |  |               app.translate ("main", "List the available audio input devices")}, | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |           {{"O", "list-audio-outputs"}, | 
					
						
							|  |  |  |               app.translate ("main", "List the available audio output devices")}, | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |           {{"s", "start-time"}, | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |               app.translate ("main", "Record from <start-time> seconds, default start immediately"), | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |               app.translate ("main", "start-time")}, | 
					
						
							|  |  |  |           {{"d", "duration"}, | 
					
						
							|  |  |  |               app.translate ("main", "Recording <duration> seconds"), | 
					
						
							|  |  |  |               app.translate ("main", "duration")}, | 
					
						
							|  |  |  |           {{"o", "output"}, | 
					
						
							|  |  |  |               app.translate ("main", "Save output as <output-file>"), | 
					
						
							|  |  |  |               app.translate ("main", "output-file")}, | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |           {{"i", "input"}, | 
					
						
							|  |  |  |               app.translate ("main", "Playback <input-file>"), | 
					
						
							|  |  |  |               app.translate ("main", "input-file")}, | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |           {{"f", "force"}, | 
					
						
							|  |  |  |               app.translate ("main", "Overwrite existing file")}, | 
					
						
							|  |  |  |           {{"r", "sample-rate"}, | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |               app.translate ("main", "Record at <sample-rate>, default 48000 Hz"), | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |               app.translate ("main", "sample-rate")}, | 
					
						
							|  |  |  |           {{"c", "num-channels"}, | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |               app.translate ("main", "Record <num> channels, default 2"), | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |               app.translate ("main", "num")}, | 
					
						
							|  |  |  |           {{"R", "recording-device-number"}, | 
					
						
							|  |  |  |               app.translate ("main", "Record from <device-number>"), | 
					
						
							|  |  |  |               app.translate ("main", "device-number")}, | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |           {{"P", "playback-device-number"}, | 
					
						
							|  |  |  |               app.translate ("main", "Playback to <device-number>"), | 
					
						
							|  |  |  |               app.translate ("main", "device-number")}, | 
					
						
							| 
									
										
										
										
											2019-05-11 16:43:48 +01:00
										 |  |  |           {{"C", "category"}, | 
					
						
							|  |  |  |               app.translate ("main", "Playback <category-name>"), | 
					
						
							|  |  |  |               app.translate ("main", "category-name")}, | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |           {{"n", "notify-interval"}, | 
					
						
							|  |  |  |               app.translate ("main", "use notify signals every <interval> milliseconds, zero to use a timer"), | 
					
						
							|  |  |  |               app.translate ("main", "interval")}, | 
					
						
							|  |  |  |           {{"b", "buffer-size"}, | 
					
						
							|  |  |  |               app.translate ("main", "audio buffer size <frames>"), | 
					
						
							|  |  |  |               app.translate ("main", "frames")}, | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       parser.process (app); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       auto input_devices = QAudioDeviceInfo::availableDevices (QAudio::AudioInput);       | 
					
						
							|  |  |  |       if (parser.isSet ("I")) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           int n {0}; | 
					
						
							|  |  |  |           for (auto const& device : input_devices) | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2020-06-13 16:04:41 +01:00
										 |  |  |               qtout << ++n << " - [" << device.deviceName () << ']' | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
 | 
					
						
							|  |  |  |                     << Qt::endl | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |                     << endl | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |                 ; | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |           return 0; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |       auto output_devices = QAudioDeviceInfo::availableDevices (QAudio::AudioOutput);       | 
					
						
							|  |  |  |       if (parser.isSet ("O")) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           int n {0}; | 
					
						
							|  |  |  |           for (auto const& device : output_devices) | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2020-06-13 16:04:41 +01:00
										 |  |  |               qtout << ++n << " - [" << device.deviceName () << ']' | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
 | 
					
						
							|  |  |  |                     << Qt::endl | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |                     << endl | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |                 ; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |           return 0; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |       bool ok; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |       int start {-1}; | 
					
						
							|  |  |  |       if (parser.isSet ("s")) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           start = parser.value ("s").toInt (&ok); | 
					
						
							|  |  |  |           if (!ok) throw std::invalid_argument {"start time not a number"}; | 
					
						
							| 
									
										
										
										
											2019-05-11 01:57:56 +01:00
										 |  |  |           if (0 > start || start > 59) throw std::invalid_argument {"0 > start > 59"}; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |       int sample_rate {48000}; | 
					
						
							|  |  |  |       if (parser.isSet ("r")) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           sample_rate = parser.value ("r").toInt (&ok); | 
					
						
							|  |  |  |           if (!ok) throw std::invalid_argument {"sample rate not a number"}; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       int num_channels {2}; | 
					
						
							|  |  |  |       if (parser.isSet ("c")) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           num_channels = parser.value ("c").toInt (&ok); | 
					
						
							|  |  |  |           if (!ok) throw std::invalid_argument {"channel count not a number"}; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       int notify_interval {0}; | 
					
						
							|  |  |  |       if (parser.isSet ("n")) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           notify_interval = parser.value ("n").toInt (&ok); | 
					
						
							|  |  |  |           if (!ok) throw std::invalid_argument {"notify interval not a number"}; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       int buffer_size {0}; | 
					
						
							|  |  |  |       if (parser.isSet ("b")) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           buffer_size = parser.value ("b").toInt (&ok); | 
					
						
							|  |  |  |           if (!ok) throw std::invalid_argument {"buffer size not a number"}; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       int input_device {0}; | 
					
						
							|  |  |  |       if (parser.isSet ("R")) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           input_device = parser.value ("R").toInt (&ok); | 
					
						
							|  |  |  |           if (!ok || 0 >= input_device || input_device > input_devices.size ()) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               throw std::invalid_argument {"invalid recording device"}; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |       int output_device {0}; | 
					
						
							|  |  |  |       if (parser.isSet ("P")) | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |         { | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |           output_device = parser.value ("P").toInt (&ok); | 
					
						
							|  |  |  |           if (!ok || 0 >= output_device || output_device > output_devices.size ()) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               throw std::invalid_argument {"invalid playback device"}; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |       if (!(parser.isSet ("o") || parser.isSet ("i"))) throw std::invalid_argument {"file required"}; | 
					
						
							|  |  |  |       if (parser.isSet ("o") && parser.isSet ("i")) throw std::invalid_argument {"specify either input or output"}; | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       QAudioFormat audio_format; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |       if (parser.isSet ("o"))   // Record
 | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |         { | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |           int duration = parser.value ("d").toInt (&ok); | 
					
						
							|  |  |  |           if (!ok) throw std::invalid_argument {"duration not a number"}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           QFileInfo ofi {parser.value ("o")}; | 
					
						
							|  |  |  |           if (!ofi.suffix ().size () && ofi.fileName ()[ofi.fileName ().size () - 1] != QChar {'.'}) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               ofi.setFile (ofi.filePath () + ".wav"); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           if (!parser.isSet ("f") && ofi.isFile ()) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               throw std::invalid_argument {"set the `-force' option to overwrite an existing output file"}; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           audio_format.setSampleRate (sample_rate); | 
					
						
							|  |  |  |           audio_format.setChannelCount (num_channels); | 
					
						
							|  |  |  |           audio_format.setSampleSize (16); | 
					
						
							|  |  |  |           audio_format.setSampleType (QAudioFormat::SignedInt); | 
					
						
							|  |  |  |           audio_format.setCodec ("audio/pcm"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-11 01:57:56 +01:00
										 |  |  |           auto source = input_device ? input_devices[input_device - 1] : QAudioDeviceInfo::defaultInputDevice (); | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |           if (!source.isFormatSupported (audio_format)) | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2020-06-13 16:04:41 +01:00
										 |  |  |               qtout << "warning, requested format not supported, using nearest" | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
 | 
					
						
							|  |  |  |                     << Qt::endl | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |                     << endl | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |                 ; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |               audio_format = source.nearestFormat (audio_format); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           BWFFile output_file {audio_format, ofi.filePath ()}; | 
					
						
							|  |  |  |           if (!output_file.open (BWFFile::WriteOnly)) throw std::invalid_argument {QString {"cannot open output file \"%1\""}.arg (ofi.filePath ()).toStdString ()}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // run the application
 | 
					
						
							|  |  |  |           Record record {start, duration, source, &output_file, notify_interval, buffer_size}; | 
					
						
							|  |  |  |           QObject::connect (&record, &Record::done, &app, &QCoreApplication::quit); | 
					
						
							|  |  |  |           return app.exec(); | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |       else                      // Playback
 | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           QFileInfo ifi {parser.value ("i")}; | 
					
						
							|  |  |  |           if (!ifi.isFile () && !ifi.suffix ().size () && ifi.fileName ()[ifi.fileName ().size () - 1] != QChar {'.'}) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               ifi.setFile (ifi.filePath () + ".wav"); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           BWFFile input_file {audio_format, ifi.filePath ()}; | 
					
						
							|  |  |  |           if (!input_file.open (BWFFile::ReadOnly)) throw std::invalid_argument {QString {"cannot open input file \"%1\""}.arg (ifi.filePath ()).toStdString ()}; | 
					
						
							| 
									
										
										
										
											2019-05-11 01:57:56 +01:00
										 |  |  |           auto sink = output_device ? output_devices[output_device - 1] : QAudioDeviceInfo::defaultOutputDevice (); | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |           if (!sink.isFormatSupported (input_file.format ())) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               throw std::invalid_argument {"audio output device does not support input file audio format"}; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |           // run the application
 | 
					
						
							| 
									
										
										
										
											2019-05-11 16:43:48 +01:00
										 |  |  |           Playback play {start, &input_file, sink, notify_interval, buffer_size, parser.value ("category")}; | 
					
						
							| 
									
										
										
										
											2019-05-10 19:38:04 +01:00
										 |  |  |           QObject::connect (&play, &Playback::done, &app, &QCoreApplication::quit); | 
					
						
							|  |  |  |           return app.exec(); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-05-10 17:27:52 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |   catch (std::exception const& e) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       std::cerr << "Error: " << e.what () << '\n'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   catch (...) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       std::cerr << "Unexpected fatal error\n"; | 
					
						
							|  |  |  |       throw; // hoping the runtime might tell us more about the exception
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   return -1; | 
					
						
							|  |  |  | } |