klweba

swift sound generator

Apr 4th, 2018
419
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 34.53 KB | None | 0 0
  1. /* Copyright (C) 2013
  2.  * swift project Community / Contributors
  3.  *
  4.  * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
  5.  * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
  6.  * including this file, may be copied, modified, propagated, or distributed except according to the terms
  7.  * contained in the LICENSE file.
  8.  */
  9.  
  10. //! \file
  11.  
  12. #ifndef BLACKSOUND_SOUNDGENERATOR_H
  13. #define BLACKSOUND_SOUNDGENERATOR_H
  14.  
  15. #include "blacksoundexport.h"
  16. #include "blackmisc/aviation/selcal.h"
  17. #include "blackmisc/audio/audiodeviceinfo.h"
  18. #include "blackmisc/pq/time.h"
  19. #include "blackmisc/pq/frequency.h"
  20. #include "blackmisc/audio/notificationsounds.h"
  21. #include <QIODevice>
  22. #include <QThread>
  23. #include <QDateTime>
  24. #include <QAudioFormat>
  25. #include <QAudioOutput>
  26. #include <QAudioDeviceInfo>
  27. #include <QMediaPlayer>
  28.  
  29. namespace BlackSound
  30. {
  31.     //! Playing simple sounds
  32.     class BLACKSOUND_EXPORT CSoundGenerator : public QIODevice
  33.     {
  34.         Q_OBJECT
  35.  
  36.     public:
  37.         //! Tone to be played
  38.         struct Tone
  39.         {
  40.             friend class CSoundGenerator;
  41.  
  42.         public:
  43.             //! Play frequency f for t milliseconds
  44.             Tone(const BlackMisc::PhysicalQuantities::CFrequency &frequency, const BlackMisc::PhysicalQuantities::CTime &duration) :
  45.                 m_frequencyHz(static_cast<int>(frequency.valueRounded(BlackMisc::PhysicalQuantities::CFrequencyUnit::Hz()))),
  46.                 m_secondaryFrequencyHz(0),
  47.                 m_durationMs(static_cast<qint64>(duration.valueRounded(BlackMisc::PhysicalQuantities::CTimeUnit::ms()))) {}
  48.  
  49.             //! Play 2 frequencies f for t milliseconds
  50.             Tone(const BlackMisc::PhysicalQuantities::CFrequency &frequency, const BlackMisc::PhysicalQuantities::CFrequency &secondaryFrequency, const BlackMisc::PhysicalQuantities::CTime &duration) :
  51.                 m_frequencyHz(static_cast<int>(frequency.valueRounded(BlackMisc::PhysicalQuantities::CFrequencyUnit::Hz()))),
  52.                 m_secondaryFrequencyHz(static_cast<int>(secondaryFrequency.valueRounded(BlackMisc::PhysicalQuantities::CFrequencyUnit::Hz()))),
  53.                 m_durationMs(static_cast<qint64>(duration.valueRounded(BlackMisc::PhysicalQuantities::CTimeUnit::ms()))) {}
  54.  
  55.         private:
  56.             int m_frequencyHz;          //!< first tone's frequency, use 0 for silence
  57.             int m_secondaryFrequencyHz; //!< second tone's frequency, or 0
  58.             qint64 m_durationMs;        //!< How long to play (duration)
  59.         };
  60.  
  61.         //! Constructor
  62.         //! \param device        device
  63.         //! \param format        audio format
  64.         //! \param tones         list of Tones
  65.         //! \param mode          play once?
  66.         //! \param parent
  67.         //! \see PlayMode
  68.         CSoundGenerator(const QAudioDeviceInfo &device, const QAudioFormat &format, const QList<Tone> &tones, BlackMisc::Audio::CNotificationSounds::PlayMode mode, QObject *parent = nullptr);
  69.  
  70.         //! Constructor
  71.         //! \param tones         list of Tones
  72.         //! \param mode          play once?
  73.         //! \param parent
  74.         //! \see PlayMode
  75.         CSoundGenerator(const QList<Tone> &tones, BlackMisc::Audio::CNotificationSounds::PlayMode mode, QObject *parent = nullptr);
  76.  
  77.         //! Destructor
  78.         virtual ~CSoundGenerator();
  79.  
  80.         //! Set volume
  81.         //! \param volume 0..100
  82.         void setVolume(int volume)
  83.         {
  84.             m_audioOutput->setVolume(qreal(volume / 100.0));
  85.         }
  86.  
  87.         //! Close device, buffer stays intact
  88.         void stop(bool destructor = false);
  89.  
  90.         //! Duration of one cycle
  91.         qint64 singleCyleDurationMs() const { return calculateDurationMs(m_tones); }
  92.  
  93.         //! \copydoc QIODevice::readData()
  94.         virtual qint64 readData(char *data, qint64 maxlen) override;
  95.  
  96.         //! \copydoc QIODevice::writeData()
  97.         //! \remarks NOT(!) used here
  98.         virtual qint64 writeData(const char *data, qint64 len) override;
  99.  
  100.         //! \copydoc QIODevice::bytesAvailable()
  101.         virtual qint64 bytesAvailable() const override;
  102.  
  103.         //! \copydoc QIODevice::seek()
  104.         virtual bool seek(qint64 pos) override
  105.         {
  106.             return m_endReached ? false : QIODevice::seek(pos);
  107.         }
  108.  
  109.         //! \copydoc QIODevice::atEnd()
  110.         virtual bool atEnd() const override
  111.         {
  112.             return m_endReached ? true : QIODevice::atEnd();
  113.         }
  114.  
  115.         //! Default audio format fo play these sounds
  116.         static QAudioFormat defaultAudioFormat();
  117.  
  118.         //! Find the closest Qt device to this audio device
  119.         //! \param audioDevice   output audio device
  120.         //! \return
  121.         static QAudioDeviceInfo findClosestOutputDevice(const BlackMisc::Audio::CAudioDeviceInfo &audioDevice);
  122.  
  123.         //! Play signal of tones once
  124.         //! \param volume    0-100
  125.         //! \param tones     list of tones
  126.         //! \param device    device to be used
  127.         //! \return generator used, important with SingleWithAutomaticDeletion automatically deleted
  128.         static CSoundGenerator *playSignal(int volume, const QList<Tone> &tones, const QAudioDeviceInfo &device = QAudioDeviceInfo::defaultOutputDevice());
  129.  
  130.         //! Play signal of tones once
  131.         //! \param volume    0-100
  132.         //! \param tones     list of tones
  133.         //! \param device    device to be used
  134.         //! \return generator used, important with SingleWithAutomaticDeletion automatically deleted
  135.         static CSoundGenerator *playSignalInBackground(int volume, const QList<CSoundGenerator::Tone> &tones, const QAudioDeviceInfo &device);
  136.  
  137.         //! Record the tones to a wav file, then play the wav file
  138.         //! \param volume    0-100
  139.         //! \param tones     list of tones
  140.         //! \param device    device to be used
  141.         static void playSignalRecorded(int volume, const QList<CSoundGenerator::Tone> &tones, const QAudioDeviceInfo &device);
  142.  
  143.         //! Play SELCAL tone
  144.         //! \param volume    0-100
  145.         //! \param selcal
  146.         //! \param device    device to be used
  147.         //! \see BlackMisc::Aviation::CSelcal
  148.         static void playSelcal(int volume, const BlackMisc::Aviation::CSelcal &selcal, const QAudioDeviceInfo &device = QAudioDeviceInfo::defaultOutputDevice());
  149.  
  150.         //! Play SELCAL tone
  151.         //! \param volume    0-100
  152.         //! \param selcal
  153.         //! \param audioDevice device to be used
  154.         //! \see BlackMisc::Aviation::CSelcal
  155.         static void playSelcal(int volume, const BlackMisc::Aviation::CSelcal &selcal, const BlackMisc::Audio::CAudioDeviceInfo &audioDevice);
  156.  
  157.         //! One cycle of tones takes t milliseconds
  158.         BlackMisc::PhysicalQuantities::CTime oneCycleDurationMs() const
  159.         {
  160.             return BlackMisc::PhysicalQuantities::CTime(m_oneCycleDurationMs, BlackMisc::PhysicalQuantities::CTimeUnit::ms());
  161.         }
  162.  
  163.         //! Play given file
  164.         //! \param volume    0-100
  165.         //! \param file
  166.         //! \param removeFileAfterPlaying delete the file, after it has been played
  167.         static void playFile(int volume, const QString &file, bool removeFileAfterPlaying);
  168.  
  169.         //! Play notification
  170.         //! \param volume    0-100
  171.         //! \param notification
  172.         static void playNotificationSound(int volume, BlackMisc::Audio::CNotificationSounds::Notification notification);
  173.  
  174.         //! For debugging purposes
  175.         static void printAllQtSoundDevices(QTextStream &qtout);
  176.  
  177.     signals:
  178.         //! Device was closed
  179.         //! \remarks With singleShot the signal indicates that sound sequence has finished
  180.         void stopped();
  181.  
  182.         //! Generator is stopping
  183.         void stopping();
  184.  
  185.     public slots:
  186.         //! Play sound, open device
  187.         //! \param volume 0..100
  188.         //! \param pull pull/push, if false push mode
  189.         void start(int volume, bool pull = true);
  190.  
  191.         //! Play sound in own thread, open device
  192.         //! \remarks always push mode
  193.         //! \param volume 0..100
  194.         void startInOwnThread(int volume);
  195.  
  196.     private slots:
  197.         //! Push mode, timer expired
  198.         void pushTimerExpired();
  199.  
  200.     private:
  201.         //! Generate tone data in internal buffer
  202.         void generateData();
  203.  
  204.     private:
  205.         QList<Tone> m_tones;                          //!< tones to be played
  206.         qint64 m_position;                            //!< position in buffer
  207.         BlackMisc::Audio::CNotificationSounds::PlayMode m_playMode; //!< end data provisioning after playing all tones, play endless loop
  208.         bool m_endReached;                            //!< indicates end in combination with single play
  209.         qint64 m_oneCycleDurationMs;                  //!< how long is one cycle of tones
  210.         QByteArray m_buffer;                          //!< generated buffer for data
  211.         QAudioDeviceInfo m_device;                    //!< audio device
  212.         QAudioFormat m_audioFormat;                   //!< used format
  213.         QScopedPointer<QAudioOutput> m_audioOutput;
  214.         QTimer    *m_pushTimer        = nullptr;      //!< Push mode timer
  215.         QIODevice *m_pushModeIODevice = nullptr;      //!< IO device when used in push mode
  216.         QThread   *m_ownThread        = nullptr;
  217.         static QDateTime s_selcalStarted;
  218.  
  219.         //! Header for saving .wav files
  220.         struct chunk
  221.         {
  222.             char        id[4];
  223.             quint32     size;
  224.         };
  225.  
  226.         //! Header for saving .wav files
  227.         struct RiffHeader
  228.         {
  229.             chunk       descriptor;     //!< "RIFF"
  230.             char        type[4];        //!< "WAVE"
  231.         };
  232.  
  233.         //! Header for saving .wav files
  234.         struct WaveHeader
  235.         {
  236.             chunk       descriptor;
  237.             quint16     audioFormat;
  238.             quint16     numChannels;
  239.             quint32     sampleRate;
  240.             quint32     byteRate;
  241.             quint16     blockAlign;
  242.             quint16     bitsPerSample;
  243.         };
  244.  
  245.         //! Header for saving .wav files
  246.         struct DataHeader
  247.         {
  248.             chunk       descriptor;
  249.         };
  250.  
  251.         //! Header for saving .wav files
  252.         struct CombinedHeader
  253.         {
  254.             RiffHeader  riff;
  255.             WaveHeader  wave;
  256.             DataHeader  data;
  257.         };
  258.  
  259.         //! "My" media player
  260.         static QMediaPlayer *mediaPlayer()
  261.         {
  262.             static QMediaPlayer *mediaPlayer = new QMediaPlayer();
  263.             return mediaPlayer;
  264.         }
  265.  
  266.         //! Duration of these tones
  267.         static qint64 calculateDurationMs(const QList<Tone> &tones);
  268.  
  269.         //! save buffer to wav file
  270.         bool saveToWavFile(const QString &fileName) const;
  271.  
  272.         //! Write amplitude to buffer
  273.         //! \param amplitude value -1 .. 1
  274.         //! \param bufferPointer current buffer pointer
  275.         void writeAmplitudeToBuffer(const double amplitude, unsigned char *bufferPointer);
  276.     };
  277. } //namespace
  278.  
  279. #endif // guard
  280.  
  281. /* Copyright (C) 2013
  282.  * swift project Community / Contributors
  283.  *
  284.  * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
  285.  * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
  286.  * including this file, may be copied, modified, propagated, or distributed except according to the terms
  287.  * contained in the LICENSE file.
  288.  */
  289.  
  290. #include "blacksound/soundgenerator.h"
  291. #include "blackmisc/directoryutils.h"
  292. #include "blackmisc/filedeleter.h"
  293. #include <QtCore/qendian.h>
  294. #include <math.h>
  295. #include <qmath.h>
  296. #include <qendian.h>
  297. #include <QMultimedia>
  298. #include <QAudioOutput>
  299. #include <QMediaPlayer>
  300. #include <QMediaPlaylist>
  301. #include <QTimer>
  302. #include <QUrl>
  303. #include <QFile>
  304. #include <QDir>
  305.  
  306. using namespace BlackMisc;
  307. using namespace BlackMisc::Aviation;
  308. using namespace BlackMisc::PhysicalQuantities;
  309. using namespace BlackMisc::Audio;
  310.  
  311. namespace BlackSound
  312. {
  313.     QDateTime CSoundGenerator::s_selcalStarted  = QDateTime::currentDateTimeUtc();
  314.  
  315.     CSoundGenerator::CSoundGenerator(const QAudioDeviceInfo &device, const QAudioFormat &format, const QList<Tone> &tones, CNotificationSounds::PlayMode mode, QObject *parent)
  316.         :   QIODevice(parent),
  317.             m_tones(tones), m_position(0), m_playMode(mode), m_endReached(false), m_oneCycleDurationMs(calculateDurationMs(tones)),
  318.             m_device(device), m_audioFormat(format), m_audioOutput(new QAudioOutput(format))
  319.     {
  320.         Q_ASSERT_X(tones.size() > 0, Q_FUNC_INFO, "No tones");
  321.     }
  322.  
  323.     CSoundGenerator::CSoundGenerator(const QList<Tone> &tones, CNotificationSounds::PlayMode mode, QObject *parent)
  324.         :   QIODevice(parent),
  325.             m_tones(tones), m_position(0), m_playMode(mode), m_endReached(false), m_oneCycleDurationMs(calculateDurationMs(tones)),
  326.             m_device(QAudioDeviceInfo::defaultOutputDevice()), m_audioFormat(CSoundGenerator::defaultAudioFormat()),
  327.             m_audioOutput(new QAudioOutput(CSoundGenerator::defaultAudioFormat()))
  328.     {
  329.         Q_ASSERT_X(tones.size() > 0, Q_FUNC_INFO, "No tones");
  330.     }
  331.  
  332.     CSoundGenerator::~CSoundGenerator()
  333.     {
  334.         this->stop(true);
  335.         if (m_ownThread) { m_ownThread->deleteLater(); }
  336.     }
  337.  
  338.     void CSoundGenerator::start(int volume, bool pull)
  339.     {
  340.         if (m_buffer.isEmpty()) this->generateData();
  341.         this->open(QIODevice::ReadOnly);
  342.         m_audioOutput->setVolume(qreal(0.01 * volume));
  343.  
  344.         if (pull)
  345.         {
  346.             // For an output device, the QAudioOutput class will pull data from the QIODevice
  347.             // (using QIODevice::read()) when more audio data is required.
  348.             m_audioOutput->start(this); // pull
  349.         }
  350.         else
  351.         {
  352.             // In push mode, the audio device provides a QIODevice instance that can be
  353.             // written or read to as needed. Typically this results in simpler code but more buffering, which may affect latency.
  354.             if (!m_pushTimer)
  355.             {
  356.                 m_pushTimer = new QTimer(this);
  357.                 bool ok = connect(m_pushTimer, &QTimer::timeout, this, &CSoundGenerator::pushTimerExpired);
  358.                 Q_ASSERT(ok);
  359.                 Q_UNUSED(ok); // suppress Clang warning in release build
  360.                 m_pushTimer->start(20);
  361.             }
  362.             m_pushModeIODevice = m_audioOutput->start(); // push, IO device not owned
  363.         }
  364.     }
  365.  
  366.     void CSoundGenerator::startInOwnThread(int volume)
  367.     {
  368.         m_ownThread = new QThread(); // deleted by signals, hence no parent
  369.         this->moveToThread(m_ownThread);
  370.         // connect(this, &CSoundGenerator::startThread, this, &CSoundGenerator::start);
  371.  
  372.         connect(m_ownThread, &QThread::started, this, [ = ]() { this->start(volume, false); });
  373.         connect(this, &CSoundGenerator::stopping, m_ownThread, &QThread::quit);
  374.  
  375.         // in auto delete mode force deleteLater when thread is finished
  376.         if (m_playMode == CNotificationSounds::SingleWithAutomaticDeletion)
  377.         {
  378.             connect(m_ownThread, &QThread::finished, this, &CSoundGenerator::deleteLater);
  379.         }
  380.  
  381.         // start thread and begin processing by calling start via signal startThread
  382.         m_ownThread->start();
  383.     }
  384.  
  385.     void CSoundGenerator::stop(bool destructor)
  386.     {
  387.         // m_audioOutput->setVolume(0); // Bug or feature, killing the applicaions volume?
  388.         if (this->isOpen())
  389.         {
  390.             // 1. isOpen avoids redundant signals
  391.             // 2. OK in destructor, see http://stackoverflow.com/a/14024955/356726
  392.             this->close(); // close IO Device
  393.             m_audioOutput->stop();
  394.             if (m_pushTimer) { m_pushTimer->stop(); }
  395.             emit this->stopped();
  396.         }
  397.         m_position = 0;
  398.         if (destructor) return;
  399.  
  400.         // trigger own termination
  401.         if (m_playMode == CNotificationSounds::SingleWithAutomaticDeletion)
  402.         {
  403.             emit this->stopping();
  404.             if (!m_ownThread) this->deleteLater(); // with own thread, thread signal will call deleteLater
  405.         }
  406.     }
  407.  
  408.     void CSoundGenerator::pushTimerExpired()
  409.     {
  410.         if (m_pushModeIODevice && !m_endReached && m_audioOutput->state() != QAudio::StoppedState)
  411.         {
  412.             int chunks = m_audioOutput->bytesFree() / m_audioOutput->periodSize();
  413.             while (chunks)
  414.             {
  415.                 // periodSize-> Returns the period size in bytes.
  416.                 //! \todo looks wrong: read() will memcpy from m_buffer.constData() to m_buffer.data()
  417.                 const qint64 len = this->read(m_buffer.data(), m_audioOutput->periodSize());
  418.                 if (len >= 0)
  419.                 {
  420.                     m_pushModeIODevice->write(m_buffer.constData(), len);
  421.                 }
  422.                 if (len != m_audioOutput->periodSize())
  423.                 {
  424.                     break; // not a complete period, so buffer is completely read
  425.                 }
  426.                 --chunks;
  427.             }
  428.         }
  429.         else
  430.         {
  431.             if (m_pushTimer)
  432.             {
  433.                 m_pushTimer->stop();
  434.                 m_pushTimer->disconnect(this);
  435.             }
  436.             if (m_playMode == CNotificationSounds::SingleWithAutomaticDeletion)
  437.             {
  438.                 this->stop();
  439.             }
  440.         }
  441.     }
  442.  
  443.     void CSoundGenerator::generateData()
  444.     {
  445.         Q_ASSERT(m_tones.size() > 0);
  446.         const int bytesPerSample = m_audioFormat.sampleSize() / 8;
  447.         const int bytesForAllChannels = m_audioFormat.channelCount() * bytesPerSample;
  448.  
  449.         qint64 totalLength = 0;
  450.         foreach(Tone t, m_tones)
  451.         {
  452.             totalLength += m_audioFormat.sampleRate() * bytesForAllChannels * t.m_durationMs / 1000;
  453.         }
  454.  
  455.         Q_ASSERT(totalLength % bytesForAllChannels == 0);
  456.         Q_UNUSED(bytesForAllChannels) // suppress warning in release builds
  457.  
  458.         m_buffer.resize(totalLength);
  459.         unsigned char *bufferPointer = reinterpret_cast<unsigned char *>(m_buffer.data()); // clazy:exclude=detaching-member
  460.  
  461.         foreach(Tone t, m_tones)
  462.         {
  463.             qint64 bytesPerTone = m_audioFormat.sampleRate() * bytesForAllChannels * t.m_durationMs / 1000;
  464.             qint64 last0AmplitudeSample = bytesPerTone; // last sample when amplitude was 0
  465.             int sampleIndexPerTone = 0;
  466.             while (bytesPerTone)
  467.             {
  468.                 // http://hyperphysics.phy-astr.gsu.edu/hbase/audio/sumdif.html
  469.                 // http://math.stackexchange.com/questions/164369/how-do-you-calculate-the-frequency-perceived-by-humans-of-two-sinusoidal-waves-a
  470.                 const double pseudoTime = double(sampleIndexPerTone % m_audioFormat.sampleRate()) / m_audioFormat.sampleRate();
  471.                 double amplitude = 0.0; // amplitude -1 -> +1 , 0 is silence
  472.                 if (t.m_frequencyHz > 10)
  473.                 {
  474.                     // the combination of two frequencies actually would have 2*amplitude,
  475.                     // but I have to normalize with amplitude -1 -> +1
  476.  
  477.                     amplitude = t.m_secondaryFrequencyHz == 0 ?
  478.                                 qSin(2 * M_PI * t.m_frequencyHz * pseudoTime) :
  479.                                 qSin(M_PI * (t.m_frequencyHz + t.m_secondaryFrequencyHz) * pseudoTime) *
  480.                                 qCos(M_PI * (t.m_frequencyHz - t.m_secondaryFrequencyHz) * pseudoTime);
  481.                 }
  482.  
  483.                 // avoid overflow
  484.                 Q_ASSERT(amplitude <= 1.0 && amplitude >= -1.0);
  485.                 if (amplitude < -1.0)
  486.                 {
  487.                     amplitude = -1.0;
  488.                 }
  489.                 else if (amplitude > 1.0)
  490.                 {
  491.                     amplitude = 1.0;
  492.                 }
  493.                 else if (qAbs(amplitude) < double(1.0 / 65535))
  494.                 {
  495.                     amplitude = 0;
  496.                     last0AmplitudeSample = bytesPerTone;
  497.                 }
  498.  
  499.                 // generate this for all channels, usually 1 channel
  500.                 for (int i = 0; i < m_audioFormat.channelCount(); ++i)
  501.                 {
  502.                     this->writeAmplitudeToBuffer(amplitude, bufferPointer);
  503.                     bufferPointer += bytesPerSample;
  504.                     bytesPerTone -= bytesPerSample;
  505.                 }
  506.                 ++sampleIndexPerTone;
  507.             }
  508.  
  509.             // fixes the range from the last 0 pass through
  510.             if (last0AmplitudeSample > 0)
  511.             {
  512.                 bufferPointer -= last0AmplitudeSample;
  513.                 while (last0AmplitudeSample)
  514.                 {
  515.                     double amplitude = 0.0; // amplitude -1 -> +1 , 0 is silence
  516.  
  517.                     // generate this for all channels, usually 1 channel
  518.                     for (int i = 0; i < m_audioFormat.channelCount(); ++i)
  519.                     {
  520.                         this->writeAmplitudeToBuffer(amplitude, bufferPointer);
  521.                         bufferPointer += bytesPerSample;
  522.                         last0AmplitudeSample -= bytesPerSample;
  523.                     }
  524.                 }
  525.             }
  526.         }
  527.     }
  528.  
  529.     void CSoundGenerator::writeAmplitudeToBuffer(const double amplitude, unsigned char *bufferPointer)
  530.     {
  531.         if (m_audioFormat.sampleSize() == 8 && m_audioFormat.sampleType() == QAudioFormat::UnSignedInt)
  532.         {
  533.             const quint8 value = static_cast<quint8>((1.0 + amplitude) / 2 * 255);
  534.             *reinterpret_cast<quint8 *>(bufferPointer) = value;
  535.         }
  536.         else if (m_audioFormat.sampleSize() == 8 && m_audioFormat.sampleType() == QAudioFormat::SignedInt)
  537.         {
  538.             const qint8 value = static_cast<qint8>(amplitude * 127);
  539.             *reinterpret_cast<qint8 *>(bufferPointer) = value;
  540.         }
  541.         else if (m_audioFormat.sampleSize() == 16 && m_audioFormat.sampleType() == QAudioFormat::UnSignedInt)
  542.         {
  543.             quint16 value = static_cast<quint16>((1.0 + amplitude) / 2 * 65535);
  544.             if (m_audioFormat.byteOrder() == QAudioFormat::LittleEndian)
  545.             {
  546.                 qToLittleEndian<quint16>(value, bufferPointer);
  547.             }
  548.             else
  549.             {
  550.                 qToBigEndian<quint16>(value, bufferPointer);
  551.             }
  552.         }
  553.         else if (m_audioFormat.sampleSize() == 16 && m_audioFormat.sampleType() == QAudioFormat::SignedInt)
  554.         {
  555.             qint16 value = static_cast<qint16>(amplitude * 32767);
  556.             if (m_audioFormat.byteOrder() == QAudioFormat::LittleEndian)
  557.             {
  558.                 qToLittleEndian<qint16>(value, bufferPointer);
  559.             }
  560.             else
  561.             {
  562.                 qToBigEndian<qint16>(value, bufferPointer);
  563.             }
  564.         }
  565.     }
  566.  
  567.     bool CSoundGenerator::saveToWavFile(const QString &fileName) const
  568.     {
  569.         QFile file(fileName);
  570.         bool success = file.open(QIODevice::WriteOnly);
  571.         if (!success) return false;
  572.  
  573.         CombinedHeader header;
  574.         constexpr auto headerLength = sizeof(CombinedHeader);
  575.         memset(&header, 0, headerLength);
  576.  
  577.         // RIFF header
  578.         if (m_audioFormat.byteOrder() == QAudioFormat::LittleEndian)
  579.             memcpy(&header.riff.descriptor.id[0], "RIFF", 4);
  580.         else
  581.             memcpy(&header.riff.descriptor.id[0], "RIFX", 4);
  582.  
  583.         qToLittleEndian<quint32>(quint32(m_buffer.size() + headerLength - 8),
  584.                                  reinterpret_cast<unsigned char *>(&header.riff.descriptor.size));
  585.         memcpy(&header.riff.type[0], "WAVE", 4);
  586.  
  587.         // WAVE header
  588.         memcpy(&header.wave.descriptor.id[0], "fmt ", 4);
  589.         qToLittleEndian<quint32>(quint32(16),
  590.                                  reinterpret_cast<unsigned char *>(&header.wave.descriptor.size));
  591.         qToLittleEndian<quint16>(quint16(1),
  592.                                  reinterpret_cast<unsigned char *>(&header.wave.audioFormat));
  593.         qToLittleEndian<quint16>(quint16(m_audioFormat.channelCount()),
  594.                                  reinterpret_cast<unsigned char *>(&header.wave.numChannels));
  595.         qToLittleEndian<quint32>(quint32(m_audioFormat.sampleRate()),
  596.                                  reinterpret_cast<unsigned char *>(&header.wave.sampleRate));
  597.         qToLittleEndian<quint32>(quint32(m_audioFormat.sampleRate() * m_audioFormat.channelCount() * m_audioFormat.sampleSize() / 8),
  598.                                  reinterpret_cast<unsigned char *>(&header.wave.byteRate));
  599.         qToLittleEndian<quint16>(quint16(m_audioFormat.channelCount() * m_audioFormat.sampleSize() / 8),
  600.                                  reinterpret_cast<unsigned char *>(&header.wave.blockAlign));
  601.         qToLittleEndian<quint16>(quint16(m_audioFormat.sampleSize()),
  602.                                  reinterpret_cast<unsigned char *>(&header.wave.bitsPerSample));
  603.  
  604.         // DATA header
  605.         memcpy(&header.data.descriptor.id[0], "data", 4);
  606.         qToLittleEndian<quint32>(quint32(m_buffer.size()),
  607.                                  reinterpret_cast<unsigned char *>(&header.data.descriptor.size));
  608.  
  609.         success = file.write(reinterpret_cast<const char *>(&header), headerLength) == headerLength;
  610.         success = success && file.write(m_buffer) == m_buffer.size();
  611.         file.close();
  612.         return success;
  613.     }
  614.  
  615.     qint64 CSoundGenerator::calculateDurationMs(const QList<CSoundGenerator::Tone> &tones)
  616.     {
  617.         if (tones.isEmpty()) { return 0; }
  618.         qint64 d = 0;
  619.         foreach(Tone t, tones)
  620.         {
  621.             d += t.m_durationMs;
  622.         }
  623.         return d;
  624.     }
  625.  
  626.     qint64 CSoundGenerator::readData(char *data, qint64 len)
  627.     {
  628.         if (len < 1) { return 0; }
  629.         if (m_endReached)
  630.         {
  631.             this->stop(); // all data read, we can stop output
  632.             return 0;
  633.         }
  634.         if (!this->isOpen()) return 0;
  635.         qint64 total = 0; // toal is used for the overflow when starting new wave again
  636.         while (len - total > 0)
  637.         {
  638.             const qint64 chunkSize = qMin((m_buffer.size() - m_position), (len - total));
  639.             memcpy(data + total, m_buffer.constData() + m_position, chunkSize);
  640.             m_position = (m_position + chunkSize) % m_buffer.size();
  641.             total += chunkSize;
  642.             if (m_position == 0 &&
  643.                     (m_playMode == CNotificationSounds::Single || m_playMode == CNotificationSounds::SingleWithAutomaticDeletion))
  644.             {
  645.                 m_endReached = true;
  646.                 break;
  647.             }
  648.         }
  649.         return total;
  650.     }
  651.  
  652.     qint64 CSoundGenerator::writeData(const char *data, qint64 len)
  653.     {
  654.         Q_UNUSED(data);
  655.         Q_UNUSED(len);
  656.         return 0;
  657.     }
  658.  
  659.     qint64 CSoundGenerator::bytesAvailable() const
  660.     {
  661.         return m_buffer.size() + QIODevice::bytesAvailable();
  662.     }
  663.  
  664.     QAudioFormat CSoundGenerator::defaultAudioFormat()
  665.     {
  666.         QAudioFormat format;
  667.         format.setSampleRate(44100);
  668.         format.setChannelCount(1);
  669.         format.setSampleSize(16); // 8 or 16 works
  670.         format.setCodec("audio/pcm");
  671.         format.setByteOrder(QAudioFormat::LittleEndian);
  672.         format.setSampleType(QAudioFormat::SignedInt);
  673.         return format;
  674.     }
  675.  
  676.     QAudioDeviceInfo CSoundGenerator::findClosestOutputDevice(const CAudioDeviceInfo &audioDevice)
  677.     {
  678.         Q_ASSERT(audioDevice.getType() == CAudioDeviceInfo::OutputDevice);
  679.         const QString lookFor = audioDevice.getName().toLower();
  680.         QAudioDeviceInfo qtDevice = QAudioDeviceInfo::defaultOutputDevice();
  681.         if (lookFor.startsWith("default")) { return qtDevice; }
  682.         int score = 0;
  683.         for (const QAudioDeviceInfo &qd : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
  684.         {
  685.             const QString cn = qd.deviceName().toLower();
  686.             if (lookFor == cn) { return qd;  } // exact match
  687.             if (cn.length() < lookFor.length())
  688.             {
  689.                 if (lookFor.contains(cn) && cn.length() > score)
  690.                 {
  691.                     qtDevice = qd;
  692.                     score = cn.length();
  693.                 }
  694.             }
  695.             else
  696.             {
  697.                 if (cn.contains(lookFor) && lookFor.length() > score)
  698.                 {
  699.                     qtDevice = qd;
  700.                     score = lookFor.length();
  701.                 }
  702.             }
  703.         }
  704.         return qtDevice;
  705.     }
  706.  
  707.     CSoundGenerator *CSoundGenerator::playSignal(int volume, const QList<CSoundGenerator::Tone> &tones, const QAudioDeviceInfo &device)
  708.     {
  709.         CSoundGenerator *generator = new CSoundGenerator(device, CSoundGenerator::defaultAudioFormat(), tones, CNotificationSounds::SingleWithAutomaticDeletion);
  710.         if (tones.isEmpty()) { return generator; } // that was easy
  711.         if (volume < 1) { return generator; }
  712.         if (generator->singleCyleDurationMs() < 10) { return generator; } // unable to hear
  713.  
  714.         // play, and maybe clean up when done
  715.         generator->start(volume);
  716.         return generator;
  717.     }
  718.  
  719.     CSoundGenerator *CSoundGenerator::playSignalInBackground(int volume, const QList<CSoundGenerator::Tone> &tones, const QAudioDeviceInfo &device)
  720.     {
  721.         CSoundGenerator *generator = new CSoundGenerator(device, CSoundGenerator::defaultAudioFormat(), tones, CNotificationSounds::SingleWithAutomaticDeletion);
  722.         if (tones.isEmpty()) { return generator; } // that was easy
  723.         if (volume < 1) { return generator; }
  724.         if (generator->singleCyleDurationMs() < 10) { return generator; } // unable to hear
  725.  
  726.         // play, and maybe clean up when done
  727.         generator->startInOwnThread(volume);
  728.         return generator;
  729.     }
  730.  
  731.     void CSoundGenerator::playSignalRecorded(int volume, const QList<CSoundGenerator::Tone> &tones, const QAudioDeviceInfo &device)
  732.     {
  733.         if (tones.isEmpty()) { return; } // that was easy
  734.         if (volume < 1) { return; }
  735.  
  736.         CSoundGenerator *generator = new CSoundGenerator(device, CSoundGenerator::defaultAudioFormat(), tones, CNotificationSounds::SingleWithAutomaticDeletion);
  737.         if (generator->singleCyleDurationMs() > 10)
  738.         {
  739.             // play, and maybe clean up when done
  740.             QString fileName = QString("blacksound").append(QString::number(QDateTime::currentMSecsSinceEpoch())).append(".wav");
  741.             fileName = QDir::temp().filePath(fileName);
  742.             generator->generateData();
  743.             generator->saveToWavFile(fileName);
  744.             CSoundGenerator::playFile(volume, fileName, true);
  745.         }
  746.         generator->deleteLater();
  747.     }
  748.  
  749.     void CSoundGenerator::playSelcal(int volume, const CSelcal &selcal, const QAudioDeviceInfo &device)
  750.     {
  751.         QList<CSoundGenerator::Tone> tones;
  752.         if (selcal.isValid())
  753.         {
  754.             QList<CFrequency> frequencies = selcal.getFrequencies();
  755.             Q_ASSERT(frequencies.size() == 4);
  756.             const CTime oneSec(1000.0, CTimeUnit::ms());
  757.             Tone t1(frequencies.at(0), frequencies.at(1), oneSec);
  758.             Tone t2(CFrequency(), oneSec / 5.0);
  759.             Tone t3(frequencies.at(2), frequencies.at(3), oneSec);
  760.             tones << t1 << t2 << t3;
  761.         }
  762.         CSoundGenerator::playSignalInBackground(volume, tones, device);
  763.         // CSoundGenerator::playSignalRecorded(volume, tones, device);
  764.     }
  765.  
  766.     void CSoundGenerator::playSelcal(int volume, const CSelcal &selcal, const CAudioDeviceInfo &audioDevice)
  767.     {
  768.         if (CSoundGenerator::s_selcalStarted.msecsTo(QDateTime::currentDateTimeUtc()) < 2500) return; // simple check not to play 2 SELCAL at the same time
  769.         CSoundGenerator::s_selcalStarted = QDateTime::currentDateTimeUtc();
  770.         CSoundGenerator::playSelcal(volume, selcal, CSoundGenerator::findClosestOutputDevice(audioDevice));
  771.     }
  772.  
  773.     void CSoundGenerator::playNotificationSound(int volume, CNotificationSounds::Notification notification)
  774.     {
  775.         QMediaPlayer *mediaPlayer = CSoundGenerator::mediaPlayer();
  776.         if (mediaPlayer->state() == QMediaPlayer::PlayingState) return;
  777.         QMediaPlaylist *playlist = mediaPlayer->playlist();
  778.         if (!playlist || playlist->isEmpty())
  779.         {
  780.             // order here is crucial, needs to be the same as in CSoundGenerator::Notification
  781.             if (!playlist) playlist = new QMediaPlaylist(mediaPlayer);
  782.             bool success = true;
  783.             success = playlist->addMedia(QUrl::fromLocalFile(CDirectoryUtils::soundFilesDirectory() + "/error.wav")) && success;
  784.             success = playlist->addMedia(QUrl::fromLocalFile(CDirectoryUtils::soundFilesDirectory() + "/login.wav")) && success;
  785.             success = playlist->addMedia(QUrl::fromLocalFile(CDirectoryUtils::soundFilesDirectory() + "/logoff.wav")) && success;
  786.             success = playlist->addMedia(QUrl::fromLocalFile(CDirectoryUtils::soundFilesDirectory() + "/privatemessage.wav")) && success;
  787.             success = playlist->addMedia(QUrl::fromLocalFile(CDirectoryUtils::soundFilesDirectory() + "/voiceroomjoined.wav")) && success;
  788.             success = playlist->addMedia(QUrl::fromLocalFile(CDirectoryUtils::soundFilesDirectory() + "/voiceroomleft.wav")) && success;
  789.  
  790.             Q_ASSERT(success);
  791.             playlist->setPlaybackMode(QMediaPlaylist::CurrentItemOnce);
  792.             mediaPlayer->setPlaylist(playlist);
  793.         }
  794.         if (notification == CNotificationSounds::NotificationsLoadSounds) return;
  795.         int index = static_cast<int>(notification);
  796.         playlist->setCurrentIndex(index);
  797.         mediaPlayer->setVolume(volume); // 0-100
  798.         mediaPlayer->play();
  799.     }
  800.  
  801.     void CSoundGenerator::playFile(int volume, const QString &file, bool removeFileAfterPlaying)
  802.     {
  803.         if (!QFile::exists(file)) { return; }
  804.         QMediaPlayer *mediaPlayer = CSoundGenerator::mediaPlayer();
  805.         QMediaResource mediaResource(QUrl(file), "audio");
  806.         QMediaContent media(mediaResource);
  807.         mediaPlayer->setMedia(media);
  808.         mediaPlayer->setVolume(volume); // 0-100
  809.         mediaPlayer->play();
  810.         // I cannot delete the file here, only after it has been played
  811.         if (removeFileAfterPlaying) { new CTimedFileDeleter(file, 1000 * 60, QCoreApplication::instance()); }
  812.     }
  813.  
  814.     void CSoundGenerator::printAllQtSoundDevices(QTextStream &out)
  815.     {
  816.         out << "output:" << endl;
  817.         for (const QAudioDeviceInfo &qd : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
  818.         {
  819.             out << qd.deviceName() << endl;
  820.         }
  821.  
  822.         out << "input:" << endl;
  823.         for (const QAudioDeviceInfo &qd : QAudioDeviceInfo::availableDevices(QAudio::AudioInput))
  824.         {
  825.             out << qd.deviceName() << endl;
  826.         }
  827.     }
  828. } // namespace
Add Comment
Please, Sign In to add comment