diff --git a/Jamulus.pro b/Jamulus.pro index a6d8dbc05c..95a0fd39b8 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -114,8 +114,10 @@ win32 { error("Error: jack.h was not found in the expected location ($${programfilesdir}). Ensure that the right JACK2 variant is installed (32bit vs. 64bit).") } - HEADERS += linux/sound.h - SOURCES += linux/sound.cpp + HEADERS += linux/sound.h \ + linux/jackclient.h + SOURCES += linux/sound.cpp \ + linux/jackclient.cpp DEFINES += WITH_JACK DEFINES += JACK_ON_WINDOWS DEFINES += _STDINT_H # supposed to solve compilation error in systemdeps.h @@ -130,14 +132,12 @@ win32 { } # Important: Keep those ASIO includes local to this build target in # order to avoid poisoning other builds license-wise. - HEADERS += windows/sound.h + HEADERS += windows/sound.h \ + windows/asiodriver.h \ + windows/asiosys.h SOURCES += windows/sound.cpp \ - windows/ASIOSDK2/common/asio.cpp \ - windows/ASIOSDK2/host/asiodrivers.cpp \ - windows/ASIOSDK2/host/pc/asiolist.cpp - INCLUDEPATH += windows/ASIOSDK2/common \ - windows/ASIOSDK2/host \ - windows/ASIOSDK2/host/pc + windows/asiodriver.cpp + INCLUDEPATH += windows/ASIOSDK2/common } } @@ -194,8 +194,10 @@ win32 { error("Error: jack.h was not found at the usual place, maybe jack is not installed") } } - HEADERS += linux/sound.h - SOURCES += linux/sound.cpp + HEADERS += linux/sound.h \ + linux/jackclient.h + SOURCES += linux/sound.cpp \ + linux/jackclient.cpp DEFINES += WITH_JACK DEFINES += JACK_REPLACES_COREAUDIO INCLUDEPATH += /usr/local/include @@ -283,8 +285,11 @@ win32 { } else { message(Jack Audio Interface Enabled.) - HEADERS += linux/sound.h - SOURCES += linux/sound.cpp + HEADERS += linux/sound.h \ + linux/jackclient.h + + SOURCES += linux/sound.cpp \ + linux/jackclient.cpp contains(CONFIG, "raspijamulus") { message(Using Jack Audio in raspijamulus.sh mode.) diff --git a/android/sound.cpp b/android/sound.cpp index 7be98c2ebb..565fae00b3 100644 --- a/android/sound.cpp +++ b/android/sound.cpp @@ -2,7 +2,7 @@ * Copyright (c) 2004-2022 * * Author(s): - * Simon Tomlinson, Volker Fischer + * Simon Tomlinson, Volker Fischer, maintained by pgScorpio * ****************************************************************************** * @@ -25,21 +25,58 @@ #include "sound.h" #include "androiddebug.cpp" +#define RING_FACTOR 20 + /* Implementation *************************************************************/ -const uint8_t CSound::RING_FACTOR = 20; +CSound* CSound::pSound = NULL; -CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* arg ), - void* arg, - const QString& strMIDISetup, - const bool, - const QString& ) : - CSoundBase ( "Oboe", fpNewProcessCallback, arg, strMIDISetup ) +CSound::CSound ( void ( *theProcessCallback ) ( CVector& psData, void* arg ), void* theProcessCallbackArg ) : + CSoundBase ( "Oboe", theProcessCallback, theProcessCallbackArg ) { + setObjectName ( "CSoundThread" ); + #ifdef ANDROIDDEBUG qInstallMessageHandler ( myMessageHandler ); #endif + + soundProperties.bHasAudioDeviceSelection = false; + soundProperties.bHasInputChannelSelection = false; + soundProperties.bHasOutputChannelSelection = false; + soundProperties.bHasInputGainSelection = true; + // Update default property texts according selected properties + soundProperties.setDefaultTexts(); + // Set any property text diversions here... + + pSound = this; +} + +#ifdef OLD_SOUND_COMPATIBILITY +// Backwards compatibility constructor +CSound::CSound ( void ( *theProcessCallback ) ( CVector& psData, void* arg ), + void* theProcessCallbackArg, + QString /* strMIDISetup */, + bool /* bNoAutoJackConnect */, + QString /* strNClientName */ ) : + CSoundBase ( "Oboe", theProcessCallback, theProcessCallbackArg ) +{ + setObjectName ( "CSoundThread" ); + +# ifdef ANDROIDDEBUG + qInstallMessageHandler ( myMessageHandler ); +# endif + + soundProperties.bHasAudioDeviceSelection = false; + soundProperties.bHasInputChannelSelection = false; + soundProperties.bHasOutputChannelSelection = false; + soundProperties.bHasInputGainSelection = true; + // Update default property texts according selected properties + soundProperties.setDefaultTexts(); + // Set any property text diversions here... + + pSound = this; } +#endif void CSound::setupCommonStreamParams ( oboe::AudioStreamBuilder* builder ) { @@ -51,11 +88,9 @@ void CSound::setupCommonStreamParams ( oboe::AudioStreamBuilder* builder ) ->setSharingMode ( oboe::SharingMode::Exclusive ) ->setChannelCount ( oboe::ChannelCount::Stereo ) ->setSampleRate ( SYSTEM_SAMPLE_RATE_HZ ) - ->setFramesPerCallback ( iOboeBufferSizeMono ) + ->setFramesPerCallback ( iDeviceBufferSize ) ->setSampleRateConversionQuality ( oboe::SampleRateConversionQuality::Medium ) ->setPerformanceMode ( oboe::PerformanceMode::LowLatency ); - - return; } void CSound::closeStream ( oboe::ManagedStream& stream ) @@ -79,7 +114,7 @@ void CSound::closeStream ( oboe::ManagedStream& stream ) } } -void CSound::openStreams() +bool CSound::openStreams() { // Setup output stream oboe::AudioStreamBuilder inBuilder, outBuilder; @@ -92,9 +127,9 @@ void CSound::openStreams() if ( result != oboe::Result::OK ) { - return; + return false; } - mPlayStream->setBufferSizeInFrames ( iOboeBufferSizeStereo ); + mPlayStream->setBufferSizeInFrames ( ( iDeviceBufferSize * 2 ) ); warnIfNotLowLatency ( mPlayStream, "PlayStream" ); printStreamDetails ( mPlayStream ); @@ -112,14 +147,23 @@ void CSound::openStreams() if ( result != oboe::Result::OK ) { closeStream ( mPlayStream ); - return; + return false; } - mRecordingStream->setBufferSizeInFrames ( iOboeBufferSizeStereo ); + mRecordingStream->setBufferSizeInFrames ( ( iDeviceBufferSize * 2 ) ); warnIfNotLowLatency ( mRecordingStream, "RecordStream" ); printStreamDetails ( mRecordingStream ); printStreamDetails ( mPlayStream ); + + return true; +} + +void CSound::closeStreams() +{ + // clean up + closeStream ( mRecordingStream ); + closeStream ( mPlayStream ); } void CSound::printStreamDetails ( oboe::ManagedStream& stream ) @@ -148,94 +192,37 @@ void CSound::warnIfNotLowLatency ( oboe::ManagedStream& stream, QString streamNa if ( stream->getPerformanceMode() != oboe::PerformanceMode::LowLatency ) { QString latencyMode = ( stream->getPerformanceMode() == oboe::PerformanceMode::None ? "None" : "Power Saving" ); + Q_UNUSED ( latencyMode ); } Q_UNUSED ( streamName ); } -void CSound::closeStreams() -{ - // clean up - closeStream ( mRecordingStream ); - closeStream ( mPlayStream ); -} - -void CSound::Start() -{ - openStreams(); - - // call base class - CSoundBase::Start(); - - // finally start the streams so the callback begins, start with inputstream first. - mRecordingStream->requestStart(); - mPlayStream->requestStart(); -} - -void CSound::Stop() -{ - closeStreams(); - - // call base class - CSoundBase::Stop(); -} - -int CSound::Init ( const int iNewPrefMonoBufferSize ) +oboe::DataCallbackResult CSound::onAudioInput ( oboe::AudioStream* oboeStream, void* audioData, int32_t numFrames ) { - // store buffer size - iOboeBufferSizeMono = iNewPrefMonoBufferSize; // 512 - - // init base class - CSoundBase::Init ( iOboeBufferSizeMono ); - - // set internal buffer size value and calculate stereo buffer size - iOboeBufferSizeStereo = 2 * iOboeBufferSizeMono; + mStats.in_callback_calls++; - // create memory for intermediate audio buffer - vecsTmpInputAudioSndCrdStereo.Init ( iOboeBufferSizeStereo ); - mOutBuffer.Init ( iOboeBufferSizeStereo * RING_FACTOR ); + int32_t channelCount = oboeStream->getChannelCount(); - return iOboeBufferSizeMono; -} - -// This is the main callback method for when an audio stream is ready to publish data to an output stream -// or has received data on an input stream. As per manual much be very careful not to do anything in this back that -// can cause delays such as sleeping, file processing, allocate memory, etc. -oboe::DataCallbackResult CSound::onAudioReady ( oboe::AudioStream* oboeStream, void* audioData, int32_t numFrames ) -{ - // only process if we are running - if ( !bRun ) + if ( channelCount != PROT_NUM_IN_CHANNELS ) { - return oboe::DataCallbackResult::Continue; - } + qDebug() << "Received " << channelCount << " Channels, expecting " << PROT_NUM_IN_CHANNELS; - if ( mStats.in_callback_calls % 1000 == 0 ) - { - mStats.log(); + return oboe::DataCallbackResult::Stop; } - if ( oboeStream == mRecordingStream.get() && audioData ) + if ( numFrames != (int32_t) iDeviceBufferSize ) { - return onAudioInput ( oboeStream, audioData, numFrames ); - } + qDebug() << "Received " << numFrames << " Frames, expecting " << iDeviceBufferSize; - if ( oboeStream == mPlayStream.get() && audioData ) - { - return onAudioOutput ( oboeStream, audioData, numFrames ); + return oboe::DataCallbackResult::Stop; } - return oboe::DataCallbackResult::Continue; -} - -oboe::DataCallbackResult CSound::onAudioInput ( oboe::AudioStream* oboeStream, void* audioData, int32_t numFrames ) -{ - mStats.in_callback_calls++; - // First things first, we need to discard the input queue a little for 500ms or so if ( mCountCallbacksToDrain > 0 ) { // discard the input buffer - const int32_t numBytes = numFrames * oboeStream->getBytesPerFrame(); + int32_t numBytes = numFrames * oboeStream->getBytesPerFrame(); memset ( audioData, 0 /* value */, numBytes ); @@ -252,42 +239,56 @@ oboe::DataCallbackResult CSound::onAudioInput ( oboe::AudioStream* oboeStream, v int16_t* intData = static_cast ( audioData ); // Copy recording data to internal vector - memcpy ( vecsTmpInputAudioSndCrdStereo.data(), intData, sizeof ( int16_t ) * numFrames * oboeStream->getChannelCount() ); + memcpy ( audioBuffer.data(), intData, sizeof ( int16_t ) * numFrames * channelCount ); - if ( numFrames != iOboeBufferSizeMono ) + if ( inputChannelsGain[0] > 1 ) { - qDebug() << "Received " << numFrames << " expecting " << iOboeBufferSizeMono; + int16_t gain = inputChannelsGain[0]; + int16_t* buffer = audioBuffer.data(); + + for ( int32_t i = 0; i < numFrames; i++ ) + { + *buffer *= gain; + buffer += channelCount; + } } - mStats.frames_in += numFrames; + if ( inputChannelsGain[1] > 1 ) + { + int16_t gain = inputChannelsGain[1]; + int16_t* buffer = audioBuffer.data(); + buffer++; // 2nd channel ! - // Tell parent class that we've put some data ready to send to the server - ProcessCallback ( vecsTmpInputAudioSndCrdStereo ); + for ( int32_t i = 0; i < numFrames; i++ ) + { + *buffer *= gain; + buffer += channelCount; + } + } - // The callback has placed in the vector the samples to play - addOutputData ( oboeStream->getChannelCount() ); + mStats.frames_in += numFrames; - return oboe::DataCallbackResult::Continue; -} + // Tell parent class that we've put some data ready to send to the server + processCallback ( audioBuffer ); -void CSound::addOutputData ( int channel_count ) -{ - QMutexLocker locker ( &MutexAudioProcessCallback ); - const std::size_t bufsize = (std::size_t) iOboeBufferSizeMono * channel_count; + uint32_t bufferSize = ( iDeviceBufferSize * PROT_NUM_IN_CHANNELS ); - // Only copy data if we have data to copy, otherwise fill with silence - if ( vecsTmpInputAudioSndCrdStereo.empty() ) + // pgScorpio: AFAIK the audioBuffer is never resized on a processCallback ! + // We can only copy data if we have enough data to copy, otherwise fill with silence + if ( audioBuffer.size() < bufferSize ) { // prime output stream buffer with silence - vecsTmpInputAudioSndCrdStereo.resize ( bufsize, 0 ); + audioBuffer.resize ( bufferSize, 0 ); } - mOutBuffer.Put ( vecsTmpInputAudioSndCrdStereo, bufsize ); + mOutBuffer.Put ( audioBuffer, bufferSize ); if ( mOutBuffer.isFull() ) { mStats.ring_overrun++; } + + return oboe::DataCallbackResult::Continue; } oboe::DataCallbackResult CSound::onAudioOutput ( oboe::AudioStream* oboeStream, void* audioData, int32_t numFrames ) @@ -295,10 +296,10 @@ oboe::DataCallbackResult CSound::onAudioOutput ( oboe::AudioStream* oboeStream, mStats.frames_out += numFrames; mStats.out_callback_calls++; - QMutexLocker locker ( &MutexAudioProcessCallback ); + QMutexLocker locker ( &mutexAudioProcessCallback ); - const int32_t to_write = numFrames * oboeStream->getChannelCount(); - const int32_t count = std::min ( mOutBuffer.GetAvailData(), to_write ); + std::size_t to_write = (std::size_t) numFrames * oboeStream->getChannelCount(); + std::size_t count = std::min ( (std::size_t) mOutBuffer.GetAvailData(), to_write ); CVector outBuffer ( count ); mOutBuffer.Get ( outBuffer, count ); @@ -307,20 +308,48 @@ oboe::DataCallbackResult CSound::onAudioOutput ( oboe::AudioStream* oboeStream, // According to the format that we've set on initialization, audioData // is an array of int16_t // - int16_t* intData = static_cast ( audioData ); - memcpy ( intData, outBuffer.data(), sizeof ( int16_t ) * count ); + memcpy ( audioData, outBuffer.data(), count * sizeof ( int16_t ) ); if ( to_write > count ) { mStats.frames_filled_out += ( to_write - count ); - memset ( intData + count, 0, sizeof ( int16_t ) * ( to_write - count ) ); + memset ( ( (int16_t*) audioData ) + count, 0, ( to_write - count ) * sizeof ( int16_t ) ); } return oboe::DataCallbackResult::Continue; } -// TODO better handling of stream closing errors +// This is the main callback method for when an audio stream is ready to publish data to an output stream +// or has received data on an input stream. As per manual much be very careful not to do anything in this back that +// can cause delays such as sleeping, file processing, allocate memory, etc. +oboe::DataCallbackResult CSound::onAudioReady ( oboe::AudioStream* oboeStream, void* audioData, int32_t numFrames ) +{ + // only process if we are running + if ( !IsStarted() ) + { + return oboe::DataCallbackResult::Continue; + } + + if ( mStats.in_callback_calls % 1000 == 0 ) + { + mStats.log(); + } + + if ( oboeStream == mRecordingStream.get() && audioData ) + { + return onAudioInput ( oboeStream, audioData, numFrames ); + } + + if ( oboeStream == mPlayStream.get() && audioData ) + { + return onAudioOutput ( oboeStream, audioData, numFrames ); + } + + return oboe::DataCallbackResult::Continue; +} + +// TODO better handling of stream closing errors (use CMsgBoxes::ShowError or strErrorList ??) void CSound::onErrorAfterClose ( oboe::AudioStream* oboeStream, oboe::Result result ) { qDebug() << "CSound::onErrorAfterClose"; @@ -329,7 +358,7 @@ void CSound::onErrorAfterClose ( oboe::AudioStream* oboeStream, oboe::Result res Q_UNUSED ( result ); } -// TODO better handling of stream closing errors +// TODO better handling of stream closing errors (use CMsgBoxes::ShowError or strErrorList ??) void CSound::onErrorBeforeClose ( oboe::AudioStream* oboeStream, oboe::Result result ) { qDebug() << "CSound::onErrorBeforeClose"; @@ -338,6 +367,10 @@ void CSound::onErrorBeforeClose ( oboe::AudioStream* oboeStream, oboe::Result re Q_UNUSED ( result ); } +//============================================================================ +// CSound::Stats +//============================================================================ + void CSound::Stats::reset() { frames_in = 0; @@ -354,3 +387,156 @@ void CSound::Stats::log() const << "frames_in: " << frames_in << ",frames_out: " << frames_out << ",frames_filled_out: " << frames_filled_out << ",in_callback_calls: " << in_callback_calls << ",out_callback_calls: " << out_callback_calls << ",ring_overrun: " << ring_overrun; } + +//============================================================================ +// CSoundbase virtuals Helpers: +//============================================================================ + +bool CSound::setBaseValues() +{ + createDeviceList(); + + clearDeviceInfo(); + + // Set the input channel names: + strInputChannelNames.append ( "input left" ); + strInputChannelNames.append ( "input right" ); + lNumInChan = 2; + + // Set added input channels: ( Always 0 for oboe ) + addAddedInputChannelNames(); + + // Set the output channel names: + strOutputChannelNames.append ( "output left" ); + strOutputChannelNames.append ( "output right" ); + lNumOutChan = 2; + + // Select input and output channels: + resetChannelMapping(); + // TODO: ChannelMapping from inifile! + + // Set the In/Out Latency: + fInOutLatencyMs = 0.0; + // TODO !!! + + return true; +} + +bool CSound::checkCapabilities() +{ + // TODO ??? + // For now anything is OK + return true; +} + +//============================================================================ +// CSoundBase virtuals: +//============================================================================ + +long CSound::createDeviceList ( bool bRescan ) +{ + Q_UNUSED ( bRescan ); + + strDeviceNames.clear(); + strDeviceNames.append ( SystemDriverTechniqueName() ); + + // Just one device, so we force selection ! + iCurrentDevice = 0; + + return lNumDevices = strDeviceNames.size(); +} + +bool CSound::checkDeviceChange ( CSoundBase::tDeviceChangeCheck mode, int iDriverIndex ) // Open device sequence handling.... +{ + if ( ( iDriverIndex < 0 ) || ( iDriverIndex >= lNumDevices ) ) + { + return false; + } + + switch ( mode ) + { + case CSoundBase::tDeviceChangeCheck::CheckOpen: + // We have no other choice than trying to start the device already... + Stop(); + iCurrentDevice = iDriverIndex; + return Start(); + + case CSoundBase::tDeviceChangeCheck::CheckCapabilities: + return checkCapabilities(); + + case CSoundBase::tDeviceChangeCheck::Activate: + return setBaseValues(); + + case CSoundBase::tDeviceChangeCheck::Abort: + Stop(); + setBaseValues(); // Still set Base values, since we still might have changed device ! + return true; + + default: + return false; + } +} + +unsigned int CSound::getDeviceBufferSize ( unsigned int iDesiredBufferSize ) +{ + unsigned int deviceBufferSize = iDesiredBufferSize; + // Round up to a multiple of 2 ! + deviceBufferSize++; + deviceBufferSize >>= 1; + deviceBufferSize <<= 1; + + if ( deviceBufferSize < 64 ) + { + deviceBufferSize = 64; + } + else if ( deviceBufferSize > 512 ) + { + deviceBufferSize = 512; + } + + return deviceBufferSize; +} + +void CSound::closeCurrentDevice() // Closes the current driver and Clears Device Info +{ + Stop(); + clearDeviceInfo(); +} + +bool CSound::start() +{ + if ( !IsStarted() ) + { + if ( openStreams() ) + { + audioBuffer.Init ( iDeviceBufferSize * 2 ); + mOutBuffer.Init ( iDeviceBufferSize * 2 * RING_FACTOR ); + mCountCallbacksToDrain = NUMCALLBACKSTODRAIN; + + if ( mRecordingStream->requestStart() == oboe::Result::OK ) + { + if ( mPlayStream->requestStart() == oboe::Result::OK ) + { + return true; + } + + mRecordingStream->requestStop(); + } + } + + closeStreams(); + return false; + } + + return true; +} + +bool CSound::stop() +{ + if ( IsStarted() ) + { + closeStreams(); + } + + return true; +} diff --git a/android/sound.h b/android/sound.h index d5fe6e8b82..87f02b9ecc 100644 --- a/android/sound.h +++ b/android/sound.h @@ -32,29 +32,14 @@ #include "buffer.h" #include +#define NUMCALLBACKSTODRAIN 10; + /* Classes ********************************************************************/ class CSound : public CSoundBase, public oboe::AudioStreamCallback { Q_OBJECT -public: - static const uint8_t RING_FACTOR; - CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* arg ), - void* arg, - const QString& strMIDISetup, - const bool, - const QString& ); - virtual ~CSound() {} - - virtual int Init ( const int iNewPrefMonoBufferSize ); - virtual void Start(); - virtual void Stop(); - - // Call backs for Oboe - virtual oboe::DataCallbackResult onAudioReady ( oboe::AudioStream* oboeStream, void* audioData, int32_t numFrames ); - virtual void onErrorAfterClose ( oboe::AudioStream* oboeStream, oboe::Result result ); - virtual void onErrorBeforeClose ( oboe::AudioStream* oboeStream, oboe::Result result ); - +private: struct Stats { Stats() { reset(); } @@ -68,31 +53,73 @@ class CSound : public CSoundBase, public oboe::AudioStreamCallback std::size_t ring_overrun; }; + // used to reach a state where the input buffer is + // empty and the garbage in the first 500ms or so is discarded + int32_t mCountCallbacksToDrain = NUMCALLBACKSTODRAIN; + + Stats mStats; + + oboe::ManagedStream mRecordingStream; + oboe::ManagedStream mPlayStream; + protected: - CVector vecsTmpInputAudioSndCrdStereo; - CBuffer mOutBuffer; - int iOboeBufferSizeMono; - int iOboeBufferSizeStereo; + CBuffer mOutBuffer; // Temporary ringbuffer for audio output data + +public: + CSound ( void ( *theProcessCallback ) ( CVector& psData, void* arg ), void* theProcessCallbackArg ); + +#ifdef OLD_SOUND_COMPATIBILITY + // Backwards compatibility constructor + CSound ( void ( *theProcessCallback ) ( CVector& psData, void* pCallbackArg ), + void* theProcessCallbackArg, + QString strMIDISetup, + bool bNoAutoJackConnect, + QString strNClientName ); +#endif + + virtual ~CSound() {} private: void setupCommonStreamParams ( oboe::AudioStreamBuilder* builder ); void printStreamDetails ( oboe::ManagedStream& stream ); - void openStreams(); + bool openStreams(); void closeStreams(); void warnIfNotLowLatency ( oboe::ManagedStream& stream, QString streamName ); void closeStream ( oboe::ManagedStream& stream ); +protected: + // oboe::AudioStreamCallback: oboe::DataCallbackResult onAudioInput ( oboe::AudioStream* oboeStream, void* audioData, int32_t numFrames ); oboe::DataCallbackResult onAudioOutput ( oboe::AudioStream* oboeStream, void* audioData, int32_t numFrames ); - void addOutputData ( int channel_count ); + virtual oboe::DataCallbackResult onAudioReady ( oboe::AudioStream* oboeStream, void* audioData, int32_t numFrames ); + virtual void onErrorAfterClose ( oboe::AudioStream* oboeStream, oboe::Result result ); + virtual void onErrorBeforeClose ( oboe::AudioStream* oboeStream, oboe::Result result ); - oboe::ManagedStream mRecordingStream; - oboe::ManagedStream mPlayStream; +protected: + // CSoundBase virtuals helpers: + bool setBaseValues(); + bool checkCapabilities(); - // used to reach a state where the input buffer is - // empty and the garbage in the first 500ms or so is discarded - static constexpr int32_t kNumCallbacksToDrain = 10; - int32_t mCountCallbacksToDrain = kNumCallbacksToDrain; - Stats mStats; + //============================================================================ + // Virtual interface to CSoundBase: + //============================================================================ +protected: // CSoundBase Mandatory pointer to instance (must be set to 'this' in the CSound constructor) + static CSound* pSound; + +public: // CSoundBase Mandatory functions. (but static functions can't be virtual) + static inline CSoundBase* pInstance() { return pSound; } + static inline const CSoundProperties& GetProperties() { return pSound->getSoundProperties(); } + +protected: + // CSoundBase virtuals: + virtual long createDeviceList ( bool bRescan = false ); // Fills strDeviceList. Returns number of devices found + virtual bool checkDeviceChange ( CSoundBase::tDeviceChangeCheck mode, int iDriverIndex ); // Open device sequence handling.... + virtual unsigned int getDeviceBufferSize ( unsigned int iDesiredBufferSize ); + + virtual void closeCurrentDevice(); // Closes the current driver and Clears Device Info + virtual bool openDeviceSetup() { return false; } + + virtual bool start(); + virtual bool stop(); }; diff --git a/ios/sound.h b/ios/sound.h index 69c3a2af5f..c8b04129ca 100644 --- a/ios/sound.h +++ b/ios/sound.h @@ -33,31 +33,34 @@ class CSound : public CSoundBase Q_OBJECT public: - CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* arg ), - void* arg, - const QString& strMIDISetup, - const bool, - const QString& ); - ~CSound(); + CSound ( void ( *fpProcessCallback ) ( CVector& psData, void* pCallbackArg ), void* pProcessCallBackArg ); + +#ifdef OLD_SOUND_COMPATIBILITY + // Backwards compatibility constructor + CSound ( void ( *fpProcessCallback ) ( CVector& psData, void* pCallbackArg ), + void* pProcessCallBackArg, + QString strMIDISetup, + bool bNoAutoJackConnect, + QString strNClientName ); +#endif - virtual int Init ( const int iNewPrefMonoBufferSize ); - virtual void Start(); - virtual void Stop(); - virtual void processBufferList ( AudioBufferList*, CSound* ); + ~CSound(); AudioUnit audioUnit; // these variables/functions should be protected but cannot since we want // to access them from the callback function - CVector vecsTmpAudioSndCrdStereo; - int iCoreAudioBufferSizeMono; - int iCoreAudioBufferSizeStereo; - bool isInitialized; + // CVector vecsTmpAudioSndCrdStereo; // Replaced by CSoundbase audioBuffer + // int iCoreAudioBufferSizeMono; // Replaced by CSoundbase iDeviceBufferSize + // int iCoreAudioBufferSizeStereo; // Always use (iDeviceBufferSize * 2) + bool isInitialized; protected: - virtual QString LoadAndInitializeDriver ( QString strDriverName, bool ); - void GetAvailableInOutDevices(); - void SwitchDevice ( QString strDriverName ); + // QMutex Mutex; // Replaced by CSoundbase mutexDeviceProperties or mutexAudioProcessCallback?? + + // virtual QString LoadAndInitializeDriver ( QString strDriverName, bool ); // Replaced by checkDeviceChange(...) + // void GetAvailableInOutDevices(); // Replaced by createDeviceList ?? + // void SwitchDevice ( QString strDriverName ); // Replaced by checkDeviceChange(...) ?? AudioBuffer buffer; AudioBufferList bufferList; @@ -69,5 +72,49 @@ class CSound : public CSoundBase UInt32 inNumberFrames, AudioBufferList* ioData ); - QMutex Mutex; + void processBufferList ( AudioBufferList* inInputData, CSound* pSound ); + + bool init(); // init now done by start() + // virtual void Start(); // Should use start() (called by CSoundBase) + // virtual void Stop(); // Should use stop() (called by CSoundBase) + // virtual void processBufferList(AudioBufferList*, CSound*); // + + // new helpers + bool setBaseValues(); + bool checkCapabilities(); + + //======================================================================== + // For clarity always list all virtual functions in separate sections at + // the end ! + // No Defaults! All virtuals should be abstract, so we don't forget to + // implemenent the neccesary virtuals in CSound. + //======================================================================== + // This Section MUST also be included in any CSound class definition ! + +protected: // CSoundBase Mandatory pointer to instance (must be set to 'this' in the CSound constructor) + static CSound* pSound; + +public: // CSoundBase Mandatory functions. (but static functions can't be virtual) + static inline CSoundBase* pInstance() { return pSound; } + static inline const CSoundProperties& GetProperties() { return pSound->getSoundProperties(); } + +protected: + //============================================================================ + // Virtual interface to CSoundBase: + //============================================================================ + // onChannelSelectionChanged() is only needed when selectedInputChannels[]/selectedOutputChannels[] can't be used in the process callback, + // but normally just restarting like: + /* + void onChannelSelectionChanged() { Restart(); } + */ + // should do the trick then + virtual void onChannelSelectionChanged(){}; + + virtual long createDeviceList ( bool bRescan = false ); // Fills strDeviceNames returns lNumDevices + virtual bool checkDeviceChange ( tDeviceChangeCheck mode, int iDriverIndex ); // Performs the different actions for a device change + virtual unsigned int getDeviceBufferSize ( unsigned int iDesiredBufferSize ); // returns the nearest possible buffersize of selected device + virtual void closeCurrentDevice(); // Closes the current driver and Clears Device Info + virtual bool openDeviceSetup() { return false; } + virtual bool start(); // Returns true if started, false if stopped + virtual bool stop(); // Returns true if stopped, false if still (partly) running }; diff --git a/ios/sound.mm b/ios/sound.mm index 1467cb5772..cc431faf26 100644 --- a/ios/sound.mm +++ b/ios/sound.mm @@ -27,13 +27,59 @@ #define kOutputBus 0 #define kInputBus 1 +CSound* CSound::pSound = NULL; + /* Implementation *************************************************************/ -CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* arg ), - void* arg, - const QString& strMIDISetup, - const bool, - const QString& ) : - CSoundBase ( "CoreAudio iOS", fpNewProcessCallback, arg, strMIDISetup ), +CSound::CSound ( void ( *fpProcessCallback ) ( CVector& psData, void* arg ), void* pProcessCallBackArg ) : + CSoundBase ( "CoreAudio iOS", fpProcessCallback, pProcessCallBackArg ), + isInitialized ( false ) +{ + try + { + NSError* audioSessionError = nil; + + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&audioSessionError]; + [[AVAudioSession sharedInstance] requestRecordPermission:^( BOOL granted ) { + if ( granted ) + { + // ok + } + else + { + // TODO - alert user + } + }]; + [[AVAudioSession sharedInstance] setMode:AVAudioSessionModeMeasurement error:&audioSessionError]; + } + catch ( const CGenErr& generr ) + { + QMessageBox::warning ( nullptr, "Sound exception", generr.GetErrorText() ); + } + + buffer.mNumberChannels = 2; + buffer.mData = malloc ( 256 * sizeof ( Float32 ) * buffer.mNumberChannels ); // max size + bufferList.mNumberBuffers = 1; + bufferList.mBuffers[0] = buffer; + + soundProperties.bHasAudioDeviceSelection = true; + soundProperties.bHasInputChannelSelection = false; + soundProperties.bHasOutputChannelSelection = false; + soundProperties.bHasInputGainSelection = false; + // Update default property texts according selected properties + soundProperties.setDefaultTexts(); + // Set any property text diversions here... + + pSound = this; +} + +#ifdef OLD_SOUND_COMPATIBILITY +// Backwards compatibility constructor +CSound::CSound ( void ( *fpProcessCallback ) ( CVector& psData, void* pCallbackArg ), + void* pProcessCallBackArg, + QString /* strMIDISetup */, + bool /* bNoAutoJackConnect */, + QString /* strNClientName */ ) : + CSoundBase ( "CoreAudio iOS", fpProcessCallback, pProcessCallBackArg ), isInitialized ( false ) { try @@ -62,7 +108,18 @@ buffer.mData = malloc ( 256 * sizeof ( Float32 ) * buffer.mNumberChannels ); // max size bufferList.mNumberBuffers = 1; bufferList.mBuffers[0] = buffer; + + soundProperties.bHasAudioDeviceSelection = true; + soundProperties.bHasInputChannelSelection = false; + soundProperties.bHasOutputChannelSelection = false; + soundProperties.bHasInputGainSelection = false; + // Update default property texts according selected properties + soundProperties.setDefaultTexts(); + // Set any property text diversions here... + + pSound = this; } +#endif CSound::~CSound() { free ( buffer.mData ); } @@ -78,11 +135,13 @@ And because Jamulus uses the same buffer to store input and output data (input i UInt32 inNumberFrames, AudioBufferList* ioData ) { + Q_UNUSED ( inRefCon ) + Q_UNUSED ( inBusNumber ) - CSound* pSound = static_cast ( inRefCon ); + // CSound* pSound = static_cast ( inRefCon ); // setting up temp buffer - pSound->buffer.mDataByteSize = pSound->iCoreAudioBufferSizeMono * sizeof ( Float32 ) * pSound->buffer.mNumberChannels; + pSound->buffer.mDataByteSize = pSound->iDeviceBufferSize * sizeof ( Float32 ) * pSound->buffer.mNumberChannels; pSound->bufferList.mBuffers[0] = pSound->buffer; // Obtain recorded samples @@ -97,10 +156,10 @@ And because Jamulus uses the same buffer to store input and output data (input i Float32* pData = (Float32*) ( ioData->mBuffers[0].mData ); // copy output data - for ( int i = 0; i < pSound->iCoreAudioBufferSizeMono; i++ ) + for ( unsigned int i = 0; i < pSound->iDeviceBufferSize; i++ ) { - pData[2 * i] = (Float32) pSound->vecsTmpAudioSndCrdStereo[2 * i] / _MAXSHORT; // left - pData[2 * i + 1] = (Float32) pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] / _MAXSHORT; // right + pData[2 * i] = (Float32) pSound->audioBuffer[2 * i] / _MAXSHORT; // left + pData[2 * i + 1] = (Float32) pSound->audioBuffer[2 * i + 1] / _MAXSHORT; // right } return noErr; @@ -108,32 +167,29 @@ And because Jamulus uses the same buffer to store input and output data (input i void CSound::processBufferList ( AudioBufferList* inInputData, CSound* pSound ) // got stereo input data { - QMutexLocker locker ( &pSound->MutexAudioProcessCallback ); + QMutexLocker locker ( &pSound->mutexAudioProcessCallback ); Float32* pData = static_cast ( inInputData->mBuffers[0].mData ); // copy input data - for ( int i = 0; i < pSound->iCoreAudioBufferSizeMono; i++ ) + for ( unsigned int i = 0; i < pSound->iDeviceBufferSize; i++ ) { // copy left and right channels separately - pSound->vecsTmpAudioSndCrdStereo[2 * i] = (short) ( pData[2 * i] * _MAXSHORT ); // left - pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] = (short) ( pData[2 * i + 1] * _MAXSHORT ); // right + pSound->audioBuffer[2 * i] = (short) ( pData[2 * i] * _MAXSHORT ); // left + pSound->audioBuffer[2 * i + 1] = (short) ( pData[2 * i + 1] * _MAXSHORT ); // right } - pSound->ProcessCallback ( pSound->vecsTmpAudioSndCrdStereo ); + pSound->processCallback ( pSound->audioBuffer ); } -// TODO - CSound::Init is called multiple times at launch to verify device capabilities. -// For iOS though, Init takes long, so should better reduce those inits for iOS (Now: 9 times/launch). -int CSound::Init ( const int iCoreAudioBufferSizeMono ) +bool CSound::init() // pgScorpio: Some of these function results could be stored and checked in checkCapabilities() { + bool ok = true; + try { - this->iCoreAudioBufferSizeMono = iCoreAudioBufferSizeMono; - - // set internal buffer size value and calculate stereo buffer size - iCoreAudioBufferSizeStereo = 2 * iCoreAudioBufferSizeMono; + iDeviceBufferSize = iProtocolBufferSize; // create memory for intermediate audio buffer - vecsTmpAudioSndCrdStereo.Init ( iCoreAudioBufferSizeStereo ); + audioBuffer.Init ( ( iDeviceBufferSize * 2 ) ); AVAudioSession* sessionInstance = [AVAudioSession sharedInstance]; @@ -146,11 +202,13 @@ And because Jamulus uses the same buffer to store input and output data (input i error:&error]; // using values from jamulus settings 64 = 2.67ms/2 - NSTimeInterval bufferDuration = iCoreAudioBufferSizeMono / Float32 ( SYSTEM_SAMPLE_RATE_HZ ); // yeah it's math + NSTimeInterval bufferDuration = iDeviceBufferSize / Float32 ( SYSTEM_SAMPLE_RATE_HZ ); // yeah it's math [sessionInstance setPreferredIOBufferDuration:bufferDuration error:&error]; // set the session's sample rate 48000 - the only supported by Jamulus [sessionInstance setPreferredSampleRate:SYSTEM_SAMPLE_RATE_HZ error:&error]; + // pgScorpio store result for checkCapabilities() ?? + [[AVAudioSession sharedInstance] setActive:YES error:&error]; OSStatus status; @@ -169,15 +227,18 @@ And because Jamulus uses the same buffer to store input and output data (input i // Get audio units status = AudioComponentInstanceNew ( inputComponent, &audioUnit ); checkStatus ( status ); + ok &= ( status == 0 ); // Enable IO for recording UInt32 flag = 1; status = AudioUnitSetProperty ( audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof ( flag ) ); checkStatus ( status ); + ok &= ( status == 0 ); // Enable IO for playback status = AudioUnitSetProperty ( audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, sizeof ( flag ) ); checkStatus ( status ); + ok &= ( status == 0 ); // Describe format AudioStreamBasicDescription audioFormat; @@ -198,6 +259,8 @@ And because Jamulus uses the same buffer to store input and output data (input i &audioFormat, sizeof ( audioFormat ) ); checkStatus ( status ); + ok &= ( status == 0 ); + status = AudioUnitSetProperty ( audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, @@ -205,6 +268,7 @@ And because Jamulus uses the same buffer to store input and output data (input i &audioFormat, sizeof ( audioFormat ) ); checkStatus ( status ); + ok &= ( status == 0 ); // Set callback AURenderCallbackStruct callbackStruct; @@ -217,14 +281,16 @@ And because Jamulus uses the same buffer to store input and output data (input i &callbackStruct, sizeof ( callbackStruct ) ); checkStatus ( status ); + ok &= ( status == 0 ); // Initialise status = AudioUnitInitialize ( audioUnit ); checkStatus ( status ); + ok &= ( status == 0 ); - SwitchDevice ( strCurDevName ); + // SwitchDevice ( strCurDevName ); ???? - if ( !isInitialized ) + if ( !isInitialized && ok ) { [[NSNotificationCenter defaultCenter] addObserverForName:AVAudioSessionRouteChangeNotification @@ -238,46 +304,74 @@ And because Jamulus uses the same buffer to store input and output data (input i emit ReinitRequest ( RS_RELOAD_RESTART_AND_INIT ); // reload the available devices frame } }]; + isInitialized = true; } - - isInitialized = true; } catch ( const CGenErr& generr ) { - QMessageBox::warning ( nullptr, "Sound init exception", generr.GetErrorText() ); + strErrorList.append ( "Sound init exception:" ); + strErrorList.append ( generr.GetErrorText() ); + isInitialized = false; } - return iCoreAudioBufferSizeMono; + return isInitialized; } -void CSound::Start() +bool CSound::start() { - // call base class - CSoundBase::Start(); - try - { - OSStatus err = AudioOutputUnitStart ( audioUnit ); - checkStatus ( err ); - } - catch ( const CGenErr& generr ) + bool ok = false; + + if ( init() ) { - QMessageBox::warning ( nullptr, "Sound start exception", generr.GetErrorText() ); + AVAudioSession* sessionInstance = [AVAudioSession sharedInstance]; + NSError* error = nil; + + // Old SwitchDevice() action: + if ( iCurrentDevice == 1 ) + { + [sessionInstance setPreferredInput:sessionInstance.availableInputs[0] error:&error]; + } + else // system default device + { + unsigned long lastInput = sessionInstance.availableInputs.count - 1; + [sessionInstance setPreferredInput:sessionInstance.availableInputs[lastInput] error:&error]; + } + + try + { + OSStatus err = AudioOutputUnitStart ( audioUnit ); + checkStatus ( err ); + ok = ( err == 0 ); + } + catch ( const CGenErr& generr ) + { + strErrorList.append ( "Sound start exception:" ); + strErrorList.append ( generr.GetErrorText() ); + return false; + } } + + return ok; } -void CSound::Stop() +bool CSound::stop() { + bool ok = false; + try { OSStatus err = AudioOutputUnitStop ( audioUnit ); checkStatus ( err ); + ok = ( err == 0 ); } catch ( const CGenErr& generr ) { - QMessageBox::warning ( nullptr, "Sound stop exception", generr.GetErrorText() ); + strErrorList.append ( "Sound stop exception:" ); + strErrorList.append ( generr.GetErrorText() ); + return false; } - // call base class - CSoundBase::Stop(); + + return ok; } void CSound::checkStatus ( int status ) @@ -288,48 +382,87 @@ And because Jamulus uses the same buffer to store input and output data (input i } } -QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool ) +void CSound::closeCurrentDevice() // Closes the current driver and Clears Device Info { - // secure lNumDevs/strDriverNames access - QMutexLocker locker ( &Mutex ); - - // reload the driver list of available sound devices - GetAvailableInOutDevices(); - - // store the current name of the driver - strCurDevName = strDriverName; - - return ""; + Stop(); + clearDeviceInfo(); } -void CSound::GetAvailableInOutDevices() +long CSound::createDeviceList ( bool ) { + strDeviceNames.clear(); + // always add system default devices for input and output as first entry - lNumDevs = 1; - strDriverNames[0] = "System Default In/Out Devices"; + strDeviceNames.append ( "System Default In/Out Devices" ); AVAudioSession* sessionInstance = [AVAudioSession sharedInstance]; - if ( sessionInstance.availableInputs.count > 1 ) { - lNumDevs = 2; - strDriverNames[1] = "in: Built-in Mic/out: System Default"; + strDeviceNames.append ( "in: Built-in Mic/out: System Default" ); } -} -void CSound::SwitchDevice ( QString strDriverName ) -{ - // find driver index from given driver name - int iDriverIdx = INVALID_INDEX; // initialize with an invalid index + lNumDevices = strDeviceNames.size(); - for ( int i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ ) + // Validate device selection + if ( lNumDevices == 1 ) { - if ( strDriverName.compare ( strDriverNames[i] ) == 0 ) - { - iDriverIdx = i; - break; - } + iCurrentDevice = 0; } + else if ( iCurrentDevice < 0 ) + { + iCurrentDevice = 0; + } + else if ( iCurrentDevice >= lNumDevices ) + { + iCurrentDevice = ( (int) lNumDevices - 1 ); + } + + soundProperties.bHasAudioDeviceSelection = ( lNumDevices > 1 ); + + return lNumDevices; +} + +bool CSound::setBaseValues() +{ + createDeviceList(); + + clearDeviceInfo(); + + // Set the input channel names: + strInputChannelNames.append ( "input left" ); + strInputChannelNames.append ( "input right" ); + lNumInChan = 2; + + // Set added input channels: ( Always 0 for iOS ) + addAddedInputChannelNames(); + + // Set the output channel names: + strOutputChannelNames.append ( "output left" ); + strOutputChannelNames.append ( "output right" ); + lNumOutChan = 2; + + // Select input and output channels: + resetChannelMapping(); + + // Set the In/Out Latency: + fInOutLatencyMs = 0.0; + // TODO !!! + + return true; +} + +bool CSound::checkCapabilities() +{ + // TODO ??? + // For now anything is OK + return true; +} + +/* +void CSound::SwitchDevice ( QString strDriverName ) pgScorpio: This functionality has been moved to start() +{ + // find driver index from given driver name + int iDriverIdx = GetIndexOfDevice(strDriverName); NSError* error = nil; @@ -340,8 +473,50 @@ And because Jamulus uses the same buffer to store input and output data (input i unsigned long lastInput = sessionInstance.availableInputs.count - 1; [sessionInstance setPreferredInput:sessionInstance.availableInputs[lastInput] error:&error]; } - else // built-in mic + else if ( iDriverIdx == 1 ) { [sessionInstance setPreferredInput:sessionInstance.availableInputs[0] error:&error]; } } +*/ + +bool CSound::checkDeviceChange ( CSoundBase::tDeviceChangeCheck mode, int iDriverIndex ) // Open device sequence handling.... +{ + if ( ( iDriverIndex < 0 ) || ( iDriverIndex >= lNumDevices ) ) + { + return false; + } + + switch ( mode ) + { + case CSoundBase::tDeviceChangeCheck::CheckOpen: + // We have no other choice than trying to start the device already... + Stop(); + iCurrentDevice = iDriverIndex; + return Start(); + + case CSoundBase::tDeviceChangeCheck::CheckCapabilities: + return checkCapabilities(); + + case CSoundBase::tDeviceChangeCheck::Activate: + return setBaseValues(); + + case CSoundBase::tDeviceChangeCheck::Abort: + Stop(); + setBaseValues(); // Still set Base values, since we still might have changed device ! + return true; + + default: + return false; + } +} + +unsigned int CSound::getDeviceBufferSize ( unsigned int iDesiredBufferSize ) +{ + if ( iDesiredBufferSize < 64 ) + { + return 64; + } + + return iDesiredBufferSize; +} diff --git a/linux/jackclient.cpp b/linux/jackclient.cpp new file mode 100644 index 0000000000..ba1583ba3e --- /dev/null +++ b/linux/jackclient.cpp @@ -0,0 +1,422 @@ +/******************************************************************************\ + * Copyright (c) 2022 + * + * Author(s): + * Peter Goderie (pgScorpio) + * + ****************************************************************************** + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * +\******************************************************************************/ + +#include "jackclient.h" + +//================================================ +// CJackClientPort class: a basic Jack client port +//================================================ + +bool CJackClientPort::Connect ( const char* connPort ) +{ + Disconnect(); + int res; + // First portname must be the output, second portname must be the input + if ( bIsInput ) + { + // I am input, so connPort must be output + res = jack_connect ( jackClient, connPort, jack_port_name ( jackPort ) /*jack_port_name ( jackPort )*/ ); + } + else + { + // I am output, so connPort must be input + res = jack_connect ( jackClient, jack_port_name ( jackPort ), connPort ); + } + + bool ok = ( res == 0 ) || ( res == EEXIST ); + + if ( ok ) + { + connName = connPort; + } + + return ( connName.size() > 0 ); +} + +bool CJackClientPort::Disconnect() +{ + if ( connName.size() ) + { + int res; + + // First portname must be the output, second portname must be the input + if ( bIsInput ) + { + // I am input, so connName must be output + res = jack_disconnect ( jackClient, connName.toLocal8Bit().data(), jack_port_name ( jackPort ) ); + } + else + { + // I am output, so connName must be input + res = jack_disconnect ( jackClient, jack_port_name ( jackPort ), connName.toLocal8Bit().data() ); + } + + bool ok = ( res == 0 ); + + // on failure assume we where not connected to connName at all + connName.clear(); + + return ok; + } + + return ( connName.size() == 0 ); +} + +const QString& CJackClientPort::GetConnections() +{ + connName.clear(); + + const char** connections = jack_port_get_connections ( jackPort ); + if ( connections ) + { + const char** connlist = connections; + while ( connlist && *connlist ) + { + + if ( connName.size() ) + { + connName += "+"; + connName += *connlist; + } + else + { + connName = *connlist; + } + + connlist++; + } + + jack_free ( connections ); + } + + return connName; +} + +//================================================ +// CJackAudioPort class: +//================================================ + +bool CJackAudioPort::getLatencyRange ( jack_latency_range_t& latrange ) +{ + latrange.min = 0; + latrange.max = 0; + + if ( jackPort == NULL ) + { + return false; + } + + jack_port_get_latency_range ( jackPort, bIsInput ? JackCaptureLatency : JackPlaybackLatency, &latrange ); + + return true; +} + +bool CJackAudioPort::setLatencyRange ( jack_latency_range_t& latrange ) +{ + if ( jackPort == NULL ) + { + return false; + } + + jack_port_set_latency_range ( jackPort, bIsInput ? JackCaptureLatency : JackPlaybackLatency, &latrange ); + + return true; +} + +//================================================ +// CJackMidiPort class: +//================================================ + +//================================================ +// CJackClient class: The Jack client and it's ports +//================================================ + +CJackClient::CJackClient ( QString aClientName ) : + strServerName ( getenv ( "JACK_DEFAULT_SERVER" ) ), + strClientName ( aClientName ), + strJackClientName ( APP_NAME ), + jackClient ( NULL ), + openOptions ( JackUseExactName ), + openStatus ( JackNoSuchClient ), + bIsActive ( false ), + AudioInput(), + AudioOutput(), + MidiInput(), + MidiOutput() +{ + if ( strServerName.isEmpty() ) + { + strServerName = "default"; + } + + qInfo() << qUtf8Printable ( + QString ( "Connecting to JACK \"%1\" instance (use the JACK_DEFAULT_SERVER environment variable to change this)." ).arg ( strServerName ) ); + + if ( !strClientName.isEmpty() ) + { + strJackClientName += " "; + strJackClientName += strClientName; + } +} + +bool CJackClient::Open() +{ + if ( jackClient == NULL ) + { + // we shouldn't have any ports ! + MidiInput.clear(); + MidiOutput.clear(); + AudioInput.clear(); + AudioOutput.clear(); + // and we can't be active ! + bIsActive = false; + + jackClient = jack_client_open ( strJackClientName.toLocal8Bit().data(), openOptions, &openStatus ); + } + + return ( ( jackClient != NULL ) && ( openStatus == 0 ) ); +} + +bool CJackClient::Close() +{ + if ( bIsActive ) + { + Deactivate(); + } + + // close client + if ( jackClient != NULL ) + { + // unregister and delete any ports: + while ( MidiInput.size() ) + { + MidiInput.last().Unregister(); + MidiInput.removeLast(); + } + + while ( MidiOutput.size() ) + { + MidiOutput.last().Unregister(); + MidiOutput.removeLast(); + } + + while ( AudioInput.size() ) + { + AudioInput.last().Unregister(); + AudioInput.removeLast(); + } + + while ( AudioOutput.size() ) + { + AudioOutput.last().Unregister(); + AudioOutput.removeLast(); + } + + bool res = ( jack_client_close ( jackClient ) == 0 ); + + jackClient = NULL; + + return res; + } + else + { + // we shouldn't have any ports ! + MidiInput.clear(); + MidiOutput.clear(); + AudioInput.clear(); + AudioOutput.clear(); + // and we can't be active ! + bIsActive = false; + } + + return true; +} + +bool CJackClient::AddAudioPort ( const QString& portName, bool bInput ) +{ + const char* thePortname = portName.toLocal8Bit().data(); + jack_port_t* newPort = NULL; + + newPort = jack_port_register ( jackClient, thePortname, JACK_DEFAULT_AUDIO_TYPE, bInput ? JackPortIsInput : JackPortIsOutput, 0 ); + + if ( newPort ) + { + const char* jackName = jack_port_name ( newPort ); + CJackAudioPort newAudioPort ( CJackAudioPort ( jackClient, newPort, portName, bInput ) ); + + if ( bInput ) + { + if ( jackName ) + AudioInput.append ( newAudioPort ); + } + else + { + if ( jackName ) + AudioOutput.append ( newAudioPort ); + } + + return true; + } + + return false; +} + +bool CJackClient::AddMidiPort ( const QString& portName, bool bInput ) +{ + jack_port_t* newPort = + jack_port_register ( jackClient, portName.toLocal8Bit().data(), JACK_DEFAULT_MIDI_TYPE, bInput ? JackPortIsInput : JackPortIsOutput, 0 ); + + if ( newPort ) + { + CJackMidiPort newMidiPort ( jackClient, newPort, portName, bInput ); + + if ( bInput ) + { + MidiInput.append ( newMidiPort ); + } + else + { + MidiOutput.append ( newMidiPort ); + } + + return true; + } + + return false; +} + +bool CJackClient::AutoConnect() +{ + // connect the audio ports, note: you cannot do this before + // the client is activated, because we cannot allow + // connections to be made to clients that are not + // running + + if ( ( jackClient == NULL ) || !bIsActive ) + { + return false; + } + + unsigned int numInConnected = 0; + unsigned int numOutConnected = 0; + bool ok = true; + const char** ports; + + // try to connect physical audio input ports + ports = GetJackPorts ( nullptr, nullptr, JackPortIsPhysical | JackPortIsOutput ); + if ( ports != nullptr ) + { + int i = 0; + while ( ports[i] && ( i < AudioInput.size() ) ) + { + if ( AudioInput[i].Connect ( ports[i] ) ) + { + numInConnected++; + } + else + { + ok = false; + } + i++; + } + + jack_free ( ports ); + } + + // try to connect physical audio output ports + ports = GetJackPorts ( nullptr, nullptr, JackPortIsPhysical | JackPortIsInput ); + if ( ports != nullptr ) + { + int i = 0; + while ( ports[i] && ( i < AudioOutput.size() ) ) + { + if ( AudioOutput[i].Connect ( ports[i] ) ) + { + numOutConnected++; + } + else + { + ok = false; + } + i++; + } + + jack_free ( ports ); + } + + return ok && ( numInConnected >= DRV_MIN_IN_CHANNELS ) && ( numOutConnected >= DRV_MIN_OUT_CHANNELS ); +} + +unsigned int CJackClient::GetAudioInputConnections() +{ + unsigned int count = 0; + for ( int i = 0; i < AudioInput.size(); i++ ) + { + if ( !AudioInput[i].GetConnections().isEmpty() ) + { + count++; + } + } + + return count; +} + +unsigned int CJackClient::GetAudioOutputConnections() +{ + unsigned int count = 0; + for ( int i = 0; i < AudioOutput.size(); i++ ) + { + if ( !AudioOutput[i].GetConnections().isEmpty() ) + { + count++; + } + } + + return count; +} + +bool CJackClient::Activate() +{ + if ( !bIsActive ) + { + bIsActive = jackClient ? ( jack_activate ( jackClient ) == 0 ) : false; + } + + return bIsActive; +} + +bool CJackClient::Deactivate() // Also disconnects ports ! +{ + if ( !jackClient ) + { + bIsActive = false; + } + + if ( bIsActive && ( jack_deactivate ( jackClient ) == 0 ) ) + { + bIsActive = false; + } + + return !bIsActive; +} diff --git a/linux/jackclient.h b/linux/jackclient.h new file mode 100644 index 0000000000..e3f4fb0023 --- /dev/null +++ b/linux/jackclient.h @@ -0,0 +1,219 @@ +#pragma once +/******************************************************************************\ + * Copyright (c) 2022 + * + * Author(s): + * Peter Goderie (pgScorpio) + * + ****************************************************************************** + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * +\******************************************************************************/ + +#include +#include +#include "util.h" + +#include +#include + +//============================================================================ +// JackClient class: Jack client API implementation for Jamulus +//============================================================================ + +class CJackClient; + +//================================================ +// CJackClientPort class: a basic Jack client port +//================================================ +class CJackClientPort +{ +protected: + friend class CJackAudioPort; + friend class CJackMidiPort; + + CJackClientPort ( jack_client_t* theJackClient, jack_port_t* theJackPort, const QString& thePortName, bool bInput ) : + jackClient ( theJackClient ), + jackPort ( theJackPort ), + portName ( thePortName ), + connName ( "" ), + bIsInput ( bInput ) + {} + + void Unregister() + { + if ( jackClient && jackPort ) + { + jack_port_unregister ( jackClient, jackPort ); + } + } + +public: +public: + jack_client_t* jackClient; + jack_port_t* jackPort; + QString portName; + QString connName; + bool bIsInput; + +public: + inline bool isOpen() { return ( jackClient && jackPort ); } + inline bool isConnected() { return ( connName.size() > 0 ); } + + inline QString GetJackPortName() { return jackPort ? jack_port_name ( jackPort ) : ""; } + +public: + inline size_t GetBufferSize ( const char* portType ) { return jack_port_type_get_buffer_size ( jackClient, portType ); } + inline void* GetBuffer ( jack_nframes_t nframes ) const { return jack_port_get_buffer ( jackPort, nframes ); } + +public: + bool Connect ( const char* connPort ); + bool Disconnect(); + const QString& GetConnections(); +}; + +//================================================ +// CJackAudioPort class: +//================================================ +class CJackAudioPort : public CJackClientPort +{ +protected: + friend class CJackClient; + friend class QVector; + + CJackAudioPort() : CJackClientPort ( NULL, NULL, "", false ) {} + + CJackAudioPort ( jack_client_t* theJackClient, jack_port_t* theJackPort, const QString& thePortName, bool bInput ) : + CJackClientPort ( theJackClient, theJackPort, thePortName, bInput ) + {} + +public: + bool getLatencyRange ( jack_latency_range_t& latrange ); + bool setLatencyRange ( jack_latency_range_t& latrange ); +}; + +//================================================ +// CJackMidiPort class: +//================================================ + +class CJackMidiPort : public CJackClientPort +{ +protected: + friend class CJackClient; + friend class QVector; + + CJackMidiPort() : CJackClientPort ( NULL, NULL, "", false ) {} + + CJackMidiPort ( jack_client_t* theJackClient, jack_port_t* theJackPort, const QString& thePortName, bool bInput ) : + CJackClientPort ( theJackClient, theJackPort, thePortName, bInput ) + {} + +public: + inline jack_nframes_t GetEventCount() const { return jack_midi_get_event_count ( jackPort ); } + inline bool GetEvent ( jack_midi_event_t* event, void* buffer, uint32_t eventIndex ) const + { + return ( jack_midi_event_get ( event, buffer, eventIndex ) == 0 ); + } +}; + +//================================================ +// CJackClient class: The Jack client and it's ports +//================================================ + +class CJackClient +{ +protected: + friend class CJackClientPort; + friend class CJackAudioPort; + friend class CJackMidiPort; + + QString strServerName; + QString strClientName; + QString strJackClientName; + + jack_client_t* jackClient; + jack_options_t openOptions; + jack_status_t openStatus; + bool bIsActive; + +public: + // Jack ports + QVector AudioInput; + QVector AudioOutput; + QVector MidiInput; + QVector MidiOutput; + +public: + CJackClient ( QString aClientName ); + virtual ~CJackClient() { Close(); } + +public: + bool Open(); + bool Close(); + + inline bool IsOpen() const { return ( jackClient != NULL ); } + + // temporarily for debugging + jack_client_t* GetJackClient() { return jackClient; } + +public: + bool AddAudioPort ( const QString& portName, bool bInput ); + bool AddMidiPort ( const QString& portName, bool bInput ); + +public: + inline bool SetProcessCallBack ( JackProcessCallback cbProcess, void* arg ) + { + return jackClient ? ( jack_set_process_callback ( jackClient, cbProcess, arg ) == 0 ) : false; + } + + inline bool SetShutDownCallBack ( JackShutdownCallback cbShutdown, void* arg ) + { + if ( jackClient ) + { + jack_on_shutdown ( jackClient, cbShutdown, arg ); + return true; + } + + return false; + } + + inline bool SetBuffersizeCallBack ( JackProcessCallback cbBuffersize, void* arg ) + { + return jackClient ? ( jack_set_buffer_size_callback ( jackClient, cbBuffersize, arg ) == 0 ) : false; + } + +public: + inline bool SetBufferSize ( jack_nframes_t nFrames ) const { return jackClient ? ( jack_set_buffer_size ( jackClient, nFrames ) == 0 ) : false; } + inline jack_nframes_t GetBufferSize() const { return jackClient ? jack_get_buffer_size ( jackClient ) : 0; } + inline jack_nframes_t GetSamplerate() const { return jackClient ? jack_get_sample_rate ( jackClient ) : 0; } + +public: + inline const char** GetJackPorts ( const char* port_name_pattern, const char* type_name_pattern, unsigned long flags ) const + { + return jackClient ? jack_get_ports ( jackClient, port_name_pattern, type_name_pattern, flags ) : NULL; + } + +public: + bool AutoConnect(); + unsigned int GetAudioInputConnections(); + unsigned int GetAudioOutputConnections(); + +public: + bool Activate(); + bool Deactivate(); // Also disconnects all ports ! + + inline bool IsActive() const { return bIsActive; } +}; diff --git a/linux/sound.cpp b/linux/sound.cpp index 0d9d354333..05e02d82b8 100644 --- a/linux/sound.cpp +++ b/linux/sound.cpp @@ -2,7 +2,7 @@ * Copyright (c) 2004-2022 * * Author(s): - * Volker Fischer + * Volker Fischer, revised and maintained by Peter Goderie (pgScorpio) * * This code is based on the simple_client example of the Jack audio interface. * @@ -25,317 +25,446 @@ \******************************************************************************/ #include "sound.h" +#include "jackclient.h" +#include "global.h" -#ifdef WITH_JACK -void CSound::OpenJack ( const bool bNoAutoJackConnect, const char* jackClientName ) +//============================================================================ +// CSound: +//============================================================================ + +CSound* CSound::pSound = NULL; + +CSound::CSound ( void ( *theProcessCallback ) ( CVector& psData, void* arg ), void* theProcessCallbackArg ) : + CSoundBase ( "Jack", theProcessCallback, theProcessCallbackArg ), + jackClient ( strClientName ), // ClientName from CSoundBase ! + bJackWasShutDown ( false ), + bAutoConnect ( true ), + iJackNumInputs ( 2 ) { - jack_status_t JackStatus; - const char* serverName; + CCommandlineOptions cCommandlineOptions; + double dValue; + + setObjectName ( "CSoundThread" ); - if ( ( serverName = getenv ( "JACK_DEFAULT_SERVER" ) ) == NULL ) + if ( cCommandlineOptions.GetFlagArgument ( CMDLN_NOJACKCONNECT ) ) { - serverName = "default"; + bAutoConnect = false; } - qInfo() << qUtf8Printable ( - QString ( "Connecting to JACK \"%1\" instance (use the JACK_DEFAULT_SERVER environment variable to change this)." ).arg ( serverName ) ); - // try to become a client of the JACK server - pJackClient = jack_client_open ( jackClientName, JackNullOption, &JackStatus ); - - if ( pJackClient == nullptr ) + if ( cCommandlineOptions.GetNumericArgument ( CMDLN_JACKINPUTS, 2, 16, dValue ) ) { - throw CGenErr ( tr ( "JACK couldn't be started automatically. " - "Please start JACK manually and check for error messages." ) ); + iJackNumInputs = static_cast ( dValue ); } - // tell the JACK server to call "process()" whenever - // there is work to be done - jack_set_process_callback ( pJackClient, process, this ); + soundProperties.bHasSetupDialog = false; - // register a "buffer size changed" callback function - jack_set_buffer_size_callback ( pJackClient, bufferSizeCallback, this ); + pSound = this; +} - // register shutdown callback function - jack_on_shutdown ( pJackClient, shutdownCallback, this ); +//=============================== +// JACK callbacks: +//=============================== - // check sample rate, if not correct, just fire error - if ( jack_get_sample_rate ( pJackClient ) != SYSTEM_SAMPLE_RATE_HZ ) +int CSound::onBufferSwitch ( jack_nframes_t nframes, void* /* arg */ ) +{ + // make sure we are locked during execution + QMutexLocker locker ( &mutexAudioProcessCallback ); + + if ( !IsStarted() ) { - throw CGenErr ( QString ( tr ( "JACK isn't running at a sample rate of %1 Hz. Please use " - "a tool like QjackCtl to set the " - "the JACK sample rate to %1 Hz." ) ) - .arg ( SYSTEM_SAMPLE_RATE_HZ ) ); + return -1; } - // create four ports (two for input, two for output -> stereo) - input_port_left = jack_port_register ( pJackClient, "input left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); + // get output selections + int iSelOutLeft = selectedOutputChannels[0]; + int iSelOutRight = selectedOutputChannels[1]; - input_port_right = jack_port_register ( pJackClient, "input right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); + // get input selections + int iSelInLeft, iSelAddInLeft; + int iSelInRight, iSelAddInRight; + getInputSelAndAddChannels ( selectedInputChannels[0], lNumInChan, lNumAddedInChan, iSelInLeft, iSelAddInLeft ); + getInputSelAndAddChannels ( selectedInputChannels[1], lNumInChan, lNumAddedInChan, iSelInRight, iSelAddInRight ); - output_port_left = jack_port_register ( pJackClient, "output left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); + if ( nframes == static_cast ( iDeviceBufferSize ) ) + { + // get input data pointers + jack_default_audio_sample_t* in_left = (jack_default_audio_sample_t*) jackClient.AudioInput[iSelInLeft].GetBuffer ( nframes ); + jack_default_audio_sample_t* in_right = (jack_default_audio_sample_t*) jackClient.AudioInput[iSelInRight].GetBuffer ( nframes ); + jack_default_audio_sample_t* add_left = + ( iSelAddInLeft >= 0 ) ? (jack_default_audio_sample_t*) jackClient.AudioInput[iSelAddInLeft].GetBuffer ( nframes ) : nullptr; + jack_default_audio_sample_t* add_right = + ( iSelAddInRight >= 0 ) ? (jack_default_audio_sample_t*) jackClient.AudioInput[iSelAddInRight].GetBuffer ( nframes ) : nullptr; - output_port_right = jack_port_register ( pJackClient, "output right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); + // copy input audio data + if ( ( in_left != nullptr ) && ( in_right != nullptr ) ) + { + for ( unsigned int i = 0; i < iDeviceBufferSize; i++ ) + { + unsigned int i_dest = ( i + i ); + audioBuffer[i_dest] = Float2Short ( in_left[i] * _MAXSHORT ); + if ( add_left != nullptr ) + audioBuffer[i_dest] += Float2Short ( add_left[i] * _MAXSHORT ); + + i_dest++; + audioBuffer[i_dest] = Float2Short ( in_right[i] * _MAXSHORT ); + if ( add_right != nullptr ) + audioBuffer[i_dest] += Float2Short ( add_right[i] * _MAXSHORT ); + } + } - if ( ( input_port_left == nullptr ) || ( input_port_right == nullptr ) || ( output_port_left == nullptr ) || ( output_port_right == nullptr ) ) - { - throw CGenErr ( QString ( tr ( "The JACK port registration failed. This is probably an error with JACK. Please stop %1 and JACK. " - "Afterwards check if another program at a sample rate of %2 Hz can connect to JACK." ) ) - .arg ( APP_NAME ) - .arg ( SYSTEM_SAMPLE_RATE_HZ ) ); - } + // call processing callback function + processCallback ( audioBuffer ); - // optional MIDI initialization - if ( iCtrlMIDIChannel != INVALID_MIDI_CH ) - { - input_port_midi = jack_port_register ( pJackClient, "input midi", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); + // get output data pointer + jack_default_audio_sample_t* out_left = (jack_default_audio_sample_t*) jackClient.AudioOutput[iSelOutLeft].GetBuffer ( nframes ); + jack_default_audio_sample_t* out_right = (jack_default_audio_sample_t*) jackClient.AudioOutput[iSelOutRight].GetBuffer ( nframes ); - if ( input_port_midi == nullptr ) + // copy output data + if ( ( out_left != nullptr ) && ( out_right != nullptr ) ) { - throw CGenErr ( QString ( tr ( "The JACK port registration failed. This is probably an error with JACK. Please stop %1 and JACK. " - "Afterwards, check if another MIDI program can connect to JACK." ) ) - .arg ( APP_NAME ) ); + for ( unsigned int i = 0; i < iDeviceBufferSize; i++ ) + { + unsigned int i_src = ( i + i ); + out_left[i] = (jack_default_audio_sample_t) audioBuffer[i_src] / _MAXSHORT; + + out_right[i] = (jack_default_audio_sample_t) audioBuffer[++i_src] / _MAXSHORT; + } } } else { - input_port_midi = nullptr; - } + // get output data pointer + jack_default_audio_sample_t* out_left = (jack_default_audio_sample_t*) jackClient.AudioOutput[iSelOutLeft].GetBuffer ( nframes ); + jack_default_audio_sample_t* out_right = (jack_default_audio_sample_t*) jackClient.AudioOutput[iSelOutRight].GetBuffer ( nframes ); - // tell the JACK server that we are ready to roll - if ( jack_activate ( pJackClient ) ) - { - throw CGenErr ( QString ( tr ( "Can't activate the JACK client. This is probably an error with JACK. Please check the JACK output." ) ) - .arg ( APP_NAME ) ); + // clear output data + if ( ( out_left != nullptr ) && ( out_right != nullptr ) ) + { + memset ( out_left, 0, sizeof ( jack_default_audio_sample_t ) * nframes ); + memset ( out_right, 0, sizeof ( jack_default_audio_sample_t ) * nframes ); + } } - if ( !bNoAutoJackConnect ) + // act on MIDI data if MIDI is enabled + if ( jackClient.MidiInput.size() ) { - // connect the ports, note: you cannot do this before - // the client is activated, because we cannot allow - // connections to be made to clients that are not - // running - const char** ports; - - // try to connect physical input ports - if ( ( ports = jack_get_ports ( pJackClient, nullptr, nullptr, JackPortIsPhysical | JackPortIsOutput ) ) != nullptr ) + void* in_midi = jackClient.MidiInput[0].GetBuffer ( nframes ); + + if ( in_midi != 0 ) { - jack_connect ( pJackClient, ports[0], jack_port_name ( input_port_left ) ); + jack_nframes_t event_count = jackClient.MidiInput[0].GetEventCount(); - // before connecting the second stereo channel, check if the input is not mono - if ( ports[1] ) + for ( jack_nframes_t j = 0; j < event_count; j++ ) { - jack_connect ( pJackClient, ports[1], jack_port_name ( input_port_right ) ); - } + jack_midi_event_t in_event; - jack_free ( ports ); - } + jack_midi_event_get ( &in_event, in_midi, j ); - // try to connect physical output ports - if ( ( ports = jack_get_ports ( pJackClient, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput ) ) != nullptr ) - { - jack_connect ( pJackClient, jack_port_name ( output_port_left ), ports[0] ); + // copy packet and send it to the MIDI parser + // clang-format off +// TODO do not call malloc in real-time callback + // clang-format on + CVector vMIDIPaketBytes ( (int) in_event.size ); - // before connecting the second stereo channel, check if the output is not mono - if ( ports[1] ) - { - jack_connect ( pJackClient, jack_port_name ( output_port_right ), ports[1] ); - } + for ( unsigned int i = 0; i < in_event.size; i++ ) + { + vMIDIPaketBytes[i] = static_cast ( in_event.buffer[i] ); + } - jack_free ( ports ); + parseMIDIMessage ( vMIDIPaketBytes ); + } } + } - // input latency - jack_latency_range_t latrange; - latrange.min = 0; - latrange.max = 0; - - jack_port_get_latency_range ( input_port_left, JackCaptureLatency, &latrange ); - int inLatency = latrange.min; // be optimistic - - // output latency - latrange.min = 0; - latrange.max = 0; + return 0; // zero on success, non-zero on error +} - jack_port_get_latency_range ( output_port_left, JackPlaybackLatency, &latrange ); - int outLatency = latrange.min; // be optimistic +int CSound::onBufferSizeCallback() +{ + QMutexLocker locker ( &mutexAudioProcessCallback ); - // compute latency by using the first input and first output - // ports and using the most optimistic values - fInOutLatencyMs = static_cast ( inLatency + outLatency ) * 1000 / SYSTEM_SAMPLE_RATE_HZ; + if ( isRunning() ) + { + emit reinitRequest ( RS_ONLY_RESTART_AND_INIT ); } + + return 0; // zero on success, non-zero on error } -void CSound::CloseJack() +void CSound::onShutdownCallback() { - // deactivate client - jack_deactivate ( pJackClient ); + QMutexLocker locker ( &mutexAudioProcessCallback ); - // unregister ports - jack_port_unregister ( pJackClient, input_port_left ); - jack_port_unregister ( pJackClient, input_port_right ); - jack_port_unregister ( pJackClient, output_port_left ); - jack_port_unregister ( pJackClient, output_port_right ); + bJackWasShutDown = true; - // close client connection to jack server - jack_client_close ( pJackClient ); + // Trying to restart should generate the error.... + emit reinitRequest ( RS_ONLY_RESTART ); } -void CSound::Start() -{ - // call base class - CSoundBase::Start(); -} +//============================================================================ +// CSoundBase: +//============================================================================ -void CSound::Stop() +void CSound::closeCurrentDevice() { - // call base class - CSoundBase::Stop(); + jackClient.Close(); + clearDeviceInfo(); } -int CSound::Init ( const int /* iNewPrefMonoBufferSize */ ) +unsigned int CSound::getDeviceBufferSize ( unsigned int iDesiredBufferSize ) { + // We can't get the actual buffersize without actualy changing the buffersize, + // so we must first stop and adjust the buffersize + CSoundStopper sound ( *this ); - // clang-format off -// try setting buffer size -// TODO seems not to work! -> no audio after this operation! -// Doesn't this give an infinite loop? The set buffer size function will call our -// registered callback which calls "EmitReinitRequestSignal()". In that function -// this CSound::Init() function is called... -//jack_set_buffer_size ( pJackClient, iNewPrefMonoBufferSize ); - // clang-format on - - // without a Jack server, Jamulus makes no sense to run, throw an error message - if ( bJackWasShutDown ) - { - throw CGenErr ( QString ( tr ( "JACK was shut down. %1 " - "requires JACK to run. Please restart %1 to " - "start JACK again. " ) ) - .arg ( APP_NAME ) ); - } + iProtocolBufferSize = iDesiredBufferSize; + jackClient.SetBufferSize ( iProtocolBufferSize ); - // get actual buffer size - iJACKBufferSizeMono = jack_get_buffer_size ( pJackClient ); + iDeviceBufferSize = jackClient.GetBufferSize(); - // init base class - CSoundBase::Init ( iJACKBufferSizeMono ); + audioBuffer.Init ( iDeviceBufferSize * 2 ); - // set internal buffer size value and calculate stereo buffer size - iJACKBufferSizeStero = 2 * iJACKBufferSizeMono; + return iDeviceBufferSize; +} - // create memory for intermediate audio buffer - vecsTmpAudioSndCrdStereo.Init ( iJACKBufferSizeStero ); +long CSound::createDeviceList ( bool bRescan ) +{ + Q_UNUSED ( bRescan ); - return iJACKBufferSizeMono; + strDeviceNames.clear(); + strDeviceNames.append ( SystemDriverTechniqueName() ); + + // Just one device, so we force selection ! + iCurrentDevice = 0; + + return lNumDevices = 1; } -// JACK callbacks -------------------------------------------------------------- -int CSound::process ( jack_nframes_t nframes, void* arg ) +bool CSound::startJack() { - CSound* pSound = static_cast ( arg ); - int i; + CSoundStopper sound ( *this ); - // make sure we are locked during execution - QMutexLocker locker ( &pSound->MutexAudioProcessCallback ); + // make sure jackClient is closed before starting + jackClient.Close(); - if ( pSound->IsRunning() && ( nframes == static_cast ( pSound->iJACKBufferSizeMono ) ) ) + if ( jackClient.Open() ) { - // get input data pointer - jack_default_audio_sample_t* in_left = (jack_default_audio_sample_t*) jack_port_get_buffer ( pSound->input_port_left, nframes ); - - jack_default_audio_sample_t* in_right = (jack_default_audio_sample_t*) jack_port_get_buffer ( pSound->input_port_right, nframes ); + // since we can open jackClient Jack is not, or no longer, shut down... + bJackWasShutDown = false; + } + else + { + if ( bJackWasShutDown ) + { + strErrorList.append ( tr ( "JACK was shut down. %1 requires JACK to run. Please start JACK again." ).arg ( APP_NAME ) ); + } + else + { + strErrorList.append ( + tr ( "JACK is not running. %1 requires JACK to run. Please start JACK first and check for error messages." ).arg ( APP_NAME ) ); + } + } - // copy input audio data - if ( ( in_left != nullptr ) && ( in_right != nullptr ) ) + if ( jackClient.IsOpen() ) + { + // create ports + if ( iJackNumInputs == 2 ) { - for ( i = 0; i < pSound->iJACKBufferSizeMono; i++ ) + jackClient.AddAudioPort ( "input left", biINPUT ); + jackClient.AddAudioPort ( "input right", biINPUT ); + } + else + { + for ( int i = 1; i <= iJackNumInputs; i++ ) { - pSound->vecsTmpAudioSndCrdStereo[2 * i] = Float2Short ( in_left[i] * _MAXSHORT ); - pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] = Float2Short ( in_right[i] * _MAXSHORT ); + + jackClient.AddAudioPort ( QString ( "input " ) + ::QString::number ( i ), biINPUT ); } } - // call processing callback function - pSound->ProcessCallback ( pSound->vecsTmpAudioSndCrdStereo ); + jackClient.AddAudioPort ( "output left", biOUTPUT ); + jackClient.AddAudioPort ( "output right", biOUTPUT ); - // get output data pointer - jack_default_audio_sample_t* out_left = (jack_default_audio_sample_t*) jack_port_get_buffer ( pSound->output_port_left, nframes ); + if ( iCtrlMIDIChannel != INVALID_MIDI_CH ) + { + jackClient.AddMidiPort ( "input midi", biINPUT ); + } - jack_default_audio_sample_t* out_right = (jack_default_audio_sample_t*) jack_port_get_buffer ( pSound->output_port_right, nframes ); + // set callbacks + jackClient.SetShutDownCallBack ( _ShutdownCallback, this ); + jackClient.SetProcessCallBack ( _BufferSwitch, this ); + jackClient.SetBuffersizeCallBack ( _BufferSizeCallback, this ); - // copy output data - if ( ( out_left != nullptr ) && ( out_right != nullptr ) ) + // prepare buffers + jackClient.SetBufferSize ( iProtocolBufferSize ); + iDeviceBufferSize = jackClient.GetBufferSize(); + audioBuffer.Init ( iDeviceBufferSize * 2 ); + + // activate jack + if ( jackClient.Activate() ) { - for ( i = 0; i < pSound->iJACKBufferSizeMono; i++ ) + // connect inputs and outputs + if ( bAutoConnect ) { - out_left[i] = (jack_default_audio_sample_t) pSound->vecsTmpAudioSndCrdStereo[2 * i] / _MAXSHORT; - - out_right[i] = (jack_default_audio_sample_t) pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] / _MAXSHORT; + // Auto connect the audio ports + jackClient.AutoConnect(); } + + // Don't we need to connect the midi port ???? + if ( iCtrlMIDIChannel != INVALID_MIDI_CH ) {} } } - else - { - // get output data pointer - jack_default_audio_sample_t* out_left = (jack_default_audio_sample_t*) jack_port_get_buffer ( pSound->output_port_left, nframes ); - jack_default_audio_sample_t* out_right = (jack_default_audio_sample_t*) jack_port_get_buffer ( pSound->output_port_right, nframes ); + return jackClient.IsOpen() && jackClient.IsActive(); +} - // clear output data - if ( ( out_left != nullptr ) && ( out_right != nullptr ) ) +bool CSound::stopJack() +{ + Stop(); + + jackClient.Deactivate(); + jackClient.Close(); + + return !jackClient.IsOpen(); +} + +bool CSound::checkCapabilities() +{ + if ( jackClient.IsOpen() ) + { + bool ok = true; + if ( jackClient.GetSamplerate() != SYSTEM_SAMPLE_RATE_HZ ) { - memset ( out_left, 0, sizeof ( jack_default_audio_sample_t ) * nframes ); + strErrorList.append ( tr ( "JACK isn't running at a sample rate of %1 Hz. Please use " + "a tool like QjackCtl to set the " + "the JACK sample rate to %1 Hz." ) + .arg ( SYSTEM_SAMPLE_RATE_HZ ) ); + ok = false; + } - memset ( out_right, 0, sizeof ( jack_default_audio_sample_t ) * nframes ); + int numInputConnections = jackClient.GetAudioInputConnections(); + int numOutputConnections = jackClient.GetAudioOutputConnections(); + if ( bAutoConnect ) // We don't seem to get any connections when not connected via autoconnect ! + { + bool connOk = true; + if ( numInputConnections < DRV_MIN_IN_CHANNELS ) + { + strErrorList.append ( tr ( "You have less than %1 inputs connected." ).arg ( DRV_MIN_IN_CHANNELS ) ); + ok = connOk = false; + } + + if ( numOutputConnections < DRV_MIN_OUT_CHANNELS ) + { + strErrorList.append ( tr ( "You have less than %1 ouputs connected." ).arg ( DRV_MIN_OUT_CHANNELS ) ); + ok = connOk = false; + } + + if ( !connOk ) + { + strErrorList.append ( tr ( "Please check your JACK connections." ) ); + } } + + return ok; } - // akt on MIDI data if MIDI is enabled - if ( pSound->input_port_midi != nullptr ) + strErrorList.append ( tr ( "Not connected to a Jack server." ) ); + + return false; +} + +bool CSound::setBaseValues() +{ + createDeviceList(); + + clearDeviceInfo(); + // Set the input channel names: + for ( lNumInChan = 0; lNumInChan < jackClient.AudioInput.size(); lNumInChan++ ) { - void* in_midi = jack_port_get_buffer ( pSound->input_port_midi, nframes ); + strInputChannelNames.append ( jackClient.AudioInput[lNumInChan].portName ); // pgScoprpio TODO use fixed names ?? + } - if ( in_midi != 0 ) - { - jack_nframes_t event_count = jack_midi_get_event_count ( in_midi ); + // Set any added input channel names: + addAddedInputChannelNames(); - for ( jack_nframes_t j = 0; j < event_count; j++ ) - { - jack_midi_event_t in_event; + // Set the output channel names: + for ( lNumOutChan = 0; lNumOutChan < jackClient.AudioOutput.size(); lNumOutChan++ ) + { + strOutputChannelNames.append ( jackClient.AudioOutput[lNumOutChan].portName ); // pgScoprpio TODO use fixed names ?? + } - jack_midi_event_get ( &in_event, in_midi, j ); + // Select input and output channels: + resetChannelMapping(); + // pgScorpio TODO: reload channel mapping from inifile - // copy packet and send it to the MIDI parser - // clang-format off -// TODO do not call malloc in real-time callback - // clang-format on - CVector vMIDIPaketBytes ( in_event.size ); + // Set the In/Out Latency: + fInOutLatencyMs = 0.0; + if ( ( jackClient.AudioInput.size() ) && ( jackClient.AudioOutput.size() ) ) + { + // compute latency by using the first input and first output + // ports and using the most optimistic values - for ( i = 0; i < static_cast ( in_event.size ); i++ ) - { - vMIDIPaketBytes[i] = static_cast ( in_event.buffer[i] ); - } - pSound->ParseMIDIMessage ( vMIDIPaketBytes ); - } - } + jack_latency_range_t inputLatencyRange; + jack_latency_range_t outputLatencyRange; + + jackClient.AudioInput[0].getLatencyRange ( inputLatencyRange ); + jackClient.AudioOutput[0].getLatencyRange ( outputLatencyRange ); + + // be optimistic + fInOutLatencyMs = static_cast ( inputLatencyRange.min + outputLatencyRange.min ) * 1000 / SYSTEM_SAMPLE_RATE_HZ; } - return 0; // zero on success, non-zero on error + return ( jackClient.IsOpen() && ( lNumInChan >= DRV_MIN_IN_CHANNELS ) && ( lNumOutChan >= DRV_MIN_OUT_CHANNELS ) ); } -int CSound::bufferSizeCallback ( jack_nframes_t, void* arg ) +// TODO: ChannelMapping from inifile! +bool CSound::checkDeviceChange ( CSoundBase::tDeviceChangeCheck mode, int iDriverIndex ) // Open device sequence handling.... { - CSound* pSound = static_cast ( arg ); + // We have just one device ! + if ( iDriverIndex != 0 ) + { + return false; + } - pSound->EmitReinitRequestSignal ( RS_ONLY_RESTART_AND_INIT ); + switch ( mode ) + { + case CSoundBase::tDeviceChangeCheck::CheckOpen: + { + // Now reset jack by closing and re-opening jack + if ( bJackWasShutDown ) + { + stopJack(); + } + if ( !startJack() ) + { + return false; + } + } + return true; - return 0; // zero on success, non-zero on error + case CSoundBase::tDeviceChangeCheck::CheckCapabilities: + return checkCapabilities(); + + case CSoundBase::tDeviceChangeCheck::Activate: + return setBaseValues(); + + case CSoundBase::tDeviceChangeCheck::Abort: + setBaseValues(); // Still try to set Base values, since there is no other device ! + return true; + + default: + return false; + } } -void CSound::shutdownCallback ( void* arg ) +bool CSound::start() { - CSound* pSound = static_cast ( arg ); + if ( !IsStarted() && jackClient.IsActive() ) + { + return true; + } - pSound->bJackWasShutDown = true; - pSound->EmitReinitRequestSignal ( RS_ONLY_RESTART_AND_INIT ); + return IsStarted(); } -#endif // WITH_JACK + +bool CSound::stop() { return true; } diff --git a/linux/sound.h b/linux/sound.h index ddbae2fe74..5b4488575a 100644 --- a/linux/sound.h +++ b/linux/sound.h @@ -2,7 +2,7 @@ * Copyright (c) 2004-2022 * * Author(s): - * Volker Fischer + * Volker Fischer, revised and maintained by Peter Goderie (pgScorpio) * ****************************************************************************** * @@ -38,119 +38,74 @@ #include "soundbase.h" #include "global.h" -#if WITH_JACK -# include -# include -#endif - -/* Definitions ****************************************************************/ -#define NUM_IN_OUT_CHANNELS 2 // always stereo +#include "jackclient.h" -// the number of periods is critical for latency -#define NUM_PERIOD_BLOCKS_IN 2 -#define NUM_PERIOD_BLOCKS_OUT 1 +// Values for bool bInput/bIsInput +#define biINPUT true +#define biOUTPUT false -#define MAX_SND_BUF_IN 200 -#define MAX_SND_BUF_OUT 200 +//============================================================================ +// CSound: +//============================================================================ -/* Classes ********************************************************************/ -#if WITH_JACK class CSound : public CSoundBase { Q_OBJECT -public: - CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* arg ), - void* arg, - const QString& strMIDISetup, - const bool bNoAutoJackConnect, - const QString& strJackClientName ) : - CSoundBase ( "Jack", fpNewProcessCallback, arg, strMIDISetup ), - iJACKBufferSizeMono ( 0 ), - bJackWasShutDown ( false ), - fInOutLatencyMs ( 0.0f ) - { - QString strJackName = QString ( APP_NAME ); - - if ( !strJackClientName.isEmpty() ) - { - strJackName += " " + strJackClientName; - } +protected: // Jack: + CJackClient jackClient; + bool bJackWasShutDown; + bool bAutoConnect; + int iJackNumInputs; - OpenJack ( bNoAutoJackConnect, strJackName.toLocal8Bit().data() ); - } + bool startJack(); + bool stopJack(); + bool checkCapabilities(); + bool setBaseValues(); - virtual ~CSound() { CloseJack(); } +public: + CSound ( void ( *theProcessCallback ) ( CVector& psData, void* arg ), void* theProcessCallbackArg ); - virtual int Init ( const int iNewPrefMonoBufferSize ); - virtual void Start(); - virtual void Stop(); + virtual ~CSound() + { + Stop(); + stopJack(); + } - virtual float GetInOutLatencyMs() { return fInOutLatencyMs; } +protected: + // CSound callbacks: + int onBufferSwitch ( jack_nframes_t nframes, void* arg ); - // these variables should be protected but cannot since we want - // to access them from the callback function - CVector vecsTmpAudioSndCrdStereo; - int iJACKBufferSizeMono; - int iJACKBufferSizeStero; - bool bJackWasShutDown; + int onBufferSizeCallback(); - jack_port_t* input_port_left; - jack_port_t* input_port_right; - jack_port_t* output_port_left; - jack_port_t* output_port_right; - jack_port_t* input_port_midi; + void onShutdownCallback(); protected: - void OpenJack ( const bool bNoAutoJackConnect, const char* jackClientName ); + // static callbacks: + static int _BufferSwitch ( jack_nframes_t nframes, void* arg ) { return pSound->onBufferSwitch ( nframes, arg ); } - void CloseJack(); + static int _BufferSizeCallback ( jack_nframes_t, void* ) { return pSound->onBufferSizeCallback(); } - // callbacks - static int process ( jack_nframes_t nframes, void* arg ); - static int bufferSizeCallback ( jack_nframes_t, void* arg ); - static void shutdownCallback ( void* ); - jack_client_t* pJackClient; + static void _ShutdownCallback ( void* ) { pSound->onShutdownCallback(); } - float fInOutLatencyMs; -}; -#else -// no sound -> dummy class definition -# include "server.h" -class CSound : public CSoundBase -{ - Q_OBJECT + //============================================================================ + // Virtual interface to CSoundBase: + //============================================================================ +protected: // CSoundBase Mandatory pointer to instance (must be set to 'this' in the CSound constructor) + static CSound* pSound; -public: - CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* pParg ), - void* pParg, - const QString& strMIDISetup, - const bool, - const QString& ) : - CSoundBase ( "nosound", fpNewProcessCallback, pParg, strMIDISetup ), - HighPrecisionTimer ( true ) - { - HighPrecisionTimer.Start(); - QObject::connect ( &HighPrecisionTimer, &CHighPrecisionTimer::timeout, this, &CSound::OnTimer ); - } - virtual ~CSound() {} - virtual int Init ( const int iNewPrefMonoBufferSize ) - { - CSoundBase::Init ( iNewPrefMonoBufferSize ); - vecsTemp.Init ( 2 * iNewPrefMonoBufferSize ); - return iNewPrefMonoBufferSize; - } - CHighPrecisionTimer HighPrecisionTimer; - CVector vecsTemp; +public: // CSoundBase Mandatory functions. (but static functions can't be virtual) + static inline CSoundBase* pInstance() { return pSound; } + static inline const CSoundProperties& GetProperties() { return pSound->getSoundProperties(); } -public slots: - void OnTimer() - { - vecsTemp.Reset ( 0 ); - if ( IsRunning() ) - { - ProcessCallback ( vecsTemp ); - } - } +protected: + virtual void onChannelSelectionChanged(){}; // Needed on macOS + + virtual long createDeviceList ( bool bRescan = false ); // Fills strDeviceNames returns lNumDevices + virtual bool checkDeviceChange ( tDeviceChangeCheck mode, int iDriverIndex ); // Performs the different actions for a device change + virtual unsigned int getDeviceBufferSize ( unsigned int iDesiredBufferSize ); // returns the nearest possible buffersize of selected device + virtual void closeCurrentDevice(); // Closes the current driver and Clears Device Info + virtual bool openDeviceSetup() { return false; } + virtual bool start(); // Should call _onStarted() just before return + virtual bool stop(); // Should call _onStopped() just before return }; -#endif // WITH_JACK diff --git a/mac/sound.cpp b/mac/sound.cpp index 1d175e2937..6e0a19f633 100644 --- a/mac/sound.cpp +++ b/mac/sound.cpp @@ -2,7 +2,7 @@ * Copyright (c) 2004-2022 * * Author(s): - * Volker Fischer + * Volker Fischer, revised and maintained by Peter Goderie (pgScorpio) * ****************************************************************************** * @@ -24,15 +24,399 @@ #include "sound.h" -/* Implementation *************************************************************/ -CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* arg ), - void* arg, - const QString& strMIDISetup, - const bool, - const QString& ) : - CSoundBase ( "CoreAudio", fpNewProcessCallback, arg, strMIDISetup ), - midiInPortRef ( static_cast ( NULL ) ) +//============================================================================ +// Helpers: +//============================================================================ + +bool ConvertCFStringToQString ( const CFStringRef stringRef, QString& sOut ) +{ + bool ok = false; + + sOut.clear(); + + // check if the string reference is a valid pointer + if ( stringRef != NULL ) + { + // first check if the string is not empty + if ( CFStringGetLength ( stringRef ) > 0 ) + { + // convert CFString in c-string (quick hack!) and then in QString + char* sC_strPropValue = (char*) malloc ( CFStringGetLength ( stringRef ) * 3 + 1 ); + + if ( CFStringGetCString ( stringRef, sC_strPropValue, CFStringGetLength ( stringRef ) * 3 + 1, kCFStringEncodingUTF8 ) ) + { + sOut = sC_strPropValue; + ok = true; + } + + free ( sC_strPropValue ); + } + } + + return ok; +} + +//============================================================================ +// Audio Hardware Helpers: +//============================================================================ + +bool ahGetDefaultDevice ( bool bInput, AudioDeviceID& deviceId ) +{ + UInt32 iPropertySize = sizeof ( AudioDeviceID ); + AudioObjectPropertyAddress stPropertyAddress; + + stPropertyAddress.mSelector = bInput ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice; + stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal; + stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; + + return ( AudioObjectGetPropertyData ( kAudioObjectSystemObject, &stPropertyAddress, 0, NULL, &iPropertySize, &deviceId ) == noErr ); +} + +bool ahGetAudioDeviceList ( CVector& vAudioDevices ) +{ + UInt32 iPropertySize = 0; + AudioObjectPropertyAddress stPropertyAddress; + + stPropertyAddress.mSelector = kAudioHardwarePropertyDevices; + stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal; + stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; + + if ( AudioObjectGetPropertyDataSize ( kAudioObjectSystemObject, &stPropertyAddress, 0, NULL, &iPropertySize ) == noErr ) + { + vAudioDevices.Init ( iPropertySize / sizeof ( AudioDeviceID ) ); + + if ( AudioObjectGetPropertyData ( kAudioObjectSystemObject, &stPropertyAddress, 0, NULL, &iPropertySize, &vAudioDevices[0] ) == noErr ) + { + return true; + } + } + + vAudioDevices.clear(); + return false; +} + +bool ahGetDeviceStreamIdList ( AudioDeviceID deviceID, bool bInput, CVector& streamIdList ) +{ + UInt32 iPropertySize = 0; + AudioObjectPropertyAddress stPropertyAddress; + + stPropertyAddress.mSelector = kAudioDevicePropertyStreams; + stPropertyAddress.mScope = bInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; + + if ( ( AudioObjectGetPropertyDataSize ( deviceID, &stPropertyAddress, 0, NULL, &iPropertySize ) == noErr ) && ( iPropertySize ) ) + { + streamIdList.Init ( iPropertySize / sizeof ( AudioStreamID ) ); + + if ( AudioObjectGetPropertyData ( deviceID, &stPropertyAddress, 0, NULL, &iPropertySize, &streamIdList[0] ) == noErr ) + { + return ( streamIdList.Size() > 0 ); + } + } + + streamIdList.clear(); + return false; +} + +bool ahGetStreamFormat ( AudioStreamID streamId, AudioStreamBasicDescription& streamFormat ) +{ + UInt32 iPropertySize; + AudioObjectPropertyAddress stPropertyAddress; + + stPropertyAddress.mSelector = kAudioStreamPropertyVirtualFormat; + stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal; + stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; + + if ( AudioObjectGetPropertyDataSize ( streamId, &stPropertyAddress, 0, NULL, &iPropertySize ) == noErr ) + { + if ( iPropertySize == sizeof ( AudioStreamBasicDescription ) ) + { + if ( AudioObjectGetPropertyData ( streamId, &stPropertyAddress, 0, NULL, &iPropertySize, &streamFormat ) == noErr ) + { + return true; + } + } + } + + memset ( &streamFormat, 0, sizeof ( streamFormat ) ); + return false; +} + +bool ahGetStreamLatency ( AudioStreamID streamId, UInt32& iLatencyFrames ) +{ + UInt32 iPropertySize = sizeof ( UInt32 ); + + AudioObjectPropertyAddress stPropertyAddress; + stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal; + stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; + stPropertyAddress.mSelector = kAudioStreamPropertyLatency; + + // number of frames of latency in the AudioStream + OSStatus ret = AudioObjectGetPropertyData ( streamId, &stPropertyAddress, 0, NULL, &iPropertySize, &iLatencyFrames ); + if ( ret != noErr ) + { + iLatencyFrames = 0; + return false; + } + + return true; +} + +bool ahGetDeviceLatency ( AudioDeviceID deviceId, bool bInput, UInt32& iLatency ) +{ + UInt32 deviceLatency = 0; + UInt32 safetyOffset = 0; + UInt32 streamLatency = 0; + + UInt32 size = sizeof ( deviceLatency ); + AudioObjectPropertyAddress stPropertyAddress; + + iLatency = 0; + + stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; + stPropertyAddress.mSelector = kAudioDevicePropertyLatency; + stPropertyAddress.mScope = bInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + if ( AudioObjectGetPropertyData ( deviceId, &stPropertyAddress, 0, nullptr, &size, &deviceLatency ) == noErr ) + { + iLatency += deviceLatency; + } + + size = sizeof ( safetyOffset ); + stPropertyAddress.mSelector = kAudioDevicePropertySafetyOffset; + if ( AudioObjectGetPropertyData ( deviceId, &stPropertyAddress, 0, nullptr, &size, &safetyOffset ) != noErr ) + { + return false; + } + + iLatency += safetyOffset; + + // Query stream latency + stPropertyAddress.mSelector = kAudioDevicePropertyStreams; + if ( AudioObjectGetPropertyDataSize ( deviceId, &stPropertyAddress, 0, nullptr, &size ) != noErr ) + { + return false; + } + + CVector streamList; + streamList.Init ( size / sizeof ( AudioStreamID ) ); + + if ( streamList.Size() == 0 ) + { + return false; + } + + if ( AudioObjectGetPropertyData ( deviceId, &stPropertyAddress, 0, nullptr, &size, &streamList[0] ) != noErr ) + { + return false; + } + + stPropertyAddress.mSelector = kAudioStreamPropertyLatency; + size = sizeof ( streamLatency ); + // We could check all streams for the device, but it only ever seems to return the stream latency on the first stream + if ( AudioObjectGetPropertyData ( streamList[0], &stPropertyAddress, 0, nullptr, &size, &streamLatency ) != noErr ) + { + return false; + } + + iLatency += streamLatency; + + return true; +} + +bool ahGetDeviceBufferList ( AudioDeviceID devID, bool bInput, CVector& vBufferList ) +{ + UInt32 iPropertySize = sizeof ( AudioBufferList ); + AudioObjectPropertyAddress stPropertyAddress; + + stPropertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration; + stPropertyAddress.mScope = bInput ? kAudioObjectPropertyScopeInput : kAudioObjectPropertyScopeOutput; + stPropertyAddress.mElement = 0; + + vBufferList.clear(); + AudioBufferList* pBufferList = NULL; + + if ( AudioObjectGetPropertyDataSize ( devID, &stPropertyAddress, 0, NULL, &iPropertySize ) == noErr ) + { + pBufferList = (AudioBufferList*) malloc ( iPropertySize ); + + if ( pBufferList && ( AudioObjectGetPropertyData ( devID, &stPropertyAddress, 0, NULL, &iPropertySize, pBufferList ) == noErr ) ) + { + const unsigned int numBuffers = pBufferList->mNumberBuffers; + for ( unsigned int i = 0; i < numBuffers; i++ ) + { + vBufferList.Add ( pBufferList->mBuffers[i] ); + } + + free ( pBufferList ); + + return ( vBufferList.Size() > 0 ); + } + } + + if ( pBufferList ) + { + free ( pBufferList ); + } + + return false; +} + +bool ahGetDeviceName ( AudioDeviceID devId, CFStringRef& stringValue ) +{ + UInt32 iPropertySize = sizeof ( CFStringRef ); + AudioObjectPropertyAddress stPropertyAddress; + + stPropertyAddress.mSelector = kAudioObjectPropertyElementName; + stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal; + stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; + + return ( AudioObjectGetPropertyData ( devId, &stPropertyAddress, 0, NULL, &iPropertySize, &stringValue ) == noErr ); +} + +bool ahGetDeviceChannelName ( AudioDeviceID devID, bool bInput, int chIndex, QString& strName ) +{ + bool ok = false; + UInt32 iPropertySize = sizeof ( CFStringRef ); + AudioObjectPropertyAddress stPropertyAddress; + + stPropertyAddress.mSelector = kAudioObjectPropertyElementName; + stPropertyAddress.mScope = bInput ? kAudioObjectPropertyScopeInput : kAudioObjectPropertyScopeOutput; + stPropertyAddress.mElement = chIndex + 1; + + strName.clear(); + CFStringRef cfName = NULL; + + if ( AudioObjectGetPropertyData ( devID, &stPropertyAddress, 0, NULL, &iPropertySize, &cfName ) == noErr ) + { + ok = ConvertCFStringToQString ( cfName, strName ); + CFRelease ( cfName ); + } + + if ( strName.length() == 0 ) + { + strName = ( bInput ) ? "In" : "Out"; + strName += " " + QString::number ( stPropertyAddress.mElement ); + + return false; + } + + return ok; +} + +bool ahGetDeviceSampleRate ( AudioDeviceID devId, Float64& sampleRate ) +{ + UInt32 iPropertySize = sizeof ( Float64 ); + AudioObjectPropertyAddress stPropertyAddress; + + stPropertyAddress.mSelector = kAudioDevicePropertyNominalSampleRate; + stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal; + stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; + + return ( AudioObjectGetPropertyData ( devId, &stPropertyAddress, 0, NULL, &iPropertySize, &sampleRate ) == noErr ); +} + +bool ahSetDeviceSampleRate ( AudioDeviceID devId, Float64 sampleRate ) +{ + UInt32 iPropertySize = sizeof ( Float64 ); + AudioObjectPropertyAddress stPropertyAddress; + + stPropertyAddress.mSelector = kAudioDevicePropertyNominalSampleRate; + stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal; + stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; + + return ( AudioObjectSetPropertyData ( devId, &stPropertyAddress, 0, NULL, iPropertySize, &sampleRate ) == noErr ); +} + +//============================================================================ +// CSound data classes: +//============================================================================ + +bool CSound::cChannelInfo::IsValid ( bool asAdded ) +{ + if ( asAdded ) + { + // Valid Added Input info ? + return ( iListIndex < 0 ) || ( ( DeviceId != (AudioDeviceID) 0 ) && ( DeviceId != (AudioDeviceID) -1 ) && ( iBuffer >= 0 ) && + ( iChanPerFrame > 0 ) && ( iInterlCh >= 0 ) && ( iInterlCh < iChanPerFrame ) ); + } + else + { + // Valid Normal Input or Output info ? + return ( iListIndex >= 0 ) && ( ( DeviceId != (AudioDeviceID) 0 ) && ( DeviceId != (AudioDeviceID) -1 ) && ( iBuffer >= 0 ) && + ( iChanPerFrame > 0 ) && ( iInterlCh >= 0 ) && ( iInterlCh < iChanPerFrame ) ); + } +} + +bool CSound::cChannelInfo::IsValidFor ( AudioDeviceID forDeviceId, bool asInput, bool asAdded ) +{ + if ( asAdded ) + { + // Valid Added Input info ? + return ( iListIndex < 0 ) || ( ( DeviceId == forDeviceId ) && ( isInput == asInput ) && ( iBuffer >= 0 ) && ( iChanPerFrame > 0 ) && + ( iInterlCh >= 0 ) && ( iInterlCh < iChanPerFrame ) ); + } + else + { + // Valid Normal Input or Output info ? + return ( iListIndex >= 0 ) && ( ( DeviceId == forDeviceId ) && ( isInput == asInput ) && ( iBuffer >= 0 ) && ( iChanPerFrame > 0 ) && + ( iInterlCh >= 0 ) && ( iInterlCh < iChanPerFrame ) ); + } +} + +bool CSound::cDeviceInfo::IsValid() { + if ( ( InputDeviceId == 0 ) || ( InputDeviceId == (AudioDeviceID) -1 ) || ( OutputDeviceId == 0 ) || ( OutputDeviceId == (AudioDeviceID) -1 ) || + ( lNumInChan <= 0 ) || ( lNumAddedInChan < 0 ) || ( lNumOutChan <= 0 ) ) + { + return false; + } + + if ( ( input.Size() != ( lNumInChan + lNumAddedInChan ) ) || ( output.Size() != lNumOutChan ) ) + { + return false; + } + + for ( int i = 0; i < lNumInChan; i++ ) + { + if ( !input[i].IsValidFor ( InputDeviceId, biINPUT, false ) ) + { + return false; + } + } + + for ( int i = 0; i < lNumAddedInChan; i++ ) + { + if ( !input[lNumInChan + i].IsValidFor ( InputDeviceId, biINPUT, true ) ) + { + return false; + } + } + + for ( int i = 0; i < output.Size(); i++ ) + { + if ( !output[i].IsValidFor ( OutputDeviceId, biOUTPUT, false ) ) + { + return false; + } + } + + return true; +} + +//============================================================================ +// CSound class: +//============================================================================ + +CSound* CSound::pSound = NULL; + +CSound::cChannelInfo CSound::noChannelInfo; + +CSound::CSound ( void ( *theProcessCallback ) ( CVector& psData, void* arg ), void* theProcessCallbackArg ) : + CSoundBase ( "CoreAudio", theProcessCallback, theProcessCallbackArg ), + midiInPortRef ( 0 ) +{ + setObjectName ( "CSoundThread" ); + // Apple Mailing Lists: Subject: GUI Apps should set kAudioHardwarePropertyRunLoop // in the HAL, From: Jeff Moore, Date: Fri, 6 Dec 2002 // Most GUI applciations have several threads on which they receive @@ -47,27 +431,15 @@ CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* AudioObjectSetPropertyData ( kAudioObjectSystemObject, &property, 0, NULL, sizeof ( CFRunLoopRef ), &theRunLoop ); // initial query for available input/output sound devices in the system - GetAvailableInOutDevices(); - - // init device index as not initialized (invalid) - lCurDev = INVALID_INDEX; - CurrentAudioInputDeviceID = 0; - CurrentAudioOutputDeviceID = 0; - iNumInChan = 0; - iNumInChanPlusAddChan = 0; - iNumOutChan = 0; - iSelInputLeftChannel = 0; - iSelInputRightChannel = 0; - iSelOutputLeftChannel = 0; - iSelOutputRightChannel = 0; + // GetAvailableInOutDevices(); // Optional MIDI initialization -------------------------------------------- if ( iCtrlMIDIChannel != INVALID_MIDI_CH ) { // create client and ports - MIDIClientRef midiClient = static_cast ( NULL ); + MIDIClientRef midiClient = 0; MIDIClientCreate ( CFSTR ( APP_NAME ), NULL, NULL, &midiClient ); - MIDIInputPortCreate ( midiClient, CFSTR ( "Input port" ), callbackMIDI, this, &midiInPortRef ); + MIDIInputPortCreate ( midiClient, CFSTR ( "Input port" ), _onMIDI, this, &midiInPortRef ); // open connections from all sources const int iNMIDISources = MIDIGetNumberOfSources(); @@ -78,91 +450,67 @@ CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* MIDIPortConnectSource ( midiInPortRef, src, NULL ); } } -} - -void CSound::GetAvailableInOutDevices() -{ - UInt32 iPropertySize = 0; - AudioObjectPropertyAddress stPropertyAddress; - - stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal; - stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; - - // first get property size of devices array and allocate memory - stPropertyAddress.mSelector = kAudioHardwarePropertyDevices; - - AudioObjectGetPropertyDataSize ( kAudioObjectSystemObject, &stPropertyAddress, 0, NULL, &iPropertySize ); - CVector vAudioDevices ( iPropertySize ); + // set my properties in CSoundbase: + soundProperties.bHasSetupDialog = false; - // now actually query all devices present in the system - AudioObjectGetPropertyData ( kAudioObjectSystemObject, &stPropertyAddress, 0, NULL, &iPropertySize, &vAudioDevices[0] ); - - // calculate device count based on size of returned data array - const UInt32 iDeviceCount = iPropertySize / sizeof ( AudioDeviceID ); + pSound = this; +} - // always add system default devices for input and output as first entry - lNumDevs = 0; - strDriverNames[lNumDevs] = "System Default In/Out Devices"; +#ifdef OLD_SOUND_COMPATIBILITY +CSound::CSound ( void ( *theProcessCallback ) ( CVector& psData, void* arg ), + void* theProcessCallbackArg, + QString /* strMIDISetup */, + bool /* bNoAutoJackConnect */, + QString /* strNClientName */ ) : + CSoundBase ( "CoreAudio", theProcessCallback, theProcessCallbackArg ), + midiInPortRef ( 0 ) +{ + setObjectName ( "CSoundThread" ); - iPropertySize = sizeof ( AudioDeviceID ); - stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; + // Apple Mailing Lists: Subject: GUI Apps should set kAudioHardwarePropertyRunLoop + // in the HAL, From: Jeff Moore, Date: Fri, 6 Dec 2002 + // Most GUI applciations have several threads on which they receive + // notifications already, so the having the HAL's thread around is wasteful. + // Here is what you should do: On the thread you want the HAL to use for + // notifications (for most apps, this will be the main thread), add the + // following lines of code: + // tell the HAL to use the current thread as it's run loop + CFRunLoopRef theRunLoop = CFRunLoopGetCurrent(); + AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - if ( AudioObjectGetPropertyData ( kAudioObjectSystemObject, &stPropertyAddress, 0, NULL, &iPropertySize, &audioInputDevice[lNumDevs] ) ) - { - throw CGenErr ( tr ( "No sound card is available in your system. " - "CoreAudio input AudioHardwareGetProperty call failed." ) ); - } + AudioObjectSetPropertyData ( kAudioObjectSystemObject, &property, 0, NULL, sizeof ( CFRunLoopRef ), &theRunLoop ); - iPropertySize = sizeof ( AudioDeviceID ); - stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + // initial query for available input/output sound devices in the system + // GetAvailableInOutDevices(); - if ( AudioObjectGetPropertyData ( kAudioObjectSystemObject, &stPropertyAddress, 0, NULL, &iPropertySize, &audioOutputDevice[lNumDevs] ) ) + // Optional MIDI initialization -------------------------------------------- + if ( iCtrlMIDIChannel != INVALID_MIDI_CH ) { - throw CGenErr ( tr ( "No sound card is available in the system. " - "CoreAudio output AudioHardwareGetProperty call failed." ) ); - } + // create client and ports + MIDIClientRef midiClient = 0; + MIDIClientCreate ( CFSTR ( APP_NAME ), NULL, NULL, &midiClient ); + MIDIInputPortCreate ( midiClient, CFSTR ( "Input port" ), _onMIDI, this, &midiInPortRef ); - lNumDevs++; // next device + // open connections from all sources + const int iNMIDISources = MIDIGetNumberOfSources(); - // add detected devices - // - // we add combined entries for input and output for each device so that we - // do not need two combo boxes in the GUI for input and output (therefore - // all possible combinations are required which can be a large number) - for ( UInt32 i = 0; i < iDeviceCount; i++ ) - { - for ( UInt32 j = 0; j < iDeviceCount; j++ ) + for ( int i = 0; i < iNMIDISources; i++ ) { - // get device infos for both current devices - QString strDeviceName_i; - QString strDeviceName_j; - bool bIsInput_i; - bool bIsInput_j; - bool bIsOutput_i; - bool bIsOutput_j; - - GetAudioDeviceInfos ( vAudioDevices[i], strDeviceName_i, bIsInput_i, bIsOutput_i ); - - GetAudioDeviceInfos ( vAudioDevices[j], strDeviceName_j, bIsInput_j, bIsOutput_j ); - - // check if i device is input and j device is output and that we are - // in range - if ( bIsInput_i && bIsOutput_j && ( lNumDevs < MAX_NUMBER_SOUND_CARDS ) ) - { - strDriverNames[lNumDevs] = "in: " + strDeviceName_i + "/out: " + strDeviceName_j; - - // store audio device IDs - audioInputDevice[lNumDevs] = vAudioDevices[i]; - audioOutputDevice[lNumDevs] = vAudioDevices[j]; - - lNumDevs++; // next device - } + MIDIEndpointRef src = MIDIGetSource ( i ); + MIDIPortConnectSource ( midiInPortRef, src, NULL ); } } + + // set my properties in CSoundbase: + soundProperties.bHasSetupDialog = false; + + pSound = this; } -void CSound::GetAudioDeviceInfos ( const AudioDeviceID DeviceID, QString& strDeviceName, bool& bIsInput, bool& bIsOutput ) +#endif + +void getAudioDeviceInfos ( int index, const AudioDeviceID DeviceID, QString& strDeviceName, bool& bIsInput, bool& bIsOutput ) { UInt32 iPropertySize; AudioObjectPropertyAddress stPropertyAddress; @@ -191,6 +539,7 @@ void CSound::GetAudioDeviceInfos ( const AudioDeviceID DeviceID, QString& strDev bIsOutput = ( iPropertySize > 0 ); // check if any output streams are available + strDeviceName.clear(); // get property name CFStringRef sPropertyStringValue = NULL; @@ -198,132 +547,257 @@ void CSound::GetAudioDeviceInfos ( const AudioDeviceID DeviceID, QString& strDev stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal; iPropertySize = sizeof ( CFStringRef ); - AudioObjectGetPropertyData ( DeviceID, &stPropertyAddress, 0, NULL, &iPropertySize, &sPropertyStringValue ); + if ( AudioObjectGetPropertyData ( DeviceID, &stPropertyAddress, 0, NULL, &iPropertySize, &sPropertyStringValue ) == noErr ) + { + // convert string + ConvertCFStringToQString ( sPropertyStringValue, strDeviceName ); + + CFRelease ( sPropertyStringValue ); + } - // convert string - if ( !ConvertCFStringToQString ( sPropertyStringValue, strDeviceName ) ) + if ( strDeviceName.length() == 0 ) { // use a default name in case the conversion did not succeed - strDeviceName = "UNKNOWN"; + strDeviceName = "Device" + QString::number ( index ); } } -int CSound::CountChannels ( AudioDeviceID devID, bool isInput ) +bool CSound::setBufferSize ( AudioDeviceID& audioInDeviceID, + AudioDeviceID& audioOutDeviceID, + unsigned int iPrefBufferSize, + unsigned int& iActualBufferSize ) { - OSStatus err; - UInt32 propSize; - int result = 0; + bool ok = true; + + UInt32 iSizeBufValue; + UInt32 iRequestedBuffersize = iPrefBufferSize; + UInt32 iActualInBufferSize = 0; + UInt32 iActualOutBufferSize = 0; + AudioObjectPropertyAddress stPropertyAddress; + + stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; + stPropertyAddress.mSelector = kAudioDevicePropertyBufferFrameSize; - if ( isInput ) + if ( audioInDeviceID ) { - vecNumInBufChan.Init ( 0 ); + iSizeBufValue = sizeof ( UInt32 ); + stPropertyAddress.mScope = kAudioObjectPropertyScopeInput; + + ok &= ( AudioObjectSetPropertyData ( audioInDeviceID, &stPropertyAddress, 0, NULL, iSizeBufValue, &iRequestedBuffersize ) == noErr ); + ok &= ( AudioObjectGetPropertyData ( audioInDeviceID, &stPropertyAddress, 0, NULL, &iSizeBufValue, &iActualInBufferSize ) == noErr ); + ok &= ( iSizeBufValue == sizeof ( UInt32 ) ); } - else + + if ( audioOutDeviceID ) { - vecNumOutBufChan.Init ( 0 ); + iSizeBufValue = sizeof ( UInt32 ); + stPropertyAddress.mScope = kAudioObjectPropertyScopeOutput; + + ok &= ( AudioObjectSetPropertyData ( audioOutDeviceID, &stPropertyAddress, 0, NULL, iSizeBufValue, &iRequestedBuffersize ) == noErr ); + ok &= ( AudioObjectGetPropertyData ( audioOutDeviceID, &stPropertyAddress, 0, NULL, &iSizeBufValue, &iActualOutBufferSize ) == noErr ); + ok &= ( iSizeBufValue == sizeof ( UInt32 ) ); } - // it seems we have multiple buffers where each buffer has only one channel, - // in that case we assume that each input channel has its own buffer - AudioObjectPropertyScope theScope = isInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + if ( audioInDeviceID && audioOutDeviceID ) + { + // Return lowest size: (they still might be zero!) + iActualBufferSize = ( iActualInBufferSize < iActualOutBufferSize ) ? iActualInBufferSize : iActualOutBufferSize; - AudioObjectPropertyAddress theAddress = { kAudioDevicePropertyStreamConfiguration, theScope, 0 }; + return ok && ( iActualBufferSize != 0 ) && ( iActualInBufferSize == iActualOutBufferSize ); + } + else if ( audioInDeviceID ) + { + iActualBufferSize = iActualInBufferSize; + return ok && ( iActualBufferSize != 0 ); + } + else if ( audioOutDeviceID ) + { + iActualBufferSize = iActualOutBufferSize; + return ok && ( iActualBufferSize != 0 ); + } - AudioObjectGetPropertyDataSize ( devID, &theAddress, 0, NULL, &propSize ); + iActualBufferSize = 0; + return false; +} - AudioBufferList* buflist = (AudioBufferList*) malloc ( propSize ); +//============================================================================ +// Callbacks: +//============================================================================ - err = AudioObjectGetPropertyData ( devID, &theAddress, 0, NULL, &propSize, buflist ); +OSStatus CSound::onDeviceNotification ( AudioDeviceID, UInt32, const AudioObjectPropertyAddress* inAddresses ) +{ + if ( ( inAddresses->mSelector == kAudioDevicePropertyDeviceHasChanged ) || ( inAddresses->mSelector == kAudioDevicePropertyDeviceIsAlive ) || + ( inAddresses->mSelector == kAudioHardwarePropertyDefaultOutputDevice ) || + ( inAddresses->mSelector == kAudioHardwarePropertyDefaultInputDevice ) ) + { + if ( ( inAddresses->mSelector == kAudioDevicePropertyDeviceHasChanged ) || ( inAddresses->mSelector == kAudioDevicePropertyDeviceIsAlive ) ) + { + // if any property of the device has changed, do a full reload + emit ReinitRequest ( tSndCrdResetType::RS_RELOAD_RESTART_AND_INIT ); + } + else + { + // for any other change in audio devices, just initiate a restart which + // triggers an update of the sound device selection combo box + emit ReinitRequest ( RS_ONLY_RESTART ); + } + } + + return noErr; +} + +OSStatus CSound::onBufferSwitch ( AudioDeviceID deviceID, + const AudioTimeStamp*, + const AudioBufferList* inInputData, + const AudioTimeStamp*, + AudioBufferList* outOutputData, + const AudioTimeStamp* ) +{ + // both, the input and output device use the same callback function + QMutexLocker locker ( &mutexAudioProcessCallback ); + static bool awaitIn = true; - if ( !err ) + if ( IsStarted() ) { - for ( UInt32 i = 0; i < buflist->mNumberBuffers; ++i ) + if ( awaitIn && inInputData && ( deviceID == selectedDevice.InputDeviceId ) ) { - // The correct value mNumberChannels for an AudioBuffer can be derived from the mChannelsPerFrame - // and the interleaved flag. For non interleaved formats, mNumberChannels is always 1. - // For interleaved formats, mNumberChannels is equal to mChannelsPerFrame. - result += buflist->mBuffers[i].mNumberChannels; + int iSelBufferLeft = selectedDevice.InLeft.iBuffer; + int iSelChanPerFrameLeft = selectedDevice.InLeft.iChanPerFrame; + int iSelInterlChLeft = selectedDevice.InLeft.iInterlCh; + + int iSelBufferRight = selectedDevice.InRight.iBuffer; + int iSelChanPerFrameRight = selectedDevice.InRight.iChanPerFrame; + int iSelInterlChRight = selectedDevice.InRight.iInterlCh; + + int iAddBufferLeft = selectedDevice.AddInLeft.iBuffer; + int iAddChanPerFrameLeft = selectedDevice.AddInLeft.iChanPerFrame; + int iAddInterlChLeft = selectedDevice.AddInLeft.iInterlCh; + + int iAddBufferRight = selectedDevice.AddInRight.iBuffer; + int iAddChanPerFrameRight = selectedDevice.AddInRight.iChanPerFrame; + int iAddInterlChRight = selectedDevice.AddInRight.iInterlCh; + + Float32* pLeftData = static_cast ( inInputData->mBuffers[iSelBufferLeft].mData ); + Float32* pRightData = static_cast ( inInputData->mBuffers[iSelBufferRight].mData ); - if ( isInput ) + // copy input data + for ( unsigned int i = 0; i < iDeviceBufferSize; i++ ) { - vecNumInBufChan.Add ( buflist->mBuffers[i].mNumberChannels ); + // copy left and right channels separately + audioBuffer[2 * i] = static_cast ( pLeftData[iSelChanPerFrameLeft * i + iSelInterlChLeft] * _MAXSHORT ); + audioBuffer[2 * i + 1] = static_cast ( pRightData[iSelChanPerFrameRight * i + iSelInterlChRight] * _MAXSHORT ); } - else + + // add an additional optional channel + if ( iAddBufferLeft >= 0 ) { - vecNumOutBufChan.Add ( buflist->mBuffers[i].mNumberChannels ); - } - } - } - free ( buflist ); + pLeftData = static_cast ( inInputData->mBuffers[iAddBufferLeft].mData ); - return result; -} + for ( unsigned int i = 0; i < iDeviceBufferSize; i++ ) + { + audioBuffer[2 * i] += static_cast ( pLeftData[iAddChanPerFrameLeft * i + iAddInterlChLeft] * _MAXSHORT ); + } + } -QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool ) -{ - // secure lNumDevs/strDriverNames access - QMutexLocker locker ( &Mutex ); + if ( iAddBufferRight >= 0 ) + { + pRightData = static_cast ( inInputData->mBuffers[iAddBufferRight].mData ); - // reload the driver list of available sound devices - GetAvailableInOutDevices(); + for ( unsigned int i = 0; i < iDeviceBufferSize; i++ ) + { + audioBuffer[2 * i + 1] += static_cast ( pRightData[iAddChanPerFrameRight * i + iAddInterlChRight] * _MAXSHORT ); + } + } - // find driver index from given driver name - int iDriverIdx = INVALID_INDEX; // initialize with an invalid index + // call processing callback function + processCallback ( audioBuffer ); + awaitIn = false; + } - for ( int i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ ) - { - if ( strDriverName.compare ( strDriverNames[i] ) == 0 ) + if ( !awaitIn && outOutputData && ( deviceID == selectedDevice.OutputDeviceId ) ) { - iDriverIdx = i; + int iSelBufferLeft = selectedDevice.OutLeft.iBuffer; + int iSelChanPerFrameLeft = selectedDevice.OutLeft.iChanPerFrame; + int iSelInterlChLeft = selectedDevice.OutLeft.iInterlCh; + + int iSelBufferRight = selectedDevice.OutRight.iBuffer; + int iSelChanPerFrameRight = selectedDevice.OutRight.iChanPerFrame; + int iSelInterlChRight = selectedDevice.OutRight.iInterlCh; + + Float32* pLeftData = static_cast ( outOutputData->mBuffers[iSelBufferLeft].mData ); + Float32* pRightData = static_cast ( outOutputData->mBuffers[iSelBufferRight].mData ); + + // copy output data + for ( unsigned int i = 0; i < iDeviceBufferSize; i++ ) + { + // copy left and right channels separately + pLeftData[iSelChanPerFrameLeft * i + iSelInterlChLeft] = (Float32) audioBuffer[2 * i] / _MAXSHORT; + pRightData[iSelChanPerFrameRight * i + iSelInterlChRight] = (Float32) audioBuffer[2 * i + 1] / _MAXSHORT; + } + awaitIn = true; } } - - // if the selected driver was not found, return an error message - if ( iDriverIdx == INVALID_INDEX ) + else { - return tr ( "The currently selected audio device is no longer present. Please check your audio device." ); + awaitIn = true; } - // check device capabilities if it fulfills our requirements - const QString strStat = CheckDeviceCapabilities ( iDriverIdx ); + return kAudioHardwareNoError; +} - // check if device is capable and if not the same device is used - if ( strStat.isEmpty() && ( strCurDevName.compare ( strDriverNames[iDriverIdx] ) != 0 ) ) +void CSound::onMIDI ( const MIDIPacketList* pktlist ) +{ + if ( midiInPortRef ) { - AudioObjectPropertyAddress stPropertyAddress; + MIDIPacket* midiPacket = const_cast ( pktlist->packet ); - // unregister callbacks if previous device was valid - if ( lCurDev != INVALID_INDEX ) + for ( unsigned int j = 0; j < pktlist->numPackets; j++ ) { - stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; - stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal; - - // unregister callback functions for device property changes - stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + // copy packet and send it to the MIDI parser + CVector vMIDIPaketBytes ( midiPacket->length ); - AudioObjectRemovePropertyListener ( kAudioObjectSystemObject, &stPropertyAddress, deviceNotification, this ); + for ( int i = 0; i < midiPacket->length; i++ ) + { + vMIDIPaketBytes[i] = static_cast ( midiPacket->data[i] ); + } - stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; + parseMIDIMessage ( vMIDIPaketBytes ); - AudioObjectRemovePropertyListener ( kAudioObjectSystemObject, &stPropertyAddress, deviceNotification, this ); + midiPacket = MIDIPacketNext ( midiPacket ); + } + } +} - stPropertyAddress.mSelector = kAudioDevicePropertyDeviceHasChanged; +void CSound::unregisterDeviceCallBacks() +{ + if ( selectedDevice.IsActive() ) + { + AudioObjectPropertyAddress stPropertyAddress; - AudioObjectRemovePropertyListener ( audioOutputDevice[lCurDev], &stPropertyAddress, deviceNotification, this ); + stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; + stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal; - AudioObjectRemovePropertyListener ( audioInputDevice[lCurDev], &stPropertyAddress, deviceNotification, this ); + stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + AudioObjectRemovePropertyListener ( kAudioObjectSystemObject, &stPropertyAddress, _onDeviceNotification, this ); - stPropertyAddress.mSelector = kAudioDevicePropertyDeviceIsAlive; + stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; + AudioObjectRemovePropertyListener ( kAudioObjectSystemObject, &stPropertyAddress, _onDeviceNotification, this ); - AudioObjectRemovePropertyListener ( audioOutputDevice[lCurDev], &stPropertyAddress, deviceNotification, this ); + stPropertyAddress.mSelector = kAudioDevicePropertyDeviceHasChanged; + AudioObjectRemovePropertyListener ( selectedDevice.InputDeviceId, &stPropertyAddress, _onDeviceNotification, this ); + AudioObjectRemovePropertyListener ( selectedDevice.InputDeviceId, &stPropertyAddress, _onDeviceNotification, this ); - AudioObjectRemovePropertyListener ( audioInputDevice[lCurDev], &stPropertyAddress, deviceNotification, this ); - } + stPropertyAddress.mSelector = kAudioDevicePropertyDeviceIsAlive; + AudioObjectRemovePropertyListener ( selectedDevice.OutputDeviceId, &stPropertyAddress, _onDeviceNotification, this ); + AudioObjectRemovePropertyListener ( selectedDevice.OutputDeviceId, &stPropertyAddress, _onDeviceNotification, this ); + } +} - // store ID of selected driver if initialization was successful - lCurDev = iDriverIdx; - CurrentAudioInputDeviceID = audioInputDevice[iDriverIdx]; - CurrentAudioOutputDeviceID = audioOutputDevice[iDriverIdx]; +void CSound::registerDeviceCallBacks() +{ + if ( selectedDevice.IsActive() ) + { + AudioObjectPropertyAddress stPropertyAddress; // register callbacks stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; @@ -331,655 +805,608 @@ QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool ) // setup callbacks for device property changes stPropertyAddress.mSelector = kAudioDevicePropertyDeviceHasChanged; - - AudioObjectAddPropertyListener ( audioInputDevice[lCurDev], &stPropertyAddress, deviceNotification, this ); - - AudioObjectAddPropertyListener ( audioOutputDevice[lCurDev], &stPropertyAddress, deviceNotification, this ); + AudioObjectAddPropertyListener ( selectedDevice.InputDeviceId, &stPropertyAddress, _onDeviceNotification, this ); + AudioObjectAddPropertyListener ( selectedDevice.OutputDeviceId, &stPropertyAddress, _onDeviceNotification, this ); stPropertyAddress.mSelector = kAudioDevicePropertyDeviceIsAlive; - - AudioObjectAddPropertyListener ( audioInputDevice[lCurDev], &stPropertyAddress, deviceNotification, this ); - - AudioObjectAddPropertyListener ( audioOutputDevice[lCurDev], &stPropertyAddress, deviceNotification, this ); + AudioObjectAddPropertyListener ( selectedDevice.InputDeviceId, &stPropertyAddress, _onDeviceNotification, this ); + AudioObjectAddPropertyListener ( selectedDevice.OutputDeviceId, &stPropertyAddress, _onDeviceNotification, this ); stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - - AudioObjectAddPropertyListener ( kAudioObjectSystemObject, &stPropertyAddress, deviceNotification, this ); + AudioObjectAddPropertyListener ( kAudioObjectSystemObject, &stPropertyAddress, _onDeviceNotification, this ); stPropertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; + AudioObjectAddPropertyListener ( kAudioObjectSystemObject, &stPropertyAddress, _onDeviceNotification, this ); + } +} - AudioObjectAddPropertyListener ( kAudioObjectSystemObject, &stPropertyAddress, deviceNotification, this ); +//============================================================================ +// Channel Info & Selection: +//============================================================================ - // the device has changed, per definition we reset the channel - // mapping to the defaults (first two available channels) - SetLeftInputChannel ( 0 ); - SetRightInputChannel ( 1 ); - SetLeftOutputChannel ( 0 ); - SetRightOutputChannel ( 1 ); +bool CSound::setSelectedDevice() +{ + bool ok = true; - // store the current name of the driver - strCurDevName = strDriverNames[iDriverIdx]; - } + unregisterDeviceCallBacks(); - return strStat; -} + selectedDevice.clear(); -QString CSound::CheckDeviceCapabilities ( const int iDriverIdx ) -{ - UInt32 iPropertySize; - AudioStreamBasicDescription CurDevStreamFormat; - Float64 inputSampleRate = 0; - Float64 outputSampleRate = 0; - const Float64 fSystemSampleRate = static_cast ( SYSTEM_SAMPLE_RATE_HZ ); - AudioObjectPropertyAddress stPropertyAddress; + // Get Channel numbers: + int iInLeft; + int iAddLeft; + getInputSelAndAddChannels ( selectedInputChannels[0], lNumInChan, lNumAddedInChan, iInLeft, iAddLeft ); - stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal; - stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; + int iInRight; + int iAddRight; + getInputSelAndAddChannels ( selectedInputChannels[1], lNumInChan, lNumAddedInChan, iInRight, iAddRight ); - // check input device sample rate - stPropertyAddress.mSelector = kAudioDevicePropertyNominalSampleRate; - iPropertySize = sizeof ( Float64 ); + int iOutLeft = selectedOutputChannels[0]; + int iOutRight = selectedOutputChannels[1]; + + selectedDevice.InputDeviceId = device[iCurrentDevice].InputDeviceId; + selectedDevice.OutputDeviceId = device[iCurrentDevice].OutputDeviceId; + ok &= ( selectedDevice.InputDeviceId != 0 ) && ( selectedDevice.OutputDeviceId != 0 ); - if ( AudioObjectGetPropertyData ( audioInputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize, &inputSampleRate ) ) + // Get Channel Ids + if ( device[iCurrentDevice].input[iInLeft].IsValid ( false ) ) + { + selectedDevice.InLeft = device[iCurrentDevice].input[iInLeft]; + } + else { - return QString ( tr ( "The audio input device is no longer available. Please check if your input device is connected correctly." ) ); + selectedDevice.InLeft = noChannelInfo; + ok = false; } - if ( inputSampleRate != fSystemSampleRate ) + if ( device[iCurrentDevice].input[iInRight].IsValid ( false ) ) { - // try to change the sample rate - if ( AudioObjectSetPropertyData ( audioInputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, sizeof ( Float64 ), &fSystemSampleRate ) != - noErr ) + selectedDevice.InRight = device[iCurrentDevice].input[iInRight]; + } + else + { + selectedDevice.InRight = noChannelInfo; + ok = false; + } + + if ( iAddLeft >= 0 ) + { + if ( device[iCurrentDevice].input[iAddLeft].IsValid ( true ) ) { - return QString ( tr ( "The sample rate on the current input device isn't %1 Hz and is therefore incompatible. " - "Please select another device or try setting the sample rate to %1 Hz " - "manually via Audio-MIDI-Setup (in Applications->Utilities)." ) ) - .arg ( SYSTEM_SAMPLE_RATE_HZ ); + selectedDevice.AddInLeft = device[iCurrentDevice].input[iAddLeft]; + } + else + { + selectedDevice.AddInLeft = noChannelInfo; + ok = false; } } - - // check output device sample rate - iPropertySize = sizeof ( Float64 ); - - if ( AudioObjectGetPropertyData ( audioOutputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize, &outputSampleRate ) ) + else { - return QString ( tr ( "The audio output device is no longer available. Please check if your output device is connected correctly." ) ); + selectedDevice.AddInLeft = noChannelInfo; } - if ( outputSampleRate != fSystemSampleRate ) + if ( iAddRight >= 0 ) { - // try to change the sample rate - if ( AudioObjectSetPropertyData ( audioOutputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, sizeof ( Float64 ), &fSystemSampleRate ) != - noErr ) + if ( device[iCurrentDevice].input[iAddRight].IsValid ( true ) ) + { + selectedDevice.AddInRight = device[iCurrentDevice].input[iAddRight]; + } + else { - return QString ( tr ( "The sample rate on the current output device isn't %1 Hz and is therefore incompatible. " - "Please select another device or try setting the sample rate to %1 Hz " - "manually via Audio-MIDI-Setup (in Applications->Utilities)." ) ) - .arg ( SYSTEM_SAMPLE_RATE_HZ ); + selectedDevice.AddInRight = noChannelInfo; + ok = false; } } - - // get the stream ID of the input device (at least one stream must always exist) - iPropertySize = 0; - stPropertyAddress.mSelector = kAudioDevicePropertyStreams; - stPropertyAddress.mScope = kAudioObjectPropertyScopeInput; - - AudioObjectGetPropertyDataSize ( audioInputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize ); - - CVector vInputStreamIDList ( iPropertySize ); - - AudioObjectGetPropertyData ( audioInputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize, &vInputStreamIDList[0] ); - - const AudioStreamID inputStreamID = vInputStreamIDList[0]; - - // get the stream ID of the output device (at least one stream must always exist) - iPropertySize = 0; - stPropertyAddress.mSelector = kAudioDevicePropertyStreams; - stPropertyAddress.mScope = kAudioObjectPropertyScopeOutput; - - AudioObjectGetPropertyDataSize ( audioOutputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize ); - - CVector vOutputStreamIDList ( iPropertySize ); - - AudioObjectGetPropertyData ( audioOutputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize, &vOutputStreamIDList[0] ); - - const AudioStreamID outputStreamID = vOutputStreamIDList[0]; - - // According to the AudioHardware documentation: "If the format is a linear PCM - // format, the data will always be presented as 32 bit, native endian floating - // point. All conversions to and from the true physical format of the hardware - // is handled by the devices driver.". - // check the input - iPropertySize = sizeof ( AudioStreamBasicDescription ); - stPropertyAddress.mSelector = kAudioStreamPropertyVirtualFormat; - stPropertyAddress.mScope = kAudioObjectPropertyScopeGlobal; - - AudioObjectGetPropertyData ( inputStreamID, &stPropertyAddress, 0, NULL, &iPropertySize, &CurDevStreamFormat ); - - if ( ( CurDevStreamFormat.mFormatID != kAudioFormatLinearPCM ) || ( CurDevStreamFormat.mFramesPerPacket != 1 ) || - ( CurDevStreamFormat.mBitsPerChannel != 32 ) || ( !( CurDevStreamFormat.mFormatFlags & kAudioFormatFlagIsFloat ) ) || - ( !( CurDevStreamFormat.mFormatFlags & kAudioFormatFlagIsPacked ) ) ) + else { - return QString ( tr ( "The stream format on the current input device isn't " - "compatible with this software. Please select another device." ) ); + selectedDevice.AddInRight = noChannelInfo; } - // check the output - AudioObjectGetPropertyData ( outputStreamID, &stPropertyAddress, 0, NULL, &iPropertySize, &CurDevStreamFormat ); - - if ( ( CurDevStreamFormat.mFormatID != kAudioFormatLinearPCM ) || ( CurDevStreamFormat.mFramesPerPacket != 1 ) || - ( CurDevStreamFormat.mBitsPerChannel != 32 ) || ( !( CurDevStreamFormat.mFormatFlags & kAudioFormatFlagIsFloat ) ) || - ( !( CurDevStreamFormat.mFormatFlags & kAudioFormatFlagIsPacked ) ) ) + if ( device[iCurrentDevice].output[iOutLeft].IsValid ( false ) ) { - return QString ( tr ( "The stream format on the current output device isn't " - "compatible with %1. Please select another device." ) ) - .arg ( APP_NAME ); + selectedDevice.OutLeft = device[iCurrentDevice].output[iOutLeft]; + } + else + { + selectedDevice.OutLeft = noChannelInfo; + ok = false; } - // store the input and out number of channels for this device - iNumInChan = CountChannels ( audioInputDevice[iDriverIdx], true ); - iNumOutChan = CountChannels ( audioOutputDevice[iDriverIdx], false ); - - // clip the number of input/output channels to our allowed maximum - if ( iNumInChan > MAX_NUM_IN_OUT_CHANNELS ) + if ( device[iCurrentDevice].output[iOutRight].IsValid ( false ) ) { - iNumInChan = MAX_NUM_IN_OUT_CHANNELS; + selectedDevice.OutRight = device[iCurrentDevice].output[iOutRight]; } - if ( iNumOutChan > MAX_NUM_IN_OUT_CHANNELS ) + else { - iNumOutChan = MAX_NUM_IN_OUT_CHANNELS; + selectedDevice.OutRight = noChannelInfo; + ok = false; } - // get the channel names of the input device - for ( int iCurInCH = 0; iCurInCH < iNumInChan; iCurInCH++ ) + if ( ok ) { - CFStringRef sPropertyStringValue = NULL; - - stPropertyAddress.mSelector = kAudioObjectPropertyElementName; - stPropertyAddress.mElement = iCurInCH + 1; - stPropertyAddress.mScope = kAudioObjectPropertyScopeInput; - iPropertySize = sizeof ( CFStringRef ); + selectedDevice.Activated = true; + registerDeviceCallBacks(); + } - AudioObjectGetPropertyData ( audioInputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize, &sPropertyStringValue ); + return ok; +} - // convert string - const bool bConvOK = ConvertCFStringToQString ( sPropertyStringValue, sChannelNamesInput[iCurInCH] ); +void CSound::setBaseChannelInfo ( cDeviceInfo& deviceSelection ) +{ + const int NumInputChannels = deviceSelection.input.Size(); + const int NumOutputChannels = deviceSelection.output.Size(); - // add the "[n]:" at the beginning as is in the Audio-Midi-Setup - if ( !bConvOK || ( iPropertySize == 0 ) ) - { - // use a default name in case there was an error or the name is empty - sChannelNamesInput[iCurInCH] = QString ( "%1: Channel %1" ).arg ( iCurInCH + 1 ); - } - else - { - sChannelNamesInput[iCurInCH].prepend ( QString ( "%1: " ).arg ( iCurInCH + 1 ) ); - } + strInputChannelNames.clear(); + for ( int i = 0; i < NumInputChannels; i++ ) + { + strInputChannelNames.append ( deviceSelection.input[i].strName ); } - // get the channel names of the output device - for ( int iCurOutCH = 0; iCurOutCH < iNumOutChan; iCurOutCH++ ) + strOutputChannelNames.clear(); + for ( int i = 0; i < NumOutputChannels; i++ ) { - CFStringRef sPropertyStringValue = NULL; + strOutputChannelNames.append ( deviceSelection.output[i].strName ); + } - stPropertyAddress.mSelector = kAudioObjectPropertyElementName; - stPropertyAddress.mElement = iCurOutCH + 1; - stPropertyAddress.mScope = kAudioObjectPropertyScopeOutput; - iPropertySize = sizeof ( CFStringRef ); + lNumInChan = deviceSelection.lNumInChan; + lNumAddedInChan = deviceSelection.lNumAddedInChan; + lNumOutChan = deviceSelection.lNumOutChan; +} - AudioObjectGetPropertyData ( audioOutputDevice[iDriverIdx], &stPropertyAddress, 0, NULL, &iPropertySize, &sPropertyStringValue ); +bool CSound::getDeviceChannelInfo ( cDeviceInfo& deviceInfo, bool bInput ) +{ + CSound::cChannelInfo channelInfo; - // convert string - const bool bConvOK = ConvertCFStringToQString ( sPropertyStringValue, sChannelNamesOutput[iCurOutCH] ); + AudioDeviceID DeviceId = bInput ? deviceInfo.InputDeviceId : deviceInfo.OutputDeviceId; + CVector& vChannelInfo = bInput ? deviceInfo.input : deviceInfo.output; - // add the "[n]:" at the beginning as is in the Audio-Midi-Setup - if ( !bConvOK || ( iPropertySize == 0 ) ) - { - // use a default name in case there was an error or the name is empty - sChannelNamesOutput[iCurOutCH] = QString ( "%1: Channel %1" ).arg ( iCurOutCH + 1 ); - } - else - { - sChannelNamesOutput[iCurOutCH].prepend ( QString ( "%1: " ).arg ( iCurOutCH + 1 ) ); - } - } + vChannelInfo.clear(); - // special case with 4 input channels: support adding channels - if ( iNumInChan == 4 ) + channelInfo.isInput = bInput; + channelInfo.DeviceId = DeviceId; + // Get Primary channels: { - // add four mixed channels (i.e. 4 normal, 4 mixed channels) - iNumInChanPlusAddChan = 8; + CVector vBufferList; - for ( int iCh = 0; iCh < iNumInChanPlusAddChan; iCh++ ) + if ( ahGetDeviceBufferList ( DeviceId, bInput, vBufferList ) ) { - int iSelCH, iSelAddCH; - - GetSelCHAndAddCH ( iCh, iNumInChan, iSelCH, iSelAddCH ); + int numBuffers = vBufferList.Size(); + int numChannels; - if ( iSelAddCH >= 0 ) + for ( int i = 0; i < numBuffers; i++ ) { - // for mixed channels, show both audio channel names to be mixed - sChannelNamesInput[iCh] = sChannelNamesInput[iSelCH] + " + " + sChannelNamesInput[iSelAddCH]; + channelInfo.iBuffer = i; + channelInfo.iChanPerFrame = numChannels = vBufferList[i].mNumberChannels; + + for ( int c = 0; c < numChannels; c++ ) + { + channelInfo.iInterlCh = c; + + channelInfo.iListIndex = vChannelInfo.Size(); + ahGetDeviceChannelName ( DeviceId, bInput, channelInfo.iListIndex, channelInfo.strName ); + vChannelInfo.Add ( channelInfo ); + } } } } + + if ( bInput ) + { + long numInChan = deviceInfo.lNumInChan = vChannelInfo.Size(); + long ChannelsToAdd = getNumInputChannelsToAdd ( numInChan ); + + int iSelChan, iAddChan; + + for ( int i = 0; i < ChannelsToAdd; i++ ) + { + getInputSelAndAddChannels ( vChannelInfo.Size(), numInChan, ChannelsToAdd, iSelChan, iAddChan ); + channelInfo = vChannelInfo[iAddChan]; + channelInfo.iListIndex = vChannelInfo.Size(); + channelInfo.strName = vChannelInfo[iSelChan].strName + " + " + vChannelInfo[iAddChan].strName; + vChannelInfo.Add ( channelInfo ); + } + + deviceInfo.lNumAddedInChan = ChannelsToAdd; + } else { - // regular case: no mixing input channels used - iNumInChanPlusAddChan = iNumInChan; + deviceInfo.lNumOutChan = vChannelInfo.Size(); } - // everything is ok, return empty string for "no error" case - return ""; + return ( vChannelInfo.Size() > 0 ); } -void CSound::UpdateChSelection() +bool StreamformatOk ( AudioStreamBasicDescription& streamDescription ) { - // calculate the selected input/output buffer and the selected interleaved - // channel index in the buffer, note that each buffer can have a different - // number of interleaved channels - int iChCnt; - int iSelCHLeft, iSelAddCHLeft; - int iSelCHRight, iSelAddCHRight; - - // initialize all buffer indexes with an invalid value - iSelInBufferLeft = INVALID_INDEX; - iSelInBufferRight = INVALID_INDEX; - iSelAddInBufferLeft = INVALID_INDEX; // if no additional channel used, this will stay on the invalid value - iSelAddInBufferRight = INVALID_INDEX; // if no additional channel used, this will stay on the invalid value - iSelOutBufferLeft = INVALID_INDEX; - iSelOutBufferRight = INVALID_INDEX; - - // input - GetSelCHAndAddCH ( iSelInputLeftChannel, iNumInChan, iSelCHLeft, iSelAddCHLeft ); - GetSelCHAndAddCH ( iSelInputRightChannel, iNumInChan, iSelCHRight, iSelAddCHRight ); + return ( ( streamDescription.mFormatID == kAudioFormatLinearPCM ) && ( streamDescription.mFramesPerPacket == 1 ) && + ( streamDescription.mBitsPerChannel == 32 ) && ( streamDescription.mFormatFlags & kAudioFormatFlagIsFloat ) && + ( streamDescription.mFormatFlags & kAudioFormatFlagIsPacked ) ); +} - iChCnt = 0; +bool CSound::CheckDeviceCapabilities ( cDeviceInfo& deviceSelection ) +{ + bool ok = true; + bool inputDeviceOk = true; + bool outputDeviceOk = true; + const Float64 fSystemSampleRate = static_cast ( SYSTEM_SAMPLE_RATE_HZ ); + Float64 inputSampleRate = 0; + Float64 outputSampleRate = 0; + AudioStreamBasicDescription CurDevStreamFormat; + CVector vInputStreamIDList; + CVector vOutputStreamIDList; - for ( int iBuf = 0; iBuf < vecNumInBufChan.Size(); iBuf++ ) + if ( !ahGetDeviceSampleRate ( deviceSelection.InputDeviceId, inputSampleRate ) ) { - iChCnt += vecNumInBufChan[iBuf]; + strErrorList.append ( + tr ( "The audio %1 device is no longer available. Please check if your %1 device is connected correctly." ).arg ( "input" ) ); + ok = inputDeviceOk = false; + } - if ( ( iSelInBufferLeft < 0 ) && ( iChCnt > iSelCHLeft ) ) + if ( ( inputDeviceOk ) && ( inputSampleRate != fSystemSampleRate ) ) + { + // try to change the sample rate + if ( !ahSetDeviceSampleRate ( deviceSelection.InputDeviceId, fSystemSampleRate ) ) { - iSelInBufferLeft = iBuf; - iSelInInterlChLeft = iSelCHLeft - iChCnt + vecNumInBufChan[iBuf]; + strErrorList.append ( tr ( "The sample rate on the current %1 device isn't %2 Hz and is therefore incompatible. " ) + .arg ( "input" ) + .arg ( SYSTEM_SAMPLE_RATE_HZ ) + + tr ( "Try setting the sample rate to % 1 Hz manually via Audio-MIDI-Setup (in Applications->Utilities)." ) + .arg ( SYSTEM_SAMPLE_RATE_HZ ) ); + ok = false; } + } - if ( ( iSelInBufferRight < 0 ) && ( iChCnt > iSelCHRight ) ) - { - iSelInBufferRight = iBuf; - iSelInInterlChRight = iSelCHRight - iChCnt + vecNumInBufChan[iBuf]; - } + if ( !ahGetDeviceSampleRate ( deviceSelection.OutputDeviceId, outputSampleRate ) ) + { + strErrorList.append ( + tr ( "The audio %1 device is no longer available. Please check if your %1 device is connected correctly." ).arg ( "output" ) ); + ok = outputDeviceOk = false; + } - if ( ( iSelAddCHLeft >= 0 ) && ( iSelAddInBufferLeft < 0 ) && ( iChCnt > iSelAddCHLeft ) ) + if ( outputDeviceOk && ( outputSampleRate != fSystemSampleRate ) ) + { + // try to change the sample rate + if ( !ahSetDeviceSampleRate ( deviceSelection.OutputDeviceId, fSystemSampleRate ) ) { - iSelAddInBufferLeft = iBuf; - iSelAddInInterlChLeft = iSelAddCHLeft - iChCnt + vecNumInBufChan[iBuf]; + strErrorList.append ( tr ( "The sample rate on the current %1 device isn't %2 Hz and is therefore incompatible. " ) + .arg ( "output" ) + .arg ( SYSTEM_SAMPLE_RATE_HZ ) + + tr ( "Try setting the sample rate to %1 Hz manually via Audio-MIDI-Setup (in Applications->Utilities)." ) + .arg ( SYSTEM_SAMPLE_RATE_HZ ) ); + ok = false; } + } - if ( ( iSelAddCHRight >= 0 ) && ( iSelAddInBufferRight < 0 ) && ( iChCnt > iSelAddCHRight ) ) + if ( inputDeviceOk ) + { + if ( !ahGetDeviceStreamIdList ( deviceSelection.InputDeviceId, biINPUT, vInputStreamIDList ) ) { - iSelAddInBufferRight = iBuf; - iSelAddInInterlChRight = iSelAddCHRight - iChCnt + vecNumInBufChan[iBuf]; + strErrorList.append ( tr ( "The input device doesn't have any valid inputs." ) ); + ok = inputDeviceOk = false; } } - // output - GetSelCHAndAddCH ( iSelOutputLeftChannel, iNumOutChan, iSelCHLeft, iSelAddCHLeft ); - GetSelCHAndAddCH ( iSelOutputRightChannel, iNumOutChan, iSelCHRight, iSelAddCHRight ); - - iChCnt = 0; - - for ( int iBuf = 0; iBuf < vecNumOutBufChan.Size(); iBuf++ ) + if ( outputDeviceOk ) { - iChCnt += vecNumOutBufChan[iBuf]; - - if ( ( iSelOutBufferLeft < 0 ) && ( iChCnt > iSelCHLeft ) ) + // get the stream ID of the output device (at least one stream must always exist) + if ( !ahGetDeviceStreamIdList ( deviceSelection.OutputDeviceId, biOUTPUT, vOutputStreamIDList ) ) { - iSelOutBufferLeft = iBuf; - iSelOutInterlChLeft = iSelCHLeft - iChCnt + vecNumOutBufChan[iBuf]; + strErrorList.append ( tr ( "The output device doesn't have any valid outputs." ) ); + ok = outputDeviceOk = false; } + } - if ( ( iSelOutBufferRight < 0 ) && ( iChCnt > iSelCHRight ) ) + if ( ok ) + { + ok = setBufferSize ( deviceSelection.InputDeviceId, deviceSelection.OutputDeviceId, iProtocolBufferSize, iDeviceBufferSize ); + if ( ok ) { - iSelOutBufferRight = iBuf; - iSelOutInterlChRight = iSelCHRight - iChCnt + vecNumOutBufChan[iBuf]; + // According to the AudioHardware documentation: "If the format is a linear PCM + // format, the data will always be presented as 32 bit, native endian floating + // point. All conversions to and from the true physical format of the hardware + // is handled by the devices driver.". + // check the input + if ( !ahGetStreamFormat ( vInputStreamIDList[0], CurDevStreamFormat ) || // pgScorpio: this one fails ! + !StreamformatOk ( CurDevStreamFormat ) ) + { + strErrorList.append ( tr ( "The stream format on the current input device isn't compatible with %1." ).arg ( APP_NAME ) ); + ok = inputDeviceOk = false; + } + + if ( !ahGetStreamFormat ( vOutputStreamIDList[0], CurDevStreamFormat ) || !StreamformatOk ( CurDevStreamFormat ) ) + { + strErrorList.append ( tr ( "The stream format on the current output device isn't compatible with %1." ).arg ( APP_NAME ) ); + ok = outputDeviceOk = false; + } } } -} -void CSound::SetLeftInputChannel ( const int iNewChan ) -{ - // apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < iNumInChanPlusAddChan ) ) - { - iSelInputLeftChannel = iNewChan; - UpdateChSelection(); - } + return ok; } -void CSound::SetRightInputChannel ( const int iNewChan ) -{ - // apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < iNumInChanPlusAddChan ) ) - { - iSelInputRightChannel = iNewChan; - UpdateChSelection(); - } -} +//============================================================================ +// CSoundBase virtuals: +//============================================================================ -void CSound::SetLeftOutputChannel ( const int iNewChan ) +long CSound::createDeviceList ( bool bRescan ) // Fills strDeviceList. Returns number of devices found { - // apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < iNumOutChan ) ) + if ( bRescan || ( lNumDevices == 0 ) || ( strDeviceNames.size() != lNumDevices ) || ( device.Size() != lNumDevices ) ) { - iSelOutputLeftChannel = iNewChan; - UpdateChSelection(); - } -} + CVector vAudioDevices; + cDeviceInfo newDevice; -void CSound::SetRightOutputChannel ( const int iNewChan ) -{ - // apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < iNumOutChan ) ) - { - iSelOutputRightChannel = iNewChan; - UpdateChSelection(); - } -} + clearDeviceInfo(); // We will have no more device to select... -void CSound::Start() -{ - // register the callback function for input and output - AudioDeviceCreateIOProcID ( audioInputDevice[lCurDev], callbackIO, this, &audioInputProcID ); + lNumDevices = 0; + strDeviceNames.clear(); + device.clear(); + + if ( !ahGetAudioDeviceList ( vAudioDevices ) ) + { + strErrorList.append ( tr ( "No audiodevices found." ) ); + return 0; + } - AudioDeviceCreateIOProcID ( audioOutputDevice[lCurDev], callbackIO, this, &audioOutputProcID ); + const UInt32 iDeviceCount = vAudioDevices.Size(); - // start the audio stream - AudioDeviceStart ( audioInputDevice[lCurDev], audioInputProcID ); - AudioDeviceStart ( audioOutputDevice[lCurDev], audioOutputProcID ); + // always add system default devices for input and output as first entry + if ( !( ahGetDefaultDevice ( biINPUT, newDevice.InputDeviceId ) && ahGetDefaultDevice ( biOUTPUT, newDevice.OutputDeviceId ) ) ) + { + strErrorList.append ( tr ( "No sound card is available in your system." ) ); + return 0; + } + getDeviceChannelInfo ( newDevice, biINPUT ); + getDeviceChannelInfo ( newDevice, biOUTPUT ); + device.Add ( newDevice ); + strDeviceNames.append ( tr ( "System Default In/Out Devices" ) ); + + QString strDeviceName_i; + QString strDeviceName_j; + bool bIsInput_i; + bool bIsOutput_i; + bool bIsInput_j; + bool bIsOutput_j; + + // First get in + out devices... + for ( UInt32 i = 0; i < iDeviceCount; i++ ) + { + getAudioDeviceInfos ( i, vAudioDevices[i], strDeviceName_i, bIsInput_i, bIsOutput_i ); + if ( bIsInput_i && bIsOutput_i ) + { + newDevice.InputDeviceId = vAudioDevices[i]; + newDevice.OutputDeviceId = vAudioDevices[i]; + getDeviceChannelInfo ( newDevice, biINPUT ); + getDeviceChannelInfo ( newDevice, biOUTPUT ); + device.Add ( newDevice ); + strDeviceNames.append ( strDeviceName_i ); + } + } - // call base class - CSoundBase::Start(); -} + // Then find all other in / out combinations... + for ( UInt32 i = 0; i < iDeviceCount; i++ ) + { + getAudioDeviceInfos ( i, vAudioDevices[i], strDeviceName_i, bIsInput_i, bIsOutput_i ); -void CSound::Stop() -{ - // stop the audio stream - AudioDeviceStop ( audioInputDevice[lCurDev], audioInputProcID ); - AudioDeviceStop ( audioOutputDevice[lCurDev], audioOutputProcID ); + if ( bIsInput_i ) + { + newDevice.InputDeviceId = vAudioDevices[i]; + getDeviceChannelInfo ( newDevice, biINPUT ); + + for ( UInt32 j = 0; j < iDeviceCount; j++ ) + { + // Skip primary in + out devices, we already got those... + if ( i != j ) + { + getAudioDeviceInfos ( j, vAudioDevices[j], strDeviceName_j, bIsInput_j, bIsOutput_j ); + + if ( bIsOutput_j ) + { + newDevice.OutputDeviceId = vAudioDevices[j]; + getDeviceChannelInfo ( newDevice, biOUTPUT ); + device.Add ( newDevice ); + strDeviceNames.append ( strDeviceName_i + " / " + strDeviceName_j ); + } + } + } + } + } - // unregister the callback function for input and output - AudioDeviceDestroyIOProcID ( audioInputDevice[lCurDev], audioInputProcID ); - AudioDeviceDestroyIOProcID ( audioOutputDevice[lCurDev], audioOutputProcID ); + lNumDevices = strDeviceNames.length(); + } - // call base class - CSoundBase::Stop(); + return lNumDevices; } -int CSound::Init ( const int iNewPrefMonoBufferSize ) +bool CSound::getCurrentLatency() { - UInt32 iActualMonoBufferSize; - - // Error message string: in case buffer sizes on input and output cannot be - // set to the same value - const QString strErrBufSize = tr ( "The buffer sizes of the current " - "input and output audio device can't be set to a common value. Please " - "select different input/output devices in your system settings." ); + if ( !selectedDevice.IsActive() ) + { + return false; + } - // try to set input buffer size - iActualMonoBufferSize = SetBufferSize ( audioInputDevice[lCurDev], true, iNewPrefMonoBufferSize ); + bool ok = true; + CVector streamIdList; + UInt32 iInputLatencyFrames = 0; + UInt32 iOutputLatencyFrames = 0; - if ( iActualMonoBufferSize != static_cast ( iNewPrefMonoBufferSize ) ) + if ( !ahGetDeviceLatency ( selectedDevice.InputDeviceId, biINPUT, iInputLatencyFrames ) ) { - // try to set the input buffer size to the output so that we - // have a matching pair - if ( SetBufferSize ( audioOutputDevice[lCurDev], false, iActualMonoBufferSize ) != iActualMonoBufferSize ) - { - throw CGenErr ( strErrBufSize ); - } + ok = false; + iInputLatencyFrames = iDeviceBufferSize; } - else + + if ( !ahGetDeviceLatency ( selectedDevice.OutputDeviceId, biOUTPUT, iOutputLatencyFrames ) ) { - // try to set output buffer size - if ( SetBufferSize ( audioOutputDevice[lCurDev], false, iNewPrefMonoBufferSize ) != static_cast ( iNewPrefMonoBufferSize ) ) - { - throw CGenErr ( strErrBufSize ); - } + ok = false; + iOutputLatencyFrames = iDeviceBufferSize; } - // store buffer size - iCoreAudioBufferSizeMono = iActualMonoBufferSize; - - // init base class - CSoundBase::Init ( iCoreAudioBufferSizeMono ); - - // set internal buffer size value and calculate stereo buffer size - iCoreAudioBufferSizeStereo = 2 * iCoreAudioBufferSizeMono; + iInputLatencyFrames += iDeviceBufferSize; + iOutputLatencyFrames += iDeviceBufferSize; - // create memory for intermediate audio buffer - vecsTmpAudioSndCrdStereo.Init ( iCoreAudioBufferSizeStereo ); + fInOutLatencyMs = ( ( static_cast ( iInputLatencyFrames ) + iOutputLatencyFrames ) * 1000 ) / SYSTEM_SAMPLE_RATE_HZ; - return iCoreAudioBufferSizeMono; + return ok; } -UInt32 CSound::SetBufferSize ( AudioDeviceID& audioDeviceID, const bool bIsInput, UInt32 iPrefBufferSize ) +bool CSound::checkDeviceChange ( CSoundBase::tDeviceChangeCheck mode, int iDriverIndex ) // Open device sequence handling.... { - AudioObjectPropertyAddress stPropertyAddress; - stPropertyAddress.mSelector = kAudioDevicePropertyBufferFrameSize; - - if ( bIsInput ) + if ( ( iDriverIndex < 0 ) || ( iDriverIndex >= lNumDevices ) ) { - stPropertyAddress.mScope = kAudioDevicePropertyScopeInput; + return false; } - else + + switch ( mode ) { - stPropertyAddress.mScope = kAudioDevicePropertyScopeOutput; - } + case CSoundBase::tDeviceChangeCheck::CheckOpen: + return device[iDriverIndex].IsValid(); + + case CSoundBase::tDeviceChangeCheck::CheckCapabilities: + return CheckDeviceCapabilities ( device[iDriverIndex] ); - stPropertyAddress.mElement = kAudioObjectPropertyElementMaster; + case CSoundBase::tDeviceChangeCheck::Activate: + clearDeviceInfo(); + setBaseChannelInfo ( device[iDriverIndex] ); + resetChannelMapping(); - // first set the value - UInt32 iSizeBufValue = sizeof ( UInt32 ); + fInOutLatencyMs = 0.0; + iCurrentDevice = iDriverIndex; - AudioObjectSetPropertyData ( audioDeviceID, &stPropertyAddress, 0, NULL, iSizeBufValue, &iPrefBufferSize ); + // TODO: ChannelMapping from inifile! - // read back which value is actually used - UInt32 iActualMonoBufferSize = 0; + return setSelectedDevice(); - AudioObjectGetPropertyData ( audioDeviceID, &stPropertyAddress, 0, NULL, &iSizeBufValue, &iActualMonoBufferSize ); + case CSoundBase::tDeviceChangeCheck::Abort: + return true; - return iActualMonoBufferSize; + default: + return false; + } } -OSStatus CSound::deviceNotification ( AudioDeviceID, UInt32, const AudioObjectPropertyAddress* inAddresses, void* inRefCon ) +unsigned int CSound::getDeviceBufferSize ( unsigned int iDesiredBufferSize ) { - CSound* pSound = static_cast ( inRefCon ); + CSoundStopper sound ( *this ); - if ( ( inAddresses->mSelector == kAudioDevicePropertyDeviceHasChanged ) || ( inAddresses->mSelector == kAudioDevicePropertyDeviceIsAlive ) || - ( inAddresses->mSelector == kAudioHardwarePropertyDefaultOutputDevice ) || - ( inAddresses->mSelector == kAudioHardwarePropertyDefaultInputDevice ) ) + if ( selectedDevice.IsActive() ) { - if ( ( inAddresses->mSelector == kAudioDevicePropertyDeviceHasChanged ) || ( inAddresses->mSelector == kAudioDevicePropertyDeviceIsAlive ) ) - { - // if any property of the device has changed, do a full reload - pSound->EmitReinitRequestSignal ( RS_RELOAD_RESTART_AND_INIT ); - } - else + unsigned int iActualBufferSize; + + if ( setBufferSize ( selectedDevice.InputDeviceId, selectedDevice.OutputDeviceId, iDesiredBufferSize, iActualBufferSize ) ) { - // for any other change in audio devices, just initiate a restart which - // triggers an update of the sound device selection combo box - pSound->EmitReinitRequestSignal ( RS_ONLY_RESTART ); + return iActualBufferSize; } } - return noErr; + return 0; } -OSStatus CSound::callbackIO ( AudioDeviceID inDevice, - const AudioTimeStamp*, - const AudioBufferList* inInputData, - const AudioTimeStamp*, - AudioBufferList* outOutputData, - const AudioTimeStamp*, - void* inRefCon ) +void CSound::closeCurrentDevice() // Closes the current driver and Clears Device Info { - CSound* pSound = static_cast ( inRefCon ); + // Can't close current device while running + Stop(); + // Clear CSoundBase device info + clearDeviceInfo(); + // Clear my selected device + selectedDevice.clear(); +} - // both, the input and output device use the same callback function - QMutexLocker locker ( &pSound->MutexAudioProcessCallback ); - - const int iCoreAudioBufferSizeMono = pSound->iCoreAudioBufferSizeMono; - const int iSelInBufferLeft = pSound->iSelInBufferLeft; - const int iSelInBufferRight = pSound->iSelInBufferRight; - const int iSelInInterlChLeft = pSound->iSelInInterlChLeft; - const int iSelInInterlChRight = pSound->iSelInInterlChRight; - const int iSelAddInBufferLeft = pSound->iSelAddInBufferLeft; - const int iSelAddInBufferRight = pSound->iSelAddInBufferRight; - const int iSelAddInInterlChLeft = pSound->iSelAddInInterlChLeft; - const int iSelAddInInterlChRight = pSound->iSelAddInInterlChRight; - const int iSelOutBufferLeft = pSound->iSelOutBufferLeft; - const int iSelOutBufferRight = pSound->iSelOutBufferRight; - const int iSelOutInterlChLeft = pSound->iSelOutInterlChLeft; - const int iSelOutInterlChRight = pSound->iSelOutInterlChRight; - const CVector& vecNumInBufChan = pSound->vecNumInBufChan; - const CVector& vecNumOutBufChan = pSound->vecNumOutBufChan; - - if ( ( inDevice == pSound->CurrentAudioInputDeviceID ) && inInputData && pSound->bRun ) - { - // check sizes (note that float32 has four bytes) - if ( ( iSelInBufferLeft >= 0 ) && ( iSelInBufferLeft < static_cast ( inInputData->mNumberBuffers ) ) && ( iSelInBufferRight >= 0 ) && - ( iSelInBufferRight < static_cast ( inInputData->mNumberBuffers ) ) && - ( iSelAddInBufferLeft < static_cast ( inInputData->mNumberBuffers ) ) && - ( iSelAddInBufferRight < static_cast ( inInputData->mNumberBuffers ) ) && - ( inInputData->mBuffers[iSelInBufferLeft].mDataByteSize == - static_cast ( vecNumInBufChan[iSelInBufferLeft] * iCoreAudioBufferSizeMono * 4 ) ) && - ( inInputData->mBuffers[iSelInBufferRight].mDataByteSize == - static_cast ( vecNumInBufChan[iSelInBufferRight] * iCoreAudioBufferSizeMono * 4 ) ) ) - { - Float32* pLeftData = static_cast ( inInputData->mBuffers[iSelInBufferLeft].mData ); - Float32* pRightData = static_cast ( inInputData->mBuffers[iSelInBufferRight].mData ); - int iNumChanPerFrameLeft = vecNumInBufChan[iSelInBufferLeft]; - int iNumChanPerFrameRight = vecNumInBufChan[iSelInBufferRight]; +//============================================================================ +// Start/Stop: +//============================================================================ - // copy input data - for ( int i = 0; i < iCoreAudioBufferSizeMono; i++ ) - { - // copy left and right channels separately - pSound->vecsTmpAudioSndCrdStereo[2 * i] = (short) ( pLeftData[iNumChanPerFrameLeft * i + iSelInInterlChLeft] * _MAXSHORT ); - pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] = (short) ( pRightData[iNumChanPerFrameRight * i + iSelInInterlChRight] * _MAXSHORT ); - } +bool CSound::prepareAudio ( bool start ) +{ + if ( !selectedDevice.IsActive() || ( iDeviceBufferSize == 0 ) ) + { + return false; + } - // add an additional optional channel - if ( iSelAddInBufferLeft >= 0 ) - { - pLeftData = static_cast ( inInputData->mBuffers[iSelAddInBufferLeft].mData ); - iNumChanPerFrameLeft = vecNumInBufChan[iSelAddInBufferLeft]; + // create memory for intermediate audio buffer + audioBuffer.Init ( PROT_NUM_IN_CHANNELS * iDeviceBufferSize ); - for ( int i = 0; i < iCoreAudioBufferSizeMono; i++ ) - { - pSound->vecsTmpAudioSndCrdStereo[2 * i] = Float2Short ( pSound->vecsTmpAudioSndCrdStereo[2 * i] + - pLeftData[iNumChanPerFrameLeft * i + iSelAddInInterlChLeft] * _MAXSHORT ); - } - } + // selectedDevice.InputIdLeft = inputInfo[selectedInputChannels[0]].deviceID; //????? + // selectedDevice.OutputIdLeft = inputInfo[selectedOutputChannels[0]].deviceID; //????? - if ( iSelAddInBufferRight >= 0 ) - { - pRightData = static_cast ( inInputData->mBuffers[iSelAddInBufferRight].mData ); - iNumChanPerFrameRight = vecNumInBufChan[iSelAddInBufferRight]; + // register the callback function for input and output + bool ok = ( AudioDeviceCreateIOProcID ( selectedDevice.InputDeviceId, _onBufferSwitch, this, &audioInputProcID ) == noErr ); + if ( ok ) + { + ok = ( AudioDeviceCreateIOProcID ( selectedDevice.OutputDeviceId, _onBufferSwitch, this, &audioOutputProcID ) == noErr ); - for ( int i = 0; i < iCoreAudioBufferSizeMono; i++ ) + if ( ok && audioInputProcID && audioOutputProcID && start ) + { + // start the audio stream + ok = ( AudioDeviceStart ( selectedDevice.InputDeviceId, audioInputProcID ) == noErr ); + if ( ok ) + { + ok = ( AudioDeviceStart ( selectedDevice.OutputDeviceId, audioOutputProcID ) == noErr ); + if ( ok ) { - pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] = Float2Short ( - pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] + pRightData[iNumChanPerFrameRight * i + iSelAddInInterlChRight] * _MAXSHORT ); + getCurrentLatency(); + + return true; } + + AudioDeviceStop ( selectedDevice.InputDeviceId, audioInputProcID ); } } - else - { - // incompatible sizes, clear work buffer - pSound->vecsTmpAudioSndCrdStereo.Reset ( 0 ); - } - - // call processing callback function - pSound->ProcessCallback ( pSound->vecsTmpAudioSndCrdStereo ); } - if ( ( inDevice == pSound->CurrentAudioOutputDeviceID ) && outOutputData && pSound->bRun ) + if ( !ok ) { - // check sizes (note that float32 has four bytes) - if ( ( iSelOutBufferLeft >= 0 ) && ( iSelOutBufferLeft < static_cast ( outOutputData->mNumberBuffers ) ) && - ( iSelOutBufferRight >= 0 ) && ( iSelOutBufferRight < static_cast ( outOutputData->mNumberBuffers ) ) && - ( outOutputData->mBuffers[iSelOutBufferLeft].mDataByteSize == - static_cast ( vecNumOutBufChan[iSelOutBufferLeft] * iCoreAudioBufferSizeMono * 4 ) ) && - ( outOutputData->mBuffers[iSelOutBufferRight].mDataByteSize == - static_cast ( vecNumOutBufChan[iSelOutBufferRight] * iCoreAudioBufferSizeMono * 4 ) ) ) + if ( audioInputProcID ) { - Float32* pLeftData = static_cast ( outOutputData->mBuffers[iSelOutBufferLeft].mData ); - Float32* pRightData = static_cast ( outOutputData->mBuffers[iSelOutBufferRight].mData ); - int iNumChanPerFrameLeft = vecNumOutBufChan[iSelOutBufferLeft]; - int iNumChanPerFrameRight = vecNumOutBufChan[iSelOutBufferRight]; - - // copy output data - for ( int i = 0; i < iCoreAudioBufferSizeMono; i++ ) - { - // copy left and right channels separately - pLeftData[iNumChanPerFrameLeft * i + iSelOutInterlChLeft] = (Float32) pSound->vecsTmpAudioSndCrdStereo[2 * i] / _MAXSHORT; - pRightData[iNumChanPerFrameRight * i + iSelOutInterlChRight] = (Float32) pSound->vecsTmpAudioSndCrdStereo[2 * i + 1] / _MAXSHORT; - } + AudioDeviceDestroyIOProcID ( selectedDevice.InputDeviceId, audioInputProcID ); + audioInputProcID = NULL; + } + if ( audioOutputProcID ) + { + AudioDeviceDestroyIOProcID ( selectedDevice.OutputDeviceId, audioOutputProcID ); + audioOutputProcID = NULL; } } - return kAudioHardwareNoError; + return ok; } -void CSound::callbackMIDI ( const MIDIPacketList* pktlist, void* refCon, void* ) +bool CSound::start() { - CSound* pSound = static_cast ( refCon ); + strErrorList.clear(); - if ( pSound->midiInPortRef != static_cast ( NULL ) ) + if ( !IsStarted() ) { - MIDIPacket* midiPacket = const_cast ( pktlist->packet ); - - for ( unsigned int j = 0; j < pktlist->numPackets; j++ ) + if ( prepareAudio ( true ) ) { - // copy packet and send it to the MIDI parser - CVector vMIDIPaketBytes ( midiPacket->length ); - for ( int i = 0; i < midiPacket->length; i++ ) - { - vMIDIPaketBytes[i] = static_cast ( midiPacket->data[i] ); - } - pSound->ParseMIDIMessage ( vMIDIPaketBytes ); - - midiPacket = MIDIPacketNext ( midiPacket ); + return true; + } + else + { + // We failed!, audio is stopped ! + strErrorList.append ( htmlBold ( tr ( "Failed to start your audio device!." ) ) ); + strErrorList.append ( tr ( "Please check your device settings..." ) ); } } + + return false; } -bool CSound::ConvertCFStringToQString ( const CFStringRef stringRef, QString& sOut ) +bool CSound::stop() { - // check if the string reference is a valid pointer - if ( stringRef != NULL ) - { - // first check if the string is not empty - if ( CFStringGetLength ( stringRef ) > 0 ) - { - // convert CFString in c-string (quick hack!) and then in QString - char* sC_strPropValue = (char*) malloc ( CFStringGetLength ( stringRef ) * 3 + 1 ); + strErrorList.clear(); - if ( CFStringGetCString ( stringRef, sC_strPropValue, CFStringGetLength ( stringRef ) * 3 + 1, kCFStringEncodingUTF8 ) ) - { - sOut = sC_strPropValue; - free ( sC_strPropValue ); + if ( IsStarted() ) + { - return true; // OK - } - } + // stop the audio stream + AudioDeviceStop ( selectedDevice.InputDeviceId, audioInputProcID ); + AudioDeviceStop ( selectedDevice.OutputDeviceId, audioOutputProcID ); - // release the string reference because it is not needed anymore - CFRelease ( stringRef ); + // unregister the callback function for input and output + AudioDeviceDestroyIOProcID ( selectedDevice.InputDeviceId, audioInputProcID ); + AudioDeviceDestroyIOProcID ( selectedDevice.OutputDeviceId, audioOutputProcID ); } - return false; // not OK + return true; } diff --git a/mac/sound.h b/mac/sound.h index e549760e79..9f129f1042 100644 --- a/mac/sound.h +++ b/mac/sound.h @@ -2,7 +2,7 @@ * Copyright (c) 2004-2022 * * Author(s): - * Volker Fischer + * Volker Fischer, revised and maintained by Peter Goderie (pgScorpio) * ****************************************************************************** * @@ -24,112 +24,255 @@ #pragma once +#ifdef _WIN32 +# define NO_COMPILING +#endif + +#ifdef NO_COMPILING +// just for writing code on Windows Visual Studio (No building or debugging, just syntax checking !) +// macOS header files folder must be in additional include path ! +# define __x86_64__ +# define __BIG_ENDIAN__ +# define __WATCHOS_PROHIBITED +# define __TVOS_PROHIBITED +# define Float32 float +# define __attribute__( a ) +# include +# include +# include +# include +# include +#endif + #include #include #include #include #include + #include "soundbase.h" #include "global.h" +// Values for bool bInput/bIsInput +#define biINPUT true +#define biOUTPUT false + +// Values for bool bOutput/bIsOutput +#define boOUTPUT true +#define boINPUT false + /* Classes ********************************************************************/ class CSound : public CSoundBase { Q_OBJECT -public: - CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* arg ), - void* arg, - const QString& strMIDISetup, - const bool, - const QString& ); - - virtual int Init ( const int iNewPrefMonoBufferSize ); - virtual void Start(); - virtual void Stop(); - - // channel selection - virtual int GetNumInputChannels() { return iNumInChanPlusAddChan; } - virtual QString GetInputChannelName ( const int iDiD ) { return sChannelNamesInput[iDiD]; } - virtual void SetLeftInputChannel ( const int iNewChan ); - virtual void SetRightInputChannel ( const int iNewChan ); - virtual int GetLeftInputChannel() { return iSelInputLeftChannel; } - virtual int GetRightInputChannel() { return iSelInputRightChannel; } - - virtual int GetNumOutputChannels() { return iNumOutChan; } - virtual QString GetOutputChannelName ( const int iDiD ) { return sChannelNamesOutput[iDiD]; } - virtual void SetLeftOutputChannel ( const int iNewChan ); - virtual void SetRightOutputChannel ( const int iNewChan ); - virtual int GetLeftOutputChannel() { return iSelOutputLeftChannel; } - virtual int GetRightOutputChannel() { return iSelOutputRightChannel; } - - // these variables/functions should be protected but cannot since we want - // to access them from the callback function - CVector vecsTmpAudioSndCrdStereo; - int iCoreAudioBufferSizeMono; - int iCoreAudioBufferSizeStereo; - AudioDeviceID CurrentAudioInputDeviceID; - AudioDeviceID CurrentAudioOutputDeviceID; - long lCurDev; - int iNumInChan; - int iNumInChanPlusAddChan; // includes additional "added" channels - int iNumOutChan; - int iSelInputLeftChannel; - int iSelInputRightChannel; - int iSelOutputLeftChannel; - int iSelOutputRightChannel; - int iSelInBufferLeft; - int iSelInBufferRight; - int iSelInInterlChLeft; - int iSelInInterlChRight; - int iSelAddInBufferLeft; - int iSelAddInBufferRight; - int iSelAddInInterlChLeft; - int iSelAddInInterlChRight; - int iSelOutBufferLeft; - int iSelOutBufferRight; - int iSelOutInterlChLeft; - int iSelOutInterlChRight; - CVector vecNumInBufChan; - CVector vecNumOutBufChan; +protected: + // Audio Channels + + class cChannelInfo + { + public: + cChannelInfo() : iListIndex ( -1 ), isInput ( false ), DeviceId ( 0 ), strName(), iBuffer ( -1 ), iChanPerFrame ( -1 ), iInterlCh ( -1 ) {} + + public: + // All values are obtained by getAvailableChannels(DeviceId, isInput, CVector& list): + // Just for validation and debugging: + int iListIndex; // index in input/output list + bool isInput; // input (true) or output (false) + AudioDeviceID DeviceId; // input/output DeviceId + + // Actually needed values: + QString strName; // Channel name to be added to strInputChannelNames/strOutputChannelNames on device selection + int iBuffer; // Buffer number on device + int iChanPerFrame; // Number of channels interlaced. + int iInterlCh; // Interlace channel number in Buffer + + bool IsValid ( bool asAdded ); // Checks if this ChannelInfo is valid as normal Input or Output or as Added Input + bool IsValidFor ( AudioDeviceID forDeviceId, + bool asInput, + bool asAdded ); // Checks if this ChannelInfo is valid as a specific Input or Output or Added Input + }; + + static cChannelInfo noChannelInfo; + + class cSelectedDevice + { + public: + cSelectedDevice() : + Activated ( false ), + InputDeviceId ( 0 ), + InLeft ( noChannelInfo ), + InRight ( noChannelInfo ), + AddInLeft ( noChannelInfo ), + AddInRight ( noChannelInfo ), + OutputDeviceId ( 0 ), + OutLeft ( noChannelInfo ), + OutRight ( noChannelInfo ) + {} + + void clear() + { + Activated = false; + InputDeviceId = 0; + InLeft = noChannelInfo; + InRight = noChannelInfo; + AddInLeft = noChannelInfo; + AddInRight = noChannelInfo; + OutputDeviceId = 0; + OutLeft = noChannelInfo; + OutRight = noChannelInfo; + } + + bool IsActive() { return Activated; } + + public: + bool Activated; // Is this cSelectedDevice set as active device ? (Set by setSelectedDevice, cleared by clear() ) + + AudioDeviceID InputDeviceId; + + cChannelInfo InLeft; + cChannelInfo InRight; + + cChannelInfo AddInLeft; + cChannelInfo AddInRight; + + AudioDeviceID OutputDeviceId; + + cChannelInfo OutLeft; + cChannelInfo OutRight; + }; + + cSelectedDevice selectedDevice; + + bool setSelectedDevice(); // sets selectedDevice according current selections in CSoundBase protected: - virtual QString LoadAndInitializeDriver ( QString strDriverName, bool ); + // Audio Devices + + // Input/Output device combinations for device selection + // Filled by createDeviceList, + // Device names should be stored in strDeviceNames and lNumDevices should be set according these lists sizes. - QString CheckDeviceCapabilities ( const int iDriverIdx ); - void UpdateChSelection(); - void GetAvailableInOutDevices(); + // input/output device info filled by createDeviceList + class cDeviceInfo + { + public: + cDeviceInfo() : InputDeviceId ( 0 ), lNumInChan ( 0 ), lNumAddedInChan ( 0 ), input(), OutputDeviceId ( 0 ), lNumOutChan ( 0 ), output(){}; - int CountChannels ( AudioDeviceID devID, bool isInput ); + bool IsValid(); - UInt32 SetBufferSize ( AudioDeviceID& audioDeviceID, const bool bIsInput, UInt32 iPrefBufferSize ); + public: + AudioDeviceID InputDeviceId; // DeviceId of the inputs device + long lNumInChan; // Should be set to input.Size() + long lNumAddedInChan; // Number of additional input channels added for mixed input channels. Set by capability check. + CVector input; // Input channel list + // + AudioDeviceID OutputDeviceId; // DeviceId of the outputs device + long lNumOutChan; // Should be set to output.Size() + CVector output; // Output channel list + }; - void GetAudioDeviceInfos ( const AudioDeviceID DeviceID, QString& strDeviceName, bool& bIsInput, bool& bIsOutput ); + CVector device; - bool ConvertCFStringToQString ( const CFStringRef stringRef, QString& sOut ); + bool getDeviceChannelInfo ( cDeviceInfo& deviceInfo, bool bInput ); // Retrieves DeviceInfo.input or DeviceInfo.output +public: + CSound ( void ( *theProcessCallback ) ( CVector& psData, void* arg ), void* theProcessCallbackArg ); + +#ifdef OLD_SOUND_COMPATIBILITY + // Backwards compatibility constructor + CSound ( void ( *theProcessCallback ) ( CVector& psData, void* pCallbackArg ), + void* theProcessCallbackArg, + QString strMIDISetup, + bool bNoAutoJackConnect, + QString strNClientName ); +#endif + +protected: + MIDIPortRef midiInPortRef; + +protected: + OSStatus onDeviceNotification ( AudioDeviceID idDevice, UInt32 unknown, const AudioObjectPropertyAddress* inAddresses ); + + OSStatus onBufferSwitch ( AudioDeviceID deviceID, + const AudioTimeStamp* pAudioTimeStamp1, + const AudioBufferList* inInputData, + const AudioTimeStamp* pAudioTimeStamp2, + AudioBufferList* outOutputData, + const AudioTimeStamp* pAudioTimeStamp3 ); + + void onMIDI ( const MIDIPacketList* pktlist ); + +protected: // callbacks - static OSStatus deviceNotification ( AudioDeviceID, UInt32, const AudioObjectPropertyAddress* inAddresses, void* inRefCon ); - static OSStatus callbackIO ( AudioDeviceID inDevice, - const AudioTimeStamp*, - const AudioBufferList* inInputData, - const AudioTimeStamp*, - AudioBufferList* outOutputData, - const AudioTimeStamp*, - void* inRefCon ); + static OSStatus _onDeviceNotification ( AudioDeviceID deviceID, + UInt32 unknown, + const AudioObjectPropertyAddress* inAddresses, + void* /* inRefCon */ ) + { + return pSound->onDeviceNotification ( deviceID, unknown, inAddresses ); + } - static void callbackMIDI ( const MIDIPacketList* pktlist, void* refCon, void* ); + static OSStatus _onBufferSwitch ( AudioDeviceID inDevice, + const AudioTimeStamp* pAudioTimeStamp1, + const AudioBufferList* inInputData, + const AudioTimeStamp* pAudioTimeStamp2, + AudioBufferList* outOutputData, + const AudioTimeStamp* pAudioTimeStamp3, + void* /* inRefCon */ ) + { + return pSound->onBufferSwitch ( inDevice, pAudioTimeStamp1, inInputData, pAudioTimeStamp2, outOutputData, pAudioTimeStamp3 ); + } - AudioDeviceID audioInputDevice[MAX_NUMBER_SOUND_CARDS]; - AudioDeviceID audioOutputDevice[MAX_NUMBER_SOUND_CARDS]; - AudioDeviceIOProcID audioInputProcID; - AudioDeviceIOProcID audioOutputProcID; + // typedef void (*MIDIReadProc)(const MIDIPacketList* pktlist, void* __nullable readProcRefCon, void* __nullable srcConnRefCon); !!?? + static void _onMIDI ( const MIDIPacketList* pktlist, void* /* readProcRefCon */, void* /* srcConnRefCon */ ) { pSound->onMIDI ( pktlist ); } - MIDIPortRef midiInPortRef; + void registerDeviceCallBacks(); + void unregisterDeviceCallBacks(); + +protected: + void setBaseChannelInfo ( cDeviceInfo& deviceSelection ); + bool setBufferSize ( AudioDeviceID& audioInDeviceID, + AudioDeviceID& audioOutDeviceID, + unsigned int iPrefBufferSize, + unsigned int& iActualBufferSize ); + bool CheckDeviceCapabilities ( cDeviceInfo& deviceSelection ); + bool getCurrentLatency(); + +protected: + // For Start: + + bool prepareAudio ( bool start ); + + AudioDeviceIOProcID audioInputProcID; // AudioDeviceIOProcID for current input device + AudioDeviceIOProcID audioOutputProcID; // AudioDeviceIOProcID for current output device + + //============================================================================ + // Virtual interface to CSoundBase: + //============================================================================ +protected: // CSoundBase Mandatory pointer to instance (must be set to 'this' in CSound constructor) + static CSound* pSound; + +public: // CSoundBase Mandatory functions. (but static functions can't be virtual) + static inline CSoundBase* pInstance() { return pSound; } + static inline const CSoundProperties& GetProperties() { return pSound->getSoundProperties(); } + +protected: + // CSoundBase internal + virtual void onChannelSelectionChanged() + { + CSoundStopper sound ( *this ); + setSelectedDevice(); + } + + virtual long createDeviceList ( bool bRescan = false ); // Fills strDeviceList. and my device list. Returns number of devices found + virtual bool checkDeviceChange ( CSoundBase::tDeviceChangeCheck mode, int iDriverIndex ); // Open device sequence handling.... + virtual unsigned int getDeviceBufferSize ( unsigned int iDesiredBufferSize ); - QString sChannelNamesInput[MAX_NUM_IN_OUT_CHANNELS]; - QString sChannelNamesOutput[MAX_NUM_IN_OUT_CHANNELS]; + virtual void closeCurrentDevice(); // Closes the current driver and Clears Device Info + virtual bool openDeviceSetup() { return false; } - QMutex Mutex; + virtual bool start(); + virtual bool stop(); }; diff --git a/src/global.h b/src/global.h index 22ae0c52bf..bd87ddcd04 100644 --- a/src/global.h +++ b/src/global.h @@ -358,19 +358,211 @@ class CCustomEvent : public QEvent int iChanNum; }; -/* Prototypes for global functions ********************************************/ -// command line parsing, TODO do not declare functions globally but in a class -QString UsageArguments ( char** argv ); - -bool GetFlagArgument ( char** argv, int& i, QString strShortOpt, QString strLongOpt ); - -bool GetStringArgument ( int argc, char** argv, int& i, QString strShortOpt, QString strLongOpt, QString& strArg ); - -bool GetNumericArgument ( int argc, - char** argv, - int& i, - QString strShortOpt, - QString strLongOpt, - double rRangeStart, - double rRangeStop, - double& rValue ); +//============================================================================ +// CMsgBoxes class: +// Use this static class to show basic Error, Warning and Info messageboxes +// For own created message boxes you should still use +// CMsgBoxes::MainForm() and CMsgBoxes::MainFormName() +//============================================================================ +#ifndef HEADLESS +# include +# define tMainform QDialog +#else +# define tMainform void +#endif + +// html text macro's (for use in message texts) +#define htmlBold( T ) "" + T + "" +#define htmlNewLine() "
" + +class CMsgBoxes +{ +protected: + static tMainform* pMainForm; + static QString strMainFormName; + +public: + static void init ( tMainform* theMainForm, QString theMainFormName ) + { + pMainForm = theMainForm; + strMainFormName = theMainFormName; + } + + static tMainform* MainForm() { return pMainForm; } + static const QString& MainFormName() { return strMainFormName; } + + // Message boxes: + static void ShowError ( QString strError ); + static void ShowWarning ( QString strWarning ); + static void ShowInfo ( QString strInfo ); +}; + +//============================================================================ +// CCommandline class: +// Note that passing commandline arguments to classes is no longer required, +// since via this class we can get commandline options anywhere. +//============================================================================ + +class CCommandline +{ +public: + CCommandline() { reset(); } + +private: + friend int main ( int argc, char** argv ); + + // Statics assigned from main () + static int argc; + static char** argv; + +public: + static QString GetProgramPath() { return QString ( *argv ); } + +public: + // sequencial parse functions using the argument index: + + static bool GetFlagArgument ( int& i, const QString& strShortOpt, const QString& strLongOpt ); + + static bool GetStringArgument ( int& i, const QString& strShortOpt, const QString& strLongOpt, QString& strArg ); + + static bool GetNumericArgument ( int& i, + const QString& strShortOpt, + const QString& strLongOpt, + double rRangeStart, + double rRangeStop, + double& rValue ); + +public: + // find and get a specific argument: + + static bool GetFlagArgument ( const QString& strShortOpt, const QString& strLongOpt ) + { + for ( int i = 1; i < argc; i++ ) + { + if ( GetFlagArgument ( i, strShortOpt, strLongOpt ) ) + { + return true; + } + } + + return false; + } + + static bool GetStringArgument ( const QString& strShortOpt, const QString& strLongOpt, QString& strArg ) + { + for ( int i = 1; i < argc; i++ ) + { + if ( GetStringArgument ( i, strShortOpt, strLongOpt, strArg ) ) + { + return true; + } + } + + return false; + } + + static bool GetNumericArgument ( const QString& strShortOpt, const QString& strLongOpt, double rRangeStart, double rRangeStop, double& rValue ) + { + for ( int i = 1; i < argc; i++ ) + { + if ( GetNumericArgument ( i, strShortOpt, strLongOpt, rRangeStart, rRangeStop, rValue ) ) + { + return true; + } + } + + return false; + } + + //================================================= + // Non statics to parse bare arguments + // (These need an instance of CCommandline) + //================================================= + +protected: + int currentIndex; + char** currentArgv; + + void reset() + { + currentArgv = argv; + currentIndex = 0; + } + +public: + QString GetFirstArgument() + { + reset(); + // Skipping program path + return GetNextArgument(); + } + + QString GetNextArgument() + { + if ( currentIndex < argc ) + { + currentArgv++; + currentIndex++; + + if ( currentIndex < argc ) + { + return QString ( *currentArgv ); + } + } + + return QString(); + } +}; + +// defines for commandline options in the style "shortopt", "longopt" +// Name is standard CMDLN_LONGOPTNAME +// These defines can be used for strShortOpt, strLongOpt parameters +// of the CCommandline functions. + +// clang-format off + +#define CMDLN_SERVER "-s", "--server" +#define CMDLN_INIFILE "-i", "--inifile" +#define CMDLN_NOGUI "-n", "--nogui" +#define CMDLN_PORT "-p", "--port" +#define CMDLN_JSONRPCPORT "--jsonrpcport", "--jsonrpcport" +#define CMDLN_JSONRPCSECRETFILE "--jsonrpcsecretfile", "--jsonrpcsecretfile" +#define CMDLN_QOS "-Q", "--qos" +#define CMDLN_NOTRANSLATION "-t", "--notranslation" +#define CMDLN_ENABLEIPV6 "-6", "--enableipv6" +#define CMDLN_DISCONONQUIT "-d", "--discononquit" +#define CMDLN_DIRECTORYSERVER "-e", "--directoryserver" +#define CMDLN_DIRECTORYFILE "--directoryfile", "--directoryfile" +#define CMDLN_LISTFILTER "-f", "--listfilter" +#define CMDLN_FASTUPDATE "-F", "--fastupdate" +#define CMDLN_LOG "-l", "--log" +#define CMDLN_LICENCE "-L", "--licence" +#define CMDLN_HTMLSTATUS "-m", "--htmlstatus" +#define CMDLN_SERVERINFO "-o", "--serverinfo" +#define CMDLN_SERVERPUBLICIP "--serverpublicip", "--serverpublicip" +#define CMDLN_DELAYPAN "-P", "--delaypan" +#define CMDLN_RECORDING "-R", "--recording" +#define CMDLN_NORECORD "--norecord", "--norecord" +#define CMDLN_SERVERBINDIP "--serverbindip", "--serverbindip" +#define CMDLN_MULTITHREADING "-T", "--multithreading" +#define CMDLN_NUMCHANNELS "-u", "--numchannels" +#define CMDLN_WELCOMEMESSAGE "-w", "--welcomemessage" +#define CMDLN_STARTMINIMIZED "-z", "--startminimized" +#define CMDLN_CONNECT "-c", "--connect" +#define CMDLN_NOJACKCONNECT "-j", "--nojackconnect" +#define CMDLN_MUTESTREAM "-M", "--mutestream" +#define CMDLN_MUTEMYOWN "--mutemyown", "--mutemyown" +#define CMDLN_CLIENTNAME "--clientname", "--clientname" +#define CMDLN_CTRLMIDICH "--ctrlmidich", "--ctrlmidich" +// Backwards compatibilyty: +#define CMDLN_CENTRALSERVER "--centralserver", "--centralserver" +// Debug options: (not in help) +#define CMDLN_SHOWALLSERVERS "--showallservers", "--showallservers" +#define CMDLN_SHOWANALYZERCONSOLE "--showanalyzerconsole", "--showanalyzerconsole" +// CMDLN_SPECIAL: Used for debugging, should NOT be in help, nor documented elsewhere! +// any option after --special is accepted +#define CMDLN_SPECIAL "--special", "--special" +// Special options for sound-redesign testing +#define CMDLN_JACKINPUTS "--jackinputs", "--jackinputs" + +// clang-format on \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 0bc792376b..7100484ce5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -53,10 +53,24 @@ extern void qt_set_sequence_auto_mnemonic ( bool bEnable ); # include "clientrpc.h" #endif +// Forward declarations ******************************************************** + +QString UsageArguments ( char** argv ); + // Implementation ************************************************************** +int CCommandline::argc = 0; +char** CCommandline::argv = NULL; + +tMainform* CMsgBoxes::pMainForm = NULL; +QString CMsgBoxes::strMainFormName = APP_NAME; + int main ( int argc, char** argv ) { + CCommandline::argc = argc; + CCommandline::argv = argv; + + CCommandline cmdLine; // We don't really need an instance here, but using one improves the readability of the code. #if defined( Q_OS_MACX ) // Mnemonic keys are default disabled in Qt for MacOS. The following function enables them. @@ -79,6 +93,7 @@ int main ( int argc, char** argv ) #else bool bIsClient = true; #endif + bool bSpecialOptions = false; // Any options after this option will be accepted ! (mostly used for debugging purpouses) bool bUseGUI = true; bool bStartMinimized = false; bool bShowComplRegConnList = false; @@ -149,7 +164,7 @@ int main ( int argc, char** argv ) // Common options: // Initialization file ------------------------------------------------- - if ( GetStringArgument ( argc, argv, i, "-i", "--inifile", strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_INIFILE, strArgument ) ) { strIniFileName = strArgument; qInfo() << qUtf8Printable ( QString ( "- initialization file name: %1" ).arg ( strIniFileName ) ); @@ -158,7 +173,7 @@ int main ( int argc, char** argv ) } // Disable GUI flag ---------------------------------------------------- - if ( GetFlagArgument ( argv, i, "-n", "--nogui" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_NOGUI ) ) { bUseGUI = false; qInfo() << "- no GUI mode chosen"; @@ -167,7 +182,7 @@ int main ( int argc, char** argv ) } // Port number --------------------------------------------------------- - if ( GetNumericArgument ( argc, argv, i, "-p", "--port", 0, 65535, rDbleArgument ) ) + if ( cmdLine.GetNumericArgument ( i, CMDLN_PORT, 0, 65535, rDbleArgument ) ) { iPortNumber = static_cast ( rDbleArgument ); bCustomPortNumberGiven = true; @@ -177,7 +192,7 @@ int main ( int argc, char** argv ) } // JSON-RPC port number ------------------------------------------------ - if ( GetNumericArgument ( argc, argv, i, "--jsonrpcport", "--jsonrpcport", 0, 65535, rDbleArgument ) ) + if ( cmdLine.GetNumericArgument ( i, CMDLN_JSONRPCPORT, 0, 65535, rDbleArgument ) ) { iJsonRpcPortNumber = static_cast ( rDbleArgument ); qInfo() << qUtf8Printable ( QString ( "- JSON-RPC port number: %1" ).arg ( iJsonRpcPortNumber ) ); @@ -186,7 +201,7 @@ int main ( int argc, char** argv ) } // JSON-RPC secret file name ------------------------------------------- - if ( GetStringArgument ( argc, argv, i, "--jsonrpcsecretfile", "--jsonrpcsecretfile", strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_JSONRPCSECRETFILE, strArgument ) ) { strJsonRpcSecretFileName = strArgument; qInfo() << qUtf8Printable ( QString ( "- JSON-RPC secret file: %1" ).arg ( strJsonRpcSecretFileName ) ); @@ -195,7 +210,7 @@ int main ( int argc, char** argv ) } // Quality of Service -------------------------------------------------- - if ( GetNumericArgument ( argc, argv, i, "-Q", "--qos", 0, 255, rDbleArgument ) ) + if ( cmdLine.GetNumericArgument ( i, CMDLN_QOS, 0, 255, rDbleArgument ) ) { iQosNumber = static_cast ( rDbleArgument ); qInfo() << qUtf8Printable ( QString ( "- selected QoS value: %1" ).arg ( iQosNumber ) ); @@ -204,7 +219,7 @@ int main ( int argc, char** argv ) } // Disable translations ------------------------------------------------ - if ( GetFlagArgument ( argv, i, "-t", "--notranslation" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_NOTRANSLATION ) ) { bUseTranslation = false; qInfo() << "- translations disabled"; @@ -213,7 +228,7 @@ int main ( int argc, char** argv ) } // Enable IPv6 --------------------------------------------------------- - if ( GetFlagArgument ( argv, i, "-6", "--enableipv6" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_ENABLEIPV6 ) ) { bEnableIPv6 = true; qInfo() << "- IPv6 enabled"; @@ -224,7 +239,7 @@ int main ( int argc, char** argv ) // Server only: // Disconnect all clients on quit -------------------------------------- - if ( GetFlagArgument ( argv, i, "-d", "--discononquit" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_DISCONONQUIT ) ) { bDisconnectAllClientsOnQuit = true; qInfo() << "- disconnect all clients on quit"; @@ -234,7 +249,7 @@ int main ( int argc, char** argv ) } // Directory server ---------------------------------------------------- - if ( GetStringArgument ( argc, argv, i, "-e", "--directoryserver", strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_DIRECTORYSERVER, strArgument ) ) { strDirectoryServer = strArgument; qInfo() << qUtf8Printable ( QString ( "- directory server: %1" ).arg ( strDirectoryServer ) ); @@ -244,12 +259,7 @@ int main ( int argc, char** argv ) } // Central server ** D E P R E C A T E D ** ---------------------------- - if ( GetStringArgument ( argc, - argv, - i, - "--centralserver", // no short form - "--centralserver", - strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_CENTRALSERVER, strArgument ) ) { strDirectoryServer = strArgument; qInfo() << qUtf8Printable ( QString ( "- directory server: %1" ).arg ( strDirectoryServer ) ); @@ -259,12 +269,7 @@ int main ( int argc, char** argv ) } // Directory file ------------------------------------------------------ - if ( GetStringArgument ( argc, - argv, - i, - "--directoryfile", // no short form - "--directoryfile", - strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_DIRECTORYFILE, strArgument ) ) { strServerListFileName = strArgument; qInfo() << qUtf8Printable ( QString ( "- directory server persistence file: %1" ).arg ( strServerListFileName ) ); @@ -274,7 +279,7 @@ int main ( int argc, char** argv ) } // Server list filter -------------------------------------------------- - if ( GetStringArgument ( argc, argv, i, "-f", "--listfilter", strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_LISTFILTER, strArgument ) ) { strServerListFilter = strArgument; qInfo() << qUtf8Printable ( QString ( "- server list filter: %1" ).arg ( strServerListFilter ) ); @@ -284,7 +289,7 @@ int main ( int argc, char** argv ) } // Use 64 samples frame size mode -------------------------------------- - if ( GetFlagArgument ( argv, i, "-F", "--fastupdate" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_FASTUPDATE ) ) { bUseDoubleSystemFrameSize = false; // 64 samples frame size qInfo() << qUtf8Printable ( QString ( "- using %1 samples frame size mode" ).arg ( SYSTEM_FRAME_SIZE_SAMPLES ) ); @@ -294,7 +299,7 @@ int main ( int argc, char** argv ) } // Use logging --------------------------------------------------------- - if ( GetStringArgument ( argc, argv, i, "-l", "--log", strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_LOG, strArgument ) ) { strLoggingFileName = strArgument; qInfo() << qUtf8Printable ( QString ( "- logging file name: %1" ).arg ( strLoggingFileName ) ); @@ -304,7 +309,7 @@ int main ( int argc, char** argv ) } // Use licence flag ---------------------------------------------------- - if ( GetFlagArgument ( argv, i, "-L", "--licence" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_LICENCE ) ) { // LT_CREATIVECOMMONS is now used just to enable the pop up eLicenceType = LT_CREATIVECOMMONS; @@ -315,7 +320,7 @@ int main ( int argc, char** argv ) } // HTML status file ---------------------------------------------------- - if ( GetStringArgument ( argc, argv, i, "-m", "--htmlstatus", strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_HTMLSTATUS, strArgument ) ) { strHTMLStatusFileName = strArgument; qInfo() << qUtf8Printable ( QString ( "- HTML status file name: %1" ).arg ( strHTMLStatusFileName ) ); @@ -325,7 +330,7 @@ int main ( int argc, char** argv ) } // Server info --------------------------------------------------------- - if ( GetStringArgument ( argc, argv, i, "-o", "--serverinfo", strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_SERVERINFO, strArgument ) ) { strServerInfo = strArgument; qInfo() << qUtf8Printable ( QString ( "- server info: %1" ).arg ( strServerInfo ) ); @@ -335,12 +340,7 @@ int main ( int argc, char** argv ) } // Server Public IP ---------------------------------------------------- - if ( GetStringArgument ( argc, - argv, - i, - "--serverpublicip", // no short form - "--serverpublicip", - strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_SERVERPUBLICIP, strArgument ) ) { strServerPublicIP = strArgument; qInfo() << qUtf8Printable ( QString ( "- server public IP: %1" ).arg ( strServerPublicIP ) ); @@ -350,7 +350,7 @@ int main ( int argc, char** argv ) } // Enable delay panning on startup ------------------------------------- - if ( GetFlagArgument ( argv, i, "-P", "--delaypan" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_DELAYPAN ) ) { bDelayPan = true; qInfo() << "- starting with delay panning"; @@ -360,7 +360,7 @@ int main ( int argc, char** argv ) } // Recording directory ------------------------------------------------- - if ( GetStringArgument ( argc, argv, i, "-R", "--recording", strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_RECORDING, strArgument ) ) { strRecordingDirName = strArgument; qInfo() << qUtf8Printable ( QString ( "- recording directory name: %1" ).arg ( strRecordingDirName ) ); @@ -370,10 +370,7 @@ int main ( int argc, char** argv ) } // Disable recording on startup ---------------------------------------- - if ( GetFlagArgument ( argv, - i, - "--norecord", // no short form - "--norecord" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_NORECORD ) ) { bDisableRecording = true; qInfo() << "- recording will not take place until enabled"; @@ -383,7 +380,7 @@ int main ( int argc, char** argv ) } // Server mode flag ---------------------------------------------------- - if ( GetFlagArgument ( argv, i, "-s", "--server" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_SERVER ) ) { bIsClient = false; qInfo() << "- server mode chosen"; @@ -393,12 +390,7 @@ int main ( int argc, char** argv ) } // Server Bind IP -------------------------------------------------- - if ( GetStringArgument ( argc, - argv, - i, - "--serverbindip", // no short form - "--serverbindip", - strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_SERVERBINDIP, strArgument ) ) { strServerBindIP = strArgument; qInfo() << qUtf8Printable ( QString ( "- server bind IP: %1" ).arg ( strServerBindIP ) ); @@ -408,7 +400,7 @@ int main ( int argc, char** argv ) } // Use multithreading -------------------------------------------------- - if ( GetFlagArgument ( argv, i, "-T", "--multithreading" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_MULTITHREADING ) ) { bUseMultithreading = true; qInfo() << "- using multithreading"; @@ -418,7 +410,7 @@ int main ( int argc, char** argv ) } // Maximum number of channels ------------------------------------------ - if ( GetNumericArgument ( argc, argv, i, "-u", "--numchannels", 1, MAX_NUM_CHANNELS, rDbleArgument ) ) + if ( cmdLine.GetNumericArgument ( i, CMDLN_NUMCHANNELS, 1, MAX_NUM_CHANNELS, rDbleArgument ) ) { iNumServerChannels = static_cast ( rDbleArgument ); @@ -430,7 +422,7 @@ int main ( int argc, char** argv ) } // Server welcome message ---------------------------------------------- - if ( GetStringArgument ( argc, argv, i, "-w", "--welcomemessage", strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_WELCOMEMESSAGE, strArgument ) ) { strWelcomeMessage = strArgument; qInfo() << qUtf8Printable ( QString ( "- welcome message: %1" ).arg ( strWelcomeMessage ) ); @@ -440,7 +432,7 @@ int main ( int argc, char** argv ) } // Start minimized ----------------------------------------------------- - if ( GetFlagArgument ( argv, i, "-z", "--startminimized" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_STARTMINIMIZED ) ) { bStartMinimized = true; qInfo() << "- start minimized enabled"; @@ -452,7 +444,7 @@ int main ( int argc, char** argv ) // Client only: // Connect on startup -------------------------------------------------- - if ( GetStringArgument ( argc, argv, i, "-c", "--connect", strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_CONNECT, strArgument ) ) { strConnOnStartupAddress = NetworkUtil::FixAddress ( strArgument ); qInfo() << qUtf8Printable ( QString ( "- connect on startup to address: %1" ).arg ( strConnOnStartupAddress ) ); @@ -462,7 +454,7 @@ int main ( int argc, char** argv ) } // Disabling auto Jack connections ------------------------------------- - if ( GetFlagArgument ( argv, i, "-j", "--nojackconnect" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_NOJACKCONNECT ) ) { bNoAutoJackConnect = true; qInfo() << "- disable auto Jack connections"; @@ -472,7 +464,7 @@ int main ( int argc, char** argv ) } // Mute stream on startup ---------------------------------------------- - if ( GetFlagArgument ( argv, i, "-M", "--mutestream" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_MUTESTREAM ) ) { bMuteStream = true; qInfo() << "- mute stream activated"; @@ -482,10 +474,7 @@ int main ( int argc, char** argv ) } // For headless client mute my own signal in personal mix -------------- - if ( GetFlagArgument ( argv, - i, - "--mutemyown", // no short form - "--mutemyown" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_MUTEMYOWN ) ) { bMuteMeInPersonalMix = true; qInfo() << "- mute me in my personal mix"; @@ -495,12 +484,7 @@ int main ( int argc, char** argv ) } // Client Name --------------------------------------------------------- - if ( GetStringArgument ( argc, - argv, - i, - "--clientname", // no short form - "--clientname", - strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_CLIENTNAME, strArgument ) ) { strClientName = strArgument; qInfo() << qUtf8Printable ( QString ( "- client name: %1" ).arg ( strClientName ) ); @@ -510,12 +494,7 @@ int main ( int argc, char** argv ) } // Controller MIDI channel --------------------------------------------- - if ( GetStringArgument ( argc, - argv, - i, - "--ctrlmidich", // no short form - "--ctrlmidich", - strArgument ) ) + if ( cmdLine.GetStringArgument ( i, CMDLN_CTRLMIDICH, strArgument ) ) { strMIDISetup = strArgument; qInfo() << qUtf8Printable ( QString ( "- MIDI controller settings: %1" ).arg ( strMIDISetup ) ); @@ -530,10 +509,7 @@ int main ( int argc, char** argv ) // Undocumented debugging command line argument: Show all registered // servers in the server list regardless if a ping to the server is // possible or not. - if ( GetFlagArgument ( argv, - i, - "--showallservers", // no short form - "--showallservers" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_SHOWALLSERVERS ) ) { bShowComplRegConnList = true; qInfo() << "- show all registered servers in server list"; @@ -545,10 +521,7 @@ int main ( int argc, char** argv ) // Show analyzer console ----------------------------------------------- // Undocumented debugging command line argument: Show the analyzer // console to debug network buffer properties. - if ( GetFlagArgument ( argv, - i, - "--showanalyzerconsole", // no short form - "--showanalyzerconsole" ) ) + if ( cmdLine.GetFlagArgument ( i, CMDLN_SHOWANALYZERCONSOLE ) ) { bShowAnalyzerConsole = true; qInfo() << "- show analyzer console"; @@ -557,14 +530,27 @@ int main ( int argc, char** argv ) continue; } - // Unknown option ------------------------------------------------------ - qCritical() << qUtf8Printable ( QString ( "%1: Unknown option '%2' -- use '--help' for help" ).arg ( argv[0] ).arg ( argv[i] ) ); + // Enable Special Options ---------------------------------------------- + if ( cmdLine.GetFlagArgument ( i, CMDLN_SPECIAL ) ) + { + bSpecialOptions = true; + qInfo() << "- Special options enabled !"; + continue; + } + + // No exit for options after the "--special" option. + // Used for debugging and testing new options... + if ( !bSpecialOptions ) + { + // Unknown option ------------------------------------------------------ + qCritical() << qUtf8Printable ( QString ( "%1: Unknown option '%2' -- use '--help' for help" ).arg ( argv[0] ).arg ( argv[i] ) ); // clicking on the Mac application bundle, the actual application // is called with weird command line args -> do not exit on these #if !( defined( Q_OS_MACX ) ) - exit ( 1 ); + exit ( 1 ); #endif + } } // Dependencies ------------------------------------------------------------ @@ -596,8 +582,7 @@ int main ( int argc, char** argv ) if ( ServerOnlyOptions.size() != 0 ) { qCritical() << qUtf8Printable ( QString ( "%1: Server only option(s) '%2' used. Did you omit '--server'?" ) - .arg ( argv[0] ) - .arg ( ServerOnlyOptions.join ( ", " ) ) ); + .arg ( CCommandline::GetProgramPath(), ServerOnlyOptions.join ( ", " ) ) ); exit ( 1 ); } @@ -901,6 +886,9 @@ int main ( int argc, char** argv ) bEnableIPv6, nullptr ); + // initialise message boxes + CMsgBoxes::init ( &ClientDlg, strClientName.isEmpty() ? QString ( APP_NAME ) : QString ( APP_NAME ) + " " + strClientName ); + // show dialog ClientDlg.show(); pApp->exec(); @@ -911,6 +899,9 @@ int main ( int argc, char** argv ) // only start application without using the GUI qInfo() << qUtf8Printable ( GetVersionAndNameStr ( false ) ); + // initialise message boxes + CMsgBoxes::init ( NULL, strClientName.isEmpty() ? QString ( APP_NAME ) : QString ( APP_NAME ) + " " + strClientName ); + pApp->exec(); } } @@ -961,6 +952,9 @@ int main ( int argc, char** argv ) // GUI object for the server CServerDlg ServerDlg ( &Server, &Settings, bStartMinimized, nullptr ); + // initialise message boxes + CMsgBoxes::init ( &ServerDlg, strClientName.isEmpty() ? QString ( APP_NAME ) : QString ( APP_NAME ) + " " + strClientName ); + // show dialog (if not the minimized flag is set) if ( !bStartMinimized ) { @@ -982,6 +976,9 @@ int main ( int argc, char** argv ) Server.SetDirectoryType ( AT_CUSTOM ); } + // initialise message boxes + CMsgBoxes::init ( NULL, strClientName.isEmpty() ? QString ( APP_NAME ) : QString ( APP_NAME ) + " " + strClientName ); + pApp->exec(); } } @@ -993,7 +990,7 @@ int main ( int argc, char** argv ) #ifndef HEADLESS if ( bUseGUI ) { - QMessageBox::critical ( nullptr, APP_NAME, generr.GetErrorText(), "Quit", nullptr ); + CMsgBoxes::ShowError ( generr.GetErrorText() ); } else #endif @@ -1010,6 +1007,36 @@ int main ( int argc, char** argv ) return 0; } +/******************************************************************************\ +* Message Boxes * +\******************************************************************************/ +void CMsgBoxes::ShowError ( QString strError ) +{ +#ifndef HEADLESS + QMessageBox::critical ( pMainForm, strMainFormName + ": " + QObject::tr ( "Error" ), strError, QObject::tr ( "Ok" ), nullptr ); +#else + qCritical() << "Error: " << strError.toLocal8Bit().data(); +#endif +} + +void CMsgBoxes::ShowWarning ( QString strWarning ) +{ +#ifndef HEADLESS + QMessageBox::warning ( pMainForm, strMainFormName + ": " + QObject::tr ( "Warning" ), strWarning, QObject::tr ( "Ok" ), nullptr ); +#else + qWarning() << "Warning: " << strWarning.toLocal8Bit().data(); +#endif +} + +void CMsgBoxes::ShowInfo ( QString strInfo ) +{ +#ifndef HEADLESS + QMessageBox::information ( pMainForm, strMainFormName + ": " + QObject::tr ( "Information" ), strInfo, QObject::tr ( "Ok" ), nullptr ); +#else + qInfo() << "Info: " << strInfo.toLocal8Bit().data(); +#endif +} + /******************************************************************************\ * Command Line Argument Parsing * \******************************************************************************/ @@ -1083,7 +1110,7 @@ QString UsageArguments ( char** argv ) // clang-format on } -bool GetFlagArgument ( char** argv, int& i, QString strShortOpt, QString strLongOpt ) +bool CCommandline::GetFlagArgument ( int& i, const QString& strShortOpt, const QString& strLongOpt ) { if ( ( !strShortOpt.compare ( argv[i] ) ) || ( !strLongOpt.compare ( argv[i] ) ) ) { @@ -1095,7 +1122,7 @@ bool GetFlagArgument ( char** argv, int& i, QString strShortOpt, QString strLong } } -bool GetStringArgument ( int argc, char** argv, int& i, QString strShortOpt, QString strLongOpt, QString& strArg ) +bool CCommandline::GetStringArgument ( int& i, const QString& strShortOpt, const QString& strLongOpt, QString& strArg ) { if ( ( !strShortOpt.compare ( argv[i] ) ) || ( !strLongOpt.compare ( argv[i] ) ) ) { @@ -1115,14 +1142,12 @@ bool GetStringArgument ( int argc, char** argv, int& i, QString strShortOpt, QSt } } -bool GetNumericArgument ( int argc, - char** argv, - int& i, - QString strShortOpt, - QString strLongOpt, - double rRangeStart, - double rRangeStop, - double& rValue ) +bool CCommandline::GetNumericArgument ( int& i, + const QString& strShortOpt, + const QString& strLongOpt, + double rRangeStart, + double rRangeStop, + double& rValue ) { if ( ( !strShortOpt.compare ( argv[i] ) ) || ( !strLongOpt.compare ( argv[i] ) ) ) { @@ -1147,4 +1172,4 @@ bool GetNumericArgument ( int argc, { return false; } -} +} \ No newline at end of file diff --git a/src/settings.cpp b/src/settings.cpp index ab9b038dfd..e8248d6690 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -458,14 +458,14 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, // custom directories // clang-format off // TODO compatibility to old version (< 3.6.1) -QString strDirectoryAddress = GetIniSetting ( IniXMLDocument, "client", "centralservaddr", "" ); // clang-format on + QString strDirectoryAddress = GetIniSetting ( IniXMLDocument, "client", "centralservaddr", "" ); for ( iIdx = 0; iIdx < MAX_NUM_SERVER_ADDR_ITEMS; iIdx++ ) { // clang-format off // TODO compatibility to old version (< 3.8.2) -strDirectoryAddress = GetIniSetting ( IniXMLDocument, "client", QString ( "centralservaddr%1" ).arg ( iIdx ), strDirectoryAddress ); // clang-format on + strDirectoryAddress = GetIniSetting ( IniXMLDocument, "client", QString ( "centralservaddr%1" ).arg ( iIdx ), strDirectoryAddress ); vstrDirectoryAddress[iIdx] = GetIniSetting ( IniXMLDocument, "client", QString ( "directoryaddress%1" ).arg ( iIdx ), strDirectoryAddress ); strDirectoryAddress = ""; } @@ -473,17 +473,19 @@ strDirectoryAddress = GetIniSetting ( IniXMLDocument, "client", QString ( "centr // directory type // clang-format off // TODO compatibility to old version (<3.4.7) -// only the case that "centralservaddr" was set in old ini must be considered -if ( !vstrDirectoryAddress[0].isEmpty() && GetFlagIniSet ( IniXMLDocument, "client", "defcentservaddr", bValue ) && !bValue ) -{ - eDirectoryType = AT_CUSTOM; -} + // clang-format on + // only the case that "centralservaddr" was set in old ini must be considered + if ( !vstrDirectoryAddress[0].isEmpty() && GetFlagIniSet ( IniXMLDocument, "client", "defcentservaddr", bValue ) && !bValue ) + { + eDirectoryType = AT_CUSTOM; + } + // clang-format off // TODO compatibility to old version (< 3.8.2) -else if ( GetNumericIniSet ( IniXMLDocument, "client", "centservaddrtype", 0, static_cast ( AT_CUSTOM ), iValue ) ) -{ - eDirectoryType = static_cast ( iValue ); -} // clang-format on + else if ( GetNumericIniSet ( IniXMLDocument, "client", "centservaddrtype", 0, static_cast ( AT_CUSTOM ), iValue ) ) + { + eDirectoryType = static_cast ( iValue ); + } else if ( GetNumericIniSet ( IniXMLDocument, "client", "directorytype", 0, static_cast ( AT_CUSTOM ), iValue ) ) { eDirectoryType = static_cast ( iValue ); @@ -822,8 +824,8 @@ void CServerSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, QString directoryAddress = ""; // clang-format off // TODO compatibility to old version < 3.8.2 -directoryAddress = GetIniSetting ( IniXMLDocument, "server", "centralservaddr", directoryAddress ); // clang-format on + // directoryAddress = GetIniSetting ( IniXMLDocument, "server", "centralservaddr", directoryAddress ); directoryAddress = GetIniSetting ( IniXMLDocument, "server", "directoryaddress", directoryAddress ); pServer->SetDirectoryAddress ( directoryAddress ); @@ -843,40 +845,43 @@ directoryAddress = GetIniSetting ( IniXMLDocument, "server", "centralservaddr", { // clang-format off // TODO compatibility to old version < 3.4.7 -if ( GetFlagIniSet ( IniXMLDocument, "server", "defcentservaddr", bValue ) ) -{ - directoryType = bValue ? AT_DEFAULT : AT_CUSTOM; -} -else - // clang-format on - + // clang-format on + if ( GetFlagIniSet ( IniXMLDocument, "server", "defcentservaddr", bValue ) ) + { + directoryType = bValue ? AT_DEFAULT : AT_CUSTOM; + } + else // if "directorytype" itself is set, use it (note "AT_NONE", "AT_DEFAULT" and "AT_CUSTOM" are min/max directory type here) // clang-format off // TODO compatibility to old version < 3.8.2 -if ( GetNumericIniSet ( IniXMLDocument, "server", "centservaddrtype", static_cast ( AT_DEFAULT ), static_cast ( AT_CUSTOM ), iValue ) ) -{ - directoryType = static_cast ( iValue ); -} -else // clang-format on if ( GetNumericIniSet ( IniXMLDocument, "server", - "directorytype", - static_cast ( AT_NONE ), + "centservaddrtype", + static_cast ( AT_DEFAULT ), static_cast ( AT_CUSTOM ), iValue ) ) - { - directoryType = static_cast ( iValue ); - } + { + directoryType = static_cast ( iValue ); + } + else if ( GetNumericIniSet ( IniXMLDocument, + "server", + "directorytype", + static_cast ( AT_NONE ), + static_cast ( AT_CUSTOM ), + iValue ) ) + { + directoryType = static_cast ( iValue ); + } // clang-format off // TODO compatibility to old version < 3.9.0 -// override type to AT_NONE if servlistenabled exists and is false -if ( GetFlagIniSet ( IniXMLDocument, "server", "servlistenabled", bValue ) && !bValue ) -{ - directoryType = AT_NONE; -} // clang-format on + // override type to AT_NONE if servlistenabled exists and is false + if ( GetFlagIniSet ( IniXMLDocument, "server", "servlistenabled", bValue ) && !bValue ) + { + directoryType = AT_NONE; + } } pServer->SetDirectoryType ( directoryType ); diff --git a/src/soundbase.cpp b/src/soundbase.cpp index 3e8f2c2b21..55dffbe848 100644 --- a/src/soundbase.cpp +++ b/src/soundbase.cpp @@ -2,7 +2,7 @@ * Copyright (c) 2004-2022 * * Author(s): - * Volker Fischer + * Volker Fischer, revised and maintained by Peter Goderie (pgScorpio) * ****************************************************************************** * @@ -35,207 +35,831 @@ char const sMidiCtlChar[] = { /* [EMidiCtlType::MuteMyself] = */ 'o', /* [EMidiCtlType::None] = */ '\0' }; -/* Implementation *************************************************************/ -CSoundBase::CSoundBase ( const QString& strNewSystemDriverTechniqueName, - void ( *fpNewProcessCallback ) ( CVector& psData, void* pParg ), - void* pParg, - const QString& strMIDISetup ) : - fpProcessCallback ( fpNewProcessCallback ), - pProcessCallbackArg ( pParg ), - bRun ( false ), - bCallbackEntered ( false ), - strSystemDriverTechniqueName ( strNewSystemDriverTechniqueName ), +//============================================================================ +// Static helpers: +//============================================================================ + +//==================================== +// Flip bits: +//==================================== + +int16_t CSoundBase::Flip16Bits ( const int16_t iIn ) +{ + uint16_t iMask = ( 1 << 15 ); + int16_t iOut = 0; + + for ( unsigned int i = 0; i < 16; i++ ) + { + // copy current bit to correct position + iOut |= ( iIn & iMask ) ? 1 : 0; + + // shift out value and mask by one bit + iOut <<= 1; + iMask >>= 1; + } + + return iOut; +} + +int32_t CSoundBase::Flip32Bits ( const int32_t iIn ) +{ + uint32_t iMask = ( static_cast ( 1 ) << 31 ); + int32_t iOut = 0; + + for ( unsigned int i = 0; i < 32; i++ ) + { + // copy current bit to correct position + iOut |= ( iIn & iMask ) ? 1 : 0; + + // shift out value and mask by one bit + iOut <<= 1; + iMask >>= 1; + } + + return iOut; +} + +int64_t CSoundBase::Flip64Bits ( const int64_t iIn ) +{ + uint64_t iMask = ( static_cast ( 1 ) << 63 ); + int64_t iOut = 0; + + for ( unsigned int i = 0; i < 64; i++ ) + { + // copy current bit to correct position + iOut |= ( iIn & iMask ) ? 1 : 0; + + // shift out value and mask by one bit + iOut <<= 1; + iMask >>= 1; + } + + return iOut; +} + +//============================================================================ +// CSoundProperties: +//============================================================================ + +void CSoundProperties::setDefaultTexts() +{ + // Audio Device: + if ( bHasAudioDeviceSelection ) + { + strAudioDeviceWhatsThis = ( "" + CSoundBase::tr ( "Audio Device" ) + ": " + + CSoundBase::tr ( "Your audio device (sound card) can be " + "selected or using %1. If the selected driver is not valid an error " + "message will be shown. " ) + .arg ( APP_NAME ) + + "
" + + CSoundBase::tr ( "If the driver is selected during an active connection, the connection " + "is stopped, the driver is changed and the connection is started again " + "automatically." ) ); + strAudioDeviceAccessibleName = CSoundBase::tr ( "Audio device selector combo box." ); + } + else + { + strAudioDeviceWhatsThis = + ( "" + CSoundBase::tr ( "Audio Device" ) + ": " + CSoundBase::tr ( "The audio device used for %1." ).arg ( APP_NAME ) + "
" + + CSoundBase::tr ( "This setting cannot be changed." ) ); + strAudioDeviceAccessibleName = CSoundBase::tr ( "Audio device name." ); + } + + // Setup Button: + strSetupButtonText = CSoundBase::tr ( "Device Settings" ), + strSetupButtonWhatsThis = htmlBold ( CSoundBase::tr ( "Sound card driver settings:" ) ) + htmlNewLine() + + CSoundBase::tr ( "This button opens the driver settings of your sound card. Some drivers " + "allow you to change buffer settings, and/or let you choose input or outputs of your device(s). " + "Always make sure to set your Audio Device Sample Rate to 48000Hz (48Khz)." + "More information can be found on jamulus.io." ); + + strSetupButtonAccessibleName = CSoundBase::tr ( "Click this button to open Audio Device Settings" ); +} + +CSoundProperties::CSoundProperties() : + bHasAudioDeviceSelection ( true ), + bHasSetupDialog ( false ), + bHasInputChannelSelection ( true ), + bHasOutputChannelSelection ( true ), + bHasInputGainSelection ( true ) +{ + setDefaultTexts(); +} + +//============================================================================ +// CSoundBase: +//============================================================================ + +CSoundBase::CSoundBase ( const QString& systemDriverTechniqueName, + void ( *theProcessCallback ) ( CVector& psData, void* pArg ), + void* theProcessCallbackArg ) : + // iDeviceBufferSize ( 0 ), + // lNumDevices ( 0 ), + // fInOutLatencyMs ( 0.0 ), + // lNumInChan ( 0 ), + // lNumAddedInChan ( 0 ), + // lNumOutChan ( 0 ), + iProtocolBufferSize ( 128 ), + strDriverTechniqueName ( systemDriverTechniqueName ), + strClientName ( "" ), + bAutoConnect ( true ), + bStarted ( false ), + bActive ( false ), + iCurrentDevice ( -1 ), iCtrlMIDIChannel ( INVALID_MIDI_CH ), - aMidiCtls ( 128 ) + aMidiCtls ( 128 ), + fpProcessCallback ( theProcessCallback ), + pProcessCallbackArg ( theProcessCallbackArg ) +{ + CCommandline cCommandline; + + setObjectName ( "CSoundThread" ); + + cCommandline.GetStringArgument ( CMDLN_CLIENTNAME, strClientName ); + + QString strMIDISetup; + if ( cCommandline.GetStringArgument ( CMDLN_CTRLMIDICH, strMIDISetup ) ) + { + parseMIDICommandLineArgument ( strMIDISetup ); + } + + clearDeviceInfo(); + resetInputChannelsGain(); + resetChannelMapping(); + + // setup timers + timerCheckActive.setSingleShot ( true ); // only check once + QObject::connect ( &timerCheckActive, &QTimer::timeout, this, &CSoundBase::onTimerCheckActive ); +} + +void CSoundBase::onTimerCheckActive() +{ + if ( !bActive ) + { + emit SoundActiveTimeout(); + CMsgBoxes::ShowWarning ( htmlBold ( tr ( "Your audio device is not working correctly." ) ) + htmlNewLine() + + tr ( "Please check your device settings or try another device." ) ); + } +} + +void CSoundBase::clearDeviceInfo() { - // parse the MIDI setup command line argument string - ParseCommandLineArgument ( strMIDISetup ); + // No current device selection + iCurrentDevice = -1; + // No input channels + lNumInChan = 0; + lNumAddedInChan = 0; + strInputChannelNames.clear(); + // No added output channels + lNumOutChan = 0; // No outnput channels + strOutputChannelNames.clear(); - // initializations for the sound card names (default) - lNumDevs = 1; - strDriverNames[0] = strSystemDriverTechniqueName; + { + // No input selection + memset ( &selectedInputChannels, 0xFF, sizeof ( selectedInputChannels ) ); + // No output selection + memset ( &selectedOutputChannels, 0xFF, sizeof ( selectedOutputChannels ) ); + } - // set current device - strCurDevName = ""; // default device + fInOutLatencyMs = 0.0; } -void CSoundBase::Stop() +void CSoundBase::resetInputChannelsGain() { - // set flag so that thread can leave the main loop - bRun = false; + QMutexLocker locker ( &mutexAudioProcessCallback ); - // wait for draining the audio process callback - QMutexLocker locker ( &MutexAudioProcessCallback ); + for ( unsigned int i = 0; i < PROT_NUM_IN_CHANNELS; i++ ) + { + inputChannelsGain[i] = 1; + } } -/******************************************************************************\ -* Device handling * -\******************************************************************************/ -QStringList CSoundBase::GetDevNames() +void CSoundBase::resetChannelMapping() +{ + QMutexLocker locker ( &mutexAudioProcessCallback ); + + if ( PROT_NUM_IN_CHANNELS > 2 ) + { + memset ( selectedInputChannels, 0xFF, sizeof ( selectedInputChannels ) ); + } + + // init selected channel numbers with defaults: use first available + // channels for input and output + selectedInputChannels[0] = 0; + selectedInputChannels[1] = ( lNumInChan > 1 ) ? 1 : 0; + selectedOutputChannels[0] = 0; + selectedOutputChannels[1] = ( lNumOutChan > 1 ) ? 1 : 0; + ; +} + +long CSoundBase::getNumInputChannelsToAdd ( long lNumInChan ) { - QMutexLocker locker ( &MutexDevProperties ); + if ( lNumInChan <= 2 ) + { + return 0; + } + + return ( lNumInChan - 2 ) * 2; +} + +void CSoundBase::getInputSelAndAddChannels ( int iSelChan, long lNumInChan, long lNumAddedInChan, int& iSelChanOut, int& iAddChanOut ) +{ + // we have a mixed channel setup, definitions: + // - mixed channel setup only for 4 physical inputs: + // SelCH == 4: Ch 0 + Ch 2 + // SelCh == 5: Ch 0 + Ch 3 + // SelCh == 6: Ch 1 + Ch 2 + // SelCh == 7: Ch 1 + Ch 3 + + if ( ( iSelChan >= 0 ) && ( iSelChan < ( lNumInChan + lNumAddedInChan ) ) ) + { + if ( iSelChan < lNumInChan ) + { + iSelChanOut = iSelChan; + iAddChanOut = INVALID_INDEX; + + return; + } + else + { + long addedChan = ( iSelChan - lNumInChan ); + long numAdds = ( lNumInChan - 2 ); + + iSelChanOut = ( addedChan / numAdds ); + iAddChanOut = ( addedChan % numAdds ) + 2; + } + + return; + } + + iSelChanOut = INVALID_INDEX; + iAddChanOut = INVALID_INDEX; +} + +long CSoundBase::addAddedInputChannelNames() +{ + int curInChannelNames = strInputChannelNames.size(); + + if ( ( lNumInChan == 0 ) || ( curInChannelNames == 0 ) || ( curInChannelNames != lNumInChan ) ) + { + // Error !!! lNumInChan and strInputChannelNames must be set and match !! + return -1; + } - QStringList slDevNames; + // Determine number of channels to add + lNumAddedInChan = getNumInputChannelsToAdd ( lNumInChan ); - // put all device names in the string list - for ( int iDev = 0; iDev < lNumDevs; iDev++ ) + // Set any added input channel names + for ( int i = 0; i < lNumAddedInChan; i++ ) { - slDevNames << strDriverNames[iDev]; + int iSelChan, iAddChan; + + getInputSelAndAddChannels ( lNumInChan + i, lNumInChan, lNumAddedInChan, iSelChan, iAddChan ); + + strInputChannelNames.append ( strInputChannelNames[iSelChan] + " + " + strInputChannelNames[iAddChan] ); } - return slDevNames; + return lNumAddedInChan; +} + +//============================================================================ +// Public Interface to CClient: +//============================================================================ + +QString CSoundBase::getErrorString() +{ + QString str ( strErrorList.join ( htmlNewLine() ) ); + strErrorList.clear(); + return str; +} + +//============================================================================ +// +//============================================================================ + +QStringList CSoundBase::GetDeviceNames() +{ + QMutexLocker locker ( &mutexDeviceProperties ); + + return strDeviceNames; +} + +QString CSoundBase::GetDeviceName ( const int index ) +{ + QMutexLocker locker ( &mutexDeviceProperties ); + + return ( ( index >= 0 ) && ( index < strDeviceNames.length() ) ) ? strDeviceNames[index] : QString(); +} + +QString CSoundBase::GetCurrentDeviceName() +{ + QMutexLocker locker ( &mutexDeviceProperties ); + + return ( ( iCurrentDevice >= 0 ) && ( iCurrentDevice < strDeviceNames.count() ) ) ? strDeviceNames[iCurrentDevice] : QString(); +} + +int CSoundBase::GetIndexOfDevice ( const QString& strDeviceName ) +{ + QMutexLocker locker ( &mutexDeviceProperties ); + + return strDeviceNames.indexOf ( strDeviceName ); +} + +int CSoundBase::GetNumInputChannels() +{ + QMutexLocker locker ( &mutexDeviceProperties ); + + return ( lNumInChan + lNumAddedInChan ); } -QString CSoundBase::SetDev ( const QString strDevName ) +QString CSoundBase::GetInputChannelName ( const int index ) { - QMutexLocker locker ( &MutexDevProperties ); + QMutexLocker locker ( &mutexDeviceProperties ); + + return ( ( index >= 0 ) && ( index < strInputChannelNames.count() ) ) ? strInputChannelNames[index] : QString(); +} + +QString CSoundBase::GetOutputChannelName ( const int index ) +{ + QMutexLocker locker ( &mutexDeviceProperties ); + + return ( ( index >= 0 ) && ( index < strOutputChannelNames.count() ) ) ? strOutputChannelNames[index] : QString(); +} - // init return parameter with "no error" - QString strReturn = ""; +void CSoundBase::SetLeftInputChannel ( const int iNewChan ) +{ + if ( !soundProperties.bHasInputChannelSelection ) + { + return; + } + + // apply parameter after input parameter check + if ( ( iNewChan >= 0 ) && ( iNewChan < ( lNumInChan + lNumAddedInChan ) ) && ( iNewChan != selectedInputChannels[0] ) ) + { + { + QMutexLocker locker ( &mutexAudioProcessCallback ); + + selectedInputChannels[0] = iNewChan; + } + + onChannelSelectionChanged(); + } +} - // init flag for "load any driver" - bool bTryLoadAnyDriver = false; +void CSoundBase::SetRightInputChannel ( const int iNewChan ) +{ + if ( !soundProperties.bHasInputChannelSelection ) + { + return; + } - // check if an ASIO driver was already initialized - if ( !strCurDevName.isEmpty() ) + // apply parameter after input parameter check + if ( ( iNewChan >= 0 ) && ( iNewChan < ( lNumInChan + lNumAddedInChan ) ) && ( iNewChan != selectedInputChannels[1] ) ) { - // a device was already been initialized and is used, first clean up - // driver - UnloadCurrentDriver(); + { + QMutexLocker locker ( &mutexAudioProcessCallback ); + + selectedInputChannels[1] = iNewChan; + } - const QString strErrorMessage = LoadAndInitializeDriver ( strDevName, false ); + onChannelSelectionChanged(); + } +} + +void CSoundBase::SetLeftOutputChannel ( const int iNewChan ) +{ + if ( !soundProperties.bHasOutputChannelSelection ) + { + return; + } - if ( !strErrorMessage.isEmpty() ) + // apply parameter after input parameter check + if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) && ( iNewChan != selectedOutputChannels[0] ) ) + { { - if ( strDevName != strCurDevName ) - { - // loading and initializing the new driver failed, go back to - // original driver and create error message - LoadAndInitializeDriver ( strCurDevName, false ); - - // store error return message - strReturn = QString ( tr ( "Can't use the selected audio device " - "because of the following error: %1 " - "The previous driver will be selected." ) - .arg ( strErrorMessage ) ); - } - else + QMutexLocker locker ( &mutexAudioProcessCallback ); + + int prevChan = selectedOutputChannels[0]; + selectedOutputChannels[0] = iNewChan; + + // Dirty hack, since we can't easily manage 2x same output + if ( selectedOutputChannels[1] == iNewChan ) { - // loading and initializing the current driver failed, try to find - // at least one usable driver - bTryLoadAnyDriver = true; + if ( ( prevChan >= 0 ) && ( prevChan != iNewChan ) ) + { + // Swap channels... + selectedOutputChannels[1] = prevChan; + } + else + { + // Select higher channel number for left + int otherChan = iNewChan + 1; + if ( otherChan >= lNumOutChan ) + { + otherChan = 0; + } + selectedInputChannels[1] = otherChan; + } } } + + onChannelSelectionChanged(); } - else +} + +void CSoundBase::SetRightOutputChannel ( const int iNewChan ) +{ + if ( !soundProperties.bHasOutputChannelSelection ) + { + return; + } + + // apply parameter after input parameter check + if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) && ( iNewChan != selectedOutputChannels[1] ) ) { - if ( !strDevName.isEmpty() ) { - // This is the first time a driver is to be initialized, we first - // try to load the selected driver, if this fails, we try to load - // the first available driver in the system. If this fails, too, we - // throw an error that no driver is available -> it does not make - // sense to start the software if no audio hardware is available. - if ( !LoadAndInitializeDriver ( strDevName, false ).isEmpty() ) + QMutexLocker locker ( &mutexAudioProcessCallback ); + + int prevChan = selectedOutputChannels[1]; + selectedOutputChannels[1] = iNewChan; + + // Dirty hack, since we can't easily manage 2x same output + if ( selectedOutputChannels[0] == iNewChan ) { - // loading and initializing the new driver failed, try to find - // at least one usable driver - bTryLoadAnyDriver = true; + if ( ( prevChan >= 0 ) && ( prevChan != iNewChan ) ) + { + // Swap channels... + selectedOutputChannels[0] = prevChan; + } + else + { + // Select lower channel number for left + int otherChan = iNewChan - 1; + if ( otherChan < 0 ) + { + otherChan = lNumOutChan - 1; + } + selectedOutputChannels[0] = otherChan; + } } } - else - { - // try to find one usable driver (select the first valid driver) - bTryLoadAnyDriver = true; - } + + onChannelSelectionChanged(); } +} - if ( bTryLoadAnyDriver ) +int CSoundBase::SetLeftInputGain ( int iGain ) +{ + if ( !soundProperties.bHasInputGainSelection ) { - // if a driver was previously selected, show a warning message - if ( !strDevName.isEmpty() ) + return 1; + } + + QMutexLocker locker ( &mutexAudioProcessCallback ); // get mutex lock + + if ( iGain < 1 ) + { + iGain = 1; + } + else if ( iGain > DRV_MAX_INPUT_GAIN ) + { + iGain = DRV_MAX_INPUT_GAIN; + } + + inputChannelsGain[0] = iGain; + + return iGain; +} + +int CSoundBase::SetRightInputGain ( int iGain ) +{ + if ( !soundProperties.bHasInputGainSelection ) + { + return 1; + } + + QMutexLocker locker ( &mutexAudioProcessCallback ); // get mutex lock + + if ( iGain < 1 ) + { + iGain = 1; + } + else if ( iGain > DRV_MAX_INPUT_GAIN ) + { + iGain = DRV_MAX_INPUT_GAIN; + } + + inputChannelsGain[1] = iGain; + + return iGain; +} + +bool CSoundBase::OpenDeviceSetup() +{ + if ( !openDeviceSetup() ) + { + // With ASIO there always is a setup button, but not all ASIO drivers support a setup dialog! + + CMsgBoxes::ShowInfo ( htmlBold ( tr ( "This audio device does not support setup from jamulus." ) ) + htmlNewLine() + + tr ( "Check the device manual to see how you can check and change it's settings." ) ); + return false; + } + + return true; +} + +//============================================================================ +// Device handling +//============================================================================ + +bool CSoundBase::selectDevice ( int iDriverIndex, bool bOpenDriverSetup, bool bTryAnyDriver ) +{ + bool driverOk = false; + bool capabilitiesOk = false; + bool aborted = false; + long lNumDevices = strDeviceNames.count(); + long retries = 0; + int startindex = iDriverIndex; + + strErrorList.clear(); + + if ( !soundProperties.bHasSetupDialog ) + { + bOpenDriverSetup = false; + } + + // find and load driver + if ( ( iDriverIndex < 0 ) || ( iDriverIndex >= lNumDevices ) ) + { + strErrorList.append ( htmlBold ( tr ( "The selected audio device is no longer present in the system." ) ) ); + strErrorList.append ( tr ( "Selecting The first usable device..." ) ); + startindex = iDriverIndex = 0; + bTryAnyDriver = true; + } + + do + { + if ( !driverOk ) { - strReturn = tr ( "The previously selected audio device " - "is no longer available or the driver has changed to an incompatible state. " - "We'll attempt to find a valid audio device, but this new audio device may cause feedback. " - "Before connecting to a server, please check your audio device settings." ); - } + driverOk = checkDeviceChange ( tDeviceChangeCheck::CheckOpen, iDriverIndex ); + + if ( !driverOk ) + { + if ( bTryAnyDriver ) + { + strErrorList.append ( tr ( "Failed to open %1" ).arg ( strDeviceNames[iDriverIndex] ) ); + iDriverIndex++; + if ( iDriverIndex >= lNumDevices ) + { + iDriverIndex = 0; + } + + if ( iDriverIndex == startindex ) + { + aborted = true; + } + else + { + strErrorList.append ( "" ); + strErrorList.append ( tr ( "Trying %1:" ).arg ( strDeviceNames[iDriverIndex] ) ); + retries++; + } + } + else + { + checkDeviceChange ( tDeviceChangeCheck::Abort, iDriverIndex ); - // try to load and initialize any valid driver - QVector vsErrorList = LoadAndInitializeFirstValidDriver(); + if ( ( strErrorList.size() == 0 ) || ( iDriverIndex != startindex ) ) + { + // Revert... + strErrorList.append ( htmlBold ( tr ( "Couldn't initialise the audio driver." ) ) ); + strErrorList.append ( tr ( "Check if your audio hardware is plugged in and verify your driver settings." ) ); + } - if ( !vsErrorList.isEmpty() ) + return false; + } + } + } + + if ( driverOk ) { - // create error message with all details - QString sErrorMessage = "" + QString ( tr ( "No usable %1 audio device found." ) ).arg ( strSystemDriverTechniqueName ) + - "

" + tr ( "These are all the available drivers with error messages:" ) + "
    "; + // check device capabilities if it fulfills our requirements + capabilitiesOk = checkDeviceChange ( tDeviceChangeCheck::CheckCapabilities, iDriverIndex ); // Checks if new device is usable - for ( int i = 0; i < lNumDevs; i++ ) + // check if device is capable + if ( !capabilitiesOk ) { - sErrorMessage += "
  • " + GetDeviceName ( i ) + ": " + vsErrorList[i] + "
  • "; + // if requested, open ASIO driver setup in case of an error + if ( bOpenDriverSetup && !bTryAnyDriver ) + { + if ( openDeviceSetup() ) + { + strErrorList.append ( htmlBold ( tr ( "Please check and correct your device settings..." ) ) ); + + QMessageBox confirm ( nullptr ); + confirm.setIcon ( QMessageBox::Warning ); + confirm.setWindowTitle ( QString ( APP_NAME ) + "-" + tr ( "Warning" ) ); + confirm.setText ( getErrorString() ); + confirm.addButton ( QMessageBox::Retry ); + confirm.addButton ( QMessageBox::Abort ); + confirm.exec(); + + strErrorList.clear(); + + switch ( confirm.result() ) + { + case QMessageBox::Retry: + break; + + case QMessageBox::Abort: + default: + aborted = true; + } + } + } + else + { + if ( bTryAnyDriver ) + { + checkDeviceChange ( tDeviceChangeCheck::Abort, iDriverIndex ); + // Try next driver... + driverOk = false; + iDriverIndex++; + if ( iDriverIndex >= lNumDevices ) + { + iDriverIndex = 0; + } + + strErrorList.append ( "" ); + if ( iDriverIndex == startindex ) + { + strErrorList.append ( htmlBold ( tr ( "No usable audio devices found." ) ) ); + aborted = true; + } + else + { + strErrorList.append ( tr ( "Trying %1:" ).arg ( strDeviceNames[iDriverIndex] ) ); + } + retries++; + } + else + { + capabilitiesOk = true; // Ignore not ok... + strErrorList.append ( "" ); + strErrorList.append ( htmlBold ( tr ( "Try to solve the above warnings." ) ) ); + strErrorList.append ( tr ( "Then try selecting the device again or select another device.." ) ); + } + } } - sErrorMessage += "
"; + } -#if defined( _WIN32 ) && !defined( WITH_JACK ) - // to be able to access the ASIO driver setup for changing, e.g., the sample rate, we - // offer the user under Windows that we open the driver setups of all registered - // ASIO drivers - sErrorMessage += "
" + tr ( "Do you want to open the ASIO driver setup to try changing your configuration to a working state?" ); + } while ( !( driverOk && capabilitiesOk ) && ( iDriverIndex < lNumDevices ) && !aborted ); - if ( QMessageBox::Yes == QMessageBox::information ( nullptr, APP_NAME, sErrorMessage, QMessageBox::Yes | QMessageBox::No ) ) + if ( aborted && strErrorList.count() ) + { + if ( bTryAnyDriver ) + { + strErrorList.insert ( 0, htmlBold ( tr ( "No device could be opened due to the following errors(s):" ) ) + htmlNewLine() ); + } + else + { + strErrorList.insert ( 0, htmlBold ( tr ( "The device could not be opened due to the following errors(s):" ) ) + htmlNewLine() ); + } + } + else if ( bTryAnyDriver && ( iDriverIndex >= lNumDevices ) ) + { + strErrorList.append ( "" ); + strErrorList.append ( htmlBold ( tr ( "No usable audio devive available!" ) ) ); + } + + if ( driverOk && capabilitiesOk ) + { + // We selected a valid driver.... + if ( strErrorList.count() ) + { + if ( retries ) { - LoadAndInitializeFirstValidDriver ( true ); + strErrorList.append ( htmlBold ( tr ( "Selecting %1" ).arg ( strDeviceNames[iDriverIndex] ) ) ); } + // If there is something in the errorlist, so these are warnings... + // Show warnings now! + CMsgBoxes::ShowWarning ( getErrorString() ); + strErrorList.clear(); - sErrorMessage = QString ( tr ( "Can't start %1. Please restart %1 and check/reconfigure your audio settings." ) ).arg ( APP_NAME ); -#endif + if ( checkDeviceChange ( tDeviceChangeCheck::Activate, iDriverIndex ) ) + { + if ( soundProperties.bHasSetupDialog ) + { + OpenDeviceSetup(); + } + } - throw CGenErr ( sErrorMessage ); + return true; + } + else + { + return checkDeviceChange ( tDeviceChangeCheck::Activate, iDriverIndex ); } } - return strReturn; + checkDeviceChange ( tDeviceChangeCheck::Abort, iDriverIndex ); + + return false; } -QVector CSoundBase::LoadAndInitializeFirstValidDriver ( const bool bOpenDriverSetup ) +bool CSoundBase::SetDevice ( const QString& strDevName, bool bOpenSetupOnError ) { - QVector vsErrorList; + QMutexLocker locker ( &mutexDeviceProperties ); - // load and initialize first valid ASIO driver - bool bValidDriverDetected = false; - int iDriverCnt = 0; + strErrorList.clear(); + createDeviceList( /* true */ ); // We should always re-create the devicelist when reading the ini file - // try all available drivers in the system ("lNumDevs" devices) - while ( !bValidDriverDetected && ( iDriverCnt < lNumDevs ) ) + int iDriverIndex = strDevName.isEmpty() ? 0 : strDeviceNames.indexOf ( strDevName ); + + if ( selectDevice ( iDriverIndex, bOpenSetupOnError, strDevName.isEmpty() ) ) // Calls openDevice and checkDeviceCapabilities { - // try to load and initialize current driver, store error message - const QString strCurError = LoadAndInitializeDriver ( GetDeviceName ( iDriverCnt ), bOpenDriverSetup ); + return true; + } - vsErrorList.append ( strCurError ); + CMsgBoxes::ShowError ( getErrorString() ); + return false; +} - if ( strCurError.isEmpty() ) - { - // initialization was successful - bValidDriverDetected = true; +#ifdef OLD_SOUND_COMPATIBILITY +// Compatibilty function +QString CSoundBase::SetDev ( QString strDevName, bool bOpenSetupOnError ) +{ + QMutexLocker locker ( &mutexDeviceProperties ); - // store ID of selected driver - strCurDevName = GetDeviceName ( iDriverCnt ); + strErrorList.clear(); + createDeviceList( /* true */ ); // We should always re-create the devicelist when reading the ini file - // empty error list shows that init was successful - vsErrorList.clear(); - } + int iDriverIndex = strDevName.isEmpty() ? 0 : strDeviceNames.indexOf ( strDevName ); + + if ( selectDevice ( iDriverIndex, bOpenSetupOnError, strDevName.isEmpty() ) ) // Calls openDevice and checkDeviceCapabilities + { + return ""; + } + + return getErrorString(); +} +#endif + +bool CSoundBase::SetDevice ( int iDriverIndex, bool bOpenSetupOnError ) +{ + QMutexLocker locker ( &mutexDeviceProperties ); - // try next driver - iDriverCnt++; + strErrorList.clear(); + createDeviceList(); // Only create devicelist if neccesary + return selectDevice ( iDriverIndex, bOpenSetupOnError ); // Calls openDevice and checkDeviceCapabilities +} + +bool CSoundBase::ReloadDevice ( bool bOpenSetupOnError ) +{ + int iWasDevice = iCurrentDevice; + bool bWasStarted = IsStarted(); + + closeCurrentDevice(); + + if ( iWasDevice >= 0 ) // Did we have a selected device ? + { + // can we re-select that device ? + if ( selectDevice ( iWasDevice, bOpenSetupOnError ) ) + { + // Re-start if it was started before... + if ( bWasStarted ) + { + Start(); + } + + return true; + } } - return vsErrorList; + return false; +} + +unsigned int CSoundBase::SetBufferSize ( unsigned int iDesiredBufferSize ) +{ + CSoundStopper sound ( *this ); + + iProtocolBufferSize = iDesiredBufferSize; + iDeviceBufferSize = getDeviceBufferSize ( iProtocolBufferSize ); + + return iDeviceBufferSize; +} + +bool CSoundBase::BufferSizeSupported ( unsigned int iDesiredBufferSize ) +{ + unsigned int iActualBufferSize = getDeviceBufferSize ( iDesiredBufferSize ); + + return ( iActualBufferSize == iDesiredBufferSize ); } /******************************************************************************\ * MIDI handling * \******************************************************************************/ -void CSoundBase::ParseCommandLineArgument ( const QString& strMIDISetup ) +void CSoundBase::parseMIDICommandLineArgument ( const QString& strMIDISetup ) { int iMIDIOffsetFader = 70; // Behringer X-TOUCH: offset of 0x46 @@ -291,8 +915,10 @@ void CSoundBase::ParseCommandLineArgument ( const QString& strMIDISetup ) for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) { if ( i + iMIDIOffsetFader > 127 ) + { break; - aMidiCtls[i + iMIDIOffsetFader] = { EMidiCtlType::Fader, i }; + } + aMidiCtls[i + iMIDIOffsetFader] = { CMidiCtlEntry::tMidiCtlType::Fader, i }; } return; } @@ -303,12 +929,14 @@ void CSoundBase::ParseCommandLineArgument ( const QString& strMIDISetup ) { QString sParm = slMIDIParams[i].trimmed(); if ( sParm.isEmpty() ) + { continue; + } int iCtrl = QString ( sMidiCtlChar ).indexOf ( sParm[0] ); if ( iCtrl < 0 ) continue; - EMidiCtlType eTyp = static_cast ( iCtrl ); + CMidiCtlEntry::tMidiCtlType eTyp = static_cast ( iCtrl ); const QStringList slP = sParm.mid ( 1 ).split ( '*' ); int iFirst = slP[0].toUInt(); @@ -325,7 +953,7 @@ void CSoundBase::ParseCommandLineArgument ( const QString& strMIDISetup ) } } -void CSoundBase::ParseMIDIMessage ( const CVector& vMIDIPaketBytes ) +void CSoundBase::parseMIDIMessage ( const CVector& vMIDIPaketBytes ) { if ( vMIDIPaketBytes.Size() > 0 ) { @@ -362,7 +990,7 @@ void CSoundBase::ParseMIDIMessage ( const CVector& vMIDIPaketBytes ) ; switch ( cCtrl.eType ) { - case Fader: + case CMidiCtlEntry::tMidiCtlType::Fader: { // we are assuming that the controller number is the same // as the audio fader index and the range is 0-127 @@ -373,27 +1001,27 @@ void CSoundBase::ParseMIDIMessage ( const CVector& vMIDIPaketBytes ) emit ControllerInFaderLevel ( cCtrl.iChannel, iFaderLevel ); } break; - case Pan: + case CMidiCtlEntry::tMidiCtlType::Pan: { // Pan levels need to be symmetric between 1 and 127 - const int iPanValue = static_cast ( static_cast ( qMax ( iValue, 1 ) - 1 ) / 126 * AUD_MIX_PAN_MAX ); + const int iPanValue = static_cast ( ( static_cast ( qMax ( iValue, 1 ) ) - 1 ) / 126 * AUD_MIX_PAN_MAX ); emit ControllerInPanValue ( cCtrl.iChannel, iPanValue ); } break; - case Solo: + case CMidiCtlEntry::tMidiCtlType::Solo: { // We depend on toggles reflecting the desired state emit ControllerInFaderIsSolo ( cCtrl.iChannel, iValue >= 0x40 ); } break; - case Mute: + case CMidiCtlEntry::tMidiCtlType::Mute: { // We depend on toggles reflecting the desired state emit ControllerInFaderIsMute ( cCtrl.iChannel, iValue >= 0x40 ); } break; - case MuteMyself: + case CMidiCtlEntry::tMidiCtlType::MuteMyself: { // We depend on toggles reflecting the desired state to Mute Myself emit ControllerInMuteMyself ( iValue >= 0x40 ); @@ -408,3 +1036,80 @@ void CSoundBase::ParseMIDIMessage ( const CVector& vMIDIPaketBytes ) } } } + +//======================================================================== +// CSoundStopper Used to temporarily stop CSound i.e. while changing settings. +// Stops CSound while in scope and Starts CSound again when going out of scope +// if it was started before. +// One can also query CSoundStopper to see if CSound was started and/or active. +//======================================================================== + +CSoundBase::CSoundStopper::CSoundStopper ( CSoundBase& sound ) : Sound ( sound ), bAborted ( false ) +{ + bSoundWasActive = Sound.IsActive(); + bSoundWasStarted = Sound.IsStarted(); + + if ( bSoundWasStarted ) + { + Sound.Stop(); + } +} + +CSoundBase::CSoundStopper::~CSoundStopper() +{ + if ( bSoundWasStarted && !bAborted ) + { + Sound.Start(); + } +} + +bool CSoundBase::Start() +{ + if ( start() ) + { + _onStarted(); + return true; + } + + _onStopped(); + + QString strError = getErrorString(); + if ( strError.size() ) + { + CMsgBoxes::ShowError ( strError ); + } + + return false; +} + +bool CSoundBase::Stop() +{ + if ( stop() ) + { + _onStopped(); + return true; + } + + // We didn't seem to be stopped + // so restart active check + _onStarted(); + + QString strError = getErrorString(); + if ( strError.size() ) + { + CMsgBoxes::ShowError ( strError ); + } + + return false; +} + +bool CSoundBase::Restart() +{ + if ( IsStarted() ) + { + Stop(); + return Start(); + } + + return false; +} diff --git a/src/soundbase.h b/src/soundbase.h index 2249904aec..4efe604afe 100644 --- a/src/soundbase.h +++ b/src/soundbase.h @@ -2,7 +2,7 @@ * Copyright (c) 2004-2022 * * Author(s): - * Volker Fischer + * Volker Fischer, revised and maintained by Peter Goderie (pgScorpio) * ****************************************************************************** * @@ -22,155 +22,517 @@ * \******************************************************************************/ +//=============================================================================== +// Additions by pgScorpio: +// +// CSoundBase: Is a base class for CSound specifying the public interface for CClient. +// +// CSound should implement all CSoundBase virtual functions which +// implement the "glue" between CSoundbase and the sound driver(s) +// +// All additional variables and functions in CSound should always be protected +// or private ! If you really need extra functionality for CClient it probably +// belongs in CSoundBase ! +// +// Note that all Public funtions start with a Capital, all private and protected +// variables and functuns should start with a lowercase letter, (but for backwards +// compatibility this is not yet the case for signals) +// and (static) callback functions start with an underscore. +// +//======================================================================================= +// Good practice for clarity of the code source: +//======================================================================================= +// All declarations should always be grouped in the order private, protected, +// and public. +// +// All class variables should always be listed first (grouped by category) +// +// Than the constructor followed by any destructor. +// (Any constructor that uses new or malloc should always have a public +// destructor to delete the allocated data! But do NOT call any virtual +// functions from a destructor, since any descendant is already destructed! +// Base classes should ALWAYS have a protected constructor and a public +// VIRTUAL destructor!, even when nothing to delete.) +// +// Than any protected static functions (grouped by category) +// Than any public static functions (grouped by category) +// +// Than any private functions (grouped by category) +// Than any protected functions (grouped by category) +// Than any public functions (grouped by category) +// +// Than any protected virtual functions (grouped by category) +// Than any public virtual functions (grouped by category) +// +// (Grouping means repeat private: protected: public: before each group +// or at least insert an empty line between groups of the same kind.) +// +// Any friends expected to access multiple protected property groups should be +// listed in a separate protected group at the beginning of the class +// declaration (comment why it should be a friend!). +// Any friends expected to access only a specific protected property group +// should be listed as first in that protected group itself (also comment why!). +// +// Functions that do NOT change any class variables should always be +// declared as a const function. ( funcname(...) const { ...; } ) +// +// Functions that do NOT use any class variables or functions should always +// be declared static or, often better, outside the class ! +// +// Finally: Always try to have the same order of definitions in the .cpp file +// as the order of declarations order in the .h file. +// +//======================================================================================= + #pragma once #include #include #include +#include + #ifndef HEADLESS # include #endif + #include "global.h" #include "util.h" +#define OLD_SOUND_COMPATIBILITY + +#define CHECK_SOUND_ACTIVE_TIME_MS 2000 // ms + +// stereo for input and output on protocol +#define PROT_NUM_IN_CHANNELS 2 +#define PROT_NUM_OUT_CHANNELS 2 + +// minimum driver requirements +#define DRV_MIN_IN_CHANNELS 1 +#define DRV_MIN_OUT_CHANNELS 2 + +// define the maximum number of audio channel for input/output we can store +// channel infos for (and therefore this is the maximum number of entries in +// the channel selection combo box regardless of the actual available number +// of channels by the audio device) +// No longer a limit in CSoundBase, but still a limit in some CSound's, and it still is a limit used in settings.... +#define DRV_MAX_IN_CHANNELS 64 +#define DRV_MAX_OUT_CHANNELS 64 + +#define DRV_MAX_INPUT_GAIN 10 + // TODO better solution with enum definition // problem: in signals it seems not to work to use CSoundBase::ESndCrdResetType -enum ESndCrdResetType + +typedef enum TSNDCRDRESETTYPE { RS_ONLY_RESTART = 1, RS_ONLY_RESTART_AND_INIT, RS_RELOAD_RESTART_AND_INIT -}; +} tSndCrdResetType; -enum EMidiCtlType -{ - Fader = 0, - Pan, - Solo, - Mute, - MuteMyself, - None -}; +#ifdef OLD_SOUND_COMPATIBILITY +# define ESndCrdResetType TSNDCRDRESETTYPE +#endif class CMidiCtlEntry { public: - CMidiCtlEntry ( EMidiCtlType eT = EMidiCtlType::None, int iC = 0 ) : eType ( eT ), iChannel ( iC ) {} - EMidiCtlType eType; + typedef enum class EMidiCtlType + { + Fader = 0, + Pan, + Solo, + Mute, + MuteMyself, + None + } tMidiCtlType; + + tMidiCtlType eType; int iChannel; + + CMidiCtlEntry ( tMidiCtlType eT = tMidiCtlType::None, int iC = 0 ) : eType ( eT ), iChannel ( iC ) {} +}; + +//======================================================================================= +// CSoundProperties class: (UI related CSound interface) +//======================================================================================= + +class CSoundProperties +{ +protected: + friend class CSoundBase; + friend class CSound; + + bool bHasAudioDeviceSelection; + QString strAudioDeviceToolTip; + QString strAudioDeviceWhatsThis; + QString strAudioDeviceAccessibleName; + + bool bHasSetupDialog; + QString strSetupButtonText; + + QString strSetupButtonToolTip; + QString strSetupButtonWhatsThis; + QString strSetupButtonAccessibleName; + + bool bHasInputChannelSelection; + bool bHasOutputChannelSelection; + bool bHasInputGainSelection; + + void setDefaultTexts(); + + CSoundProperties(); // Sets the default values for CSoundProperties, + // Constructor of CSound should set modified values if needed. + +public: + inline bool HasAudioDeviceSelection() const { return bHasAudioDeviceSelection; } + inline const QString& AudioDeviceToolTip() const { return strAudioDeviceToolTip; } + inline const QString& AudioDeviceWhatsThis() const { return strAudioDeviceWhatsThis; } + inline const QString& AudioDeviceAccessibleName() const { return strAudioDeviceAccessibleName; } + + inline bool HasSetupDialog() const { return bHasSetupDialog; } + inline const QString& SetupButtonText() const { return strSetupButtonText; } + + inline const QString& SetupButtonToolTip() const { return strSetupButtonToolTip; } + inline const QString& SetupButtonWhatsThis() const { return strSetupButtonWhatsThis; } + inline const QString& SetupButtonAccessibleName() const { return strSetupButtonAccessibleName; } + + inline bool HasInputChannelSelection() const { return bHasInputChannelSelection; } + inline bool HasOutputChannelSelection() const { return bHasOutputChannelSelection; } + inline bool HasInputGainSelection() const { return bHasInputGainSelection; } + + // Add more ui texts here if needed... + // (Make sure to also set defaults and/or set values in CSound constructor) }; -/* Classes ********************************************************************/ +//======================================================================================= +// CSoundBase class: +//======================================================================================= + class CSoundBase : public QThread { Q_OBJECT +protected: + CSoundProperties soundProperties; // CSound Information for the user interface + + const CSoundProperties& getSoundProperties() const { return soundProperties; } + public: - CSoundBase ( const QString& strNewSystemDriverTechniqueName, - void ( *fpNewProcessCallback ) ( CVector& psData, void* pParg ), - void* pParg, - const QString& strMIDISetup ); + // utility functions + static int16_t Flip16Bits ( const int16_t iIn ); + static int32_t Flip32Bits ( const int32_t iIn ); + static int64_t Flip64Bits ( const int64_t iIn ); - virtual int Init ( const int iNewPrefMonoBufferSize ) { return iNewPrefMonoBufferSize; } - virtual void Start() - { - bRun = true; - bCallbackEntered = false; - } - virtual void Stop(); +protected: + // Only to be called from a descendant: + CSoundBase ( const QString& systemDriverTechniqueName, + void ( *theProcessCallback ) ( CVector& psData, void* pParg ), + void* theCallBackArg ); - // device selection - QStringList GetDevNames(); - QString SetDev ( const QString strDevName ); - QString GetDev() +public: + // destructors MUST be public, but base classes should always define a virtual destructor! + virtual ~CSoundBase() { - QMutexLocker locker ( &MutexDevProperties ); - return strCurDevName; + // Nothing to delete here, but since this is a base class there MUST be a public VIRTUAL destructor! } - virtual int GetNumInputChannels() { return 2; } - virtual QString GetInputChannelName ( const int ) { return "Default"; } - virtual void SetLeftInputChannel ( const int ) {} - virtual void SetRightInputChannel ( const int ) {} - virtual int GetLeftInputChannel() { return 0; } - virtual int GetRightInputChannel() { return 1; } +protected: + QMutex mutexAudioProcessCallback; // Use in audio processing callback and when changing channel selections ! + QMutex mutexDeviceProperties; // Use when accessing device properties in callbacks or changing device properties ! - virtual int GetNumOutputChannels() { return 2; } - virtual QString GetOutputChannelName ( const int ) { return "Default"; } - virtual void SetLeftOutputChannel ( const int ) {} - virtual void SetRightOutputChannel ( const int ) {} - virtual int GetLeftOutputChannel() { return 0; } - virtual int GetRightOutputChannel() { return 1; } + QTimer timerCheckActive; - virtual float GetInOutLatencyMs() { return 0.0f; } // "0.0" means no latency is available + unsigned int iProtocolBufferSize; // The buffersize requested by SetBufferSize (Expected by the protocol) + unsigned int iDeviceBufferSize; // The buffersize returned by SetBufferSize (The audio device's actual buffersize) - virtual void OpenDriverSetup() {} + CVector audioBuffer; // The audio buffer passed in the sound callback, used for both input and output by onBufferSwitch() - bool IsRunning() const { return bRun; } - bool IsCallbackEntered() const { return bCallbackEntered; } +protected: + QString strDriverTechniqueName; + QString strClientName; + bool bAutoConnect; - // TODO this should be protected but since it is used - // in a callback function it has to be public -> better solution - void EmitReinitRequestSignal ( const ESndCrdResetType eSndCrdResetType ) { emit ReinitRequest ( eSndCrdResetType ); } +private: + bool bStarted; // Set by _onStart, reset by _onStop + bool bActive; // Set by _onCallback, reset by _onStart and _onStop -protected: - virtual QString LoadAndInitializeDriver ( QString, bool ) { return ""; } - virtual void UnloadCurrentDriver() {} - QVector LoadAndInitializeFirstValidDriver ( const bool bOpenDriverSetup = false ); - void ParseCommandLineArgument ( const QString& strMIDISetup ); - QString GetDeviceName ( const int iDiD ) { return strDriverNames[iDiD]; } + inline void _onStarted() + { + bStarted = true; + bActive = false; + timerCheckActive.start ( CHECK_SOUND_ACTIVE_TIME_MS ); + } - static void GetSelCHAndAddCH ( const int iSelCH, const int iNumInChan, int& iSelCHOut, int& iSelAddCHOut ) + inline void _onStopped() { - // we have a mixed channel setup, definitions: - // - mixed channel setup only for 4 physical inputs: - // SelCH == 4: Ch 0 + Ch 2 - // SelCh == 5: Ch 0 + Ch 3 - // SelCh == 6: Ch 1 + Ch 2 - // SelCh == 7: Ch 1 + Ch 3 - if ( iSelCH >= iNumInChan ) - { - iSelAddCHOut = ( ( iSelCH - iNumInChan ) % 2 ) + 2; - iSelCHOut = ( iSelCH - iNumInChan ) / 2; - } - else + timerCheckActive.stop(); + + // make sure the working thread is actually done + // (by checking the locked state) + if ( mutexAudioProcessCallback.tryLock ( 5000 ) ) { - iSelAddCHOut = INVALID_INDEX; // set it to an invalid number - iSelCHOut = iSelCH; + mutexAudioProcessCallback.unlock(); } + + bStarted = false; + bActive = false; } - // function pointer to callback function +protected: + // Control of privates: + //============================================================================ + // Descendant callbacks: + // Functions to be called from corresponding + // virtual functions in the derived class + //============================================================================ + + // pgScorpio TODO: These functions should also control the connection timeout timer to check bCallbackEntered ! + // This timer is now running in CClient, but a connection timeout should be a signal too ! + + inline void _onProcessCallback() { bActive = true; } + +protected: + // Device list: (Should be filled with availe device names by the virtual function createDeviceList() ) + long lNumDevices; // The number of devices available for selection. + QStringList strDeviceNames; // Should be a lNumDevices long list of devicenames + + int iCurrentDevice; + +protected: + // Device Info: (Should be set by the virtual function activateNewDevice()) + long lNumInChan; + long lNumAddedInChan; // number of fictive input channels added for i.e. mixed input channels + long lNumOutChan; + + float fInOutLatencyMs; + + QStringList strInputChannelNames; // Should be (lNumInChan + lNumAddedInChan) long + QStringList strOutputChannelNames; // Should be (lNumOutChan) long + + void clearDeviceInfo(); + +protected: + // Channel selection: + int inputChannelsGain[PROT_NUM_IN_CHANNELS]; // Array with gain factor for input channels + int selectedInputChannels[PROT_NUM_IN_CHANNELS]; // Array with indexes of selected input channels + int selectedOutputChannels[PROT_NUM_OUT_CHANNELS]; // Array with indexes of selected output channels + + void resetInputChannelsGain(); // Sets all protocol InputChannel gains to 1 + void resetChannelMapping(); // Sets default input/output channel selection (first available channels) + +protected: + static long getNumInputChannelsToAdd ( long lNumInChan ); + static void getInputSelAndAddChannels ( int iSelChan, long lNumInChan, long lNumAddedInChan, int& iSelCHOut, int& iAddCHOut ); + + long addAddedInputChannelNames(); // Sets lNumOutChan. And adds added channel names to strInputChannelNames + // lNumInChan and strInputChannelNames must be set first !! + +protected: + // Misc. + QStringList strErrorList; // List with errorstrings to be returned by + + QString getErrorString(); + +protected: + // Midi handling + int iCtrlMIDIChannel; + QVector aMidiCtls; + +protected: + // Callbacks + + // pointer to callback function void ( *fpProcessCallback ) ( CVector& psData, void* arg ); + + // pointer to callback arguments void* pProcessCallbackArg; // callback function call for derived classes - void ProcessCallback ( CVector& psData ) + inline void processCallback ( CVector& psData ) { - bCallbackEntered = true; + _onProcessCallback(); ( *fpProcessCallback ) ( psData, pProcessCallbackArg ); } - void ParseMIDIMessage ( const CVector& vMIDIPaketBytes ); +signals: + void ReinitRequest ( int iSndCrdResetType ); + void SoundActiveTimeout(); - bool bRun; - bool bCallbackEntered; - QMutex MutexAudioProcessCallback; - QMutex MutexDevProperties; +protected slots: + void onTimerCheckActive(); - QString strSystemDriverTechniqueName; - int iCtrlMIDIChannel; - QVector aMidiCtls; - - long lNumDevs; - QString strCurDevName; - QString strDriverNames[MAX_NUMBER_SOUND_CARDS]; +protected: + //======================================================================== + // MIDI handling: + //======================================================================== + void parseMIDICommandLineArgument ( const QString& strMIDISetup ); + void parseMIDIMessage ( const CVector& vMIDIPaketBytes ); -signals: - void ReinitRequest ( int iSndCrdResetType ); +signals: // Signals from midi: void ControllerInFaderLevel ( int iChannelIdx, int iValue ); void ControllerInPanValue ( int iChannelIdx, int iValue ); void ControllerInFaderIsSolo ( int iChannelIdx, bool bIsSolo ); void ControllerInFaderIsMute ( int iChannelIdx, bool bIsMute ); void ControllerInMuteMyself ( bool bMute ); + +protected: + // device selection + // selectDevice calls a checkDeviceChange() sequence (checkOpen, checkCapabilities, Activate|Abort) + // Any Errors should be reported in strErorList + // So if false is returned one should call GetErrorList( QStringList& errorList ); + bool selectDevice ( int iDriverIndex, bool bOpenDriverSetup, bool bTryAnyDriver = false ); + +public: + //============================================================================ + // Interface to CClient: + //============================================================================ + + bool Start(); + bool Stop(); + bool Restart(); + + inline const QString& SystemDriverTechniqueName() const { return strDriverTechniqueName; } + + inline bool IsStarted() const { return bStarted; } + inline bool IsActive() const { return bStarted && bActive; } + + int GetNumDevices() const { return strDeviceNames.count(); } + + QStringList GetDeviceNames(); + QString GetDeviceName ( const int index ); + QString GetCurrentDeviceName(); + + int GetCurrentDeviceIndex() const { return iCurrentDevice; } + int GetIndexOfDevice ( const QString& strDeviceName ); + + int GetNumInputChannels(); + inline int GetNumOutputChannels() const { return lNumOutChan; } + + QString GetInputChannelName ( const int index ); + QString GetOutputChannelName ( const int index ); + + void SetLeftInputChannel ( const int iNewChan ); + inline int GetLeftInputChannel() const { return selectedInputChannels[0]; } + + void SetRightInputChannel ( const int iNewChan ); + inline int GetRightInputChannel() const { return selectedInputChannels[1]; } + + void SetLeftOutputChannel ( const int iNewChan ); + inline int GetLeftOutputChannel() const { return selectedOutputChannels[0]; } + + void SetRightOutputChannel ( const int iNewChan ); + int GetRightOutputChannel() const { return selectedOutputChannels[1]; } + + int SetLeftInputGain ( int iGain ); + inline int GetLeftInputGain() const { return inputChannelsGain[0]; }; + + int SetRightInputGain ( int iGain ); + inline int GetRightInputGain() const { return inputChannelsGain[1]; }; + + inline float GetInOutLatencyMs() const { return fInOutLatencyMs; } + + bool OpenDeviceSetup(); + +#ifdef OLD_SOUND_COMPATIBILITY + int Init ( int requestedBufferSize ) { return SetBufferSize ( requestedBufferSize ); } + bool IsRunning() { return bStarted; } + bool IsCallbackEntered() const { return bActive; } + void OpenDriverSetup() { openDeviceSetup(); } + QStringList GetDevNames() { return strDeviceNames; } + QString GetDev() { return GetCurrentDeviceName(); } + + QString SetDev ( QString strDevName, bool bOpenSetupOnError = false ); +#endif + +public: + // device selection + // If false is returned use GetErrorList( QStringList& errorList ) to get the list of error descriptions. + // + // pgScorpio: Set by name should only be used for selection from name in ini file. + // For now bTryAnydriver is only set if there was no device in the inifile (empty string) + // + bool SetDevice ( const QString& strDevName, bool bOpenSetupOnError = false ); + bool SetDevice ( int iDriverIndex, bool bOpenSetupOnError = false ); + + // Restart device after settings change... + bool ReloadDevice ( bool bOpenSetupOnError = false ); + + unsigned int SetBufferSize ( unsigned int iDesiredBufferSize ); + bool BufferSizeSupported ( unsigned int iDesiredBufferSize ); + // { returns true if the current selected device can use exactly this buffersize.} + +public: + //======================================================================== + // CSoundStopper Used to temporarily stop CSound i.e. while changing settings. + // While in scope it Stops CSound when it is running and Starts CSound again, + // when going out of scope if CSound was initially started. + // One can also query CSoundStopper to see if CSound was started and/or active. + // Use SoundStopper.Abort() to prevent restarting when going out of scope. + //======================================================================== + class CSoundStopper + { + protected: + CSoundBase& Sound; + bool bSoundWasStarted; + bool bSoundWasActive; + bool bAborted; + + public: + CSoundStopper ( CSoundBase& sound ); + ~CSoundStopper(); + bool WasStarted() { return bSoundWasStarted; } + bool WasActive() { return bSoundWasActive; } + bool IsAborted() { return bAborted; } + + bool Abort() // Do not Restart! + { + bAborted = true; + return bSoundWasStarted; + } + }; + +protected: + typedef enum class TDEVICECHANGECHECK + { + Abort = -1, + CheckOpen, + CheckCapabilities, + Activate, + } tDeviceChangeCheck; + + //======================================================================== + // pgScorpio: For clarity always list all virtual functions in separate + // sections at the end ! + // In this case no Defaults! All virtuals should be abstract, + // so we don't forget to implemenent the neccesary virtuals + // in CSound + //======================================================================== + /* This Section MUST also be included in any CSound class definition ! + +protected: // CSoundBase Mandatory pointer to instance (must be set to 'this' in the CSound constructor) + static CSound* pSound; + +public: // CSoundBase Mandatory functions. (but static functions can't be virtual) + static inline CSoundBase* pInstance() { return pSound; } + static inline const CSoundProperties& GetProperties() { return pSound->getSoundProperties(); } + */ +protected: + //============================================================================ + // Virtual interface to CSoundBase: + //============================================================================ + // onChannelSelectionChanged() is only needed when selectedInputChannels[]/selectedOutputChannels[] can't be used in the process callback, + // but normally just restarting should do the trick then, so in that case use: + /* + virtual void onChannelSelectionChanged() { Restart(); } + */ + virtual void onChannelSelectionChanged(){}; + + virtual long createDeviceList ( bool bRescan = false ) = 0; // Fills strDeviceNames returns lNumDevices + virtual bool checkDeviceChange ( tDeviceChangeCheck mode, int iDriverIndex ) = 0; // Performs the different actions for a device change + virtual unsigned int getDeviceBufferSize ( unsigned int iDesiredBufferSize ) = 0; // returns the nearest possible buffersize of selected device + virtual void closeCurrentDevice() = 0; // Closes the current driver and Clears Device Info + virtual bool openDeviceSetup() = 0; // Opens the device setup dialog. Returns false if a device setup dialog could not be opened. + virtual bool start() = 0; // Returns true if started, false if stopped + virtual bool stop() = 0; // Returns true if stopped, false if still (partly) running }; diff --git a/windows/asiodriver.cpp b/windows/asiodriver.cpp new file mode 100644 index 0000000000..51fd1c06c4 --- /dev/null +++ b/windows/asiodriver.cpp @@ -0,0 +1,516 @@ +#include "asiodriver.h" + +//============================================================================ +// CAsioDummy: a Dummy ASIO driver +// Defines NO_ASIO_DRIVER, a pointer to a dummy asio interface +// which can be accessed, but always returns ASE_NotPresent or other +// default values. +// +// Note: +// All invalid pIASIO pointer (IASIO*) values should be NO_ASIO_DRIVER, +// and so pointing to the dummy driver ! +//============================================================================ + +const char AsioDummyDriverName[] = "No ASIO Driver"; +const char AsioDummyDriverError[] = "Error: No ASIO Driver!"; + +const ASIOChannelInfo NoAsioChannelInfo = { + /* long channel; */ 0, + /* ASIOBool isInput; */ ASIOFalse, + /* ASIOBool isActive; */ ASIOFalse, + /* long channelGroup; */ -1, + /* ASIOSampleType type; */ ASIOSTInt16MSB, + /* char name[32]; */ "None" }; + +class CAsioDummy : public IASIO +{ +public: // IUnknown: + virtual HRESULT STDMETHODCALLTYPE QueryInterface ( const IID&, void** ) { return 0xC00D0071 /*NS_E_NO_DEVICE*/; } + virtual ULONG STDMETHODCALLTYPE AddRef ( void ) { return 0; } + virtual ULONG STDMETHODCALLTYPE Release ( void ) { return 0; } + +public: + virtual ASIOBool init ( void* ) { return ASIOFalse; } + virtual void getDriverName ( char* name ) { strcpy ( name, AsioDummyDriverName ); } + virtual long getDriverVersion() { return 0; } + virtual void getErrorMessage ( char* msg ) { strcpy ( msg, AsioDummyDriverError ); } + virtual ASIOError start() { return ASE_NotPresent; } + virtual ASIOError stop() { return ASE_NotPresent; } + virtual ASIOError getChannels ( long* ci, long* co ) + { + *ci = *co = 0; + return ASE_NotPresent; + } + virtual ASIOError getLatencies ( long* iL, long* oL ) + { + *iL = *oL = 0; + return ASE_NotPresent; + } + virtual ASIOError getBufferSize ( long* minS, long* maxS, long* prfS, long* gr ) + { + *minS = *maxS = *prfS = *gr = 0; + return ASE_NotPresent; + } + virtual ASIOError canSampleRate ( ASIOSampleRate ) { return ASE_NotPresent; } + virtual ASIOError getSampleRate ( ASIOSampleRate* sr ) + { + *sr = 0; + return ASE_NotPresent; + } + virtual ASIOError setSampleRate ( ASIOSampleRate ) { return ASE_NotPresent; } + virtual ASIOError getClockSources ( ASIOClockSource* c, long* s ) + { + memset ( c, 0, sizeof ( ASIOClockSource ) ); + *s = 0; + return ASE_NotPresent; + } + virtual ASIOError setClockSource ( long ) { return ASE_NotPresent; } + virtual ASIOError getSamplePosition ( ASIOSamples*, ASIOTimeStamp* ) { return ASE_NotPresent; } + virtual ASIOError getChannelInfo ( ASIOChannelInfo* inf ) + { + memcpy ( inf, &NoAsioChannelInfo, sizeof ( ASIOChannelInfo ) ); + return ASE_NotPresent; + } + virtual ASIOError disposeBuffers() { return ASE_NotPresent; } + virtual ASIOError controlPanel() { return ASE_NotPresent; } + virtual ASIOError future ( long, void* ) { return ASE_NotPresent; } + virtual ASIOError outputReady() { return ASE_NotPresent; } + + virtual ASIOError createBuffers ( ASIOBufferInfo* bufferInfos, long numChannels, long /* bufferSize */, ASIOCallbacks* /*callbacks*/ ) + { + ASIOBufferInfo* info = bufferInfos; + for ( long i = 0; ( i < numChannels ); i++, info++ ) + { + info->buffers[0] = NULL; + info->buffers[1] = NULL; + } + + return ASE_NotPresent; + } +}; + +CAsioDummy NoAsioDriver; // Dummy asio interface if no driver is selected. +const pIASIO NO_ASIO_DRIVER = ( (pIASIO) ( &NoAsioDriver ) ); // All pIASIO pointers NOT referencing an opened driver should use NO_ASIO_DRIVER + // to point to NoAsioDriver + +//============================================================================ +// IASIO helper Functions +//============================================================================ + +bool AsioIsOpen ( pIASIO& asio ) +{ + if ( !asio ) + { + // NULL pointer not allowed ! + // Should point to NoAsioDriver. + asio = NO_ASIO_DRIVER; + } + + return ( asio != NO_ASIO_DRIVER ); +}; + +//============================================================================ +// Local helper Functions for obtaining the AsioDriverData +//============================================================================ + +bool FindAsioDriverPath ( char* clsidstr, char* dllpath, int dllpathsize ) +{ + HKEY hkEnum, hksub, hkpath; + char databuf[512]; + LSTATUS cr; + DWORD datatype, datasize; + DWORD index; + HFILE hfile; + bool keyfound = FALSE; + bool pathfound = FALSE; + + CharLowerBuff ( clsidstr, static_cast ( strlen ( clsidstr ) ) ); //@PGO static_cast ! + if ( ( cr = RegOpenKey ( HKEY_CLASSES_ROOT, ASIO_COM_CLSID, &hkEnum ) ) == ERROR_SUCCESS ) + { + index = 0; + while ( cr == ERROR_SUCCESS && !keyfound ) + { + cr = RegEnumKey ( hkEnum, index++, (LPTSTR) databuf, 512 ); + if ( cr == ERROR_SUCCESS ) + { + CharLowerBuff ( databuf, static_cast ( strlen ( databuf ) ) ); + if ( strcmp ( databuf, clsidstr ) == 0 ) + { + keyfound = true; // break out + + if ( ( cr = RegOpenKeyEx ( hkEnum, (LPCTSTR) databuf, 0, KEY_READ, &hksub ) ) == ERROR_SUCCESS ) + { + if ( ( cr = RegOpenKeyEx ( hksub, (LPCTSTR) ASIO_INPROC_SERVER, 0, KEY_READ, &hkpath ) ) == ERROR_SUCCESS ) + { + datatype = REG_SZ; + datasize = (DWORD) dllpathsize; + cr = RegQueryValueEx ( hkpath, 0, 0, &datatype, (LPBYTE) dllpath, &datasize ); + if ( cr == ERROR_SUCCESS ) + { + OFSTRUCT ofs; + memset ( &ofs, 0, sizeof ( OFSTRUCT ) ); + ofs.cBytes = sizeof ( OFSTRUCT ); + hfile = OpenFile ( dllpath, &ofs, OF_EXIST ); + + pathfound = ( hfile ); + } + RegCloseKey ( hkpath ); + } + RegCloseKey ( hksub ); + } + } + } + } + + RegCloseKey ( hkEnum ); + } + + return pathfound; +} + +bool GetAsioDriverData ( tAsioDrvData* data, HKEY hkey, char* keyname ) +{ + // typedef struct TASIODRVDATA + // { + // char drvname[MAXDRVNAMELEN + 1]; + // CLSID clsid; // Driver CLSID + // char dllpath[MAXPATHLEN + 1]; // Path to the driver dlll + // IASIO* asio; // Pointer to the ASIO interface when opened, should point to NO_ASIO_DRIVER if closed + // } tAsioDrvData; + + if ( !data ) + { + return false; + } + + bool ok = false; + HKEY hksub; + char databuf[256]; + WORD wData[100]; + DWORD datatype, datasize; + LSTATUS cr; + + memset ( data, 0, sizeof ( tAsioDrvData ) ); + data->index = -1; + data->openData.asio = NO_ASIO_DRIVER; + + if ( ( cr = RegOpenKeyEx ( hkey, (LPCTSTR) keyname, 0, KEY_READ, &hksub ) ) == ERROR_SUCCESS ) + { + datatype = REG_SZ; + datasize = 256; + cr = RegQueryValueEx ( hksub, ASIO_COM_CLSID, 0, &datatype, (LPBYTE) databuf, &datasize ); + if ( cr == ERROR_SUCCESS ) + { + // Get dllpath + if ( FindAsioDriverPath ( databuf, data->dllpath, MAX_ASIO_PATHLEN ) ) + { + ok = true; + + // Set CLSID + MultiByteToWideChar ( CP_ACP, 0, (LPCSTR) databuf, -1, (LPWSTR) wData, 100 ); + if ( ( cr = CLSIDFromString ( (LPOLESTR) wData, &data->clsid ) ) != S_OK ) + { + memset ( &data->clsid, 0, sizeof ( data->clsid ) ); + ok = false; + } + + // Get drvname + datatype = REG_SZ; + datasize = sizeof ( databuf ); + cr = RegQueryValueEx ( hksub, ASIODRV_DESC, 0, &datatype, (LPBYTE) databuf, &datasize ); + if ( cr == ERROR_SUCCESS ) + { + strcpy_s ( data->drvname, sizeof ( data->drvname ), databuf ); + } + else + { + strcpy_s ( data->drvname, sizeof ( data->drvname ), keyname ); + } + } + } + + RegCloseKey ( hksub ); + } + + return ok; +} + +//============================================================================ +// GetAsioDriverDataList: Obtains AsioDrvData of all available asio drivers. +//============================================================================ + +long GetAsioDriverDataList ( tVAsioDrvDataList& vAsioDrvDataList ) +{ + DWORD lDriverIndex = 0; + HKEY hkEnum = 0; + char keyname[MAX_ASIO_DRVNAMELEN + 1]; + LONG cr; + + vAsioDrvDataList.clear(); + + cr = RegOpenKey ( HKEY_LOCAL_MACHINE, ASIO_PATH, &hkEnum ); + + if ( cr == ERROR_SUCCESS ) + { + do + { + if ( ( cr = RegEnumKey ( hkEnum, lDriverIndex, (LPTSTR) keyname, sizeof ( keyname ) ) ) == ERROR_SUCCESS ) + { + tAsioDrvData AsioDrvData; + + if ( GetAsioDriverData ( &AsioDrvData, hkEnum, keyname ) ) + { + AsioDrvData.index = vAsioDrvDataList.count(); + vAsioDrvDataList.append ( AsioDrvData ); + } + } + else + { + break; + } + + lDriverIndex++; + + } while ( true ); + } + + if ( hkEnum ) + { + RegCloseKey ( hkEnum ); + } + + return vAsioDrvDataList.count(); +} + +//============================================================================ +// AsioDrvData functions: (low level AsioDrvData handling) +//============================================================================ + +bool AsioDriverIsValid ( tAsioDrvData* asioDrvData ) +{ + if ( asioDrvData ) + { + return ( asioDrvData->drvname[0] && // Must have a drvname + asioDrvData->dllpath[0] && // And a dllPath + ( asioDrvData->clsid.Data1 || asioDrvData->clsid.Data2 || asioDrvData->clsid.Data4 || + asioDrvData->clsid.Data4[0] ) // And a non zero clsid + ); + } + + return false; +} + +bool AsioDriverIsOpen ( tAsioDrvData* asioDrvData ) +{ + if ( asioDrvData ) + { + return AsioIsOpen ( asioDrvData->openData.asio ); + } + + return false; +}; + +bool OpenAsioDriver ( tAsioDrvData* asioDrvData ) +{ + HRESULT hr; + + if ( !asioDrvData ) + { + return false; + } + + if ( AsioDriverIsOpen ( asioDrvData ) ) + { + return true; + } + + // Safety check: should already be deleted ! + if ( asioDrvData->openData.addedInChannelData ) + { + delete[] asioDrvData->openData.addedInChannelData; + } + + if ( asioDrvData->openData.inChannelData ) + { + delete asioDrvData->openData.inChannelData; + } + + if ( asioDrvData->openData.outChannelData ) + { + delete asioDrvData->openData.outChannelData; + } + + memset ( &asioDrvData->openData, 0, sizeof ( asioDrvData->openData ) ); + asioDrvData->openData.asio = NO_ASIO_DRIVER; + + // CoCreateInstance( + // _In_ REFCLSID rclsid, lpdrv->clsid + // _In_opt_ LPUNKNOWN pUnkOuter, 0 + // _In_ DWORD dwClsContext, CLSCTX_INPROC_SERVER + // _In_ REFIID riid, CHECK: Using lpdrv->clsid instead of a iid ??? + // _COM_Outptr_ _At_(*ppv, _Post_readable_size_(_Inexpressible_(varies))) LPVOID FAR * ppv + // ); + + hr = CoCreateInstance ( asioDrvData->clsid, 0, CLSCTX_INPROC_SERVER, asioDrvData->clsid, (LPVOID*) &( asioDrvData->openData.asio ) ); + if ( SUCCEEDED ( hr ) ) + { + asioDrvData->openData.driverinfo.asioVersion = ASIO_HOST_VERSION; + if ( asioDrvData->openData.asio->init ( &asioDrvData->openData.driverinfo ) ) + { + if ( asioDrvData->drvname[0] == 0 ) + { + asioDrvData->openData.asio->getDriverName ( asioDrvData->drvname ); + } + + asioDrvData->openData.asio->getChannels ( &asioDrvData->openData.lNumInChan, &asioDrvData->openData.lNumOutChan ); + if ( asioDrvData->openData.lNumInChan ) + { + asioDrvData->openData.inChannelData = new ASIOChannelInfo[asioDrvData->openData.lNumInChan]; + for ( long i = 0; i < asioDrvData->openData.lNumInChan; i++ ) + { + asioDrvData->openData.inChannelData[i].channel = i; + asioDrvData->openData.inChannelData[i].isInput = ASIO_INPUT; + asioDrvData->openData.asio->getChannelInfo ( &asioDrvData->openData.inChannelData[i] ); + } + } + if ( asioDrvData->openData.lNumOutChan ) + { + asioDrvData->openData.outChannelData = new ASIOChannelInfo[asioDrvData->openData.lNumOutChan]; + for ( long i = 0; i < asioDrvData->openData.lNumOutChan; i++ ) + { + asioDrvData->openData.outChannelData[i].channel = i; + asioDrvData->openData.outChannelData[i].isInput = ASIO_OUTPUT; + asioDrvData->openData.asio->getChannelInfo ( &asioDrvData->openData.outChannelData[i] ); + } + } + return true; + } + } + + asioDrvData->openData.asio = NO_ASIO_DRIVER; + + return false; +} + +bool CloseAsioDriver ( tAsioDrvData* asioDrvData ) +{ + if ( !asioDrvData ) + { + return false; + } + + if ( AsioDriverIsOpen ( asioDrvData ) ) + { + asioDrvData->openData.asio->Release(); + } + + if ( asioDrvData->openData.addedInChannelData ) + { + delete[] asioDrvData->openData.addedInChannelData; + } + + if ( asioDrvData->openData.inChannelData ) + { + delete[] asioDrvData->openData.inChannelData; + } + + if ( asioDrvData->openData.outChannelData ) + { + delete[] asioDrvData->openData.outChannelData; + } + + memset ( &asioDrvData->openData, 0, sizeof ( asioDrvData->openData ) ); + asioDrvData->openData.asio = NO_ASIO_DRIVER; + + return true; +} + +//============================================================================ +// CAsioDriver: Wrapper for tAsioDrvData. +// Implements the normal ASIO driver access. +//============================================================================ + +CAsioDriver::CAsioDriver ( tAsioDrvData* pDrvData ) +{ + memset ( ( (tAsioDrvData*) this ), 0, sizeof ( tAsioDrvData ) ); + index = -1; + openData.asio = NO_ASIO_DRIVER; + + if ( pDrvData ) + { + AssignFrom ( pDrvData ); + } +} + +bool CAsioDriver::AssignFrom ( tAsioDrvData* pDrvData ) +{ + Close(); + + if ( pDrvData ) + { + // Move to this driver + *( (tAsioDrvData*) ( this ) ) = *pDrvData; + + // Other driver is no longer open + memset ( &pDrvData->openData, 0, sizeof ( pDrvData->openData ) ); + pDrvData->openData.asio = NO_ASIO_DRIVER; + } + else + { + memset ( ( (tAsioDrvData*) ( this ) ), 0, sizeof ( tAsioDrvData ) ); + index = -1; + openData.asio = NO_ASIO_DRIVER; + } + + return AsioDriverIsValid ( this ); +} + +ASIOAddedChannelInfo* CAsioDriver::setNumAddedInputChannels ( long numChan ) +{ + if ( IsOpen() ) + { + if ( openData.addedInChannelData ) + { + delete[] openData.addedInChannelData; + } + openData.addedInChannelData = new ASIOAddedChannelInfo[numChan]; + openData.lNumAddedInChan = numChan; + memset ( openData.addedInChannelData, 0, ( sizeof ( ASIOAddedChannelInfo ) * numChan ) ); + for ( long i = 0; i < numChan; i++ ) + { + openData.addedInChannelData[i].ChannelData.channel = ( openData.lNumInChan + i ); + openData.addedInChannelData[i].ChannelData.isInput = ASIO_INPUT; + } + + return openData.addedInChannelData; + } + else + { + return NULL; + } +} + +long CAsioDriver::GetInputChannelNames ( QStringList& list ) +{ + for ( long i = 0; i < openData.lNumInChan; i++ ) + { + list.append ( openData.inChannelData[i].name ); + } + + for ( long i = 0; i < openData.lNumAddedInChan; i++ ) + { + list.append ( openData.addedInChannelData[i].ChannelData.name ); + } + + return ( openData.lNumInChan + openData.lNumAddedInChan ); +} + +long CAsioDriver::GetOutputChannelNames ( QStringList& list ) +{ + for ( long i = 0; i < openData.lNumOutChan; i++ ) + { + list.append ( openData.outChannelData[i].name ); + } + + return openData.lNumOutChan; +} diff --git a/windows/asiodriver.h b/windows/asiodriver.h new file mode 100644 index 0000000000..52d1ce7e0d --- /dev/null +++ b/windows/asiodriver.h @@ -0,0 +1,208 @@ +#pragma once +#include "asiosys.h" +#include +#include + +// ****************************************************************** +// ASIO Registry definitions: +// ****************************************************************** +#define ASIODRV_DESC "description" +#define ASIO_INPROC_SERVER "InprocServer32" +#define ASIO_PATH "software\\asio" +#define ASIO_COM_CLSID "clsid" + +//============================================================================ +// tAsioDrvData: +// Basic part is obtained from registry by GetAsioDriverData +// The openData struct is initialised on Open() and only valid while open. +//============================================================================ + +// For AddedChannelData we need extra storage space for the 2nd channelname! +// Luckily the name field is at the end of the ASIOChannelInfo struct, +// so we can expand the needed storage space by defining another field after the sruct. +typedef struct +{ + ASIOChannelInfo ChannelData; + char AddedName[34]; // 3 chars for " + " and another additional 31 character name +} ASIOAddedChannelInfo; + +typedef struct TASIODRVDATA +{ + long index; // index in the asioDriverData list + + // Data from registry: + char drvname[MAX_ASIO_DRVNAMELEN + 1]; // Driver name + char dllpath[MAX_ASIO_PATHLEN + 1]; // Path to the driver dlll + CLSID clsid; // Driver CLSID + + struct ASIO_OPEN_DATA + { + //============================================================== + // Data set by OpenAsioDriver() and cleared by CloseAsioDriver: + //============================================================== + IASIO* asio; // Pointer to the ASIO interface: Set by CoCreateInstance, should point to NO_ASIO_DRIVER if closed + + // Obtained from ASIO driverinfo set by asio->ASIOInit( &driverinfo ) + ASIODriverInfo driverinfo; + + // Obtained from asio->ASIOGetChannels( &lNumInChan, &lNumOutChan ): + long lNumInChan; + long lNumOutChan; + + // Obtained from ASIOGetChannelInfo: + ASIOChannelInfo* inChannelData; + ASIOChannelInfo* outChannelData; + + // Set by setNumAddedInputChannels (Usually from capability check) + long lNumAddedInChan; + ASIOAddedChannelInfo* addedInChannelData; + + // Set by capability check: + bool capabilitiesOk; + } openData; +} tAsioDrvData; + +#define tVAsioDrvDataList QVector + +//============================================================================ +// ASIO driver helper functions: +//============================================================================ + +extern bool FindAsioDriverPath ( char* clsidstr, char* dllpath, int dllpathsize ); +extern bool GetAsioDriverData ( tAsioDrvData* data, HKEY hkey, char* keyname ); + +//============================================================================ +// ASIO GetAsioDriverDataList: +// Retrieves AsioDrvData for all available ASIO devices on the system. +//============================================================================ + +extern long GetAsioDriverDataList ( tVAsioDrvDataList& vAsioDrvDataList ); + +//============================================================================ +// ASIO asioDriverData Open/Close functions: +// Once a driver is opened you can access the driver via asioDrvData.asio +//============================================================================ + +extern bool AsioDriverIsValid ( tAsioDrvData* asioDrvData ); // Returns true if non NULL pointer and drvname, dllpath and clsid are all not empty. +extern bool AsioDriverIsOpen ( + tAsioDrvData* asioDrvData ); // Will also set asioDrvData->asio to NO_ASIO_DRIVER if asioDrvData->asio is a NULL pointer! +extern bool OpenAsioDriver ( tAsioDrvData* asioDrvData ); +extern bool CloseAsioDriver ( tAsioDrvData* asioDrvData ); + +//============================================================================ +// CAsioDriver: Wrapper for tAsioDrvData. +// Implements the normal ASIO driver access. +//============================================================================ + +class CAsioDriver : public tAsioDrvData +{ +public: + CAsioDriver ( tAsioDrvData* pDrvData = NULL ); + + ~CAsioDriver() { Close(); } + + // AssignFrom: Switch to another driver, + // Closes this driver if it was open. + // Copies the new driver data. + // Returns true if assigned to a new driver. + // The source DrvData will always be in closed state after assignment to a CAsioDriver! + + bool AssignFrom ( tAsioDrvData* pDrvData ); + +public: + // WARNING! Also AddedInputChannels are only valid while opened ! + + // setNumAddedInputChannels creates numChan added channels and sets openData.lNumAddedInChan + // Only channel number and isInput are set. Channel names and other values still have to be set. + + ASIOAddedChannelInfo* setNumAddedInputChannels ( long numChan ); + +public: // Channel info: + // WARNING! Channel info is only available when opened. And there are NO checks on indexes! + // So first check isOpen() and numXxxxChannels() before accessing channel data !! + + long GetInputChannelNames ( QStringList& list ); + long GetOutputChannelNames ( QStringList& list ); + +public: + inline pIASIO iasiodrv() + { + return openData.asio; + }; // Use iasiodrv()->asioFunction if you need the actual returned ASIOError ! + // See iasiodrv.h + +public: + inline bool IsValid() { return AsioDriverIsValid ( this ); } + inline bool IsOpen() { return AsioDriverIsOpen ( this ); } + + inline bool Open() { return OpenAsioDriver ( this ); } // Calls init(&driverinfo) and retrieves channel info too! + inline bool Close() { return CloseAsioDriver ( this ); } // Also destroys the channel info! + +public: // iASIO functions (driver must be open first !) + inline bool start() { return ASIOError_OK ( openData.asio->start() ); } + inline bool stop() { return ASIOError_OK ( openData.asio->stop() ); } + + inline long driverVersion() { return openData.asio->getDriverVersion(); } + + inline long numInputChannels() { return openData.lNumInChan; } + inline long numAddedInputChannels() { return openData.lNumAddedInChan; } + inline long numTotalInputChannels() { return ( openData.lNumInChan + openData.lNumAddedInChan ); } + + inline long numOutputChannels() { return openData.lNumOutChan; } + + inline const ASIOChannelInfo& inputChannelInfo ( long index ) + { + return ( index < openData.lNumInChan ) ? openData.inChannelData[index] : openData.addedInChannelData[index - openData.lNumInChan].ChannelData; + } + + inline const ASIOChannelInfo& outputChannelInfo ( long index ) { return openData.outChannelData[index]; } + + inline bool canSampleRate ( ASIOSampleRate sampleRate ) { return ASIOError_OK ( openData.asio->canSampleRate ( sampleRate ) ); } + inline bool setSampleRate ( ASIOSampleRate sampleRate ) { return ASIOError_OK ( openData.asio->setSampleRate ( sampleRate ) ); } + inline bool getSampleRate ( ASIOSampleRate* sampleRate ) { return ASIOError_OK ( openData.asio->getSampleRate ( sampleRate ) ); } + + inline bool getBufferSize ( long* minSize, long* maxSize, long* preferredSize, long* granularity ) + { + return ASIOError_OK ( openData.asio->getBufferSize ( minSize, maxSize, preferredSize, granularity ) ); + } + inline bool createBuffers ( ASIOBufferInfo* bufferInfos, long numChannels, long bufferSize, ASIOCallbacks* callbacks ) + { + return ASIOError_OK ( openData.asio->createBuffers ( bufferInfos, numChannels, bufferSize, callbacks ) ); + } + inline bool disposeBuffers() { return ASIOError_OK ( openData.asio->disposeBuffers() ); } + + inline bool getSamplePosition ( ASIOSamples* sPos, ASIOTimeStamp* tStamp ) + { + return ASIOError_OK ( openData.asio->getSamplePosition ( sPos, tStamp ) ); + } + + inline bool getClockSources ( ASIOClockSource* clocks, long* numSources ) + { + return ASIOError_OK ( openData.asio->getClockSources ( clocks, numSources ) ); + } + inline bool setClockSource ( long reference ) { return ASIOError_OK ( openData.asio->setClockSource ( reference ) ); } + + inline bool getLatencies ( long* inputLatency, long* outputLatency ) + { + return ASIOError_OK ( openData.asio->getLatencies ( inputLatency, outputLatency ) ); + } + + inline bool outputReady() { return ASIOError_OK ( openData.asio->outputReady() ); } + + inline bool controlPanel() { return ASIOError_OK ( openData.asio->controlPanel() ); } + + inline void getErrorMessage ( char* string ) { openData.asio->getErrorMessage ( string ); } + + inline bool future ( long selector, void* opt ) { return ASIOError_OK ( openData.asio->future ( selector, opt ) ); } +}; + +//============================================================================ +// Pointer pointing to a dummy IASIO interface +// Used for the asio pointer of not opened asio drivers so it's always +// safe to access the pointer. (Though all calls will return a fail.) +//============================================================================ + +extern const pIASIO NO_ASIO_DRIVER; // All pIASIO pointers NOT referencing an opened driver should use NO_ASIO_DRIVER + // to point to NoAsioDriver + +extern bool AsioIsOpen ( pIASIO& asio ); // Will also set asio to NO_ASIO_DRIVER if it happens to be a NULL pointer! diff --git a/windows/asiosys.h b/windows/asiosys.h new file mode 100644 index 0000000000..b405f56c73 --- /dev/null +++ b/windows/asiosys.h @@ -0,0 +1,53 @@ +//============================================================================ +// asiosys.h +// Jamulus Windows ASIO definitions for the ASIOSDK +// Always include asiosys.h to include the ASIOSDK ! +// Never include iasiodrv.h or asio.h directly +//============================================================================ + +#ifndef __asiosys__ +#define __asiosys__ + +#include "windows.h" + +// ASIO_HOST_VERSION: 0 or >= 2 !! See asio.h +#define ASIO_HOST_VERSION 2 + +#define ASIO_LITTLE_ENDIAN 1 +#define ASIO_CPU_X86 1 + +#define NATIVE_INT64 0 +#define IEEE754_64FLOAT 1 + +#define DRVERR_OK 0 +#define DRVERR -5000 +#define DRVERR_INVALID_PARAM DRVERR - 1 +#define DRVERR_DEVICE_NOT_FOUND DRVERR - 2 +#define DRVERR_DEVICE_NOT_OPEN DRVERR - 3 +#define DRVERR_DEVICE_ALREADY_OPEN DRVERR - 4 +#define DRVERR_DEVICE_CANNOT_OPEN DRVERR - 5 + +#define MAX_ASIO_DRIVERS 64 + +// Max string lengths, buffers should be LEN + 1 for string terminating zero. +#define MAX_ASIO_PATHLEN 511 +#define MAX_ASIO_DRVNAMELEN 127 + +#define __ASIODRIVER_FWD_DEFINED__ + +// Values for ASIOChannelInfo.isInput +#define ASIO_INPUT ASIOTrue +#define ASIO_OUTPUT ASIOFalse + +#include "iasiodrv.h" // will also include asio.h + +typedef IASIO* pIASIO; + +inline bool ASIOError_OK ( ASIOError error ) +{ + return ( ( error == ASE_OK ) || // 0 normal success return value + ( error == ASE_SUCCESS ) // 1061701536, unique success return value for ASIOFuture calls + ); +} + +#endif diff --git a/windows/sound.cpp b/windows/sound.cpp index 58ad034e8a..0ea78e9f78 100644 --- a/windows/sound.cpp +++ b/windows/sound.cpp @@ -2,7 +2,7 @@ * Copyright (c) 2004-2022 * * Author(s): - * Volker Fischer + * Volker Fischer, revised and maintained by Peter Goderie (pgScorpio) * * Description: * Sound card interface for Windows operating systems @@ -27,1118 +27,838 @@ #include "sound.h" -/* Implementation *************************************************************/ -// external references -extern AsioDrivers* asioDrivers; -bool loadAsioDriver ( char* name ); +//============================================================================ +// Helpers for checkNewDeviceCapabilities +//============================================================================ -// pointer to our sound object -CSound* pSound; - -/******************************************************************************\ -* Common * -\******************************************************************************/ -QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDriverSetup ) -{ - // find and load driver - int iDriverIdx = INVALID_INDEX; // initialize with an invalid index - - for ( int i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ ) - { - if ( strDriverName.compare ( cDriverNames[i] ) == 0 ) - { - iDriverIdx = i; - } - } - - // if the selected driver was not found, return an error message - if ( iDriverIdx == INVALID_INDEX ) - { - return tr ( "The selected audio device is no longer present in the system. Please check your audio device." ); - } - - // Save number of channels from last driver - // Need to save these (but not the driver name) as CheckDeviceCapabilities() overwrites them - long lNumInChanPrev = lNumInChan; - long lNumOutChanPrev = lNumOutChan; - - loadAsioDriver ( cDriverNames[iDriverIdx] ); - - // According to the docs, driverInfo.asioVersion and driverInfo.sysRef - // should be set, but we haven't being doing that and it seems to work - // okay... - memset ( &driverInfo, 0, sizeof driverInfo ); - - if ( ASIOInit ( &driverInfo ) != ASE_OK ) - { - // clean up and return error string - asioDrivers->removeCurrentDriver(); - return tr ( "Couldn't initialise the audio driver. Check if your audio hardware is plugged in and verify your driver settings." ); - } - - // check device capabilities if it fulfills our requirements - const QString strStat = CheckDeviceCapabilities(); // also sets lNumInChan and lNumOutChan - - // check if device is capable - if ( strStat.isEmpty() ) - { - // Reset channel mapping if the sound card name has changed or the number of channels has changed - if ( ( strCurDevName.compare ( strDriverNames[iDriverIdx] ) != 0 ) || ( lNumInChanPrev != lNumInChan ) || ( lNumOutChanPrev != lNumOutChan ) ) - { - // In order to fix https://github.com/jamulussoftware/jamulus/issues/796 - // this code runs after a change in the ASIO driver (not when changing the ASIO input selection.) - - // mapping to the defaults (first two available channels) - ResetChannelMapping(); - - // store ID of selected driver if initialization was successful - strCurDevName = cDriverNames[iDriverIdx]; - } - } - else - { - // if requested, open ASIO driver setup in case of an error - if ( bOpenDriverSetup ) - { - OpenDriverSetup(); - QMessageBox::question ( nullptr, - APP_NAME, - "Are you done with your ASIO driver settings of " + GetDeviceName ( iDriverIdx ) + "?", - QMessageBox::Yes ); - } - - // driver cannot be used, clean up - asioDrivers->removeCurrentDriver(); - } - - return strStat; -} - -void CSound::UnloadCurrentDriver() +inline bool SampleTypeSupported ( const ASIOSampleType SamType ) { - // clean up ASIO stuff - if ( bRun ) - { - Stop(); - } - if ( bufferInfos[0].buffers[0] ) - { - ASIODisposeBuffers(); - bufferInfos[0].buffers[0] = NULL; - } - ASIOExit(); - asioDrivers->removeCurrentDriver(); -} - -QString CSound::CheckDeviceCapabilities() -{ - // This function checks if our required input/output channel - // properties are supported by the selected device. If the return - // string is empty, the device can be used, otherwise the error - // message is returned. - - // check the sample rate - const ASIOError CanSaRateReturn = ASIOCanSampleRate ( SYSTEM_SAMPLE_RATE_HZ ); - - if ( ( CanSaRateReturn == ASE_NoClock ) || ( CanSaRateReturn == ASE_NotPresent ) ) - { - // return error string - return QString ( tr ( "The selected audio device is incompatible " - "since it doesn't support a sample rate of %1 Hz. Please select another " - "device." ) ) - .arg ( SYSTEM_SAMPLE_RATE_HZ ); - } - - // check if sample rate can be set - const ASIOError SetSaRateReturn = ASIOSetSampleRate ( SYSTEM_SAMPLE_RATE_HZ ); - - if ( ( SetSaRateReturn == ASE_NoClock ) || ( SetSaRateReturn == ASE_InvalidMode ) || ( SetSaRateReturn == ASE_NotPresent ) ) - { - // return error string - return QString ( tr ( "The current audio device configuration is incompatible " - "because the sample rate couldn't be set to %2 Hz. Please check for a hardware switch or " - "driver setting to set the sample rate manually and restart %1." ) ) - .arg ( APP_NAME ) - .arg ( SYSTEM_SAMPLE_RATE_HZ ); - } - - // check the number of available channels - ASIOGetChannels ( &lNumInChan, &lNumOutChan ); - - if ( ( lNumInChan < NUM_IN_OUT_CHANNELS ) || ( lNumOutChan < NUM_IN_OUT_CHANNELS ) ) - { - // return error string - return QString ( tr ( "The selected audio device is incompatible since it doesn't support " - "%1 in/out channels. Please select another device or configuration." ) ) - .arg ( NUM_IN_OUT_CHANNELS ); - } - - // clip number of input/output channels to our maximum - if ( lNumInChan > MAX_NUM_IN_OUT_CHANNELS ) - { - lNumInChan = MAX_NUM_IN_OUT_CHANNELS; - } - if ( lNumOutChan > MAX_NUM_IN_OUT_CHANNELS ) - { - lNumOutChan = MAX_NUM_IN_OUT_CHANNELS; - } - - // query channel infos for all available input channels - bool bInputChMixingSupported = true; - - for ( int i = 0; i < lNumInChan; i++ ) - { - // setup for input channels - channelInfosInput[i].isInput = ASIOTrue; - channelInfosInput[i].channel = i; - - ASIOGetChannelInfo ( &channelInfosInput[i] ); - - // Check supported sample formats. - // Actually, it would be enough to have at least two channels which - // support the required sample format. But since we have support for - // all known sample types, the following check should always pass and - // therefore we throw the error message on any channel which does not - // fulfill the sample format requirement (quick hack solution). - if ( !CheckSampleTypeSupported ( channelInfosInput[i].type ) ) - { - // return error string - return tr ( "The selected audio device is incompatible since " - "the required audio sample format isn't available. Please use another device." ); - } - - // store the name of the channel and check if channel mixing is supported - channelInputName[i] = channelInfosInput[i].name; - - if ( !CheckSampleTypeSupportedForCHMixing ( channelInfosInput[i].type ) ) - { - bInputChMixingSupported = false; - } - } - - // query channel infos for all available output channels - for ( int i = 0; i < lNumOutChan; i++ ) - { - // setup for output channels - channelInfosOutput[i].isInput = ASIOFalse; - channelInfosOutput[i].channel = i; - - ASIOGetChannelInfo ( &channelInfosOutput[i] ); - - // Check supported sample formats. - // Actually, it would be enough to have at least two channels which - // support the required sample format. But since we have support for - // all known sample types, the following check should always pass and - // therefore we throw the error message on any channel which does not - // fulfill the sample format requirement (quick hack solution). - if ( !CheckSampleTypeSupported ( channelInfosOutput[i].type ) ) - { - // return error string - return tr ( "The selected audio device is incompatible since " - "the required audio sample format isn't available. Please use another device." ); - } - } - - // special case with 4 input channels: support adding channels - if ( ( lNumInChan == 4 ) && bInputChMixingSupported ) - { - // add four mixed channels (i.e. 4 normal, 4 mixed channels) - lNumInChanPlusAddChan = 8; - - for ( int iCh = 0; iCh < lNumInChanPlusAddChan; iCh++ ) - { - int iSelCH, iSelAddCH; - - GetSelCHAndAddCH ( iCh, lNumInChan, iSelCH, iSelAddCH ); - - if ( iSelAddCH >= 0 ) - { - // for mixed channels, show both audio channel names to be mixed - channelInputName[iCh] = channelInputName[iSelCH] + " + " + channelInputName[iSelAddCH]; - } - } - } - else - { - // regular case: no mixing input channels used - lNumInChanPlusAddChan = lNumInChan; - } - - // everything is ok, return empty string for "no error" case - return ""; -} - -void CSound::SetLeftInputChannel ( const int iNewChan ) -{ - // apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < lNumInChanPlusAddChan ) ) - { - vSelectedInputChannels[0] = iNewChan; - } -} - -void CSound::SetRightInputChannel ( const int iNewChan ) -{ - // apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < lNumInChanPlusAddChan ) ) - { - vSelectedInputChannels[1] = iNewChan; - } -} - -void CSound::SetLeftOutputChannel ( const int iNewChan ) -{ - // apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) ) - { - vSelectedOutputChannels[0] = iNewChan; - } + // check for supported sample types + return ( ( SamType == ASIOSTInt16LSB ) || ( SamType == ASIOSTInt24LSB ) || ( SamType == ASIOSTInt32LSB ) || ( SamType == ASIOSTFloat32LSB ) || + ( SamType == ASIOSTFloat64LSB ) || ( SamType == ASIOSTInt32LSB16 ) || ( SamType == ASIOSTInt32LSB18 ) || + ( SamType == ASIOSTInt32LSB20 ) || ( SamType == ASIOSTInt32LSB24 ) || ( SamType == ASIOSTInt16MSB ) || ( SamType == ASIOSTInt24MSB ) || + ( SamType == ASIOSTInt32MSB ) || ( SamType == ASIOSTFloat32MSB ) || ( SamType == ASIOSTFloat64MSB ) || ( SamType == ASIOSTInt32MSB16 ) || + ( SamType == ASIOSTInt32MSB18 ) || ( SamType == ASIOSTInt32MSB20 ) || ( SamType == ASIOSTInt32MSB24 ) ); } -void CSound::SetRightOutputChannel ( const int iNewChan ) -{ - // apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) ) - { - vSelectedOutputChannels[1] = iNewChan; - } -} +//============================================================================ +// ASIO callback statics +//============================================================================ -int CSound::GetActualBufferSize ( const int iDesiredBufferSizeMono ) -{ - int iActualBufferSizeMono; - - // query the usable buffer sizes - ASIOGetBufferSize ( &HWBufferInfo.lMinSize, &HWBufferInfo.lMaxSize, &HWBufferInfo.lPreferredSize, &HWBufferInfo.lGranularity ); - - // clang-format off -/* -// TEST -#include -QMessageBox::information ( 0, "APP_NAME", QString("lMinSize: %1, lMaxSize: %2, lPreferredSize: %3, lGranularity: %4"). - arg(HWBufferInfo.lMinSize).arg(HWBufferInfo.lMaxSize).arg(HWBufferInfo.lPreferredSize).arg(HWBufferInfo.lGranularity) ); -_exit(1); -*/ - // clang-format on - - // clang-format off -// TODO see https://github.com/EddieRingle/portaudio/blob/master/src/hostapi/asio/pa_asio.cpp#L1654 (SelectHostBufferSizeForUnspecifiedUserFramesPerBuffer) - // clang-format on - - // calculate "nearest" buffer size and set internal parameter accordingly - // first check minimum and maximum values - if ( iDesiredBufferSizeMono <= HWBufferInfo.lMinSize ) - { - iActualBufferSizeMono = HWBufferInfo.lMinSize; - } - else - { - if ( iDesiredBufferSizeMono >= HWBufferInfo.lMaxSize ) - { - iActualBufferSizeMono = HWBufferInfo.lMaxSize; - } - else - { - // ASIO SDK 2.2: "Notes: When minimum and maximum buffer size are - // equal, the preferred buffer size has to be the same value as - // well; granularity should be 0 in this case." - if ( HWBufferInfo.lMinSize == HWBufferInfo.lMaxSize ) - { - iActualBufferSizeMono = HWBufferInfo.lMinSize; - } - else - { - if ( ( HWBufferInfo.lGranularity < -1 ) || ( HWBufferInfo.lGranularity == 0 ) ) - { - // Special case (seen for EMU audio cards): granularity is - // zero or less than zero (make sure to exclude the special - // case of -1). - // There is no definition of this case in the ASIO SDK - // document. We assume here that all buffer sizes in between - // minimum and maximum buffer sizes are allowed. - iActualBufferSizeMono = iDesiredBufferSizeMono; - } - else - { - // General case -------------------------------------------- - // initialization - int iTrialBufSize = HWBufferInfo.lMinSize; - int iLastTrialBufSize = HWBufferInfo.lMinSize; - bool bSizeFound = false; - - // test loop - while ( ( iTrialBufSize <= HWBufferInfo.lMaxSize ) && ( !bSizeFound ) ) - { - if ( iTrialBufSize >= iDesiredBufferSizeMono ) - { - // test which buffer size fits better: the old one or the - // current one - if ( ( iTrialBufSize - iDesiredBufferSizeMono ) > ( iDesiredBufferSizeMono - iLastTrialBufSize ) ) - { - iTrialBufSize = iLastTrialBufSize; - } - - // exit while loop - bSizeFound = true; - } - - if ( !bSizeFound ) - { - // store old trial buffer size - iLastTrialBufSize = iTrialBufSize; - - // increment trial buffer size (check for special - // case first) - if ( HWBufferInfo.lGranularity == -1 ) - { - // special case: buffer sizes are a power of 2 - iTrialBufSize *= 2; - } - else - { - iTrialBufSize += HWBufferInfo.lGranularity; - } - } - } - - // clip trial buffer size (it may happen in the while - // routine that "iTrialBufSize" is larger than "lMaxSize" in - // case "lMaxSize - lMinSize" is not divisible by the - // granularity) - if ( iTrialBufSize > HWBufferInfo.lMaxSize ) - { - iTrialBufSize = HWBufferInfo.lMaxSize; - } - - // set ASIO buffer size - iActualBufferSizeMono = iTrialBufSize; - } - } - } - } +CSound* CSound::pSound = NULL; +ASIOCallbacks CSound::asioCallbacks; // Pointers to the asio driver callbacks (ASIO driver -> Application) - return iActualBufferSizeMono; -} +//============================================================================ +// CSound: +//============================================================================ -int CSound::Init ( const int iNewPrefMonoBufferSize ) +CSound::CSound ( void ( *theProcessCallback ) ( CVector& psData, void* arg ), void* theProcessCallbackArg ) : + CSoundBase ( "ASIO", theProcessCallback, theProcessCallbackArg ), + asioDriversLoaded ( false ) { - ASIOMutex.lock(); // get mutex lock - { - // get the actual sound card buffer size which is supported - // by the audio hardware - iASIOBufferSizeMono = GetActualBufferSize ( iNewPrefMonoBufferSize ); + setObjectName ( "CSoundThread" ); - // init base class - CSoundBase::Init ( iASIOBufferSizeMono ); - - // set internal buffer size value and calculate stereo buffer size - iASIOBufferSizeStereo = 2 * iASIOBufferSizeMono; - - // set the sample rate - ASIOSetSampleRate ( SYSTEM_SAMPLE_RATE_HZ ); - - // create memory for intermediate audio buffer - vecsMultChanAudioSndCrd.Init ( iASIOBufferSizeStereo ); - - // create and activate ASIO buffers (buffer size in samples), - // dispose old buffers (if any) - ASIODisposeBuffers(); - - // prepare input channels - for ( int i = 0; i < lNumInChan; i++ ) - { - bufferInfos[i].isInput = ASIOTrue; - bufferInfos[i].channelNum = i; - bufferInfos[i].buffers[0] = 0; - bufferInfos[i].buffers[1] = 0; - } - - // prepare output channels - for ( int i = 0; i < lNumOutChan; i++ ) - { - bufferInfos[lNumInChan + i].isInput = ASIOFalse; - bufferInfos[lNumInChan + i].channelNum = i; - bufferInfos[lNumInChan + i].buffers[0] = 0; - bufferInfos[lNumInChan + i].buffers[1] = 0; - } + asioDriverData.clear(); - ASIOCreateBuffers ( bufferInfos, lNumInChan + lNumOutChan, iASIOBufferSizeMono, &asioCallbacks ); - - // query the latency of the driver - long lInputLatency = 0; - long lOutputLatency = 0; - - if ( ASIOGetLatencies ( &lInputLatency, &lOutputLatency ) != ASE_NotPresent ) - { - // add the input and output latencies (returned in number of - // samples) and calculate the time in ms - fInOutLatencyMs = ( static_cast ( lInputLatency ) + lOutputLatency ) * 1000 / SYSTEM_SAMPLE_RATE_HZ; - } - else - { - // no latency available - fInOutLatencyMs = 0.0f; - } - - // check whether the driver requires the ASIOOutputReady() optimization - // (can be used by the driver to reduce output latency by one block) - bASIOPostOutput = ( ASIOOutputReady() == ASE_OK ); - } - ASIOMutex.unlock(); - - return iASIOBufferSizeMono; -} + // set up the asioCallback structure + asioCallbacks.bufferSwitch = &_onBufferSwitch; + asioCallbacks.sampleRateDidChange = &_onSampleRateChanged; + asioCallbacks.asioMessage = &_onAsioMessages; + asioCallbacks.bufferSwitchTimeInfo = &_onBufferSwitchTimeInfo; -void CSound::Start() -{ - // start audio - ASIOStart(); + // We assume NULL'd pointers in this structure indicate that buffers are not + // allocated yet (see UnloadCurrentDriver). + memset ( bufferInfos, 0, sizeof bufferInfos ); - // call base class - CSoundBase::Start(); + // Set my properties in CSoundbase: + soundProperties.bHasSetupDialog = true; + // Reset default texts according new soundProperties + soundProperties.setDefaultTexts(); + // Set specific texts according this CSound implementation + soundProperties.strSetupButtonText = tr ( "ASIO Device Settings" ); + soundProperties.strSetupButtonToolTip = tr ( "Opens the driver settings when available..." ) + "
" + + tr ( "Note: %1 currently only supports devices with a sample rate of %2 Hz. " + "You may need to re-select the driver before any changed settings will take effect." ) + .arg ( APP_NAME ) + .arg ( SYSTEM_SAMPLE_RATE_HZ ) + + htmlNewLine() + tr ( "For more help see jamulus.io." ); + soundProperties.strSetupButtonAccessibleName = CSoundBase::tr ( "ASIO Device Settings push button" ); + + soundProperties.strAudioDeviceWhatsThis = ( "" + tr ( "Audio Device" ) + ": " + + tr ( "Under the Windows operating system the ASIO driver (sound card) can be " + "selected using %1. If the selected driver is not valid an error " + "message will be shown. " + "Under macOS the input and output hardware can be selected." ) + .arg ( APP_NAME ) + + "
" + + tr ( "If the driver is selected during an active connection, the connection " + "is stopped, the driver is changed and the connection is started again " + "automatically." ) ); + + soundProperties.strAudioDeviceToolTip = tr ( "If the ASIO4ALL driver is used, " + "please note that this driver usually introduces approx. 10-30 ms of " + "additional audio delay. Using a sound card with a native ASIO driver " + "is therefore recommended." ) + + htmlNewLine() + + tr ( "If you are using the kX ASIO " + "driver, make sure to connect the ASIO inputs in the kX DSP settings " + "panel." ); + + // init pointer to our sound object for the asio callback functions + pSound = this; } -void CSound::Stop() +#ifdef OLD_SOUND_COMPATIBILITY +// Backwards compatibility constructor +CSound::CSound ( void ( *fpProcessCallback ) ( CVector& psData, void* pCallbackArg ), + void* theProcessCallbackArg, + QString /* strMIDISetup */, + bool /* bNoAutoJackConnect */, + QString /* strNClientName */ ) : + CSoundBase ( "ASIO", fpProcessCallback, theProcessCallbackArg ), + asioDriversLoaded ( false ) { - // stop audio - ASIOStop(); + setObjectName ( "CSoundThread" ); - // call base class - CSoundBase::Stop(); + asioDriverData.clear(); - // make sure the working thread is actually done - // (by checking the locked state) - if ( ASIOMutex.tryLock ( 5000 ) ) - { - ASIOMutex.unlock(); - } -} - -CSound::CSound ( void ( *fpNewCallback ) ( CVector& psData, void* arg ), - void* arg, - const QString& strMIDISetup, - const bool, - const QString& ) : - CSoundBase ( "ASIO", fpNewCallback, arg, strMIDISetup ), - lNumInChan ( 0 ), - lNumInChanPlusAddChan ( 0 ), - lNumOutChan ( 0 ), - fInOutLatencyMs ( 0.0f ), // "0.0" means that no latency value is available - vSelectedInputChannels ( NUM_IN_OUT_CHANNELS ), - vSelectedOutputChannels ( NUM_IN_OUT_CHANNELS ) -{ - int i; - - // init pointer to our sound object - pSound = this; + // set up the asioCallback structure + asioCallbacks.bufferSwitch = &_onBufferSwitch; + asioCallbacks.sampleRateDidChange = &_onSampleRateChanged; + asioCallbacks.asioMessage = &_onAsioMessages; + asioCallbacks.bufferSwitchTimeInfo = &_onBufferSwitchTimeInfo; // We assume NULL'd pointers in this structure indicate that buffers are not // allocated yet (see UnloadCurrentDriver). memset ( bufferInfos, 0, sizeof bufferInfos ); - // get available ASIO driver names in system - for ( i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ ) - { - // allocate memory for driver names - cDriverNames[i] = new char[32]; - } - - char cDummyName[] = "dummy"; - loadAsioDriver ( cDummyName ); // to initialize external object - lNumDevs = asioDrivers->getDriverNames ( cDriverNames, MAX_NUMBER_SOUND_CARDS ); - - // in case we do not have a driver available, throw error - if ( lNumDevs == 0 ) - { - throw CGenErr ( "" + tr ( "No ASIO audio device driver found." ) + "

" + - QString ( tr ( "Please install an ASIO driver before running %1. " - "If you own a device with ASIO support, install its official ASIO driver. " - "If not, you'll need to install a universal driver like ASIO4ALL." ) ) - .arg ( APP_NAME ) ); - } - asioDrivers->removeCurrentDriver(); - - // copy driver names to base class but internally we still have to use - // the char* variable because of the ASIO API :-( - for ( i = 0; i < lNumDevs; i++ ) - { - strDriverNames[i] = cDriverNames[i]; - } - - // init device index as not initialized (invalid) - strCurDevName = ""; - - // init channel mapping - ResetChannelMapping(); - - // set up the asioCallback structure - asioCallbacks.bufferSwitch = &bufferSwitch; - asioCallbacks.sampleRateDidChange = &sampleRateChanged; - asioCallbacks.asioMessage = &asioMessages; - asioCallbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfo; -} - -void CSound::ResetChannelMapping() -{ - // init selected channel numbers with defaults: use first available - // channels for input and output - vSelectedInputChannels[0] = 0; - vSelectedInputChannels[1] = 1; - vSelectedOutputChannels[0] = 0; - vSelectedOutputChannels[1] = 1; + // Set my properties in CSoundbase: + soundProperties.bHasSetupDialog = true; + // Reset default texts according new soundProperties + soundProperties.setDefaultTexts(); + // Set specific texts according this CSound implementation + soundProperties.strSetupButtonText = tr ( "ASIO Device Settings" ); + soundProperties.strSetupButtonToolTip = tr ( "Opens the driver settings when available..." ) + "
" + + tr ( "Note: %1 currently only supports devices with a sample rate of %2 Hz. " + "You may need to re-select the driver before any changed settings will take effect." ) + .arg ( APP_NAME ) + .arg ( SYSTEM_SAMPLE_RATE_HZ ) + + htmlNewLine() + tr ( "For more help see jamulus.io." ); + soundProperties.strSetupButtonAccessibleName = CSoundBase::tr ( "ASIO Device Settings push button" ); + + soundProperties.strAudioDeviceWhatsThis = ( "" + tr ( "Audio Device" ) + ": " + + tr ( "Under the Windows operating system the ASIO driver (sound card) can be " + "selected using %1. If the selected driver is not valid an error " + "message will be shown. " + "Under macOS the input and output hardware can be selected." ) + .arg ( APP_NAME ) + + "
" + + tr ( "If the driver is selected during an active connection, the connection " + "is stopped, the driver is changed and the connection is started again " + "automatically." ) ); + + soundProperties.strAudioDeviceToolTip = tr ( "If the ASIO4ALL driver is used, " + "please note that this driver usually introduces approx. 10-30 ms of " + "additional audio delay. Using a sound card with a native ASIO driver " + "is therefore recommended." ) + + htmlNewLine() + + tr ( "If you are using the kX ASIO " + "driver, make sure to connect the ASIO inputs in the kX DSP settings " + "panel." ); + + // init pointer to our sound object for the asio callback functions + pSound = this; } +#endif -// ASIO callbacks ------------------------------------------------------------- -ASIOTime* CSound::bufferSwitchTimeInfo ( ASIOTime*, long index, ASIOBool processNow ) -{ - bufferSwitch ( index, processNow ); - return 0L; -} +//============================================================================ +// ASIO callback implementations +//============================================================================ -bool CSound::CheckSampleTypeSupported ( const ASIOSampleType SamType ) +void CSound::onBufferSwitch ( long index, ASIOBool /*processNow*/ ) { - // check for supported sample types - return ( ( SamType == ASIOSTInt16LSB ) || ( SamType == ASIOSTInt24LSB ) || ( SamType == ASIOSTInt32LSB ) || ( SamType == ASIOSTFloat32LSB ) || - ( SamType == ASIOSTFloat64LSB ) || ( SamType == ASIOSTInt32LSB16 ) || ( SamType == ASIOSTInt32LSB18 ) || - ( SamType == ASIOSTInt32LSB20 ) || ( SamType == ASIOSTInt32LSB24 ) || ( SamType == ASIOSTInt16MSB ) || ( SamType == ASIOSTInt24MSB ) || - ( SamType == ASIOSTInt32MSB ) || ( SamType == ASIOSTFloat32MSB ) || ( SamType == ASIOSTFloat64MSB ) || ( SamType == ASIOSTInt32MSB16 ) || - ( SamType == ASIOSTInt32MSB18 ) || ( SamType == ASIOSTInt32MSB20 ) || ( SamType == ASIOSTInt32MSB24 ) ); -} + // pgSorpio: Impoved readability by adding the intermediate pointers pAsioSelInput, pAsioAddInput and pAsioSelOutput. + // And so its immediately clear mixing can be supported for ANY supported sample type! + // (Just adding another int16_t using pAsioAddInput instead of pAsioSelInput!) + // + // Also added input muting here + // + // perform the processing for input and output + QMutexLocker locker ( &mutexAudioProcessCallback ); // get mutex lock -bool CSound::CheckSampleTypeSupportedForCHMixing ( const ASIOSampleType SamType ) -{ - // check for supported sample types for audio channel mixing (see bufferSwitch) - return ( ( SamType == ASIOSTInt16LSB ) || ( SamType == ASIOSTInt24LSB ) || ( SamType == ASIOSTInt32LSB ) ); -} + // CAPTURE ------------------------------------------------------------- + for ( unsigned int i = 0; i < PROT_NUM_IN_CHANNELS; i++ ) + { + int iSelCH, iSelAddCH; + getInputSelAndAddChannels ( selectedInputChannels[i], lNumInChan, lNumAddedInChan, iSelCH, iSelAddCH ); -void CSound::bufferSwitch ( long index, ASIOBool ) -{ - int iCurSample; + int iInputGain = inputChannelsGain[i]; - // get references to class members - int& iASIOBufferSizeMono = pSound->iASIOBufferSizeMono; - CVector& vecsMultChanAudioSndCrd = pSound->vecsMultChanAudioSndCrd; + void* pAsioSelInput = bufferInfos[iSelCH].buffers[index]; + void* pAsioAddInput = ( iSelAddCH >= 0 ) ? bufferInfos[iSelAddCH].buffers[index] : NULL; - // perform the processing for input and output - pSound->ASIOMutex.lock(); // get mutex lock - { - // CAPTURE ------------------------------------------------------------- - for ( int i = 0; i < NUM_IN_OUT_CHANNELS; i++ ) + // copy new captured block in thread transfer buffer (copy + // mono data interleaved in stereo buffer) + switch ( asioDriver.inputChannelInfo ( iSelCH ).type ) { - int iSelCH, iSelAddCH; - - GetSelCHAndAddCH ( pSound->vSelectedInputChannels[i], pSound->lNumInChan, iSelCH, iSelAddCH ); + case ASIOSTInt16LSB: + { + // no type conversion required, just copy operation + int16_t* pASIOBuf = static_cast ( pAsioSelInput ); - // copy new captured block in thread transfer buffer (copy - // mono data interleaved in stereo buffer) - switch ( pSound->channelInfosInput[iSelCH].type ) + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - case ASIOSTInt16LSB: + audioBuffer[2 * iCurSample + i] = pASIOBuf[iCurSample] * iInputGain; + } + + if ( pAsioAddInput ) { - // no type conversion required, just copy operation - int16_t* pASIOBuf = static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ); + // mix input channels case: + int16_t* pASIOBufAdd = static_cast ( pAsioAddInput ); - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = pASIOBuf[iCurSample]; + audioBuffer[2 * iCurSample + i] += pASIOBufAdd[iCurSample] * iInputGain; } + } + break; + } - if ( iSelAddCH >= 0 ) - { - // mix input channels case: - int16_t* pASIOBufAdd = static_cast ( pSound->bufferInfos[iSelAddCH].buffers[index] ); - - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - Float2Short ( (float) vecsMultChanAudioSndCrd[2 * iCurSample + i] + (float) pASIOBufAdd[iCurSample] ); - } - } - break; + case ASIOSTInt24LSB: + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + int iCurSam = 0; + memcpy ( &iCurSam, ( (char*) pAsioSelInput ) + iCurSample * 3, 3 ); + iCurSam >>= 8; + + audioBuffer[2 * iCurSample + i] = static_cast ( iCurSam ) * iInputGain; } - case ASIOSTInt24LSB: - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + if ( pAsioAddInput ) + { + // mix input channels case: + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { int iCurSam = 0; - memcpy ( &iCurSam, ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, 3 ); + memcpy ( &iCurSam, ( (char*) pAsioAddInput ) + iCurSample * 3, 3 ); iCurSam >>= 8; - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( iCurSam ); + audioBuffer[2 * iCurSample + i] += iCurSam * iInputGain; + ; } + } + break; - if ( iSelAddCH >= 0 ) - { - // mix input channels case: - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - int iCurSam = 0; - memcpy ( &iCurSam, ( (char*) pSound->bufferInfos[iSelAddCH].buffers[index] ) + iCurSample * 3, 3 ); - iCurSam >>= 8; - - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - Float2Short ( (float) vecsMultChanAudioSndCrd[2 * iCurSample + i] + (float) static_cast ( iCurSam ) ); - } - } - break; + case ASIOSTInt32LSB: + { + int32_t* pASIOBuf = static_cast ( pAsioSelInput ); - case ASIOSTInt32LSB: + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - int32_t* pASIOBuf = static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ); + audioBuffer[2 * iCurSample + i] = static_cast ( pASIOBuf[iCurSample] >> 16 ) * iInputGain; + } - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( pASIOBuf[iCurSample] >> 16 ); - } + if ( pAsioAddInput ) + { + // mix input channels case: + int32_t* pASIOBuf = static_cast ( pAsioSelInput ); - if ( iSelAddCH >= 0 ) + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - // mix input channels case: - int32_t* pASIOBufAdd = static_cast ( pSound->bufferInfos[iSelAddCH].buffers[index] ); - - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = Float2Short ( (float) vecsMultChanAudioSndCrd[2 * iCurSample + i] + - (float) static_cast ( pASIOBufAdd[iCurSample] >> 16 ) ); - } + audioBuffer[2 * iCurSample + i] += static_cast ( pASIOBuf[iCurSample] >> 16 ) * iInputGain; } - break; } + break; + } - case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture - // clang-format off + case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + audioBuffer[2 * iCurSample + i] = static_cast ( static_cast ( pAsioSelInput )[iCurSample] * _MAXSHORT ) * iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - static_cast ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] * _MAXSHORT ); + audioBuffer[2 * iCurSample + i] += + static_cast ( static_cast ( pAsioAddInput )[iCurSample] * _MAXSHORT ) * iInputGain; } - break; + } + break; - case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture - // clang-format off + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + audioBuffer[2 * iCurSample + i] += + static_cast ( static_cast ( pAsioSelInput )[iCurSample] * _MAXSHORT ) * iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - static_cast ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] * _MAXSHORT ); + audioBuffer[2 * iCurSample + i] += + static_cast ( static_cast ( pAsioAddInput )[iCurSample] * _MAXSHORT ) * iInputGain; } - break; + } + break; - case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment - // clang-format off + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + audioBuffer[2 * iCurSample + i] = static_cast ( static_cast ( pAsioSelInput )[iCurSample] & 0xFFFF ) * iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - static_cast ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0xFFFF ); + audioBuffer[2 * iCurSample + i] += + static_cast ( static_cast ( pAsioAddInput )[iCurSample] & 0xFFFF ) * iInputGain; } - break; + } + break; - case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment - // clang-format off + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + audioBuffer[2 * iCurSample + i] = + static_cast ( ( static_cast ( pAsioSelInput )[iCurSample] & 0x3FFFF ) >> 2 ) * iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - static_cast ( ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0x3FFFF ) >> 2 ); + audioBuffer[2 * iCurSample + i] += + static_cast ( ( static_cast ( pAsioAddInput )[iCurSample] & 0x3FFFF ) >> 2 ) * iInputGain; } - break; + } + break; - case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment - // clang-format off + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + audioBuffer[2 * iCurSample + i] = + static_cast ( ( static_cast ( pAsioSelInput )[iCurSample] & 0xFFFFF ) >> 4 ) * iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - static_cast ( ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0xFFFFF ) >> 4 ); + audioBuffer[2 * iCurSample + i] += + static_cast ( ( static_cast ( pAsioAddInput )[iCurSample] & 0xFFFFF ) >> 4 ) * iInputGain; } - break; + } + break; - case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment - // clang-format off + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + audioBuffer[2 * iCurSample + i] = + static_cast ( ( static_cast ( pAsioSelInput )[iCurSample] & 0xFFFFFF ) >> 8 ) * iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - static_cast ( ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0xFFFFFF ) >> 8 ); + audioBuffer[2 * iCurSample + i] += + static_cast ( ( static_cast ( pAsioAddInput )[iCurSample] & 0xFFFFFF ) >> 8 ) * iInputGain; } - break; + } + break; - case ASIOSTInt16MSB: - // clang-format off + case ASIOSTInt16MSB: + // clang-format off // NOT YET TESTED - // clang-format on - // flip bits - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + // flip bits + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + audioBuffer[2 * iCurSample + i] = Flip16Bits ( ( static_cast ( pAsioSelInput ) )[iCurSample] ) * iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - Flip16Bits ( ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ) )[iCurSample] ); + audioBuffer[2 * iCurSample + i] += Flip16Bits ( ( static_cast ( pAsioAddInput ) )[iCurSample] ) * iInputGain; } - break; + } + break; - case ASIOSTInt24MSB: - // clang-format off + case ASIOSTInt24MSB: + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // because the bits are flipped, we do not have to perform the + // shift by 8 bits + int iCurSam = 0; + memcpy ( &iCurSam, ( (char*) pAsioSelInput ) + iCurSample * 3, 3 ); + + audioBuffer[2 * iCurSample + i] = Flip16Bits ( static_cast ( iCurSam ) ) * iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { // because the bits are flipped, we do not have to perform the // shift by 8 bits int iCurSam = 0; - memcpy ( &iCurSam, ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, 3 ); + memcpy ( &iCurSam, ( (char*) pAsioAddInput ) + iCurSample * 3, 3 ); - vecsMultChanAudioSndCrd[2 * iCurSample + i] = Flip16Bits ( static_cast ( iCurSam ) ); + audioBuffer[2 * iCurSample + i] += Flip16Bits ( static_cast ( iCurSam ) ) * iInputGain; } - break; + } + break; - case ASIOSTInt32MSB: - // clang-format off + case ASIOSTInt32MSB: + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // flip bits and convert to 16 bit + audioBuffer[2 * iCurSample + i] = + static_cast ( Flip32Bits ( static_cast ( pAsioSelInput )[iCurSample] ) >> 16 ) * iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { // flip bits and convert to 16 bit - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) >> 16 ); + audioBuffer[2 * iCurSample + i] += + static_cast ( Flip32Bits ( static_cast ( pAsioAddInput )[iCurSample] ) >> 16 ) * iInputGain; } - break; + } + break; - case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture - // clang-format off + case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + audioBuffer[2 * iCurSample + i] = + static_cast ( static_cast ( Flip32Bits ( static_cast ( pAsioSelInput )[iCurSample] ) ) * _MAXSHORT ) * + iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - static_cast ( Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) ) * - _MAXSHORT ); + audioBuffer[2 * iCurSample + i] += + static_cast ( static_cast ( Flip32Bits ( static_cast ( pAsioAddInput )[iCurSample] ) ) * + _MAXSHORT ) * + iInputGain; } - break; + } + break; - case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture - // clang-format off + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + audioBuffer[2 * iCurSample + i] = + static_cast ( static_cast ( Flip64Bits ( static_cast ( pAsioSelInput )[iCurSample] ) ) * _MAXSHORT ) * + iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - static_cast ( Flip64Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) ) * - _MAXSHORT ); + audioBuffer[2 * iCurSample + i] += + static_cast ( static_cast ( Flip64Bits ( static_cast ( pAsioAddInput )[iCurSample] ) ) * + _MAXSHORT ) * + iInputGain; } - break; + } + break; - case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment - // clang-format off + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + audioBuffer[2 * iCurSample + i] = + static_cast ( Flip32Bits ( static_cast ( pAsioSelInput )[iCurSample] ) & 0xFFFF ) * iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0xFFFF ); + audioBuffer[2 * iCurSample + i] += + static_cast ( Flip32Bits ( static_cast ( pAsioAddInput )[iCurSample] ) & 0xFFFF ) * iInputGain; } - break; + } + break; - case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment - // clang-format off + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + audioBuffer[2 * iCurSample + i] = + static_cast ( ( Flip32Bits ( static_cast ( pAsioSelInput )[iCurSample] ) & 0x3FFFF ) >> 2 ) * iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - ( Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0x3FFFF ) >> 2 ); + audioBuffer[2 * iCurSample + i] += + static_cast ( ( Flip32Bits ( static_cast ( pAsioAddInput )[iCurSample] ) & 0x3FFFF ) >> 2 ) * iInputGain; } - break; + } + break; - case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment - // clang-format off + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + audioBuffer[2 * iCurSample + i] = + static_cast ( ( Flip32Bits ( static_cast ( pAsioSelInput )[iCurSample] ) & 0xFFFFF ) >> 4 ) * iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - ( Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0xFFFFF ) >> 4 ); + audioBuffer[2 * iCurSample + i] += + static_cast ( ( Flip32Bits ( static_cast ( pAsioAddInput )[iCurSample] ) & 0xFFFFF ) >> 4 ) * iInputGain; } - break; + } + break; - case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment - // clang-format off + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + audioBuffer[2 * iCurSample + i] = + static_cast ( ( Flip32Bits ( static_cast ( pAsioSelInput )[iCurSample] ) & 0xFFFFFF ) >> 8 ) * iInputGain; + } + + if ( pAsioAddInput ) + { + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - ( Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0xFFFFFF ) >> 8 ); + audioBuffer[2 * iCurSample + i] += + static_cast ( ( Flip32Bits ( static_cast ( pAsioAddInput )[iCurSample] ) & 0xFFFFFF ) >> 8 ) * iInputGain; } - break; } + break; } + } + + // call processing callback function + processCallback ( audioBuffer ); - // call processing callback function - pSound->ProcessCallback ( vecsMultChanAudioSndCrd ); + // PLAYBACK ------------------------------------------------------------ + for ( unsigned int i = 0; i < PROT_NUM_OUT_CHANNELS; i++ ) + { + const int iSelCH = lNumInChan + selectedOutputChannels[i]; + void* pAsioSelOutput = bufferInfos[iSelCH].buffers[index]; - // PLAYBACK ------------------------------------------------------------ - for ( int i = 0; i < NUM_IN_OUT_CHANNELS; i++ ) + // copy data from sound card in output buffer (copy + // interleaved stereo data in mono sound card buffer) + switch ( asioDriver.outputChannelInfo ( selectedOutputChannels[i] ).type ) { - const int iSelCH = pSound->lNumInChan + pSound->vSelectedOutputChannels[i]; + case ASIOSTInt16LSB: + { + // no type conversion required, just copy operation + int16_t* pASIOBuf = static_cast ( pAsioSelOutput ); - // copy data from sound card in output buffer (copy - // interleaved stereo data in mono sound card buffer) - switch ( pSound->channelInfosOutput[pSound->vSelectedOutputChannels[i]].type ) - { - case ASIOSTInt16LSB: + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) { - // no type conversion required, just copy operation - int16_t* pASIOBuf = static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ); - - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - pASIOBuf[iCurSample] = vecsMultChanAudioSndCrd[2 * iCurSample + i]; - } - break; + pASIOBuf[iCurSample] = audioBuffer[iCurSample * 2 + i]; } + break; + } - case ASIOSTInt24LSB: - // clang-format off + case ASIOSTInt24LSB: + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert current sample in 24 bit format - int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // convert current sample in 24 bit format + int32_t iCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - iCurSam <<= 8; + iCurSam <<= 8; - memcpy ( ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, &iCurSam, 3 ); - } - break; + memcpy ( ( (char*) pAsioSelOutput ) + iCurSample * 3, &iCurSam, 3 ); + } + break; - case ASIOSTInt32LSB: - { - int32_t* pASIOBuf = static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ); + case ASIOSTInt32LSB: + { + int32_t* pASIOBuf = static_cast ( pAsioSelOutput ); - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - pASIOBuf[iCurSample] = ( iCurSam << 16 ); - } - break; + pASIOBuf[iCurSample] = ( iCurSam << 16 ); } + break; + } - case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture - // clang-format off + case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - const float fCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + const float fCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = fCurSam / _MAXSHORT; - } - break; + static_cast ( pAsioSelOutput )[iCurSample] = fCurSam / _MAXSHORT; + } + break; - case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture - // clang-format off + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - const double fCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + const double fCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = fCurSam / _MAXSHORT; - } - break; + static_cast ( pAsioSelOutput )[iCurSample] = fCurSam / _MAXSHORT; + } + break; - case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment - // clang-format off + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = iCurSam; - } - break; + static_cast ( pAsioSelOutput )[iCurSample] = iCurSam; + } + break; - case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment - // clang-format off + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = ( iCurSam << 2 ); - } - break; + static_cast ( pAsioSelOutput )[iCurSample] = ( iCurSam << 2 ); + } + break; - case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment - // clang-format off + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = ( iCurSam << 4 ); - } - break; + static_cast ( pAsioSelOutput )[iCurSample] = ( iCurSam << 4 ); + } + break; - case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment - // clang-format off + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = ( iCurSam << 8 ); - } - break; + static_cast ( pAsioSelOutput )[iCurSample] = ( iCurSam << 8 ); + } + break; - case ASIOSTInt16MSB: - // clang-format off + case ASIOSTInt16MSB: + // clang-format off // NOT YET TESTED - // clang-format on - // flip bits - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - ( (int16_t*) pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = - Flip16Bits ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - } - break; + // clang-format on + // flip bits + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + ( (int16_t*) pAsioSelOutput )[iCurSample] = Flip16Bits ( audioBuffer[iCurSample * 2 + i] ); + } + break; - case ASIOSTInt24MSB: - // clang-format off + case ASIOSTInt24MSB: + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // because the bits are flipped, we do not have to perform the - // shift by 8 bits - int32_t iCurSam = static_cast ( Flip16Bits ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ) ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // because the bits are flipped, we do not have to perform the + // shift by 8 bits + int32_t iCurSam = static_cast ( Flip16Bits ( audioBuffer[iCurSample * 2 + i] ) ); - memcpy ( ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, &iCurSam, 3 ); - } - break; + memcpy ( ( (char*) pAsioSelOutput ) + iCurSample * 3, &iCurSam, 3 ); + } + break; - case ASIOSTInt32MSB: - // clang-format off + case ASIOSTInt32MSB: + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit and flip bits - int iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // convert to 32 bit and flip bits + int iCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam << 16 ); - } - break; + static_cast ( pAsioSelOutput )[iCurSample] = Flip32Bits ( iCurSam << 16 ); + } + break; - case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture - // clang-format off + case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - const float fCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + const float fCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = - static_cast ( Flip32Bits ( static_cast ( fCurSam / _MAXSHORT ) ) ); - } - break; + static_cast ( pAsioSelOutput )[iCurSample] = + static_cast ( Flip32Bits ( static_cast ( fCurSam / _MAXSHORT ) ) ); + } + break; - case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture - // clang-format off + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - const double fCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + const double fCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = - static_cast ( Flip64Bits ( static_cast ( fCurSam / _MAXSHORT ) ) ); - } - break; + static_cast ( pAsioSelOutput )[iCurSample] = + static_cast ( Flip64Bits ( static_cast ( fCurSam / _MAXSHORT ) ) ); + } + break; - case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment - // clang-format off + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam ); - } - break; + static_cast ( pAsioSelOutput )[iCurSample] = Flip32Bits ( iCurSam ); + } + break; - case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment - // clang-format off + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam << 2 ); - } - break; + static_cast ( pAsioSelOutput )[iCurSample] = Flip32Bits ( iCurSam << 2 ); + } + break; - case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment - // clang-format off + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam << 4 ); - } - break; + static_cast ( pAsioSelOutput )[iCurSample] = Flip32Bits ( iCurSam << 4 ); + } + break; - case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment - // clang-format off + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + // clang-format off // NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + // clang-format on + for ( unsigned int iCurSample = 0; iCurSample < iDeviceBufferSize; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( audioBuffer[iCurSample * 2 + i] ); - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam << 8 ); - } - break; + static_cast ( pAsioSelOutput )[iCurSample] = Flip32Bits ( iCurSam << 8 ); } + break; } + } - // Finally if the driver supports the ASIOOutputReady() optimization, - // do it here, all data are in place ----------------------------------- - if ( pSound->bASIOPostOutput ) - { - ASIOOutputReady(); - } + // Finally if the driver supports the ASIOOutputReady() optimization, + // do it here, all data are in place now + if ( bASIOPostOutput ) + { + asioDriver.outputReady(); + } +} + +ASIOTime* CSound::onBufferSwitchTimeInfo ( ASIOTime*, long index, ASIOBool processNow ) +{ + onBufferSwitch ( index, processNow ); + return 0L; +} + +void CSound::onSampleRateChanged ( ASIOSampleRate sampleRate ) +{ + if ( sampleRate != SYSTEM_SAMPLE_RATE_HZ ) + { + Stop(); + // implementation error ??? + // how to notify CClient ?? } - pSound->ASIOMutex.unlock(); } -long CSound::asioMessages ( long selector, long, void*, double* ) +long CSound::onAsioMessages ( long selector, long, void*, double* ) { long ret = 0; @@ -1151,12 +871,12 @@ long CSound::asioMessages ( long selector, long, void*, double* ) // both messages might be send if the buffer size changes case kAsioBufferSizeChange: - pSound->EmitReinitRequestSignal ( RS_ONLY_RESTART_AND_INIT ); + emit ReinitRequest ( tSndCrdResetType::RS_ONLY_RESTART_AND_INIT ); ret = 1L; // 1L if request is accepted or 0 otherwise break; case kAsioResetRequest: - pSound->EmitReinitRequestSignal ( RS_RELOAD_RESTART_AND_INIT ); + emit ReinitRequest ( tSndCrdResetType::RS_RELOAD_RESTART_AND_INIT ); ret = 1L; // 1L if request is accepted or 0 otherwise break; } @@ -1164,56 +884,461 @@ long CSound::asioMessages ( long selector, long, void*, double* ) return ret; } -int16_t CSound::Flip16Bits ( const int16_t iIn ) +//============================================================================ +// CSound Internal +//============================================================================ + +//==================================== +// ASIO Stuff +//==================================== + +void CSound::closeAllAsioDrivers() +{ + newAsioDriver.Close(); + closeCurrentDevice(); // Closes asioDriver and clears DeviceInfo + + // Just make sure all other driver in the list are closed too... + for ( long i = 0; i < lNumDevices; i++ ) + { + CloseAsioDriver ( &asioDriverData[i] ); + } +} + +bool CSound::prepareAsio ( bool bStartAsio ) +{ + bool ok = true; + + // dispose old buffers (if any) + asioDriver.disposeBuffers(); // implies ASIOStop() ! + + // create memory for intermediate audio buffer + audioBuffer.Init ( PROT_NUM_IN_CHANNELS * iDeviceBufferSize ); + + long numInputChannels = asioDriver.numInputChannels(); + long numOutputChannels = asioDriver.numOutputChannels(); + + long bufferIndex = 0; + + // prepare input channels + for ( int i = 0; i < numInputChannels; i++ ) + { + bufferInfos[bufferIndex].isInput = ASIO_INPUT; + bufferInfos[bufferIndex].channelNum = i; + bufferInfos[bufferIndex].buffers[0] = 0; + bufferInfos[bufferIndex].buffers[1] = 0; + bufferIndex++; + } + + // prepare output channels + for ( int i = 0; i < numOutputChannels; i++ ) + { + bufferInfos[bufferIndex].isInput = ASIO_OUTPUT; + bufferInfos[bufferIndex].channelNum = i; + bufferInfos[bufferIndex].buffers[0] = 0; + bufferInfos[bufferIndex].buffers[1] = 0; + bufferIndex++; + } + + // create and activate ASIO buffers (buffer size in samples), + if ( !asioDriver.createBuffers ( bufferInfos, bufferIndex, iDeviceBufferSize, &asioCallbacks ) ) + { + ok = false; + } + + // check whether the driver requires the ASIOOutputReady() optimization + // (can be used by the driver to reduce output latency by one block) + bASIOPostOutput = ( asioDriver.outputReady() == ASE_OK ); + + // set the sample rate (make sure we get the correct latency) + if ( !asioDriver.setSampleRate ( SYSTEM_SAMPLE_RATE_HZ ) ) + { + ok = false; + } + + // Query the latency of the driver + long lInputLatency = 0; + long lOutputLatency = 0; + // Latency is returned in number of samples + if ( !asioDriver.getLatencies ( &lInputLatency, &lOutputLatency ) ) + { + // No latency available + // Assume just the buffer delay + lInputLatency = lOutputLatency = iDeviceBufferSize; + } + + // Calculate total latency in ms + { + float fLatency; + + fLatency = lInputLatency; + fLatency += lOutputLatency; + fLatency *= 1000; + fLatency /= SYSTEM_SAMPLE_RATE_HZ; + + fInOutLatencyMs = fLatency; + } + + if ( ok && bStartAsio ) + { + return asioDriver.start(); + } + + return ok; +} + +unsigned int CSound::getDeviceBufferSize ( unsigned int iDesiredBufferSize ) +{ + int iActualBufferSize = iDesiredBufferSize; + + // Limits to be obtained from the asio driver. + // Init all to zero, just in case the asioDriver.getBufferSize call fails. + long lMinSize = 0; + long lMaxSize = 0; + long lPreferredSize = 0; + long lGranularity = 0; + + // query the asio driver + asioDriver.getBufferSize ( &lMinSize, &lMaxSize, &lPreferredSize, &lGranularity ); + + // calculate "nearest" buffer size within limits + // + // first check minimum and maximum limits + if ( iActualBufferSize < lMinSize ) + { + iActualBufferSize = lMinSize; + } + + if ( iActualBufferSize > lMaxSize ) + { + iActualBufferSize = lMaxSize; + } + + if ( iActualBufferSize == lPreferredSize ) + { + // No need to check any further + return iActualBufferSize; + } + + if ( lMaxSize > lMinSize ) // Do we have any choice ?? + { + if ( lGranularity == -1 ) + { + // Size must be a power of 2 + // So lMinSize and lMaxSize are expected be powers of 2 too! + // Start with lMinSize... + long lTrialBufSize = lMinSize; + do + { + // And double this size... + lTrialBufSize *= 2; + // Until we get above the maximum size or above the current actual (requested) size... + } while ( ( lTrialBufSize < lMaxSize ) && ( lTrialBufSize < iActualBufferSize ) ); + + // Now take the previous power of two... + iActualBufferSize = ( lTrialBufSize / 2 ); + } + else if ( lGranularity > 1 ) + { + // Size must be a multiple of lGranularity, + // Round to a lGranularity multiple by determining any remainder + long remainder = ( iActualBufferSize % lGranularity ); + if ( remainder ) + { + // Round down by subtracting the remainder... + iActualBufferSize -= remainder; + + // Use rounded down size if this is actually the preferred buffer size + if ( iActualBufferSize == lPreferredSize ) + { + return iActualBufferSize; + } + + // Otherwise see if we should, and could, have rounded up... + if ( ( remainder >= ( lGranularity / 2 ) ) && // Should we have rounded up ? + ( iActualBufferSize <= ( lMaxSize - lGranularity ) ) // And can we round up ? + ) + { + // Change rounded down size to rounded up size by adding 1 granularity... + iActualBufferSize += lGranularity; + } + } + } + // There are no definitions for other values of granularity in the ASIO SDK ! + // Assume everything else should never occur and is OK as long as the size is within min/max limits. + } + + return iActualBufferSize; +} + +bool CSound::checkNewDeviceCapabilities() +{ + // This function checks if our required input/output channel + // properties are supported by the selected device. If the return + // string is empty, the device can be used, otherwise the error + // message is returned. + bool ok = true; + + if ( !newAsioDriver.IsOpen() ) + { + strErrorList.append ( QString ( "Coding Error: Calling CheckDeviceCapabilities() with no newAsioDriver open! " ) ); + return false; + } + + // check the sample rate + if ( !newAsioDriver.canSampleRate ( SYSTEM_SAMPLE_RATE_HZ ) ) + { + ok = false; + // return error string + strErrorList.append ( tr ( "The selected audio device does not support a sample rate of %1 Hz. " ).arg ( SYSTEM_SAMPLE_RATE_HZ ) ); + } + else + { + // check if sample rate can be set + if ( !newAsioDriver.setSampleRate ( SYSTEM_SAMPLE_RATE_HZ ) ) + { + ok = false; + strErrorList.append ( tr ( "The audio devices sample rate could not be set to %2 Hz. " ).arg ( SYSTEM_SAMPLE_RATE_HZ ) ); + } + } + + if ( newAsioDriver.numInputChannels() < DRV_MIN_IN_CHANNELS ) + { + ok = false; + strErrorList.append ( tr ( "The selected audio device doesn not support at least" + "%1 %2 channel(s)." ) + .arg ( DRV_MIN_IN_CHANNELS ) + .arg ( tr ( "input" ) ) ); + } + + if ( newAsioDriver.numOutputChannels() < DRV_MIN_OUT_CHANNELS ) + { + ok = false; + strErrorList.append ( tr ( "The selected audio device doesn not support at least" + "%1 %2 channel(s)." ) + .arg ( DRV_MIN_OUT_CHANNELS ) + .arg ( tr ( "output" ) ) ); + } + + // query channel infos for all available input channels + bool channelOk = true; + for ( int i = 0; i < newAsioDriver.numInputChannels(); i++ ) + { + const ASIOChannelInfo& channel = newAsioDriver.inputChannelInfo ( i ); + + if ( !SampleTypeSupported ( channel.type ) ) + { + // return error string + channelOk = false; + } + } + + if ( !channelOk ) + { + ok = false; + strErrorList.append ( tr ( "The selected audio device is incompatible since " + "the required %1 audio sample format isn't available." ) + .arg ( tr ( "input)" ) ) ); + } + + // query channel infos for all available output channels + channelOk = true; + for ( int i = 0; i < newAsioDriver.numOutputChannels(); i++ ) + { + const ASIOChannelInfo& channel = newAsioDriver.outputChannelInfo ( i ); + + // Check supported sample formats. + // Actually, it would be enough to have at least two channels which + // support the required sample format. But since we have support for + // all known sample types, the following check should always pass and + // therefore we throw the error message on any channel which does not + // fulfill the sample format requirement (quick hack solution). + if ( !SampleTypeSupported ( channel.type ) ) + { + channelOk = false; + } + } + + if ( !channelOk ) + { + ok = false; + strErrorList.append ( tr ( "The selected audio device is incompatible since " + "the required %1 audio sample format isn't available." ) + .arg ( tr ( "output)" ) ) ); + } + + // special case with more than 2 input channels (was exactly 4 channels): support adding channels + // But what the hack is this used for ??? Only used for Jack ?? But mixing can be done in Jack, so NOT KISS! + if ( ( newAsioDriver.numInputChannels() > 2 ) ) + { + // Total available name size for AddedChannelData + const long addednamesize = ( sizeof ( ASIOAddedChannelInfo::ChannelData.name ) + sizeof ( ASIOAddedChannelInfo::AddedName ) ); + + // add mixed channels (i.e. 4 normal, 4 mixed channels) + const long numInputChan = newAsioDriver.numInputChannels(); + const long numAddedChan = getNumInputChannelsToAdd ( numInputChan ); + ASIOAddedChannelInfo* addedChannel = newAsioDriver.setNumAddedInputChannels ( numAddedChan ); + + for ( long i = 0; i < numAddedChan; i++ ) + { + int iSelChan, iAddChan; + + getInputSelAndAddChannels ( numInputChan + i, numInputChan, numAddedChan, iSelChan, iAddChan ); + + char* combinedName = addedChannel[i].ChannelData.name; + const char* firstName = newAsioDriver.inputChannelInfo ( iSelChan ).name; + const char* secondName = newAsioDriver.inputChannelInfo ( iAddChan ).name; + + if ( ( iSelChan >= 0 ) && ( iAddChan >= 0 ) ) + { + strncpy_s ( combinedName, addednamesize, firstName, sizeof ( ASIOChannelInfo::name ) ); + + strcat_s ( combinedName, addednamesize, " + " ); + + strncat_s ( combinedName, addednamesize, secondName, sizeof ( ASIOChannelInfo::name ) ); + } + } + } + + newAsioDriver.openData.capabilitiesOk = ok; + + return ok; +} + +//============================================================================ +// CSoundBase internal +//============================================================================ + +long CSound::createDeviceList ( bool bRescan ) { - uint16_t iMask = ( 1 << 15 ); - int16_t iOut = 0; - for ( unsigned int i = 0; i < 16; i++ ) + if ( bRescan && asioDriversLoaded ) + { + closeAllAsioDrivers(); + asioDriversLoaded = false; + } + + if ( !asioDriversLoaded ) { - // copy current bit to correct position - iOut |= ( iIn & iMask ) ? 1 : 0; + strDeviceNames.clear(); + lNumDevices = GetAsioDriverDataList ( asioDriverData ); + for ( long i = 0; i < lNumDevices; i++ ) + { + strDeviceNames.append ( asioDriverData[i].drvname ); + } - // shift out value and mask by one bit - iOut <<= 1; - iMask >>= 1; + asioDriversLoaded = ( lNumDevices != 0 ); + + return lNumDevices; } - return iOut; + return lNumDevices; } -int32_t CSound::Flip32Bits ( const int32_t iIn ) +bool CSound::checkDeviceChange ( tDeviceChangeCheck mode, int iDriverIndex ) { - uint32_t iMask = ( static_cast ( 1 ) << 31 ); - int32_t iOut = 0; + if ( ( iDriverIndex < 0 ) || ( iDriverIndex >= lNumDevices ) ) + { + // Invalid index ! + return false; + } + + if ( ( mode != CSoundBase::tDeviceChangeCheck::CheckOpen ) && ( !newAsioDriver.IsOpen() ) ) + { + // All other modes are only valid if a newDriver is successfully opened first. + return false; + } - for ( unsigned int i = 0; i < 32; i++ ) + switch ( mode ) { - // copy current bit to correct position - iOut |= ( iIn & iMask ) ? 1 : 0; + case tDeviceChangeCheck::Abort: // Not changing to newDRiver ! + return newAsioDriver.Close(); + + case tDeviceChangeCheck::CheckOpen: // Open the device we are gona check + newAsioDriver.AssignFrom ( &asioDriverData[iDriverIndex] ); + return newAsioDriver.Open(); + + case tDeviceChangeCheck::Activate: // Actually changing current device ! + if ( asioDriver.AssignFrom ( &newAsioDriver ) ) + { + clearDeviceInfo(); + + lNumInChan = asioDriver.openData.lNumInChan; + lNumAddedInChan = asioDriver.openData.lNumAddedInChan; + + lNumOutChan = asioDriver.openData.lNumOutChan; + + asioDriver.GetInputChannelNames ( strInputChannelNames ); + asioDriver.GetOutputChannelNames ( strOutputChannelNames ); - // shift out value and mask by one bit - iOut <<= 1; - iMask >>= 1; + iCurrentDevice = asioDriver.index; + resetChannelMapping(); + // TODO: read ChannelMapping from ini settings ! + + return true; + } + return false; + + case tDeviceChangeCheck::CheckCapabilities: + return checkNewDeviceCapabilities(); + + default: + return false; + } +} + +void CSound::closeCurrentDevice() +{ + if ( IsStarted() ) + { + Stop(); } - return iOut; + asioDriver.disposeBuffers(); + asioDriver.Close(); + + clearDeviceInfo(); + memset ( bufferInfos, 0, sizeof ( bufferInfos ) ); } -int64_t CSound::Flip64Bits ( const int64_t iIn ) +//============================================================================ +// CSoundBase Interface to CClient: +//============================================================================ + +bool CSound::start() { - uint64_t iMask = ( static_cast ( 1 ) << 63 ); - int64_t iOut = 0; + if ( !IsStarted() ) + { + // prepare start and try to start asio + if ( prepareAsio ( true ) ) + { + strErrorList.clear(); + + return true; + } + else + { + // We failed!, asio is stopped ! + // notify base class + strErrorList.clear(); + strErrorList.append ( htmlBold ( tr ( "Failed to start your audio device!." ) ) ); + strErrorList.append ( tr ( "Please check your device settings..." ) ); + + return false; + } + } + + return IsStarted(); +} - for ( unsigned int i = 0; i < 64; i++ ) +bool CSound::stop() +{ + if ( IsStarted() ) { - // copy current bit to correct position - iOut |= ( iIn & iMask ) ? 1 : 0; + // stop audio + asioDriver.stop(); - // shift out value and mask by one bit - iOut <<= 1; - iMask >>= 1; + return true; } - return iOut; + return !IsStarted(); } diff --git a/windows/sound.h b/windows/sound.h index d3b8f3d485..7569bd6256 100644 --- a/windows/sound.h +++ b/windows/sound.h @@ -2,7 +2,7 @@ * Copyright (c) 2004-2022 * * Author(s): - * Volker Fischer + * Volker Fischer, revised and maintained by Peter Goderie (pgScorpio) * ****************************************************************************** * @@ -24,114 +24,113 @@ #pragma once -#include -#include +//============================================================================ +// System includes +//============================================================================ + #include "../src/util.h" #include "../src/global.h" #include "../src/soundbase.h" -// The following includes require the ASIO SDK to be placed in -// windows/ASIOSDK2 during build. -// Important: -// - Do not copy parts of ASIO SDK into the Jamulus source tree without -// further consideration as it would make the license situation more -// complicated. -// - When building yourself, read and understand the -// Steinberg ASIO SDK Licensing Agreement and verify whether you might be -// obliged to sign it as well, especially when considering distribution -// of Jamulus Windows binaries with ASIO support. +//============================================================================ +// ASIO includes +//============================================================================ + #include "asiosys.h" -#include "asio.h" -#include "asiodrivers.h" +#include "asiodriver.h" -/* Definitions ****************************************************************/ -// stereo for input and output -#define NUM_IN_OUT_CHANNELS 2 +//============================================================================ +// CSound +//============================================================================ -/* Classes ********************************************************************/ class CSound : public CSoundBase { Q_OBJECT public: - CSound ( void ( *fpNewCallback ) ( CVector& psData, void* arg ), void* arg, const QString& strMIDISetup, const bool, const QString& ); - - virtual ~CSound() { UnloadCurrentDriver(); } + CSound ( void ( *theProcessCallback ) ( CVector& psData, void* arg ), void* theProcessCallbackArg ); + +#ifdef OLD_SOUND_COMPATIBILITY + // Backwards compatibility constructor + CSound ( void ( *theProcessCallback ) ( CVector& psData, void* pCallbackArg ), + void* theProcessCallbackArg, + QString strMIDISetup, + bool bNoAutoJackConnect, + QString strNClientName ); +#endif + + virtual ~CSound() + { + closeCurrentDevice(); + closeAllAsioDrivers(); + } - virtual int Init ( const int iNewPrefMonoBufferSize ); - virtual void Start(); - virtual void Stop(); +protected: + // ASIO data + bool bASIOPostOutput; - virtual void OpenDriverSetup() { ASIOControlPanel(); } + bool asioDriversLoaded; + tVAsioDrvDataList asioDriverData; - // channel selection - virtual int GetNumInputChannels() { return static_cast ( lNumInChanPlusAddChan ); } - virtual QString GetInputChannelName ( const int iDiD ) { return channelInputName[iDiD]; } - virtual void SetLeftInputChannel ( const int iNewChan ); - virtual void SetRightInputChannel ( const int iNewChan ); - virtual int GetLeftInputChannel() { return vSelectedInputChannels[0]; } - virtual int GetRightInputChannel() { return vSelectedInputChannels[1]; } + CAsioDriver asioDriver; // The current selected asio driver + CAsioDriver newAsioDriver; // the new asio driver opened by checkDeviceChange(CheckOpen, ...), + // to be checked by checkDeviceChange(CheckCapabilities, ...) + // and set as asioDriver by checkDeviceChange(Activate, ...) or closed by checkDeviceChange(Abort, ...) - virtual int GetNumOutputChannels() { return static_cast ( lNumOutChan ); } - virtual QString GetOutputChannelName ( const int iDiD ) { return channelInfosOutput[iDiD].name; } - virtual void SetLeftOutputChannel ( const int iNewChan ); - virtual void SetRightOutputChannel ( const int iNewChan ); - virtual int GetLeftOutputChannel() { return vSelectedOutputChannels[0]; } - virtual int GetRightOutputChannel() { return vSelectedOutputChannels[1]; } + ASIOBufferInfo bufferInfos[DRV_MAX_IN_CHANNELS + DRV_MAX_OUT_CHANNELS]; // TODO: Use CVector ? - virtual float GetInOutLatencyMs() { return fInOutLatencyMs; } +protected: + // ASIO callback implementations + void onBufferSwitch ( long index, ASIOBool processNow ); + ASIOTime* onBufferSwitchTimeInfo ( ASIOTime* timeInfo, long index, ASIOBool processNow ); + void onSampleRateChanged ( ASIOSampleRate sampleRate ); + long onAsioMessages ( long selector, long value, void* message, double* opt ); protected: - virtual QString LoadAndInitializeDriver ( QString strDriverName, bool bOpenDriverSetup ); - virtual void UnloadCurrentDriver(); - int GetActualBufferSize ( const int iDesiredBufferSizeMono ); - QString CheckDeviceCapabilities(); - bool CheckSampleTypeSupported ( const ASIOSampleType SamType ); - bool CheckSampleTypeSupportedForCHMixing ( const ASIOSampleType SamType ); - void ResetChannelMapping(); - - int iASIOBufferSizeMono; - int iASIOBufferSizeStereo; - - long lNumInChan; - long lNumInChanPlusAddChan; // includes additional "added" channels - long lNumOutChan; - float fInOutLatencyMs; - CVector vSelectedInputChannels; - CVector vSelectedOutputChannels; - - CVector vecsMultChanAudioSndCrd; - - QMutex ASIOMutex; - - // utility functions - static int16_t Flip16Bits ( const int16_t iIn ); - static int32_t Flip32Bits ( const int32_t iIn ); - static int64_t Flip64Bits ( const int64_t iIn ); - - // audio hardware buffer info - struct sHWBufferInfo + // callbacks + + static ASIOCallbacks asioCallbacks; + + static void _onBufferSwitch ( long index, ASIOBool processNow ) { pSound->onBufferSwitch ( index, processNow ); } + + static ASIOTime* _onBufferSwitchTimeInfo ( ASIOTime* timeInfo, long index, ASIOBool processNow ) { - long lMinSize; - long lMaxSize; - long lPreferredSize; - long lGranularity; - } HWBufferInfo; - - // ASIO stuff - ASIODriverInfo driverInfo; - ASIOBufferInfo bufferInfos[2 * MAX_NUM_IN_OUT_CHANNELS]; // for input and output buffers -> "2 *" - ASIOChannelInfo channelInfosInput[MAX_NUM_IN_OUT_CHANNELS]; - QString channelInputName[MAX_NUM_IN_OUT_CHANNELS]; - ASIOChannelInfo channelInfosOutput[MAX_NUM_IN_OUT_CHANNELS]; - bool bASIOPostOutput; - ASIOCallbacks asioCallbacks; + return pSound->onBufferSwitchTimeInfo ( timeInfo, index, processNow ); + } - // callbacks - static void bufferSwitch ( long index, ASIOBool processNow ); - static ASIOTime* bufferSwitchTimeInfo ( ASIOTime* timeInfo, long index, ASIOBool processNow ); - static void sampleRateChanged ( ASIOSampleRate ) {} - static long asioMessages ( long selector, long value, void* message, double* opt ); + static void _onSampleRateChanged ( ASIOSampleRate sampleRate ) { pSound->onSampleRateChanged ( sampleRate ); } + + static long _onAsioMessages ( long selector, long value, void* message, double* opt ) + { + return pSound->onAsioMessages ( selector, value, message, opt ); + } + +protected: + // CSound Internal + void closeAllAsioDrivers(); + bool prepareAsio ( bool bStartAsio ); // Called before starting + + bool checkNewDeviceCapabilities(); // used by checkDeviceChange( checkCapabilities, iDriverIndex) + + //============================================================================ + // Virtual interface to CSoundBase: + //============================================================================ +protected: // CSoundBase Mandatory pointer to instance (must be set to 'this' in the CSound constructor) + static CSound* pSound; + +public: // CSoundBase Mandatory functions. (but static functions can't be virtual) + static inline CSoundBase* pInstance() { return pSound; } + static inline const CSoundProperties& GetProperties() { return pSound->getSoundProperties(); } + +protected: + // CSoundBase internal + virtual long createDeviceList ( bool bRescan = false ); // Fills strDeviceList. Returns number of devices found + virtual bool checkDeviceChange ( CSoundBase::tDeviceChangeCheck mode, int iDriverIndex ); // Open device sequence handling.... + virtual unsigned int getDeviceBufferSize ( unsigned int iDesiredBufferSize ); + + virtual void closeCurrentDevice(); // Closes the current driver and Clears Device Info + virtual bool openDeviceSetup() { return asioDriver.controlPanel(); } - char* cDriverNames[MAX_NUMBER_SOUND_CARDS]; + virtual bool start(); + virtual bool stop(); };