From 870cc89a398c5844d0092900f6e864a7e97e9a6a Mon Sep 17 00:00:00 2001 From: pgScorpio Date: Fri, 8 Apr 2022 00:14:52 +0200 Subject: [PATCH] New 'first stage' step for sound-redesign This PR modifies soundbase and all sound implementations at once, because all CSound implementations depend on CSoundbase, so these can't be merged one by one in steps. This first step however still has backwards compatibility to the rest of the code. The second stage steps will be removing the backwards compatibility (which a.o. will improve error handling), so than we need to modify main.cpp, client.cpp and probably some other files too. but this can be done in multiple PR'steps. The third stage steps will be using the new soundproperties functionality which will affect several gui files, but also these changes can be merged in multiple steps. NOTES: Since this PR Needs #2583 Global MsgBoxes and Commandline to be implemented first, this commit has to include these changes into global.h and main.cpp too. The avoid clang-format issues again this PR also includes a modified settings.cpp. --- Jamulus.pro | 31 +- android/sound.cpp | 406 +++++--- android/sound.h | 89 +- ios/sound.h | 83 +- ios/sound.mm | 323 +++++-- linux/jackclient.cpp | 422 +++++++++ linux/jackclient.h | 219 +++++ linux/sound.cpp | 549 ++++++----- linux/sound.h | 147 +-- mac/sound.cpp | 1729 ++++++++++++++++++++------------- mac/sound.h | 305 ++++-- src/global.h | 224 ++++- src/main.cpp | 215 +++-- src/settings.cpp | 73 +- src/soundbase.cpp | 983 ++++++++++++++++--- src/soundbase.h | 540 +++++++++-- windows/asiodriver.cpp | 516 ++++++++++ windows/asiodriver.h | 208 ++++ windows/asiosys.h | 53 ++ windows/sound.cpp | 2047 +++++++++++++++++++++------------------- windows/sound.h | 177 ++-- 21 files changed, 6632 insertions(+), 2707 deletions(-) create mode 100644 linux/jackclient.cpp create mode 100644 linux/jackclient.h create mode 100644 windows/asiodriver.cpp create mode 100644 windows/asiodriver.h create mode 100644 windows/asiosys.h 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(); };