From 0ccecde67d50bb04cc870500265a31948a328d5c Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sun, 10 Jan 2021 10:59:53 -0500 Subject: [PATCH 01/17] Add portaudio backend skeleton code --- .gitmodules | 3 + Jamulus.pro | 127 +++++++++++++++++++++++++++++++++++------ libs/portaudio | 1 + src/client.h | 7 ++- src/portaudiosound.cpp | 44 ++++++++++++++ src/portaudiosound.h | 43 ++++++++++++++ 6 files changed, 208 insertions(+), 17 deletions(-) create mode 160000 libs/portaudio create mode 100644 src/portaudiosound.cpp create mode 100644 src/portaudiosound.h diff --git a/.gitmodules b/.gitmodules index c3cb19fa66..2a905c568d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "tools/jamulus-server-remote"] path = tools/jamulus-server-remote url = ../../vdellamea/jamulus-server-remote +[submodule "libs/portaudio"] + path = libs/portaudio + url = ../../npostavs/portaudio.git diff --git a/Jamulus.pro b/Jamulus.pro index 3394cfe7ab..2613f50e41 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -56,6 +56,11 @@ INCLUDEPATH_OPUS = libs/opus/include \ libs/opus/silk/fixed \ libs/opus +INCLUDEPATH_PORTAUDIO = libs/portaudio/include libs/portaudio/src/common +win32 { + INCLUDEPATH_PORTAUDIO += libs/portaudio/src/os/win +} + DEFINES += APP_VERSION=\\\"$$VERSION\\\" \ CUSTOM_MODES \ _REENTRANT @@ -64,31 +69,40 @@ DEFINES += APP_VERSION=\\\"$$VERSION\\\" \ # TODO as soon as we drop support for the old Qt version, remove the following line DEFINES += QT_NO_DEPRECATED_WARNINGS +# msvc uses a different options syntax for adding libraries. +defineReplace(libnames) { + libopts = + names = $$1 + for(libname, names) { + win32-msvc* { + libopts += $${libname}.lib + } else { + libopts += -l$${libname} + } + } + return($$libopts) +} + win32 { DEFINES -= UNICODE # fixes issue with ASIO SDK (asiolist.cpp is not unicode compatible) - DEFINES += NOMINMAX # solves a compiler error in qdatetime.h (Qt5) - HEADERS += windows/sound.h - SOURCES += windows/sound.cpp \ - windows/ASIOSDK2/common/asio.cpp \ + !CONFIG(portaudio) { + HEADERS += windows/sound.h + SOURCES += windows/sound.cpp + } + win32-msvc* { + DEFINES += NOMINMAX # solves a compiler error in qdatetime.h (Qt5) when compiling with MSVC, breaks portaudio build under mingw + } + + SOURCES += windows/ASIOSDK2/common/asio.cpp \ windows/ASIOSDK2/host/asiodrivers.cpp \ windows/ASIOSDK2/host/pc/asiolist.cpp RC_FILE = windows/mainicon.rc INCLUDEPATH += windows/ASIOSDK2/common \ windows/ASIOSDK2/host \ windows/ASIOSDK2/host/pc - mingw* { - LIBS += -lole32 \ - -luser32 \ - -ladvapi32 \ - -lwinmm \ - -lws2_32 - } else { + LIBS += $$libnames(ole32 user32 advapi32 winmm ws2_32) + win32-msvc* { QMAKE_LFLAGS += /DYNAMICBASE:NO # fixes crash with libjack64.dll, see https://github.com/jamulussoftware/jamulus/issues/93 - LIBS += ole32.lib \ - user32.lib \ - advapi32.lib \ - winmm.lib \ - ws2_32.lib } # replace ASIO with jack if requested @@ -512,6 +526,16 @@ HEADERS_OPUS_X86 = libs/opus/celt/x86/celt_lpc_sse.h \ libs/opus/celt/x86/x86cpu.h \ $$files(libs/opus/silk/x86/*.h) +HEADERS_PORTAUDIO = $$files(libs/portaudio/include/*.h) $$files(libs/portaudio/src/common/*.h) + +win32 { + HEADERS_PORTAUDIO += $$files(libs/portaudio/src/os/win/*.h) \ + $$files(libs/portaudio/src/hostapi/asio/*.h) \ + $$files(libs/portaudio/src/hostapi/wdmks/*.h) \ + $$files(libs/portaudio/src/hostapi/wmme/*.h) \ + $$files(libs/portaudio/src/hostapi/wasapi/*.h) +} + SOURCES += src/buffer.cpp \ src/channel.cpp \ src/client.cpp \ @@ -710,6 +734,47 @@ contains(QT_ARCH, armeabi-v7a) | contains(QT_ARCH, arm64-v8a) { } DEFINES_OPUS += OPUS_BUILD=1 USE_ALLOCA=1 OPUS_HAVE_RTCD=1 HAVE_LRINTF=1 HAVE_LRINT=1 + +SOURCES_PORTAUDIO = $$files(libs/portaudio/src/common/*.c) +SOURCES_CXX_PORTAUDIO = + +win32 { + SOURCES_PORTAUDIO += $$files(libs/portaudio/src/os/win/*.c) \ + $$files(libs/portaudio/src/hostapi/wdmks/*.c) \ + $$files(libs/portaudio/src/hostapi/wmme/*.c) \ + $$files(libs/portaudio/src/hostapi/wasapi/*.c) + SOURCES_CXX_PORTAUDIO += $$files(libs/portaudio/src/hostapi/asio/*.cpp) + # Adapter for C++ compiler-specific calling convention not needed for msvc + win32-msvc* { + SOURCES_CXX_PORTAUDIO -= libs/portaudio/src/hostapi/asio/iasiothiscallresolver.cpp + } +} else:unix { + # FIXME: also some hostapi files, probably. + SOURCES_PORTAUDIO += $$files(libs/portaudio/src/os/unix/*.c) +} + +# I can't figure out how to make the custom compiler stuff work for +# msvc, we'll have to live with portaudio compile warnings in that +# build. +!win32-msvc* { + # Suppress warnings from portaudio sources, with -w + # NOTE: we set portaudio(cc|cxx).variable_out to OBJECTS down near the + # bottom depending on the configuration. + # See also + # - https://wiki.qt.io/Undocumented_QMake#Custom_tools + # - https://stackoverflow.com/questions/27683777/how-to-specify-compiler-flag-to-a-single-source-file-with-qmake + portaudiocc.name = portaudiocc + portaudiocc.input = SOURCES_PORTAUDIO + portaudiocc.dependency_type = TYPE_C + portaudiocc.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_IN_BASE}$${first(QMAKE_EXT_OBJ)} + portaudiocc.commands = ${CC} $(CFLAGS) -w $(INCPATH) -c ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} + portaudiocxx.name = portaudiocxx + portaudiocxx.input = SOURCES_CXX_PORTAUDIO + portaudiocxx.dependency_type = TYPE_C + portaudiocxx.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_IN_BASE}$${first(QMAKE_EXT_OBJ)} + portaudiocxx.commands = ${CXX} $(CXXFLAGS) -w $(INCPATH) -c ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} +} + DISTFILES += ChangeLog \ COPYING \ CONTRIBUTING.md \ @@ -1074,6 +1139,9 @@ DISTFILES_OPUS += libs/opus/AUTHORS \ libs/opus/celt/arm/armopts.s.in \ libs/opus/celt/arm/celt_pitch_xcorr_arm.s \ +DISTFILES_PORTAUDIO += libs/portaudio/LICENSE.txt \ + libs/portaudio/README.txt + contains(CONFIG, "headless") { DEFINES += HEADLESS } else { @@ -1138,6 +1206,33 @@ contains(CONFIG, "opus_shared_lib") { } } + +CONFIG(portaudio) { + DEFINES += USE_PORTAUDIO + HEADERS += src/portaudiosound.h + SOURCES += src/portaudiosound.cpp + win32 { + CONFIG(portaudio_shared_lib) { + LIBS += $$libnames(portaudio) + } else { + DEFINES += PA_USE_ASIO=1 + INCLUDEPATH += $$INCLUDEPATH_PORTAUDIO + HEADERS += $$HEADERS_PORTAUDIO + mingw { + portaudiocxx.variable_out = OBJECTS + portaudiocc.variable_out = OBJECTS + QMAKE_EXTRA_COMPILERS += portaudiocc portaudiocxx + } else { + SOURCES += $$SOURCES_PORTAUDIO $$SOURCES_CXX_PORTAUDIO + } + DISTFILES += $$DISTFILES_PORTAUDIO + } + LIBS += $$libnames(winmm ole32 uuid setupapi) + } else { + error( "portaudio only tested on win32 for now" ) + } +} + # disable version check if requested (#370) contains(CONFIG, "disable_version_check") { message(The version check is disabled.) diff --git a/libs/portaudio b/libs/portaudio new file mode 160000 index 0000000000..d7e79160a3 --- /dev/null +++ b/libs/portaudio @@ -0,0 +1 @@ +Subproject commit d7e79160a3670dc89ad35417ea511d9196964927 diff --git a/src/client.h b/src/client.h index 929da28853..3237b5259a 100644 --- a/src/client.h +++ b/src/client.h @@ -44,7 +44,12 @@ # include "vstsound.h" #else # if defined( _WIN32 ) && !defined( JACK_REPLACES_ASIO ) -# include "../windows/sound.h" +# ifdef USE_PORTAUDIO +// FIXME: this shouldn't be windows specific +# include "portaudiosound.h" +# else +# include "../windows/sound.h" +# endif # else # if ( defined( Q_OS_MACX ) ) && !defined( JACK_REPLACES_COREAUDIO ) # include "../mac/sound.h" diff --git a/src/portaudiosound.cpp b/src/portaudiosound.cpp new file mode 100644 index 0000000000..4ff3237cb7 --- /dev/null +++ b/src/portaudiosound.cpp @@ -0,0 +1,44 @@ +/******************************************************************************\ + * Copyright (c) 2021 + * + * Author(s): + * Noam Postavsky + * + * Description: + * Sound card interface using the portaudio library + * + ****************************************************************************** + * + * 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 "portaudiosound.h" + +#include + +CSound::CSound ( void (*fpNewProcessCallback) ( CVector& psData, void* arg ), + void* arg, + const QString& strMIDISetup, + const bool, + const QString& ) : + CSoundBase ( "portaudio", fpNewProcessCallback, arg, strMIDISetup ) +{ + PaError err = Pa_Initialize(); + if ( err != paNoError ) + { + throw CGenErr ( tr ( "Failed to initialize PortAudio: %1" ).arg ( Pa_GetErrorText ( err ) ) ); + } +} diff --git a/src/portaudiosound.h b/src/portaudiosound.h new file mode 100644 index 0000000000..e97e7f97e4 --- /dev/null +++ b/src/portaudiosound.h @@ -0,0 +1,43 @@ +/******************************************************************************\ + * Copyright (c) 2021 + * + * Author(s): + * Noam Postavsky + * + ****************************************************************************** + * + * 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 + * +\******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include "util.h" +#include "soundbase.h" +#include "global.h" + +class CSound : public CSoundBase +{ +public: + CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* arg ), + void* arg, + const QString& strMIDISetup, + const bool, + const QString& ); +}; From 50791f728fbfc46e24eb586dcf29a5e081053ba4 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sun, 10 Jan 2021 19:48:16 -0500 Subject: [PATCH 02/17] Make portaudio backend work with ASIO stereo devices --- src/portaudiosound.cpp | 197 ++++++++++++++++++++++++++++++++++++++++- src/portaudiosound.h | 24 +++++ 2 files changed, 218 insertions(+), 3 deletions(-) diff --git a/src/portaudiosound.cpp b/src/portaudiosound.cpp index 4ff3237cb7..e33a2f91d0 100644 --- a/src/portaudiosound.cpp +++ b/src/portaudiosound.cpp @@ -26,19 +26,210 @@ \******************************************************************************/ #include "portaudiosound.h" - -#include +#include CSound::CSound ( void (*fpNewProcessCallback) ( CVector& psData, void* arg ), void* arg, const QString& strMIDISetup, const bool, const QString& ) : - CSoundBase ( "portaudio", fpNewProcessCallback, arg, strMIDISetup ) + CSoundBase ( "portaudio", fpNewProcessCallback, arg, strMIDISetup ), + deviceIndex ( -1 ), + deviceStream ( NULL ) { PaError err = Pa_Initialize(); if ( err != paNoError ) { throw CGenErr ( tr ( "Failed to initialize PortAudio: %1" ).arg ( Pa_GetErrorText ( err ) ) ); } + + // Find ASIO API index. TODO: support non-ASIO APIs. + PaHostApiIndex apiCount = Pa_GetHostApiCount(); + if ( apiCount < 0 ) + { + throw CGenErr ( tr ( "Failed to get API count" ) ); + } + PaHostApiIndex apiIndex = -1; + const PaHostApiInfo* apiInfo = NULL; + for ( PaHostApiIndex i = 0; i < apiCount; i++ ) + { + apiInfo = Pa_GetHostApiInfo ( i ); + if ( apiInfo->type == paASIO ) + { + apiIndex = i; + break; + } + } + if ( apiIndex < 0 || apiInfo->deviceCount == 0 ) + { + throw CGenErr ( tr ( "No ASIO devices found" ) ); + } + asioIndex = apiIndex; + + lNumDevs = std::min ( apiInfo->deviceCount, MAX_NUMBER_SOUND_CARDS ); + for ( int i = 0; i < lNumDevs; i++ ) + { + PaDeviceIndex devIndex = Pa_HostApiDeviceIndexToDeviceIndex ( asioIndex, i ); + const PaDeviceInfo* devInfo = Pa_GetDeviceInfo ( devIndex ); + strDriverNames[i] = devInfo->name; + } +} + +int CSound::Init ( const int iNewPrefMonoBufferSize ) +{ + if ( deviceIndex >= 0 ) + { + long minBufferSize, maxBufferSize, prefBufferSize, granularity; + PaAsio_GetAvailableBufferSizes ( deviceIndex, &minBufferSize, &maxBufferSize, &prefBufferSize, &granularity ); + if ( iNewPrefMonoBufferSize > maxBufferSize ) + { + iPrefMonoBufferSize = maxBufferSize; + } + else if ( iNewPrefMonoBufferSize < minBufferSize ) + { + iPrefMonoBufferSize = minBufferSize; + } + else + { + // Requested size is within the range. + int bufferSize = minBufferSize; + while ( bufferSize < iNewPrefMonoBufferSize ) + { + if ( granularity == -1 ) // available buffer size values are powers of two. + { + bufferSize *= 2; + } + else + { + bufferSize += granularity; + } + } + iPrefMonoBufferSize = bufferSize; + } + } + else + { + iPrefMonoBufferSize = iNewPrefMonoBufferSize; + } + + vecsAudioData.Init ( iPrefMonoBufferSize * 2 ); + return CSoundBase::Init ( iPrefMonoBufferSize ); +} + +CSound::~CSound() { Pa_Terminate(); } + +QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDriverSetup ) +{ + (void) bOpenDriverSetup; // FIXME: respect this + + int deviceIndex = -1; + for ( int i = 0; i < lNumDevs; i++ ) + { + PaDeviceIndex devIndex = Pa_HostApiDeviceIndexToDeviceIndex ( asioIndex, i ); + const PaDeviceInfo* devInfo = Pa_GetDeviceInfo ( devIndex ); + if ( strDriverName.compare ( devInfo->name ) == 0 ) + { + deviceIndex = devIndex; + break; + } + } + if ( deviceIndex < 0 ) + { + return tr ( "The current selected audio device is no longer present in the system." ); + } + + const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo ( deviceIndex ); + + if ( deviceInfo->maxInputChannels < 2 || deviceInfo->maxOutputChannels < 2 ) + { + // FIXME: handle mono devices. + return tr ( "Less than 2 channels supported" ); + } + + if ( deviceStream != NULL ) + { + Pa_CloseStream ( deviceStream ); + deviceStream = NULL; + } + + PaStreamParameters paInputParams; + paInputParams.device = deviceIndex; + paInputParams.channelCount = std::min ( 2, deviceInfo->maxInputChannels ); + paInputParams.sampleFormat = paInt16; + paInputParams.suggestedLatency = deviceInfo->defaultLowInputLatency; + paInputParams.hostApiSpecificStreamInfo = NULL; + + PaStreamParameters paOutputParams; + paOutputParams.device = deviceIndex; + paOutputParams.channelCount = std::min ( 2, deviceInfo->maxOutputChannels ); + paOutputParams.sampleFormat = paInt16; + paOutputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency; + paOutputParams.hostApiSpecificStreamInfo = NULL; + + PaError err = Pa_OpenStream ( &deviceStream, + &paInputParams, + &paOutputParams, + SYSTEM_SAMPLE_RATE_HZ, + iPrefMonoBufferSize, + paNoFlag, + &CSound::paStreamCallback, + this ); + + if ( err != paNoError ) + { + return tr ( "Could not open Portaudio stream: %1, %2" ).arg ( Pa_GetErrorText ( err ) ).arg ( Pa_GetLastHostErrorInfo()->errorText ); + } + + return ""; +} + +void CSound::UnloadCurrentDriver() +{ + if ( deviceStream != NULL ) + { + Pa_CloseStream ( deviceStream ); + deviceStream = NULL; + } +} + +int CSound::paStreamCallback ( const void* input, + void* output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData ) +{ + (void) timeInfo; + (void) statusFlags; + + CSound* pSound = static_cast ( userData ); + CVector& vecsAudioData = pSound->vecsAudioData; + + // CAPTURE --------------------------------- + memcpy ( &vecsAudioData[0], input, sizeof ( int16_t ) * frameCount * 2 ); + + pSound->ProcessCallback ( vecsAudioData ); + + // PLAYBACK ------------------------------------------------------------ + memcpy ( output, &vecsAudioData[0], sizeof ( int16_t ) * frameCount * 2 ); + + return paContinue; +} + +void CSound::Start() +{ + // start audio + Pa_StartStream ( deviceStream ); + + // call base class + CSoundBase::Start(); +} + +void CSound::Stop() +{ + // stop audio + Pa_StopStream ( deviceStream ); + + // call base class + CSoundBase::Stop(); } diff --git a/src/portaudiosound.h b/src/portaudiosound.h index e97e7f97e4..6663054d4e 100644 --- a/src/portaudiosound.h +++ b/src/portaudiosound.h @@ -32,6 +32,8 @@ #include "soundbase.h" #include "global.h" +#include + class CSound : public CSoundBase { public: @@ -40,4 +42,26 @@ class CSound : public CSoundBase const QString& strMIDISetup, const bool, const QString& ); + virtual ~CSound(); + + virtual int Init ( const int iNewPrefMonoBufferSize ); + virtual void Start(); + virtual void Stop(); + +protected: + virtual QString LoadAndInitializeDriver ( QString, bool ); + virtual void UnloadCurrentDriver(); + + static int paStreamCallback ( const void* input, + void* output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void* userData ); + + PaHostApiIndex asioIndex; + PaDeviceIndex deviceIndex; + PaStream* deviceStream; + int iPrefMonoBufferSize; + CVector vecsAudioData; }; From 54306929324cf6d54b27c32bfaf07165ab2162df Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 16 Jan 2021 23:45:17 -0500 Subject: [PATCH 03/17] Fix initial device buffer size detection Make sure we actually check the selected device in CSound::Init(). Note that changing the device buffer size from its ASIO control panel does not work (i.e., the change is not detected by Jamulus, restarting Jamulus is required). --- src/portaudiosound.cpp | 28 +++++++++++++++++++--------- src/portaudiosound.h | 2 ++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/portaudiosound.cpp b/src/portaudiosound.cpp index e33a2f91d0..43f721b11b 100644 --- a/src/portaudiosound.cpp +++ b/src/portaudiosound.cpp @@ -81,7 +81,11 @@ int CSound::Init ( const int iNewPrefMonoBufferSize ) { long minBufferSize, maxBufferSize, prefBufferSize, granularity; PaAsio_GetAvailableBufferSizes ( deviceIndex, &minBufferSize, &maxBufferSize, &prefBufferSize, &granularity ); - if ( iNewPrefMonoBufferSize > maxBufferSize ) + if ( granularity == 0 ) // no options, just take the preferred one. + { + iPrefMonoBufferSize = prefBufferSize; + } + else if ( iNewPrefMonoBufferSize > maxBufferSize ) { iPrefMonoBufferSize = maxBufferSize; } @@ -118,27 +122,31 @@ int CSound::Init ( const int iNewPrefMonoBufferSize ) CSound::~CSound() { Pa_Terminate(); } -QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDriverSetup ) +PaDeviceIndex CSound::DeviceIndexFromName ( const QString& strDriverName ) { - (void) bOpenDriverSetup; // FIXME: respect this - - int deviceIndex = -1; for ( int i = 0; i < lNumDevs; i++ ) { PaDeviceIndex devIndex = Pa_HostApiDeviceIndexToDeviceIndex ( asioIndex, i ); const PaDeviceInfo* devInfo = Pa_GetDeviceInfo ( devIndex ); if ( strDriverName.compare ( devInfo->name ) == 0 ) { - deviceIndex = devIndex; - break; + return devIndex; } } - if ( deviceIndex < 0 ) + return -1; +} + +QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDriverSetup ) +{ + (void) bOpenDriverSetup; // FIXME: respect this + + int devIndex = DeviceIndexFromName ( strDriverName ); + if ( devIndex < 0 ) { return tr ( "The current selected audio device is no longer present in the system." ); } - const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo ( deviceIndex ); + const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo ( devIndex ); if ( deviceInfo->maxInputChannels < 2 || deviceInfo->maxOutputChannels < 2 ) { @@ -151,6 +159,7 @@ QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDrive Pa_CloseStream ( deviceStream ); deviceStream = NULL; } + deviceIndex = devIndex; PaStreamParameters paInputParams; paInputParams.device = deviceIndex; @@ -189,6 +198,7 @@ void CSound::UnloadCurrentDriver() { Pa_CloseStream ( deviceStream ); deviceStream = NULL; + deviceIndex = -1; } } diff --git a/src/portaudiosound.h b/src/portaudiosound.h index 6663054d4e..0bef42ff4b 100644 --- a/src/portaudiosound.h +++ b/src/portaudiosound.h @@ -52,6 +52,8 @@ class CSound : public CSoundBase virtual QString LoadAndInitializeDriver ( QString, bool ); virtual void UnloadCurrentDriver(); + PaDeviceIndex DeviceIndexFromName ( const QString& strDriverName ); + static int paStreamCallback ( const void* input, void* output, unsigned long frameCount, From aea324df4bdb3ad5b503bc65d4182501daab8413 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Tue, 19 Jan 2021 21:23:31 -0500 Subject: [PATCH 04/17] Detect and handle ASIO buffer size changes Note that this uses a non-upstreamed patch to portaudio (set via submodule commit). --- libs/portaudio | 2 +- src/portaudiosound.cpp | 34 ++++++++++++++++++++++++++++++---- src/portaudiosound.h | 1 + 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/libs/portaudio b/libs/portaudio index d7e79160a3..3988fd9ebc 160000 --- a/libs/portaudio +++ b/libs/portaudio @@ -1 +1 @@ -Subproject commit d7e79160a3670dc89ad35417ea511d9196964927 +Subproject commit 3988fd9ebc10cd7b1834ec6e0c0c01c9f6dbf27a diff --git a/src/portaudiosound.cpp b/src/portaudiosound.cpp index 43f721b11b..ec181ba4bd 100644 --- a/src/portaudiosound.cpp +++ b/src/portaudiosound.cpp @@ -28,7 +28,18 @@ #include "portaudiosound.h" #include -CSound::CSound ( void (*fpNewProcessCallback) ( CVector& psData, void* arg ), +// Needed for buffer size change callback +static CSound* pThisSound; + +static void bufferSizeChangeCallback( long newBufferSize ) +{ + (void) newBufferSize; + pThisSound->EmitReinitRequestSignal ( RS_ONLY_RESTART_AND_INIT ); +} +static void resetRequestCallback() { pThisSound->EmitReinitRequestSignal ( RS_ONLY_RESTART_AND_INIT ); } + + +CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* arg ), void* arg, const QString& strMIDISetup, const bool, @@ -37,12 +48,17 @@ CSound::CSound ( void (*fpNewProcessCallback) ( CVector& psData, void* ar deviceIndex ( -1 ), deviceStream ( NULL ) { + pThisSound = this; + PaError err = Pa_Initialize(); if ( err != paNoError ) { throw CGenErr ( tr ( "Failed to initialize PortAudio: %1" ).arg ( Pa_GetErrorText ( err ) ) ); } + PaAsio_RegisterBufferSizeChangeCallback ( &bufferSizeChangeCallback ); + PaAsio_RegisterResetRequestCallback ( &resetRequestCallback ); + // Find ASIO API index. TODO: support non-ASIO APIs. PaHostApiIndex apiCount = Pa_GetHostApiCount(); if ( apiCount < 0 ) @@ -117,6 +133,11 @@ int CSound::Init ( const int iNewPrefMonoBufferSize ) } vecsAudioData.Init ( iPrefMonoBufferSize * 2 ); + if ( deviceStream && deviceIndex >= 0 ) + { + ReinitializeDriver ( deviceIndex ); + } + return CSoundBase::Init ( iPrefMonoBufferSize ); } @@ -145,7 +166,11 @@ QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDrive { return tr ( "The current selected audio device is no longer present in the system." ); } + return ReinitializeDriver ( devIndex ); +} +QString CSound::ReinitializeDriver ( int devIndex ) +{ const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo ( devIndex ); if ( deviceInfo->maxInputChannels < 2 || deviceInfo->maxOutputChannels < 2 ) @@ -158,18 +183,18 @@ QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDrive { Pa_CloseStream ( deviceStream ); deviceStream = NULL; + deviceIndex = -1; } - deviceIndex = devIndex; PaStreamParameters paInputParams; - paInputParams.device = deviceIndex; + paInputParams.device = devIndex; paInputParams.channelCount = std::min ( 2, deviceInfo->maxInputChannels ); paInputParams.sampleFormat = paInt16; paInputParams.suggestedLatency = deviceInfo->defaultLowInputLatency; paInputParams.hostApiSpecificStreamInfo = NULL; PaStreamParameters paOutputParams; - paOutputParams.device = deviceIndex; + paOutputParams.device = devIndex; paOutputParams.channelCount = std::min ( 2, deviceInfo->maxOutputChannels ); paOutputParams.sampleFormat = paInt16; paOutputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency; @@ -189,6 +214,7 @@ QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDrive return tr ( "Could not open Portaudio stream: %1, %2" ).arg ( Pa_GetErrorText ( err ) ).arg ( Pa_GetLastHostErrorInfo()->errorText ); } + deviceIndex = devIndex; return ""; } diff --git a/src/portaudiosound.h b/src/portaudiosound.h index 0bef42ff4b..02f82dbb31 100644 --- a/src/portaudiosound.h +++ b/src/portaudiosound.h @@ -50,6 +50,7 @@ class CSound : public CSoundBase protected: virtual QString LoadAndInitializeDriver ( QString, bool ); + QString ReinitializeDriver ( int devIndex ); virtual void UnloadCurrentDriver(); PaDeviceIndex DeviceIndexFromName ( const QString& strDriverName ); From eb7838f2649df4f7543d252e58535e22d8c3fc37 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Thu, 21 Jan 2021 09:01:26 -0500 Subject: [PATCH 05/17] Add ASIOControlPanel() call --- src/portaudiosound.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/portaudiosound.h b/src/portaudiosound.h index 02f82dbb31..aa6128d819 100644 --- a/src/portaudiosound.h +++ b/src/portaudiosound.h @@ -32,6 +32,14 @@ #include "soundbase.h" #include "global.h" +#ifdef WIN32 +// copy the ASIO SDK in the llcon/windows directory: "llcon/windows/ASIOSDK2" to +// get it work +# include "asiosys.h" +# include "asio.h" +# include "asiodrivers.h" +#endif // WIN32 + #include class CSound : public CSoundBase @@ -48,6 +56,15 @@ class CSound : public CSoundBase virtual void Start(); virtual void Stop(); +#ifdef WIN32 + // Portaudio's function for this takes a device index as a parameter. So it + // needs to reopen ASIO in order to get the right driver loaded. Because of + // that it also requires passing HWND of the main window. This is fairly + // awkward, so just call the ASIO function directly for the currently loaded + // driver. + virtual void OpenDriverSetup() { ASIOControlPanel(); } +#endif // WIN32 + protected: virtual QString LoadAndInitializeDriver ( QString, bool ); QString ReinitializeDriver ( int devIndex ); From d7dff725a31471a01bad776a9b6776d738900979 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Wed, 3 Feb 2021 22:06:22 -0500 Subject: [PATCH 06/17] Handle channel selection --- src/portaudiosound.cpp | 113 +++++++++++++++++++++++++++++++++++++---- src/portaudiosound.h | 18 +++++++ 2 files changed, 121 insertions(+), 10 deletions(-) diff --git a/src/portaudiosound.cpp b/src/portaudiosound.cpp index ec181ba4bd..c8f47f033d 100644 --- a/src/portaudiosound.cpp +++ b/src/portaudiosound.cpp @@ -46,7 +46,9 @@ CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* const QString& ) : CSoundBase ( "portaudio", fpNewProcessCallback, arg, strMIDISetup ), deviceIndex ( -1 ), - deviceStream ( NULL ) + deviceStream ( NULL ), + vSelectedInputChannels ( NUM_IN_OUT_CHANNELS ), + vSelectedOutputChannels ( NUM_IN_OUT_CHANNELS ) { pThisSound = this; @@ -132,7 +134,7 @@ int CSound::Init ( const int iNewPrefMonoBufferSize ) iPrefMonoBufferSize = iNewPrefMonoBufferSize; } - vecsAudioData.Init ( iPrefMonoBufferSize * 2 ); + vecsAudioData.Init ( iPrefMonoBufferSize * NUM_IN_OUT_CHANNELS ); if ( deviceStream && deviceIndex >= 0 ) { ReinitializeDriver ( deviceIndex ); @@ -157,6 +159,82 @@ PaDeviceIndex CSound::DeviceIndexFromName ( const QString& strDriverName ) return -1; } +int CSound::GetNumInputChannels() +{ + if ( deviceIndex >= 0 ) + { + const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo ( deviceIndex ); + return deviceInfo->maxInputChannels; + } + return CSoundBase::GetNumInputChannels(); +} +int CSound::GetNumOutputChannels() +{ + if ( deviceIndex >= 0 ) + { + const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo ( deviceIndex ); + return deviceInfo->maxOutputChannels; + } + return CSoundBase::GetNumOutputChannels(); +} + +QString CSound::GetInputChannelName ( const int channel ) +{ + if ( deviceIndex >= 0 ) + { + const char* channelName; + PaError err = PaAsio_GetInputChannelName ( deviceIndex, channel, &channelName ); + if ( err == paNoError ) + { + return QString ( channelName ); + } + } + return CSoundBase::GetInputChannelName ( channel ); +} +QString CSound::GetOutputChannelName ( const int channel ) +{ + if ( deviceIndex >= 0 ) + { + const char* channelName; + PaError err = PaAsio_GetOutputChannelName ( deviceIndex, channel, &channelName ); + if ( err == paNoError ) + { + return QString ( channelName ); + } + } + return CSoundBase::GetOutputChannelName ( channel ); +} + +void CSound::SetLeftInputChannel ( const int channel ) +{ + if ( channel < GetNumInputChannels() ) + { + vSelectedInputChannels[0] = channel; + } +} +void CSound::SetRightInputChannel ( const int channel ) +{ + if ( channel < GetNumInputChannels() ) + { + vSelectedInputChannels[1] = channel; + } +} + +void CSound::SetLeftOutputChannel ( const int channel ) +{ + if ( channel < GetNumOutputChannels() ) + { + vSelectedOutputChannels[0] = channel; + } +} +void CSound::SetRightOutputChannel ( const int channel ) +{ + if ( channel < GetNumOutputChannels() ) + { + vSelectedOutputChannels[1] = channel; + } +} + QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDriverSetup ) { (void) bOpenDriverSetup; // FIXME: respect this @@ -173,10 +251,11 @@ QString CSound::ReinitializeDriver ( int devIndex ) { const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo ( devIndex ); - if ( deviceInfo->maxInputChannels < 2 || deviceInfo->maxOutputChannels < 2 ) + if ( deviceInfo->maxInputChannels < NUM_IN_OUT_CHANNELS || + deviceInfo->maxOutputChannels < NUM_IN_OUT_CHANNELS ) { // FIXME: handle mono devices. - return tr ( "Less than 2 channels supported" ); + return tr ( "Less than 2 channels not supported" ); } if ( deviceStream != NULL ) @@ -187,18 +266,30 @@ QString CSound::ReinitializeDriver ( int devIndex ) } PaStreamParameters paInputParams; + PaAsioStreamInfo asioInputInfo; paInputParams.device = devIndex; - paInputParams.channelCount = std::min ( 2, deviceInfo->maxInputChannels ); + paInputParams.channelCount = std::min ( NUM_IN_OUT_CHANNELS, deviceInfo->maxInputChannels ); paInputParams.sampleFormat = paInt16; paInputParams.suggestedLatency = deviceInfo->defaultLowInputLatency; - paInputParams.hostApiSpecificStreamInfo = NULL; + paInputParams.hostApiSpecificStreamInfo = &asioInputInfo; + asioInputInfo.size = sizeof asioInputInfo; + asioInputInfo.hostApiType = paASIO; + asioInputInfo.version = 1; + asioInputInfo.flags = paAsioUseChannelSelectors; + asioInputInfo.channelSelectors = &vSelectedInputChannels[0]; PaStreamParameters paOutputParams; + PaAsioStreamInfo asioOutputInfo; paOutputParams.device = devIndex; - paOutputParams.channelCount = std::min ( 2, deviceInfo->maxOutputChannels ); + paOutputParams.channelCount = std::min ( NUM_IN_OUT_CHANNELS, deviceInfo->maxOutputChannels ); paOutputParams.sampleFormat = paInt16; paOutputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency; - paOutputParams.hostApiSpecificStreamInfo = NULL; + paOutputParams.hostApiSpecificStreamInfo = &asioOutputInfo; + asioOutputInfo.size = sizeof asioOutputInfo; + asioOutputInfo.hostApiType = paASIO; + asioOutputInfo.version = 1; + asioOutputInfo.flags = paAsioUseChannelSelectors; + asioOutputInfo.channelSelectors = &vSelectedOutputChannels[0]; PaError err = Pa_OpenStream ( &deviceStream, &paInputParams, @@ -214,6 +305,8 @@ QString CSound::ReinitializeDriver ( int devIndex ) return tr ( "Could not open Portaudio stream: %1, %2" ).arg ( Pa_GetErrorText ( err ) ).arg ( Pa_GetLastHostErrorInfo()->errorText ); } + strCurDevName = deviceInfo->name; + deviceIndex = devIndex; return ""; } @@ -242,12 +335,12 @@ int CSound::paStreamCallback ( const void* input, CVector& vecsAudioData = pSound->vecsAudioData; // CAPTURE --------------------------------- - memcpy ( &vecsAudioData[0], input, sizeof ( int16_t ) * frameCount * 2 ); + memcpy ( &vecsAudioData[0], input, sizeof ( int16_t ) * frameCount * NUM_IN_OUT_CHANNELS ); pSound->ProcessCallback ( vecsAudioData ); // PLAYBACK ------------------------------------------------------------ - memcpy ( output, &vecsAudioData[0], sizeof ( int16_t ) * frameCount * 2 ); + memcpy ( output, &vecsAudioData[0], sizeof ( int16_t ) * frameCount * NUM_IN_OUT_CHANNELS ); return paContinue; } diff --git a/src/portaudiosound.h b/src/portaudiosound.h index aa6128d819..da18fbf658 100644 --- a/src/portaudiosound.h +++ b/src/portaudiosound.h @@ -42,6 +42,8 @@ #include +#define NUM_IN_OUT_CHANNELS 2 // always stereo + class CSound : public CSoundBase { public: @@ -56,6 +58,20 @@ class CSound : public CSoundBase virtual void Start(); virtual void Stop(); + virtual int GetNumInputChannels(); + virtual QString GetInputChannelName ( const int ); + virtual void SetLeftInputChannel ( const int ); + virtual void SetRightInputChannel ( const int ); + virtual int GetLeftInputChannel() { return vSelectedInputChannels[0]; } + virtual int GetRightInputChannel() { return vSelectedInputChannels[1]; } + + virtual int GetNumOutputChannels(); + virtual QString GetOutputChannelName ( const int ); + virtual void SetLeftOutputChannel ( const int ); + virtual void SetRightOutputChannel ( const int ); + virtual int GetLeftOutputChannel() { return vSelectedOutputChannels[0]; } + virtual int GetRightOutputChannel() { return vSelectedOutputChannels[1]; } + #ifdef WIN32 // Portaudio's function for this takes a device index as a parameter. So it // needs to reopen ASIO in order to get the right driver loaded. Because of @@ -82,6 +98,8 @@ class CSound : public CSoundBase PaHostApiIndex asioIndex; PaDeviceIndex deviceIndex; PaStream* deviceStream; + CVector vSelectedInputChannels; + CVector vSelectedOutputChannels; int iPrefMonoBufferSize; CVector vecsAudioData; }; From 1e8feab8d33ad463c19f3a0000b22223e1cddd2f Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Fri, 5 Mar 2021 08:57:57 -0500 Subject: [PATCH 07/17] Fully restart PortAudio on resetRequest --- libs/portaudio | 2 +- src/portaudiosound.cpp | 43 ++++++++++++++++++++++++++---------------- src/portaudiosound.h | 1 + 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/libs/portaudio b/libs/portaudio index 3988fd9ebc..e0d9b73b01 160000 --- a/libs/portaudio +++ b/libs/portaudio @@ -1 +1 @@ -Subproject commit 3988fd9ebc10cd7b1834ec6e0c0c01c9f6dbf27a +Subproject commit e0d9b73b01acee8fb607edccc0d8cfa385ffd680 diff --git a/src/portaudiosound.cpp b/src/portaudiosound.cpp index c8f47f033d..6822501cda 100644 --- a/src/portaudiosound.cpp +++ b/src/portaudiosound.cpp @@ -28,15 +28,10 @@ #include "portaudiosound.h" #include -// Needed for buffer size change callback +// Needed for reset request callback static CSound* pThisSound; -static void bufferSizeChangeCallback( long newBufferSize ) -{ - (void) newBufferSize; - pThisSound->EmitReinitRequestSignal ( RS_ONLY_RESTART_AND_INIT ); -} -static void resetRequestCallback() { pThisSound->EmitReinitRequestSignal ( RS_ONLY_RESTART_AND_INIT ); } +static void resetRequestCallback() { pThisSound->EmitReinitRequestSignal ( RS_RELOAD_RESTART_AND_INIT ); } CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* arg ), @@ -52,13 +47,23 @@ CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* { pThisSound = this; + lNumDevs = std::min ( InitPa(), MAX_NUMBER_SOUND_CARDS ); + for ( int i = 0; i < lNumDevs; i++ ) + { + PaDeviceIndex devIndex = Pa_HostApiDeviceIndexToDeviceIndex ( asioIndex, i ); + const PaDeviceInfo* devInfo = Pa_GetDeviceInfo ( devIndex ); + strDriverNames[i] = devInfo->name; + } +} + +int CSound::InitPa() +{ PaError err = Pa_Initialize(); if ( err != paNoError ) { throw CGenErr ( tr ( "Failed to initialize PortAudio: %1" ).arg ( Pa_GetErrorText ( err ) ) ); } - PaAsio_RegisterBufferSizeChangeCallback ( &bufferSizeChangeCallback ); PaAsio_RegisterResetRequestCallback ( &resetRequestCallback ); // Find ASIO API index. TODO: support non-ASIO APIs. @@ -84,13 +89,7 @@ CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* } asioIndex = apiIndex; - lNumDevs = std::min ( apiInfo->deviceCount, MAX_NUMBER_SOUND_CARDS ); - for ( int i = 0; i < lNumDevs; i++ ) - { - PaDeviceIndex devIndex = Pa_HostApiDeviceIndexToDeviceIndex ( asioIndex, i ); - const PaDeviceInfo* devInfo = Pa_GetDeviceInfo ( devIndex ); - strDriverNames[i] = devInfo->name; - } + return apiInfo->deviceCount; } int CSound::Init ( const int iNewPrefMonoBufferSize ) @@ -147,7 +146,14 @@ CSound::~CSound() { Pa_Terminate(); } PaDeviceIndex CSound::DeviceIndexFromName ( const QString& strDriverName ) { - for ( int i = 0; i < lNumDevs; i++ ) + if ( asioIndex < 0 ) + { + InitPa(); + } + + const PaHostApiInfo* apiInfo = Pa_GetHostApiInfo ( asioIndex ); + + for ( int i = 0; i < apiInfo->deviceCount; i++ ) { PaDeviceIndex devIndex = Pa_HostApiDeviceIndexToDeviceIndex ( asioIndex, i ); const PaDeviceInfo* devInfo = Pa_GetDeviceInfo ( devIndex ); @@ -319,6 +325,11 @@ void CSound::UnloadCurrentDriver() deviceStream = NULL; deviceIndex = -1; } + if ( asioIndex >= 0 ) + { + Pa_Terminate(); + asioIndex = -1; + } } int CSound::paStreamCallback ( const void* input, diff --git a/src/portaudiosound.h b/src/portaudiosound.h index da18fbf658..6b1bbe260b 100644 --- a/src/portaudiosound.h +++ b/src/portaudiosound.h @@ -85,6 +85,7 @@ class CSound : public CSoundBase virtual QString LoadAndInitializeDriver ( QString, bool ); QString ReinitializeDriver ( int devIndex ); virtual void UnloadCurrentDriver(); + int InitPa(); PaDeviceIndex DeviceIndexFromName ( const QString& strDriverName ); From 1a67fe04e67369e1acbe50f08a5e4f6512f0c6f9 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sat, 6 Mar 2021 15:07:03 -0500 Subject: [PATCH 08/17] Set suggested latency to 0 Otherwise the ASIO4ALL buffer size is set too large. Also try harder to open the ASIO control panel. --- src/portaudiosound.cpp | 30 +++++++++++++++++++++++++----- src/portaudiosound.h | 7 +------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/portaudiosound.cpp b/src/portaudiosound.cpp index 6822501cda..613d80de17 100644 --- a/src/portaudiosound.cpp +++ b/src/portaudiosound.cpp @@ -273,10 +273,13 @@ QString CSound::ReinitializeDriver ( int devIndex ) PaStreamParameters paInputParams; PaAsioStreamInfo asioInputInfo; - paInputParams.device = devIndex; - paInputParams.channelCount = std::min ( NUM_IN_OUT_CHANNELS, deviceInfo->maxInputChannels ); - paInputParams.sampleFormat = paInt16; - paInputParams.suggestedLatency = deviceInfo->defaultLowInputLatency; + paInputParams.device = devIndex; + paInputParams.channelCount = std::min (NUM_IN_OUT_CHANNELS, deviceInfo->maxInputChannels); + paInputParams.sampleFormat = paInt16; + // NOTE: Setting latency to deviceInfo->defaultLowInputLatency seems like it + // would be sensible, but gives an overly large buffer size at least in the + // case of ASIO4ALL. Put 0 instead. + paInputParams.suggestedLatency = 0; paInputParams.hostApiSpecificStreamInfo = &asioInputInfo; asioInputInfo.size = sizeof asioInputInfo; asioInputInfo.hostApiType = paASIO; @@ -289,7 +292,7 @@ QString CSound::ReinitializeDriver ( int devIndex ) paOutputParams.device = devIndex; paOutputParams.channelCount = std::min ( NUM_IN_OUT_CHANNELS, deviceInfo->maxOutputChannels ); paOutputParams.sampleFormat = paInt16; - paOutputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency; + paOutputParams.suggestedLatency = 0; // See paInputParams.suggestedLatency commentary. paOutputParams.hostApiSpecificStreamInfo = &asioOutputInfo; asioOutputInfo.size = sizeof asioOutputInfo; asioOutputInfo.hostApiType = paASIO; @@ -332,6 +335,23 @@ void CSound::UnloadCurrentDriver() } } +#ifdef WIN32 +void CSound::OpenDriverSetup() +{ + if ( deviceStream ) + { + // PaAsio_ShowControlPanel() doesn't work when the device is opened. + ASIOControlPanel(); + } + else + { + int index = DeviceIndexFromName ( strCurDevName ); + // pass NULL (?) for system specific ptr. + PaAsio_ShowControlPanel ( index, NULL ); + } +} +#endif // WIN32 + int CSound::paStreamCallback ( const void* input, void* output, unsigned long frameCount, diff --git a/src/portaudiosound.h b/src/portaudiosound.h index 6b1bbe260b..a6fa48f1c4 100644 --- a/src/portaudiosound.h +++ b/src/portaudiosound.h @@ -73,12 +73,7 @@ class CSound : public CSoundBase virtual int GetRightOutputChannel() { return vSelectedOutputChannels[1]; } #ifdef WIN32 - // Portaudio's function for this takes a device index as a parameter. So it - // needs to reopen ASIO in order to get the right driver loaded. Because of - // that it also requires passing HWND of the main window. This is fairly - // awkward, so just call the ASIO function directly for the currently loaded - // driver. - virtual void OpenDriverSetup() { ASIOControlPanel(); } + virtual void OpenDriverSetup(); #endif // WIN32 protected: From 1514db3c843f9054e0c531cf834fa9b484078524 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Thu, 11 Mar 2021 17:31:17 -0500 Subject: [PATCH 09/17] Mention PortAudio in library list --- src/util.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/util.cpp b/src/util.cpp index 3502a64920..898afe9313 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -422,7 +422,13 @@ CAboutDlg::CAboutDlg ( QWidget* parent ) : CBaseDlg ( parent ) " Open Clip Art Library (OCAL), " "http://openclipart.org

" "

" + - tr ( "Country flag icons by Mark James" ) + ", http://www.famfamfam.com

" ); + tr ( "Country flag icons by Mark James" ) + + ", http://www.famfamfam.com

" +# ifdef USE_PORTAUDIO + "

" + + tr ( "PortAudio - Portable Cross-platform Audio I/O" ) + ", http://www.portaudio.com/

" +# endif // USE_PORTAUDIO + ); // contributors list txvContributors->setText ( "

Volker Fischer (corrados)

" From 39e5d5388498b578919da7e12a9ecf4316dbaabe Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Sun, 14 Mar 2021 00:49:57 -0500 Subject: [PATCH 10/17] Support WASAPI and WDM-KS with PortAudio --- Jamulus.pro | 2 +- src/client.h | 2 + src/clientsettingsdlg.cpp | 14 ++- src/main.cpp | 28 ++++- src/portaudiosound.cpp | 250 +++++++++++++++++++++++++++----------- src/portaudiosound.h | 17 ++- src/soundbase.h | 1 + windows/sound.h | 1 + 8 files changed, 229 insertions(+), 86 deletions(-) diff --git a/Jamulus.pro b/Jamulus.pro index 2613f50e41..faafa008c0 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -1215,7 +1215,7 @@ CONFIG(portaudio) { CONFIG(portaudio_shared_lib) { LIBS += $$libnames(portaudio) } else { - DEFINES += PA_USE_ASIO=1 + DEFINES += PA_USE_ASIO=1 PA_USE_WASAPI=1 PA_USE_WDMKS=1 INCLUDEPATH += $$INCLUDEPATH_PORTAUDIO HEADERS += $$HEADERS_PORTAUDIO mingw { diff --git a/src/client.h b/src/client.h index 3237b5259a..fba0ea1850 100644 --- a/src/client.h +++ b/src/client.h @@ -199,6 +199,8 @@ class CClient : public QObject int GetSndCrdLeftOutputChannel() { return Sound.GetLeftOutputChannel(); } int GetSndCrdRightOutputChannel() { return Sound.GetRightOutputChannel(); } + bool HasControlPanel() { return Sound.HasControlPanel(); } + void SetSndCrdPrefFrameSizeFactor ( const int iNewFactor ); int GetSndCrdPrefFrameSizeFactor() { return iSndCrdPrefFrameSizeFactor; } diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index ba0d79fcef..b1bfb9c53b 100644 --- a/src/clientsettingsdlg.cpp +++ b/src/clientsettingsdlg.cpp @@ -368,12 +368,14 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet chbDetectFeedback->setAccessibleName ( tr ( "Feedback Protection check box" ) ); // init driver button -#ifdef _WIN32 - butDriverSetup->setText ( tr ( "ASIO Device Settings" ) ); -#else - // no use for this button for MacOS/Linux right now -> hide it - butDriverSetup->hide(); -#endif + if ( pNCliP->HasControlPanel() ) + { + butDriverSetup->setText ( tr ( "ASIO Device Settings" ) ); + } + else + { + butDriverSetup->hide(); + } // init audio in fader sldAudioPan->setRange ( AUD_FADER_IN_MIN, AUD_FADER_IN_MAX ); diff --git a/src/main.cpp b/src/main.cpp index 68144f3ce4..04f3172972 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -96,6 +96,7 @@ int main ( int argc, char** argv ) QString strServerListFilter = ""; QString strWelcomeMessage = ""; QString strClientName = ""; + QString strApiName = ""; #if !defined( HEADLESS ) && defined( _WIN32 ) if ( AttachConsole ( ATTACH_PARENT_PROCESS ) ) @@ -246,6 +247,16 @@ int main ( int argc, char** argv ) continue; } +#ifdef USE_PORTAUDIO + if ( GetStringArgument ( argc, argv, i, "--api", "--api", strArgument ) ) + { + strApiName = strArgument; + qInfo() << QString ( "- PortAudio API: %1" ).arg ( strApiName ); + CommandLineOptions << "--api"; + continue; + } +#endif // USE_PORTAUDIO + // Use logging --------------------------------------------------------- if ( GetStringArgument ( argc, argv, i, "-l", "--log", strArgument ) ) { @@ -602,8 +613,17 @@ int main ( int argc, char** argv ) { // Client: // actual client object - CClient - Client ( iPortNumber, iQosNumber, strConnOnStartupAddress, strMIDISetup, bNoAutoJackConnect, strClientName, bMuteMeInPersonalMix ); + CClient Client ( iPortNumber, + iQosNumber, + strConnOnStartupAddress, + strMIDISetup, + bNoAutoJackConnect, +#if defined (_WIN32) && defined (USE_PORTAUDIO) + strApiName, // TODO: don't abuse Jack client name for this. +#else + strClientName, +#endif + bMuteMeInPersonalMix ); // load settings from init-file (command line options override) CClientSettings Settings ( &Client, strIniFileName ); @@ -786,6 +806,10 @@ QString UsageArguments ( char** argv ) " -j, --nojackconnect disable auto JACK connections\n" " --ctrlmidich MIDI controller channel to listen\n" " --clientname client name (window title and JACK client name)\n" +#ifdef USE_PORTAUDIO + " --api choose which PortAudio API to use, possible choices:\n" + " " + CSound::GetPaApiNames() + "\n" +#endif // USE_PORTAUDIO "\n" "Example: " + QString ( argv[0] ) + " -s --inifile myinifile.ini\n" "\n" diff --git a/src/portaudiosound.cpp b/src/portaudiosound.cpp index 613d80de17..729cb68a94 100644 --- a/src/portaudiosound.cpp +++ b/src/portaudiosound.cpp @@ -38,24 +38,86 @@ CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* void* arg, const QString& strMIDISetup, const bool, - const QString& ) : + const QString& strApiName ) : CSoundBase ( "portaudio", fpNewProcessCallback, arg, strMIDISetup ), - deviceIndex ( -1 ), + strSelectApiName ( strApiName.isEmpty() ? "ASIO" : strApiName ), + inDeviceIndex ( -1 ), + outDeviceIndex ( -1 ), deviceStream ( NULL ), vSelectedInputChannels ( NUM_IN_OUT_CHANNELS ), vSelectedOutputChannels ( NUM_IN_OUT_CHANNELS ) { pThisSound = this; - lNumDevs = std::min ( InitPa(), MAX_NUMBER_SOUND_CARDS ); - for ( int i = 0; i < lNumDevs; i++ ) + int numPortAudioDevices = std::min ( InitPa(), MAX_NUMBER_SOUND_CARDS ); + lNumDevs = 0; + for ( int i = 0; i < numPortAudioDevices; i++ ) { - PaDeviceIndex devIndex = Pa_HostApiDeviceIndexToDeviceIndex ( asioIndex, i ); + PaDeviceIndex devIndex = Pa_HostApiDeviceIndexToDeviceIndex ( selectedApiIndex, i ); const PaDeviceInfo* devInfo = Pa_GetDeviceInfo ( devIndex ); - strDriverNames[i] = devInfo->name; + if ( devInfo->maxInputChannels > 0 && devInfo->maxOutputChannels > 0 ) + { + // Duplex device. + paInputDeviceIndices[lNumDevs] = devIndex; + paOutputDeviceIndices[lNumDevs] = devIndex; + strDriverNames[lNumDevs++] = devInfo->name; + } + else if ( devInfo->maxInputChannels > 0) + { + // For each input device, construct virtual duplex devices by + // combining it with every output device (i.e., Cartesian product of + // input and output devices). + for ( int j = 0; j < numPortAudioDevices; j++ ) + { + PaDeviceIndex outDevIndex = Pa_HostApiDeviceIndexToDeviceIndex ( selectedApiIndex, j ); + const PaDeviceInfo* outDevInfo = Pa_GetDeviceInfo ( outDevIndex ); + if ( outDevInfo->maxOutputChannels > 0 && outDevInfo->maxInputChannels == 0 ) + { + paInputDeviceIndices[lNumDevs] = devIndex; + paOutputDeviceIndices[lNumDevs] = outDevIndex; + strDriverNames[lNumDevs++] = QString ( "in: " ) + devInfo->name + "/out: " + outDevInfo->name; + } + } + } } } +QString CSound::GetPaApiNames() +{ + // NOTE: It is okay to initialize PortAudio even if it already has been + // initialized, so long as every Pa_Initialize call is balanced with a + // Pa_Terminate. + class PaInitInScope + { + public: + PaError err; + PaInitInScope() : err ( Pa_Initialize() ) {} + ~PaInitInScope() { Pa_Terminate(); } + } paInScope; + + if ( paInScope.err != paNoError ) + { + return ""; + } + + PaHostApiIndex apiCount = Pa_GetHostApiCount(); + if ( apiCount < 0 ) + { + return ""; + } + + QStringList apiNames; + for ( PaHostApiIndex i = 0; i < apiCount; i++ ) + { + const PaHostApiInfo* apiInfo = Pa_GetHostApiInfo ( i ); + QString name = apiInfo->name; + name = name.section ( ' ', -1 ); // Drop "Windows " from "Windows " names. + apiNames << name; + } + + return apiNames.join ( ", " ); +} + int CSound::InitPa() { PaError err = Pa_Initialize(); @@ -64,40 +126,48 @@ int CSound::InitPa() throw CGenErr ( tr ( "Failed to initialize PortAudio: %1" ).arg ( Pa_GetErrorText ( err ) ) ); } - PaAsio_RegisterResetRequestCallback ( &resetRequestCallback ); - - // Find ASIO API index. TODO: support non-ASIO APIs. PaHostApiIndex apiCount = Pa_GetHostApiCount(); if ( apiCount < 0 ) { throw CGenErr ( tr ( "Failed to get API count" ) ); } + PaHostApiIndex apiIndex = -1; const PaHostApiInfo* apiInfo = NULL; for ( PaHostApiIndex i = 0; i < apiCount; i++ ) { - apiInfo = Pa_GetHostApiInfo ( i ); - if ( apiInfo->type == paASIO ) + apiInfo = Pa_GetHostApiInfo ( i ); + QString name = apiInfo->name; + name = name.section ( ' ', -1 ); // Drop "Windows " from "Windows " names. + if ( strSelectApiName.compare ( name, Qt::CaseInsensitive ) == 0 ) { +#ifdef _WIN32 + if ( apiInfo->type == paASIO ) + { + PaAsio_RegisterResetRequestCallback ( &resetRequestCallback ); + } +#endif // _WIN32 + apiIndex = i; break; } } if ( apiIndex < 0 || apiInfo->deviceCount == 0 ) { - throw CGenErr ( tr ( "No ASIO devices found" ) ); + throw CGenErr ( tr ( "No %1 devices found" ).arg ( strSelectApiName ) ); } - asioIndex = apiIndex; + selectedApiIndex = apiIndex; return apiInfo->deviceCount; } int CSound::Init ( const int iNewPrefMonoBufferSize ) { - if ( deviceIndex >= 0 ) + const PaHostApiInfo* apiInfo = Pa_GetHostApiInfo ( selectedApiIndex ); + if ( inDeviceIndex >= 0 && apiInfo->type == paASIO ) { long minBufferSize, maxBufferSize, prefBufferSize, granularity; - PaAsio_GetAvailableBufferSizes ( deviceIndex, &minBufferSize, &maxBufferSize, &prefBufferSize, &granularity ); + PaAsio_GetAvailableBufferSizes ( inDeviceIndex, &minBufferSize, &maxBufferSize, &prefBufferSize, &granularity ); if ( granularity == 0 ) // no options, just take the preferred one. { iPrefMonoBufferSize = prefBufferSize; @@ -134,9 +204,9 @@ int CSound::Init ( const int iNewPrefMonoBufferSize ) } vecsAudioData.Init ( iPrefMonoBufferSize * NUM_IN_OUT_CHANNELS ); - if ( deviceStream && deviceIndex >= 0 ) + if ( deviceStream && inDeviceIndex >= 0 ) { - ReinitializeDriver ( deviceIndex ); + ReinitializeDriver ( inDeviceIndex, outDeviceIndex ); } return CSoundBase::Init ( iPrefMonoBufferSize ); @@ -144,41 +214,50 @@ int CSound::Init ( const int iNewPrefMonoBufferSize ) CSound::~CSound() { Pa_Terminate(); } -PaDeviceIndex CSound::DeviceIndexFromName ( const QString& strDriverName ) +bool CSound::DeviceIndexFromName ( const QString& strDriverName, PaDeviceIndex& inIndex, PaDeviceIndex& outIndex ) { - if ( asioIndex < 0 ) + inIndex = -1; + outIndex = -1; + + if ( selectedApiIndex < 0 ) { InitPa(); } - const PaHostApiInfo* apiInfo = Pa_GetHostApiInfo ( asioIndex ); - - for ( int i = 0; i < apiInfo->deviceCount; i++ ) + // find driver index from given driver name + int iDriverIdx = INVALID_INDEX; // initialize with an invalid index + for ( int i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ ) { - PaDeviceIndex devIndex = Pa_HostApiDeviceIndexToDeviceIndex ( asioIndex, i ); - const PaDeviceInfo* devInfo = Pa_GetDeviceInfo ( devIndex ); - if ( strDriverName.compare ( devInfo->name ) == 0 ) + if ( strDriverName.compare ( strDriverNames[i] ) == 0 ) { - return devIndex; + iDriverIdx = i; } } - return -1; + + if ( iDriverIdx == INVALID_INDEX ) + { + return false; + } + + inIndex = paInputDeviceIndices[iDriverIdx]; + outIndex = paOutputDeviceIndices[iDriverIdx]; + return true; } int CSound::GetNumInputChannels() { - if ( deviceIndex >= 0 ) + if ( inDeviceIndex >= 0 ) { - const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo ( deviceIndex ); + const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo ( inDeviceIndex ); return deviceInfo->maxInputChannels; } return CSoundBase::GetNumInputChannels(); } int CSound::GetNumOutputChannels() { - if ( deviceIndex >= 0 ) + if ( outDeviceIndex >= 0 ) { - const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo ( deviceIndex ); + const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo ( outDeviceIndex ); return deviceInfo->maxOutputChannels; } return CSoundBase::GetNumOutputChannels(); @@ -186,10 +265,11 @@ int CSound::GetNumOutputChannels() QString CSound::GetInputChannelName ( const int channel ) { - if ( deviceIndex >= 0 ) + const PaHostApiInfo* apiInfo = Pa_GetHostApiInfo ( selectedApiIndex ); + if ( inDeviceIndex >= 0 && apiInfo->type == paASIO ) { const char* channelName; - PaError err = PaAsio_GetInputChannelName ( deviceIndex, channel, &channelName ); + PaError err = PaAsio_GetInputChannelName ( inDeviceIndex, channel, &channelName ); if ( err == paNoError ) { return QString ( channelName ); @@ -199,10 +279,11 @@ QString CSound::GetInputChannelName ( const int channel ) } QString CSound::GetOutputChannelName ( const int channel ) { - if ( deviceIndex >= 0 ) + const PaHostApiInfo* apiInfo = Pa_GetHostApiInfo ( selectedApiIndex ); + if ( outDeviceIndex >= 0 && apiInfo->type == paASIO ) { const char* channelName; - PaError err = PaAsio_GetOutputChannelName ( deviceIndex, channel, &channelName ); + PaError err = PaAsio_GetOutputChannelName ( outDeviceIndex, channel, &channelName ); if ( err == paNoError ) { return QString ( channelName ); @@ -245,20 +326,27 @@ QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDrive { (void) bOpenDriverSetup; // FIXME: respect this - int devIndex = DeviceIndexFromName ( strDriverName ); - if ( devIndex < 0 ) + PaDeviceIndex inIndex, outIndex; + bool haveDevice = DeviceIndexFromName ( strDriverName, inIndex, outIndex ); + if ( !haveDevice ) { return tr ( "The current selected audio device is no longer present in the system." ); } - return ReinitializeDriver ( devIndex ); + QString err = ReinitializeDriver ( inIndex, outIndex ); + if ( err.isEmpty() ) + { + strCurDevName = strDriverName; + } + return err; } -QString CSound::ReinitializeDriver ( int devIndex ) +QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outIndex ) { - const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo ( devIndex ); + const PaDeviceInfo* inDeviceInfo = Pa_GetDeviceInfo ( inIndex ); + const PaDeviceInfo* outDeviceInfo = Pa_GetDeviceInfo ( outIndex ); - if ( deviceInfo->maxInputChannels < NUM_IN_OUT_CHANNELS || - deviceInfo->maxOutputChannels < NUM_IN_OUT_CHANNELS ) + if ( inDeviceInfo->maxInputChannels < NUM_IN_OUT_CHANNELS || + outDeviceInfo->maxOutputChannels < NUM_IN_OUT_CHANNELS ) { // FIXME: handle mono devices. return tr ( "Less than 2 channels not supported" ); @@ -267,38 +355,55 @@ QString CSound::ReinitializeDriver ( int devIndex ) if ( deviceStream != NULL ) { Pa_CloseStream ( deviceStream ); - deviceStream = NULL; - deviceIndex = -1; + deviceStream = NULL; + inDeviceIndex = -1; + outDeviceIndex = -1; } + const PaHostApiInfo* apiInfo = Pa_GetHostApiInfo ( selectedApiIndex ); + PaStreamParameters paInputParams; PaAsioStreamInfo asioInputInfo; - paInputParams.device = devIndex; - paInputParams.channelCount = std::min (NUM_IN_OUT_CHANNELS, deviceInfo->maxInputChannels); + paInputParams.device = inIndex; + paInputParams.channelCount = std::min ( NUM_IN_OUT_CHANNELS, inDeviceInfo->maxInputChannels ); paInputParams.sampleFormat = paInt16; // NOTE: Setting latency to deviceInfo->defaultLowInputLatency seems like it // would be sensible, but gives an overly large buffer size at least in the // case of ASIO4ALL. Put 0 instead. - paInputParams.suggestedLatency = 0; - paInputParams.hostApiSpecificStreamInfo = &asioInputInfo; - asioInputInfo.size = sizeof asioInputInfo; - asioInputInfo.hostApiType = paASIO; - asioInputInfo.version = 1; - asioInputInfo.flags = paAsioUseChannelSelectors; - asioInputInfo.channelSelectors = &vSelectedInputChannels[0]; + paInputParams.suggestedLatency = 0; + if ( apiInfo->type == paASIO ) + { + paInputParams.hostApiSpecificStreamInfo = &asioInputInfo; + asioInputInfo.size = sizeof asioInputInfo; + asioInputInfo.hostApiType = paASIO; + asioInputInfo.version = 1; + asioInputInfo.flags = paAsioUseChannelSelectors; + asioInputInfo.channelSelectors = &vSelectedInputChannels[0]; + } + else + { + paInputParams.hostApiSpecificStreamInfo = NULL; + } PaStreamParameters paOutputParams; PaAsioStreamInfo asioOutputInfo; - paOutputParams.device = devIndex; - paOutputParams.channelCount = std::min ( NUM_IN_OUT_CHANNELS, deviceInfo->maxOutputChannels ); - paOutputParams.sampleFormat = paInt16; - paOutputParams.suggestedLatency = 0; // See paInputParams.suggestedLatency commentary. - paOutputParams.hostApiSpecificStreamInfo = &asioOutputInfo; - asioOutputInfo.size = sizeof asioOutputInfo; - asioOutputInfo.hostApiType = paASIO; - asioOutputInfo.version = 1; - asioOutputInfo.flags = paAsioUseChannelSelectors; - asioOutputInfo.channelSelectors = &vSelectedOutputChannels[0]; + paOutputParams.device = outIndex; + paOutputParams.channelCount = std::min (NUM_IN_OUT_CHANNELS, outDeviceInfo->maxOutputChannels); + paOutputParams.sampleFormat = paInt16; + paOutputParams.suggestedLatency = 0; // See paInputParams.suggestedLatency commentary. + if ( apiInfo->type == paASIO ) + { + paOutputParams.hostApiSpecificStreamInfo = &asioOutputInfo; + asioOutputInfo.size = sizeof asioOutputInfo; + asioOutputInfo.hostApiType = paASIO; + asioOutputInfo.version = 1; + asioOutputInfo.flags = paAsioUseChannelSelectors; + asioOutputInfo.channelSelectors = &vSelectedOutputChannels[0]; + } + else + { + paOutputParams.hostApiSpecificStreamInfo = NULL; + } PaError err = Pa_OpenStream ( &deviceStream, &paInputParams, @@ -314,9 +419,8 @@ QString CSound::ReinitializeDriver ( int devIndex ) return tr ( "Could not open Portaudio stream: %1, %2" ).arg ( Pa_GetErrorText ( err ) ).arg ( Pa_GetLastHostErrorInfo()->errorText ); } - strCurDevName = deviceInfo->name; - - deviceIndex = devIndex; + inDeviceIndex = inIndex; + outDeviceIndex = outIndex; return ""; } @@ -325,13 +429,14 @@ void CSound::UnloadCurrentDriver() if ( deviceStream != NULL ) { Pa_CloseStream ( deviceStream ); - deviceStream = NULL; - deviceIndex = -1; + deviceStream = NULL; + inDeviceIndex = -1; + outDeviceIndex = -1; } - if ( asioIndex >= 0 ) + if ( selectedApiIndex >= 0 ) { Pa_Terminate(); - asioIndex = -1; + selectedApiIndex = -1; } } @@ -345,9 +450,10 @@ void CSound::OpenDriverSetup() } else { - int index = DeviceIndexFromName ( strCurDevName ); + PaDeviceIndex inIndex, outIndex; + DeviceIndexFromName ( strCurDevName, inIndex, outIndex ); // pass NULL (?) for system specific ptr. - PaAsio_ShowControlPanel ( index, NULL ); + PaAsio_ShowControlPanel ( inIndex, NULL ); } } #endif // WIN32 diff --git a/src/portaudiosound.h b/src/portaudiosound.h index a6fa48f1c4..4173c52222 100644 --- a/src/portaudiosound.h +++ b/src/portaudiosound.h @@ -51,7 +51,7 @@ class CSound : public CSoundBase void* arg, const QString& strMIDISetup, const bool, - const QString& ); + const QString& apiName ); virtual ~CSound(); virtual int Init ( const int iNewPrefMonoBufferSize ); @@ -72,17 +72,20 @@ class CSound : public CSoundBase virtual int GetLeftOutputChannel() { return vSelectedOutputChannels[0]; } virtual int GetRightOutputChannel() { return vSelectedOutputChannels[1]; } + static QString GetPaApiNames(); + #ifdef WIN32 virtual void OpenDriverSetup(); + virtual bool HasControlPanel() { return strSelectApiName.compare ( "ASIO", Qt::CaseInsensitive ) == 0; } #endif // WIN32 protected: virtual QString LoadAndInitializeDriver ( QString, bool ); - QString ReinitializeDriver ( int devIndex ); + QString ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outIndex ); virtual void UnloadCurrentDriver(); int InitPa(); - PaDeviceIndex DeviceIndexFromName ( const QString& strDriverName ); + bool DeviceIndexFromName ( const QString& strDriverName, PaDeviceIndex& inIndex, PaDeviceIndex& outIndex ); static int paStreamCallback ( const void* input, void* output, @@ -91,8 +94,12 @@ class CSound : public CSoundBase PaStreamCallbackFlags statusFlags, void* userData ); - PaHostApiIndex asioIndex; - PaDeviceIndex deviceIndex; + const QString strSelectApiName; + PaHostApiIndex selectedApiIndex; + PaDeviceIndex paInputDeviceIndices[MAX_NUMBER_SOUND_CARDS]; + PaDeviceIndex paOutputDeviceIndices[MAX_NUMBER_SOUND_CARDS]; + + PaDeviceIndex inDeviceIndex, outDeviceIndex; PaStream* deviceStream; CVector vSelectedInputChannels; CVector vSelectedOutputChannels; diff --git a/src/soundbase.h b/src/soundbase.h index 50fd790d34..7ea19652fa 100644 --- a/src/soundbase.h +++ b/src/soundbase.h @@ -103,6 +103,7 @@ class CSoundBase : public QThread virtual float GetInOutLatencyMs() { return 0.0f; } // "0.0" means no latency is available + virtual bool HasControlPanel() { return false; } // only true with ASIO (Windows) virtual void OpenDriverSetup() {} bool IsRunning() const { return bRun; } diff --git a/windows/sound.h b/windows/sound.h index 433229fc25..c815b3ec14 100644 --- a/windows/sound.h +++ b/windows/sound.h @@ -55,6 +55,7 @@ class CSound : public CSoundBase virtual void Stop(); virtual void OpenDriverSetup() { ASIOControlPanel(); } + virtual bool HasControlPanel() { return true; } // always using ASIO // channel selection virtual int GetNumInputChannels() { return static_cast ( lNumInChanPlusAddChan ); } From daba2f82ee5aade75d12075788fc4e73b86398fe Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Mon, 15 Mar 2021 21:48:45 -0400 Subject: [PATCH 11/17] Don't put 0 for suggestLatency, it breaks WDM-KS --- src/portaudiosound.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/portaudiosound.cpp b/src/portaudiosound.cpp index 729cb68a94..f80773a0a4 100644 --- a/src/portaudiosound.cpp +++ b/src/portaudiosound.cpp @@ -369,8 +369,10 @@ QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outInd paInputParams.sampleFormat = paInt16; // NOTE: Setting latency to deviceInfo->defaultLowInputLatency seems like it // would be sensible, but gives an overly large buffer size at least in the - // case of ASIO4ALL. Put 0 instead. - paInputParams.suggestedLatency = 0; + // case of ASIO4ALL. On the other hand, putting 0 causes an error with + // WDM-KS devices. Put a latency value that corresponds to our intended. + // buffer size. + paInputParams.suggestedLatency = (PaTime) iPrefMonoBufferSize / SYSTEM_SAMPLE_RATE_HZ; if ( apiInfo->type == paASIO ) { paInputParams.hostApiSpecificStreamInfo = &asioInputInfo; @@ -388,9 +390,9 @@ QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outInd PaStreamParameters paOutputParams; PaAsioStreamInfo asioOutputInfo; paOutputParams.device = outIndex; - paOutputParams.channelCount = std::min (NUM_IN_OUT_CHANNELS, outDeviceInfo->maxOutputChannels); + paOutputParams.channelCount = std::min ( NUM_IN_OUT_CHANNELS, outDeviceInfo->maxOutputChannels ); paOutputParams.sampleFormat = paInt16; - paOutputParams.suggestedLatency = 0; // See paInputParams.suggestedLatency commentary. + paOutputParams.suggestedLatency = paInputParams.suggestedLatency; if ( apiInfo->type == paASIO ) { paOutputParams.hostApiSpecificStreamInfo = &asioOutputInfo; From 3586bdab68013a4d537b09e1b6b6f91026bac71f Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Wed, 31 Mar 2021 20:26:27 -0400 Subject: [PATCH 12/17] Add support for WASAPI exclusive mode --- src/client.h | 3 +- src/clientsettingsdlg.cpp | 40 ++++++++++++++++++- src/clientsettingsdlg.h | 2 + src/clientsettingsdlgbase.ui | 25 ++++++++++++ src/portaudiosound.cpp | 74 +++++++++++++++++++++++++++++++++++- src/portaudiosound.h | 6 ++- src/soundbase.h | 19 ++++++++- 7 files changed, 163 insertions(+), 6 deletions(-) diff --git a/src/client.h b/src/client.h index fba0ea1850..a0921a2ba5 100644 --- a/src/client.h +++ b/src/client.h @@ -199,7 +199,8 @@ class CClient : public QObject int GetSndCrdLeftOutputChannel() { return Sound.GetLeftOutputChannel(); } int GetSndCrdRightOutputChannel() { return Sound.GetRightOutputChannel(); } - bool HasControlPanel() { return Sound.HasControlPanel(); } + EPaApiSettings GetExtraSettings() { return Sound.GetExtraSettings(); } + void SetSndCrdWasapiMode ( PaWasapiMode mode ) { Sound.SetWasapiMode ( mode ); } void SetSndCrdPrefFrameSizeFactor ( const int iNewFactor ); int GetSndCrdPrefFrameSizeFactor() { return iSndCrdPrefFrameSizeFactor; } diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index b1bfb9c53b..4d63d8911a 100644 --- a/src/clientsettingsdlg.cpp +++ b/src/clientsettingsdlg.cpp @@ -367,8 +367,26 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet chbDetectFeedback->setWhatsThis ( strDetectFeedback ); chbDetectFeedback->setAccessibleName ( tr ( "Feedback Protection check box" ) ); + if ( pNCliP->GetExtraSettings() != PaApiWasapiModes ) + { + rbtWasapiShared->hide(); + rbtWasapiLowLatency->hide(); + rbtWasapiExclusive->hide(); + } + else + { + // PortAudio doesn't support low latency mode yet, see + // https://github.com/PortAudio/portaudio/issues/385. + rbtWasapiLowLatency->hide(); + + rbtWasapiShared->setChecked ( rbtWasapiShared ); // TODO: Save choice in settings + WasapiModeButtonGroup.addButton ( rbtWasapiShared ); + WasapiModeButtonGroup.addButton ( rbtWasapiLowLatency ); + WasapiModeButtonGroup.addButton ( rbtWasapiExclusive ); + } + // init driver button - if ( pNCliP->HasControlPanel() ) + if ( pNCliP->GetExtraSettings() == PaApiControlPanel ) { butDriverSetup->setText ( tr ( "ASIO Device Settings" ) ); } @@ -665,6 +683,10 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet static_cast ( &QButtonGroup::buttonClicked ), this, &CClientSettingsDlg::OnSndCrdBufferDelayButtonGroupClicked ); + QObject::connect ( &WasapiModeButtonGroup, + static_cast ( &QButtonGroup::buttonClicked ), + this, + &CClientSettingsDlg::OnWasapiModeButtonGroupClicked ); // spinners QObject::connect ( spnMixerRows, @@ -984,6 +1006,22 @@ void CClientSettingsDlg::OnSndCrdBufferDelayButtonGroupClicked ( QAbstractButton UpdateDisplay(); } +void CClientSettingsDlg::OnWasapiModeButtonGroupClicked ( QAbstractButton* button ) +{ + if ( button == rbtWasapiExclusive ) + { + pClient->SetSndCrdWasapiMode ( WasapiModeExclusive ); + } + else if ( button == rbtWasapiLowLatency ) + { + pClient->SetSndCrdWasapiMode ( WasapiModeLowLatency ); + } + else // if ( button == rbtWasapiShared ) + { + pClient->SetSndCrdWasapiMode ( WasapiModeShared ); + } +} + void CClientSettingsDlg::UpdateUploadRate() { // update upstream rate information label diff --git a/src/clientsettingsdlg.h b/src/clientsettingsdlg.h index 53e05fd1bb..ca34a22464 100644 --- a/src/clientsettingsdlg.h +++ b/src/clientsettingsdlg.h @@ -75,6 +75,7 @@ class CClientSettingsDlg : public CBaseDlg, private Ui_CClientSettingsDlgBase CClientSettings* pSettings; QTimer TimerStatus; QButtonGroup SndCrdBufferDelayButtonGroup; + QButtonGroup WasapiModeButtonGroup; public slots: void OnTimerStatus() { UpdateDisplay(); } @@ -87,6 +88,7 @@ public slots: void OnNewClientLevelEditingFinished() { pSettings->iNewClientFaderLevel = edtNewClientLevel->text().toInt(); } void OnInputBoostChanged(); void OnSndCrdBufferDelayButtonGroupClicked ( QAbstractButton* button ); + void OnWasapiModeButtonGroupClicked ( QAbstractButton* button ); void OnSoundcardActivated ( int iSndDevIdx ); void OnLInChanActivated ( int iChanIdx ); void OnRInChanActivated ( int iChanIdx ); diff --git a/src/clientsettingsdlgbase.ui b/src/clientsettingsdlgbase.ui index 70e091f195..381475d4c9 100644 --- a/src/clientsettingsdlgbase.ui +++ b/src/clientsettingsdlgbase.ui @@ -358,6 +358,31 @@ + + + + + + Shared + + + + + + + Low Latency + + + + + + + Exclusive + + + + + diff --git a/src/portaudiosound.cpp b/src/portaudiosound.cpp index f80773a0a4..b23875c82c 100644 --- a/src/portaudiosound.cpp +++ b/src/portaudiosound.cpp @@ -26,7 +26,10 @@ \******************************************************************************/ #include "portaudiosound.h" -#include +#ifdef _WIN32 +# include +# include +#endif // WIN32 // Needed for reset request callback static CSound* pThisSound; @@ -340,6 +343,37 @@ QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDrive return err; } +void CSound::SetWasapiMode ( PaWasapiMode mode ) +{ + switch ( mode ) + { + case WasapiModeExclusive: + wasapiFlag = paWinWasapiExclusive; + break; + + // PortAudio doesn't support low latency mode yet, see + // https://github.com/PortAudio/portaudio/issues/385. + case WasapiModeLowLatency: + case WasapiModeShared: + default: + wasapiFlag = 0; + break; + } + if ( inDeviceIndex > 0 || outDeviceIndex > 0 ) + { + bool running = bRun; + if ( running ) + { + Stop(); + } + ReinitializeDriver ( inDeviceIndex, outDeviceIndex ); + if ( running ) + { + Start(); + } + } +} + QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outIndex ) { const PaDeviceInfo* inDeviceInfo = Pa_GetDeviceInfo ( inIndex ); @@ -364,6 +398,7 @@ QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outInd PaStreamParameters paInputParams; PaAsioStreamInfo asioInputInfo; + PaWasapiStreamInfo wasapiInputInfo; paInputParams.device = inIndex; paInputParams.channelCount = std::min ( NUM_IN_OUT_CHANNELS, inDeviceInfo->maxInputChannels ); paInputParams.sampleFormat = paInt16; @@ -382,6 +417,15 @@ QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outInd asioInputInfo.flags = paAsioUseChannelSelectors; asioInputInfo.channelSelectors = &vSelectedInputChannels[0]; } + else if ( apiInfo->type == paWASAPI ) + { + paInputParams.hostApiSpecificStreamInfo = &wasapiInputInfo; + memset ( &wasapiInputInfo, 0, sizeof wasapiInputInfo ); + wasapiInputInfo.hostApiType = paWASAPI; + wasapiInputInfo.size = sizeof wasapiInputInfo; + wasapiInputInfo.version = 1; + wasapiInputInfo.flags = wasapiFlag; + } else { paInputParams.hostApiSpecificStreamInfo = NULL; @@ -389,6 +433,7 @@ QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outInd PaStreamParameters paOutputParams; PaAsioStreamInfo asioOutputInfo; + PaWasapiStreamInfo wasapiOutputInfo; paOutputParams.device = outIndex; paOutputParams.channelCount = std::min ( NUM_IN_OUT_CHANNELS, outDeviceInfo->maxOutputChannels ); paOutputParams.sampleFormat = paInt16; @@ -402,6 +447,15 @@ QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outInd asioOutputInfo.flags = paAsioUseChannelSelectors; asioOutputInfo.channelSelectors = &vSelectedOutputChannels[0]; } + else if ( apiInfo->type == paWASAPI ) + { + paOutputParams.hostApiSpecificStreamInfo = &wasapiOutputInfo; + memset ( &wasapiOutputInfo, 0, sizeof wasapiOutputInfo ); + wasapiOutputInfo.hostApiType = paWASAPI; + wasapiOutputInfo.size = sizeof wasapiOutputInfo; + wasapiOutputInfo.version = 1; + wasapiOutputInfo.flags = wasapiFlag; + } else { paOutputParams.hostApiSpecificStreamInfo = NULL; @@ -442,6 +496,24 @@ void CSound::UnloadCurrentDriver() } } +EPaApiSettings CSound::GetExtraSettings() +{ +#ifdef WIN32 + if ( strSelectApiName.compare ( "ASIO", Qt::CaseInsensitive ) == 0 ) + { + return PaApiControlPanel; + } + else if ( strSelectApiName.compare ( "WASAPI", Qt::CaseInsensitive ) == 0 ) + { + return PaApiWasapiModes; + } + else +#endif // WIN32 + { + return PaApiNoExtraSettings; + } +} + #ifdef WIN32 void CSound::OpenDriverSetup() { diff --git a/src/portaudiosound.h b/src/portaudiosound.h index 4173c52222..fb1be16021 100644 --- a/src/portaudiosound.h +++ b/src/portaudiosound.h @@ -74,9 +74,12 @@ class CSound : public CSoundBase static QString GetPaApiNames(); + virtual EPaApiSettings GetExtraSettings(); + virtual void SetWasapiMode ( PaWasapiMode mode ); + + #ifdef WIN32 virtual void OpenDriverSetup(); - virtual bool HasControlPanel() { return strSelectApiName.compare ( "ASIO", Qt::CaseInsensitive ) == 0; } #endif // WIN32 protected: @@ -99,6 +102,7 @@ class CSound : public CSoundBase PaDeviceIndex paInputDeviceIndices[MAX_NUMBER_SOUND_CARDS]; PaDeviceIndex paOutputDeviceIndices[MAX_NUMBER_SOUND_CARDS]; + int wasapiFlag; PaDeviceIndex inDeviceIndex, outDeviceIndex; PaStream* deviceStream; CVector vSelectedInputChannels; diff --git a/src/soundbase.h b/src/soundbase.h index 7ea19652fa..db1e49acd5 100644 --- a/src/soundbase.h +++ b/src/soundbase.h @@ -51,6 +51,20 @@ enum EMidiCtlType None }; +enum EPaApiSettings +{ + PaApiNoExtraSettings = 0, + PaApiControlPanel, // ASIO + PaApiWasapiModes, // WASAPI +}; + +enum PaWasapiMode +{ + WasapiModeShared = 0, + WasapiModeLowLatency, + WasapiModeExclusive, +}; + class CMidiCtlEntry { public: @@ -103,8 +117,9 @@ class CSoundBase : public QThread virtual float GetInOutLatencyMs() { return 0.0f; } // "0.0" means no latency is available - virtual bool HasControlPanel() { return false; } // only true with ASIO (Windows) - virtual void OpenDriverSetup() {} + virtual void OpenDriverSetup() {} + virtual EPaApiSettings GetExtraSettings() { return PaApiNoExtraSettings; } + virtual void SetWasapiMode ( PaWasapiMode mode ) { (void) mode; } bool IsRunning() const { return bRun; } bool IsCallbackEntered() const { return bCallbackEntered; } From e85e31e790eca325da67c24f5a901f3f19d3e91f Mon Sep 17 00:00:00 2001 From: nefarius2001 Date: Wed, 10 Mar 2021 22:43:53 +0100 Subject: [PATCH 13/17] add separate portaudio-build --- .github/workflows/autobuild.yml | 8 + ...ild_windowsinstaller_2_build_portaudio.ps1 | 31 ++ ...indowsinstaller_3_copy_files_portaudio.ps1 | 57 ++++ windows/deploy_windows_portaudio.ps1 | 302 ++++++++++++++++++ 4 files changed, 398 insertions(+) create mode 100644 autobuild/windows/autobuild_windowsinstaller_2_build_portaudio.ps1 create mode 100644 autobuild/windows/autobuild_windowsinstaller_3_copy_files_portaudio.ps1 create mode 100644 windows/deploy_windows_portaudio.ps1 diff --git a/.github/workflows/autobuild.yml b/.github/workflows/autobuild.yml index 6131504d9c..52580ac327 100644 --- a/.github/workflows/autobuild.yml +++ b/.github/workflows/autobuild.yml @@ -140,6 +140,14 @@ jobs: cmd3_postbuild: powershell .\autobuild\windows\autobuild_windowsinstaller_3_copy_files.ps1 uses_codeql: true + - config_name: Windows portaudio (artifact+codeQL) + target_os: windows + building_on_os: windows-latest + cmd1_prebuild: powershell .\autobuild\windows\autobuild_windowsinstaller_1_prepare.ps1 + cmd2_build: powershell .\autobuild\windows\autobuild_windowsinstaller_2_build_portaudio.ps1 + cmd3_postbuild: powershell .\autobuild\windows\autobuild_windowsinstaller_3_copy_files_portaudio.ps1 + uses_codeql: true + runs-on: ${{ matrix.config.building_on_os }} steps: diff --git a/autobuild/windows/autobuild_windowsinstaller_2_build_portaudio.ps1 b/autobuild/windows/autobuild_windowsinstaller_2_build_portaudio.ps1 new file mode 100644 index 0000000000..f078e5fd27 --- /dev/null +++ b/autobuild/windows/autobuild_windowsinstaller_2_build_portaudio.ps1 @@ -0,0 +1,31 @@ +# Powershell + +# autobuild_2_build: actual build process + + +#################### +### PARAMETERS ### +#################### + +# Get the source path via parameter +param ( + [string] $jamulus_project_path = $Env:jamulus_project_path +) +# Sanity check of parameters +if (("$jamulus_project_path" -eq $null) -or ("$jamulus_project_path" -eq "")) { + throw "expecting ""jamulus_project_path"" as parameter or ENV" +} elseif (!(Test-Path -Path $jamulus_project_path)) { + throw "non.existing jamulus_project_path: $jamulus_project_path" +} else { + echo "jamulus_project_path is valid: $jamulus_project_path" +} + + +################### +### PROCEDURE ### +################### + +echo "Build installer..." +# Build the installer +powershell "$jamulus_project_path\windows\deploy_windows_portaudio.ps1" "C:\Qt\5.15.2" + diff --git a/autobuild/windows/autobuild_windowsinstaller_3_copy_files_portaudio.ps1 b/autobuild/windows/autobuild_windowsinstaller_3_copy_files_portaudio.ps1 new file mode 100644 index 0000000000..70fca34f83 --- /dev/null +++ b/autobuild/windows/autobuild_windowsinstaller_3_copy_files_portaudio.ps1 @@ -0,0 +1,57 @@ +# Powershell + +# autobuild_3_copy_files: copy the built files to deploy folder + + +#################### +### PARAMETERS ### +#################### + +# Get the source path via parameter +param ( + [string] $jamulus_project_path = $Env:jamulus_project_path, + [string] $jamulus_buildversionstring = $Env:jamulus_buildversionstring +) +# Sanity check of parameters +if (("$jamulus_project_path" -eq $null) -or ("$jamulus_project_path" -eq "")) { + throw "expecting ""jamulus_project_path"" as parameter or ENV" +} elseif (!(Test-Path -Path "$jamulus_project_path")) { + throw "non.existing jamulus_project_path: $jamulus_project_path" +} else { + echo "jamulus_project_path is valid: $jamulus_project_path" +} +if (($jamulus_buildversionstring -eq $null) -or ($jamulus_buildversionstring -eq "")) { + echo "expecting ""jamulus_buildversionstring"" as parameter or ENV" + echo "using ""NoVersion"" as jamulus_buildversionstring for filenames" + $jamulus_buildversionstring = "NoVersion" +} + + +################### +### PROCEDURE ### +################### + +# Rename the file +echo "rename" +$artifact_deploy_filename = "jamulus_portaudio_${Env:jamulus_buildversionstring}_win.exe" +echo "rename deploy file to $artifact_deploy_filename" +cp "$jamulus_project_path\deploy\Jamulus*installer-win.exe" "$jamulus_project_path\deploy\$artifact_deploy_filename" + + + + +Function github_output_value +{ + param ( + [Parameter(Mandatory=$true)] + [string] $name, + [Parameter(Mandatory=$true)] + [string] $value + ) + + echo "github_output_value() $name = $value" + echo "::set-output name=$name::$value" +} + + +github_output_value -name "artifact_1" -value "$artifact_deploy_filename" diff --git a/windows/deploy_windows_portaudio.ps1 b/windows/deploy_windows_portaudio.ps1 new file mode 100644 index 0000000000..2393f31df3 --- /dev/null +++ b/windows/deploy_windows_portaudio.ps1 @@ -0,0 +1,302 @@ +param( + # Replace default path with system Qt installation folder if necessary + [string] $QtInstallPath = "C:\Qt\5.12.3", + [string] $QtCompile32 = "msvc2019", + [string] $QtCompile64 = "msvc2019_64", + [string] $AsioSDKName = "ASIOSDK2.3.2", + [string] $AsioSDKUrl = "https://www.steinberg.net/sdk_downloads/ASIOSDK2.3.2.zip", + [string] $NsisName = "nsis-3.06.1", + [string] $NsisUrl = "https://downloads.sourceforge.net/project/nsis/NSIS%203/3.06.1/nsis-3.06.1.zip" +) + +# change directory to the directory above (if needed) +Set-Location -Path "$PSScriptRoot\..\" + +# Global constants +$RootPath = "$PWD" +$BuildPath = "$RootPath\build" +$DeployPath = "$RootPath\deploy" +$WindowsPath ="$RootPath\windows" +$AppName = "Jamulus" + +# Stop at all errors +$ErrorActionPreference = "Stop" + + +# Execute native command with errorlevel handling +Function Invoke-Native-Command { + Param( + [string] $Command, + [string[]] $Arguments + ) + + & "$Command" @Arguments + + if ($LastExitCode -Ne 0) + { + Throw "Native command $Command returned with exit code $LastExitCode" + } +} + +# Cleanup existing build folders +Function Clean-Build-Environment +{ + if (Test-Path -Path $BuildPath) { Remove-Item -Path $BuildPath -Recurse -Force } + if (Test-Path -Path $DeployPath) { Remove-Item -Path $DeployPath -Recurse -Force } + + New-Item -Path $BuildPath -ItemType Directory + New-Item -Path $DeployPath -ItemType Directory +} + +# For sourceforge links we need to get the correct mirror (especially NISIS) Thanks: https://www.powershellmagazine.com/2013/01/29/pstip-retrieve-a-redirected-url/ +Function Get-RedirectedUrl { + + Param ( + [Parameter(Mandatory=$true)] + [String]$URL + ) + + $request = [System.Net.WebRequest]::Create($url) + $request.AllowAutoRedirect=$false + $response=$request.GetResponse() + + if ($response.StatusCode -eq "Found") + { + $response.GetResponseHeader("Location") + } +} + +function Initialize-Module-Here ($m) { # see https://stackoverflow.com/a/51692402 + + # If module is imported say that and do nothing + if (Get-Module | Where-Object {$_.Name -eq $m}) { + Write-Output "Module $m is already imported." + } + else { + + # If module is not imported, but available on disk then import + if (Get-Module -ListAvailable | Where-Object {$_.Name -eq $m}) { + Import-Module $m + } + else { + + # If module is not imported, not available on disk, but is in online gallery then install and import + if (Find-Module -Name $m | Where-Object {$_.Name -eq $m}) { + Install-Module -Name $m -Force -Verbose -Scope CurrentUser + Import-Module $m + } + else { + + # If module is not imported, not available and not in online gallery then abort + Write-Output "Module $m not imported, not available and not in online gallery, exiting." + EXIT 1 + } + } + } +} + +# Download and uncompress dependency in ZIP format +Function Install-Dependency +{ + param( + [Parameter(Mandatory=$true)] + [string] $Uri, + [Parameter(Mandatory=$true)] + [string] $Name, + [Parameter(Mandatory=$true)] + [string] $Destination + ) + + if (Test-Path -Path "$WindowsPath\$Destination") { return } + + $TempFileName = [System.IO.Path]::GetTempFileName() + ".zip" + $TempDir = [System.IO.Path]::GetTempPath() + + if ($Uri -Match "downloads.sourceforge.net") + { + $Uri = Get-RedirectedUrl -URL $Uri + } + + Invoke-WebRequest -Uri $Uri -OutFile $TempFileName + echo $TempFileName + Expand-Archive -Path $TempFileName -DestinationPath $TempDir -Force + echo $WindowsPath\$Destination + Move-Item -Path "$TempDir\$Name" -Destination "$WindowsPath\$Destination" -Force + Remove-Item -Path $TempFileName -Force +} + +# Install VSSetup (Visual Studio detection), ASIO SDK and NSIS Installer +Function Install-Dependencies +{ + if (-not (Get-PackageProvider -Name nuget).Name -eq "nuget") { + Install-PackageProvider -Name "Nuget" -Scope CurrentUser -Force + } + Initialize-Module-Here -m "VSSetup" + Install-Dependency -Uri $AsioSDKUrl ` + -Name $AsioSDKName -Destination "ASIOSDK2" + Install-Dependency -Uri $NsisUrl ` + -Name $NsisName -Destination "NSIS" +} + +# Setup environment variables and build tool paths +Function Initialize-Build-Environment +{ + param( + [Parameter(Mandatory=$true)] + [string] $QtInstallPath, + [Parameter(Mandatory=$true)] + [string] $BuildArch + ) + + # Look for Visual Studio/Build Tools 2017 or later (version 15.0 or above) + $VsInstallPath = Get-VSSetupInstance | ` + Select-VSSetupInstance -Product "*" -Version "15.0" -Latest | ` + Select-Object -ExpandProperty "InstallationPath" + + if ($VsInstallPath -Eq "") { $VsInstallPath = "" } + + if ($BuildArch -Eq "x86_64") + { + $VcVarsBin = "$VsInstallPath\VC\Auxiliary\build\vcvars64.bat" + $QtMsvcSpecPath = "$QtInstallPath\$QtCompile64\bin" + } + else + { + $VcVarsBin = "$VsInstallPath\VC\Auxiliary\build\vcvars32.bat" + $QtMsvcSpecPath = "$QtInstallPath\$QtCompile32\bin" + } + + # Setup Qt executables paths for later calls + Set-Item Env:QtQmakePath "$QtMsvcSpecPath\qmake.exe" + Set-Item Env:QtWinDeployPath "$QtMsvcSpecPath\windeployqt.exe" + + "" + "**********************************************************************" + "Using Visual Studio/Build Tools environment settings located at" + $VcVarsBin + "**********************************************************************" + "" + "**********************************************************************" + "Using Qt binaries for Visual C++ located at" + $QtMsvcSpecPath + "**********************************************************************" + "" + + if (-Not (Test-Path -Path $VcVarsBin)) + { + Throw "Microsoft Visual Studio ($BuildArch variant) is not installed. " + ` + "Please install Visual Studio 2017 or above it before running this script." + } + + if (-Not (Test-Path -Path $Env:QtQmakePath)) + { + Throw "The Qt binaries for Microsoft Visual C++ 2017 (msvc2017) could not be located. " + ` + "Please install Qt with support for MSVC 2017 before running this script," + ` + "then call this script with the Qt install location, for example C:\Qt\5.12.3" + } + + # Import environment variables set by vcvarsXX.bat into current scope + $EnvDump = [System.IO.Path]::GetTempFileName() + Invoke-Native-Command -Command "cmd" ` + -Arguments ("/c", "`"$VcVarsBin`" && set > `"$EnvDump`"") + + foreach ($_ in Get-Content -Path $EnvDump) + { + if ($_ -Match "^([^=]+)=(.*)$") + { + Set-Item "Env:$($Matches[1])" $Matches[2] + } + } + + Remove-Item -Path $EnvDump -Force +} + +# Build Jamulus x86_64 and x86 +Function Build-App +{ + param( + [Parameter(Mandatory=$true)] + [string] $BuildConfig, + [Parameter(Mandatory=$true)] + [string] $BuildArch + ) + + Invoke-Native-Command -Command "$Env:QtQmakePath" ` + -Arguments ("$RootPath\$AppName.pro", "CONFIG+=$BuildConfig $BuildArch", "CONFIG+=portaudio", ` + "-o", "$BuildPath\Makefile") + + Set-Location -Path $BuildPath + Invoke-Native-Command -Command "nmake" -Arguments ("$BuildConfig") + Invoke-Native-Command -Command "$Env:QtWinDeployPath" ` + -Arguments ("--$BuildConfig", "--compiler-runtime", "--dir=$DeployPath\$BuildArch", + "$BuildPath\$BuildConfig\$AppName.exe") + + Move-Item -Path "$BuildPath\$BuildConfig\$AppName.exe" -Destination "$DeployPath\$BuildArch" -Force + Invoke-Native-Command -Command "nmake" -Arguments ("clean") + Set-Location -Path $RootPath +} + +# Build and deploy Jamulus 64bit and 32bit variants +function Build-App-Variants +{ + param( + [Parameter(Mandatory=$true)] + [string] $QtInstallPath + ) + + foreach ($_ in ("x86_64", "x86")) + { + $OriginalEnv = Get-ChildItem Env: + Initialize-Build-Environment -QtInstallPath $QtInstallPath -BuildArch $_ + Build-App -BuildConfig "release" -BuildArch $_ + $OriginalEnv | % { Set-Item "Env:$($_.Name)" $_.Value } + } +} + +# Build Windows installer +Function Build-Installer +{ + foreach ($_ in Get-Content -Path "$RootPath\$AppName.pro") + { + if ($_ -Match "^VERSION *= *(.*)$") + { + $AppVersion = $Matches[1] + break + } + } + + Invoke-Native-Command -Command "$WindowsPath\NSIS\makensis" ` + -Arguments ("/v4", "/DAPP_NAME=$AppName", "/DAPP_VERSION=$AppVersion", ` + "/DROOT_PATH=$RootPath", "/DWINDOWS_PATH=$WindowsPath", "/DDEPLOY_PATH=$DeployPath", ` + "$WindowsPath\installer.nsi") +} + +# Build and copy NS-Process dll +Function Build-NSProcess +{ + param( + [Parameter(Mandatory=$true)] + [string] $QtInstallPath + ) + if (!(Test-Path -path "$WindowsPath\nsProcess.dll")) { + + echo "Building nsProcess..." + + $OriginalEnv = Get-ChildItem Env: + Initialize-Build-Environment -QtInstallPath $QtInstallPath -BuildArch "x86" + + Invoke-Native-Command -Command "msbuild" ` + -Arguments ("$WindowsPath\nsProcess\nsProcess.sln", '/p:Configuration="Release UNICODE"', ` + "/p:Platform=Win32") + + Move-Item -Path "$WindowsPath\nsProcess\Release\nsProcess.dll" -Destination "$WindowsPath\nsProcess.dll" -Force + Remove-Item -Path "$WindowsPath\nsProcess\Release\" -Force -Recurse + $OriginalEnv | % { Set-Item "Env:$($_.Name)" $_.Value } + } +} + +Clean-Build-Environment +Install-Dependencies +Build-App-Variants -QtInstallPath $QtInstallPath +Build-NSProcess -QtInstallPath $QtInstallPath +Build-Installer From b4ae9e87c098834439d8e19720295e99835932f2 Mon Sep 17 00:00:00 2001 From: nefarius2001 Date: Sun, 14 Mar 2021 19:58:39 +0100 Subject: [PATCH 14/17] build windows installer with env-variable --- Jamulus.pro | 6 + ...ild_windowsinstaller_2_build_portaudio.ps1 | 5 +- ...utobuild_windowsinstaller_3_copy_files.ps1 | 2 +- windows/deploy_windows.ps1 | 12 +- windows/deploy_windows_portaudio.ps1 | 302 ------------------ 5 files changed, 22 insertions(+), 305 deletions(-) delete mode 100644 windows/deploy_windows_portaudio.ps1 diff --git a/Jamulus.pro b/Jamulus.pro index faafa008c0..159c8a44cb 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -24,6 +24,12 @@ CONFIG += qt \ thread \ lrelease +CONFIG (portaudio) { + message("driver WINDOWS portaudio") +} else { + message("driver WINDOWS standard") +} + QT += network \ xml \ concurrent diff --git a/autobuild/windows/autobuild_windowsinstaller_2_build_portaudio.ps1 b/autobuild/windows/autobuild_windowsinstaller_2_build_portaudio.ps1 index f078e5fd27..38ff1e38da 100644 --- a/autobuild/windows/autobuild_windowsinstaller_2_build_portaudio.ps1 +++ b/autobuild/windows/autobuild_windowsinstaller_2_build_portaudio.ps1 @@ -25,7 +25,10 @@ if (("$jamulus_project_path" -eq $null) -or ("$jamulus_project_path" -eq "")) { ### PROCEDURE ### ################### +# set variable to build with portaudio +$Env:jamulus_build_config="portaudio" + echo "Build installer..." # Build the installer -powershell "$jamulus_project_path\windows\deploy_windows_portaudio.ps1" "C:\Qt\5.15.2" +powershell "$jamulus_project_path\windows\deploy_windows.ps1" "C:\Qt\5.15.2" diff --git a/autobuild/windows/autobuild_windowsinstaller_3_copy_files.ps1 b/autobuild/windows/autobuild_windowsinstaller_3_copy_files.ps1 index 96ded41736..203f1e8102 100644 --- a/autobuild/windows/autobuild_windowsinstaller_3_copy_files.ps1 +++ b/autobuild/windows/autobuild_windowsinstaller_3_copy_files.ps1 @@ -33,7 +33,7 @@ if (($jamulus_buildversionstring -eq $null) -or ($jamulus_buildversionstring -eq # Rename the file echo "rename" -$artifact_deploy_filename = "jamulus_${Env:jamulus_buildversionstring}_win.exe" +$artifact_deploy_filename = "jamulus_standard_${Env:jamulus_buildversionstring}_win.exe" echo "rename deploy file to $artifact_deploy_filename" cp "$jamulus_project_path\deploy\Jamulus*installer-win.exe" "$jamulus_project_path\deploy\$artifact_deploy_filename" diff --git a/windows/deploy_windows.ps1 b/windows/deploy_windows.ps1 index fd2d46f552..2fe6d3140f 100644 --- a/windows/deploy_windows.ps1 +++ b/windows/deploy_windows.ps1 @@ -221,9 +221,19 @@ Function Build-App [string] $BuildArch ) - Invoke-Native-Command -Command "$Env:QtQmakePath" ` + if ($Env:jamulus_build_config) { + # implemented for portaudio-variant + echo "jamulus_build_config: additional config ""$Env:jamulus_build_config""" + Invoke-Native-Command -Command "$Env:QtQmakePath" ` + -Arguments ("$RootPath\$AppName.pro", "CONFIG+=$BuildConfig $BuildArch", "CONFIG+=$Env:jamulus_build_config", ` + "-o", "$BuildPath\Makefile") + } else { + echo "jamulus_build_config: no additional config " + Invoke-Native-Command -Command "$Env:QtQmakePath" ` -Arguments ("$RootPath\$AppName.pro", "CONFIG+=$BuildConfig $BuildArch", ` "-o", "$BuildPath\Makefile") + } + Set-Location -Path $BuildPath Invoke-Native-Command -Command "nmake" -Arguments ("$BuildConfig") diff --git a/windows/deploy_windows_portaudio.ps1 b/windows/deploy_windows_portaudio.ps1 deleted file mode 100644 index 2393f31df3..0000000000 --- a/windows/deploy_windows_portaudio.ps1 +++ /dev/null @@ -1,302 +0,0 @@ -param( - # Replace default path with system Qt installation folder if necessary - [string] $QtInstallPath = "C:\Qt\5.12.3", - [string] $QtCompile32 = "msvc2019", - [string] $QtCompile64 = "msvc2019_64", - [string] $AsioSDKName = "ASIOSDK2.3.2", - [string] $AsioSDKUrl = "https://www.steinberg.net/sdk_downloads/ASIOSDK2.3.2.zip", - [string] $NsisName = "nsis-3.06.1", - [string] $NsisUrl = "https://downloads.sourceforge.net/project/nsis/NSIS%203/3.06.1/nsis-3.06.1.zip" -) - -# change directory to the directory above (if needed) -Set-Location -Path "$PSScriptRoot\..\" - -# Global constants -$RootPath = "$PWD" -$BuildPath = "$RootPath\build" -$DeployPath = "$RootPath\deploy" -$WindowsPath ="$RootPath\windows" -$AppName = "Jamulus" - -# Stop at all errors -$ErrorActionPreference = "Stop" - - -# Execute native command with errorlevel handling -Function Invoke-Native-Command { - Param( - [string] $Command, - [string[]] $Arguments - ) - - & "$Command" @Arguments - - if ($LastExitCode -Ne 0) - { - Throw "Native command $Command returned with exit code $LastExitCode" - } -} - -# Cleanup existing build folders -Function Clean-Build-Environment -{ - if (Test-Path -Path $BuildPath) { Remove-Item -Path $BuildPath -Recurse -Force } - if (Test-Path -Path $DeployPath) { Remove-Item -Path $DeployPath -Recurse -Force } - - New-Item -Path $BuildPath -ItemType Directory - New-Item -Path $DeployPath -ItemType Directory -} - -# For sourceforge links we need to get the correct mirror (especially NISIS) Thanks: https://www.powershellmagazine.com/2013/01/29/pstip-retrieve-a-redirected-url/ -Function Get-RedirectedUrl { - - Param ( - [Parameter(Mandatory=$true)] - [String]$URL - ) - - $request = [System.Net.WebRequest]::Create($url) - $request.AllowAutoRedirect=$false - $response=$request.GetResponse() - - if ($response.StatusCode -eq "Found") - { - $response.GetResponseHeader("Location") - } -} - -function Initialize-Module-Here ($m) { # see https://stackoverflow.com/a/51692402 - - # If module is imported say that and do nothing - if (Get-Module | Where-Object {$_.Name -eq $m}) { - Write-Output "Module $m is already imported." - } - else { - - # If module is not imported, but available on disk then import - if (Get-Module -ListAvailable | Where-Object {$_.Name -eq $m}) { - Import-Module $m - } - else { - - # If module is not imported, not available on disk, but is in online gallery then install and import - if (Find-Module -Name $m | Where-Object {$_.Name -eq $m}) { - Install-Module -Name $m -Force -Verbose -Scope CurrentUser - Import-Module $m - } - else { - - # If module is not imported, not available and not in online gallery then abort - Write-Output "Module $m not imported, not available and not in online gallery, exiting." - EXIT 1 - } - } - } -} - -# Download and uncompress dependency in ZIP format -Function Install-Dependency -{ - param( - [Parameter(Mandatory=$true)] - [string] $Uri, - [Parameter(Mandatory=$true)] - [string] $Name, - [Parameter(Mandatory=$true)] - [string] $Destination - ) - - if (Test-Path -Path "$WindowsPath\$Destination") { return } - - $TempFileName = [System.IO.Path]::GetTempFileName() + ".zip" - $TempDir = [System.IO.Path]::GetTempPath() - - if ($Uri -Match "downloads.sourceforge.net") - { - $Uri = Get-RedirectedUrl -URL $Uri - } - - Invoke-WebRequest -Uri $Uri -OutFile $TempFileName - echo $TempFileName - Expand-Archive -Path $TempFileName -DestinationPath $TempDir -Force - echo $WindowsPath\$Destination - Move-Item -Path "$TempDir\$Name" -Destination "$WindowsPath\$Destination" -Force - Remove-Item -Path $TempFileName -Force -} - -# Install VSSetup (Visual Studio detection), ASIO SDK and NSIS Installer -Function Install-Dependencies -{ - if (-not (Get-PackageProvider -Name nuget).Name -eq "nuget") { - Install-PackageProvider -Name "Nuget" -Scope CurrentUser -Force - } - Initialize-Module-Here -m "VSSetup" - Install-Dependency -Uri $AsioSDKUrl ` - -Name $AsioSDKName -Destination "ASIOSDK2" - Install-Dependency -Uri $NsisUrl ` - -Name $NsisName -Destination "NSIS" -} - -# Setup environment variables and build tool paths -Function Initialize-Build-Environment -{ - param( - [Parameter(Mandatory=$true)] - [string] $QtInstallPath, - [Parameter(Mandatory=$true)] - [string] $BuildArch - ) - - # Look for Visual Studio/Build Tools 2017 or later (version 15.0 or above) - $VsInstallPath = Get-VSSetupInstance | ` - Select-VSSetupInstance -Product "*" -Version "15.0" -Latest | ` - Select-Object -ExpandProperty "InstallationPath" - - if ($VsInstallPath -Eq "") { $VsInstallPath = "" } - - if ($BuildArch -Eq "x86_64") - { - $VcVarsBin = "$VsInstallPath\VC\Auxiliary\build\vcvars64.bat" - $QtMsvcSpecPath = "$QtInstallPath\$QtCompile64\bin" - } - else - { - $VcVarsBin = "$VsInstallPath\VC\Auxiliary\build\vcvars32.bat" - $QtMsvcSpecPath = "$QtInstallPath\$QtCompile32\bin" - } - - # Setup Qt executables paths for later calls - Set-Item Env:QtQmakePath "$QtMsvcSpecPath\qmake.exe" - Set-Item Env:QtWinDeployPath "$QtMsvcSpecPath\windeployqt.exe" - - "" - "**********************************************************************" - "Using Visual Studio/Build Tools environment settings located at" - $VcVarsBin - "**********************************************************************" - "" - "**********************************************************************" - "Using Qt binaries for Visual C++ located at" - $QtMsvcSpecPath - "**********************************************************************" - "" - - if (-Not (Test-Path -Path $VcVarsBin)) - { - Throw "Microsoft Visual Studio ($BuildArch variant) is not installed. " + ` - "Please install Visual Studio 2017 or above it before running this script." - } - - if (-Not (Test-Path -Path $Env:QtQmakePath)) - { - Throw "The Qt binaries for Microsoft Visual C++ 2017 (msvc2017) could not be located. " + ` - "Please install Qt with support for MSVC 2017 before running this script," + ` - "then call this script with the Qt install location, for example C:\Qt\5.12.3" - } - - # Import environment variables set by vcvarsXX.bat into current scope - $EnvDump = [System.IO.Path]::GetTempFileName() - Invoke-Native-Command -Command "cmd" ` - -Arguments ("/c", "`"$VcVarsBin`" && set > `"$EnvDump`"") - - foreach ($_ in Get-Content -Path $EnvDump) - { - if ($_ -Match "^([^=]+)=(.*)$") - { - Set-Item "Env:$($Matches[1])" $Matches[2] - } - } - - Remove-Item -Path $EnvDump -Force -} - -# Build Jamulus x86_64 and x86 -Function Build-App -{ - param( - [Parameter(Mandatory=$true)] - [string] $BuildConfig, - [Parameter(Mandatory=$true)] - [string] $BuildArch - ) - - Invoke-Native-Command -Command "$Env:QtQmakePath" ` - -Arguments ("$RootPath\$AppName.pro", "CONFIG+=$BuildConfig $BuildArch", "CONFIG+=portaudio", ` - "-o", "$BuildPath\Makefile") - - Set-Location -Path $BuildPath - Invoke-Native-Command -Command "nmake" -Arguments ("$BuildConfig") - Invoke-Native-Command -Command "$Env:QtWinDeployPath" ` - -Arguments ("--$BuildConfig", "--compiler-runtime", "--dir=$DeployPath\$BuildArch", - "$BuildPath\$BuildConfig\$AppName.exe") - - Move-Item -Path "$BuildPath\$BuildConfig\$AppName.exe" -Destination "$DeployPath\$BuildArch" -Force - Invoke-Native-Command -Command "nmake" -Arguments ("clean") - Set-Location -Path $RootPath -} - -# Build and deploy Jamulus 64bit and 32bit variants -function Build-App-Variants -{ - param( - [Parameter(Mandatory=$true)] - [string] $QtInstallPath - ) - - foreach ($_ in ("x86_64", "x86")) - { - $OriginalEnv = Get-ChildItem Env: - Initialize-Build-Environment -QtInstallPath $QtInstallPath -BuildArch $_ - Build-App -BuildConfig "release" -BuildArch $_ - $OriginalEnv | % { Set-Item "Env:$($_.Name)" $_.Value } - } -} - -# Build Windows installer -Function Build-Installer -{ - foreach ($_ in Get-Content -Path "$RootPath\$AppName.pro") - { - if ($_ -Match "^VERSION *= *(.*)$") - { - $AppVersion = $Matches[1] - break - } - } - - Invoke-Native-Command -Command "$WindowsPath\NSIS\makensis" ` - -Arguments ("/v4", "/DAPP_NAME=$AppName", "/DAPP_VERSION=$AppVersion", ` - "/DROOT_PATH=$RootPath", "/DWINDOWS_PATH=$WindowsPath", "/DDEPLOY_PATH=$DeployPath", ` - "$WindowsPath\installer.nsi") -} - -# Build and copy NS-Process dll -Function Build-NSProcess -{ - param( - [Parameter(Mandatory=$true)] - [string] $QtInstallPath - ) - if (!(Test-Path -path "$WindowsPath\nsProcess.dll")) { - - echo "Building nsProcess..." - - $OriginalEnv = Get-ChildItem Env: - Initialize-Build-Environment -QtInstallPath $QtInstallPath -BuildArch "x86" - - Invoke-Native-Command -Command "msbuild" ` - -Arguments ("$WindowsPath\nsProcess\nsProcess.sln", '/p:Configuration="Release UNICODE"', ` - "/p:Platform=Win32") - - Move-Item -Path "$WindowsPath\nsProcess\Release\nsProcess.dll" -Destination "$WindowsPath\nsProcess.dll" -Force - Remove-Item -Path "$WindowsPath\nsProcess\Release\" -Force -Recurse - $OriginalEnv | % { Set-Item "Env:$($_.Name)" $_.Value } - } -} - -Clean-Build-Environment -Install-Dependencies -Build-App-Variants -QtInstallPath $QtInstallPath -Build-NSProcess -QtInstallPath $QtInstallPath -Build-Installer From 808d6837053f362508856614fe0734412a6b6e6d Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Wed, 14 Apr 2021 09:03:59 -0400 Subject: [PATCH 15/17] Support mono devices --- src/portaudiosound.cpp | 37 ++++++++++++++++++++++++++++--------- src/portaudiosound.h | 1 + 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/portaudiosound.cpp b/src/portaudiosound.cpp index b23875c82c..820f51b5e4 100644 --- a/src/portaudiosound.cpp +++ b/src/portaudiosound.cpp @@ -379,13 +379,6 @@ QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outInd const PaDeviceInfo* inDeviceInfo = Pa_GetDeviceInfo ( inIndex ); const PaDeviceInfo* outDeviceInfo = Pa_GetDeviceInfo ( outIndex ); - if ( inDeviceInfo->maxInputChannels < NUM_IN_OUT_CHANNELS || - outDeviceInfo->maxOutputChannels < NUM_IN_OUT_CHANNELS ) - { - // FIXME: handle mono devices. - return tr ( "Less than 2 channels not supported" ); - } - if ( deviceStream != NULL ) { Pa_CloseStream ( deviceStream ); @@ -401,6 +394,7 @@ QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outInd PaWasapiStreamInfo wasapiInputInfo; paInputParams.device = inIndex; paInputParams.channelCount = std::min ( NUM_IN_OUT_CHANNELS, inDeviceInfo->maxInputChannels ); + bMonoInput = ( paInputParams.channelCount == 1 ); paInputParams.sampleFormat = paInt16; // NOTE: Setting latency to deviceInfo->defaultLowInputLatency seems like it // would be sensible, but gives an overly large buffer size at least in the @@ -436,6 +430,7 @@ QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outInd PaWasapiStreamInfo wasapiOutputInfo; paOutputParams.device = outIndex; paOutputParams.channelCount = std::min ( NUM_IN_OUT_CHANNELS, outDeviceInfo->maxOutputChannels ); + bMonoOutput = ( paOutputParams.channelCount == 1 ); paOutputParams.sampleFormat = paInt16; paOutputParams.suggestedLatency = paInputParams.suggestedLatency; if ( apiInfo->type == paASIO ) @@ -546,12 +541,36 @@ int CSound::paStreamCallback ( const void* input, CVector& vecsAudioData = pSound->vecsAudioData; // CAPTURE --------------------------------- - memcpy ( &vecsAudioData[0], input, sizeof ( int16_t ) * frameCount * NUM_IN_OUT_CHANNELS ); + if ( pSound->bMonoInput ) + { + const uint16_t* inputFrames = static_cast ( input ); + for ( unsigned long frame = 0; frame < frameCount; frame++ ) + { + vecsAudioData[NUM_IN_OUT_CHANNELS * frame] = inputFrames[frame]; + vecsAudioData[NUM_IN_OUT_CHANNELS * frame + 1] = inputFrames[frame]; + } + } + else + { + memcpy ( &vecsAudioData[0], input, sizeof ( int16_t ) * frameCount * NUM_IN_OUT_CHANNELS ); + } pSound->ProcessCallback ( vecsAudioData ); // PLAYBACK ------------------------------------------------------------ - memcpy ( output, &vecsAudioData[0], sizeof ( int16_t ) * frameCount * NUM_IN_OUT_CHANNELS ); + if ( pSound->bMonoOutput ) + { + uint16_t* outputFrames = static_cast ( output ); + for ( unsigned long frame = 0; frame < frameCount; frame++ ) + { + // Should this be averaged instead of added? + outputFrames[frame] = vecsAudioData[NUM_IN_OUT_CHANNELS * frame] + vecsAudioData[NUM_IN_OUT_CHANNELS * frame + 1]; + } + } + else + { + memcpy ( output, &vecsAudioData[0], sizeof ( int16_t ) * frameCount * NUM_IN_OUT_CHANNELS ); + } return paContinue; } diff --git a/src/portaudiosound.h b/src/portaudiosound.h index fb1be16021..dfd2c2d336 100644 --- a/src/portaudiosound.h +++ b/src/portaudiosound.h @@ -104,6 +104,7 @@ class CSound : public CSoundBase int wasapiFlag; PaDeviceIndex inDeviceIndex, outDeviceIndex; + bool bMonoInput, bMonoOutput; PaStream* deviceStream; CVector vSelectedInputChannels; CVector vSelectedOutputChannels; From bb017bd53de7b033dfed0f6957bdc36af5ba520b Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Wed, 14 Apr 2021 09:04:14 -0400 Subject: [PATCH 16/17] Add WASAPI and WDM shortcuts This lets people who are unfamiliar with command line options try the other APIs out. Note that the shortcuts also use a separate .inifile, since the device names tend to be a bit different across APIs; and Jamulus doesn't handle missing devices very nicely. --- windows/installer.nsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/windows/installer.nsi b/windows/installer.nsi index 559e85fc96..8d7bf5a56f 100644 --- a/windows/installer.nsi +++ b/windows/installer.nsi @@ -176,6 +176,8 @@ Var bRunApp ; Add the Start Menu shortcuts CreateDirectory "$SMPROGRAMS\${APP_NAME}" CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" "$INSTDIR\${APP_EXE}" + CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME} WASAPI.lnk" "$INSTDIR\${APP_EXE}" "--inifile %APPDATA%\jamulus\wasapi.ini --api WASAPI" + CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME} WDM.lnk" "$INSTDIR\${APP_EXE}" "--inifile %APPDATA%\jamulus\wdm-ks.ini --api WDM-KS" CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME} Server.lnk" "$INSTDIR\${APP_EXE}" "-s" "$INSTDIR\servericon.ico" CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME} Uninstall.lnk" "$INSTDIR\${UNINSTALL_EXE}" From 8113c4f26feedcd6cce79a385ae99c070ab010d3 Mon Sep 17 00:00:00 2001 From: Noam Postavsky Date: Wed, 16 Jun 2021 19:13:11 -0400 Subject: [PATCH 17/17] Support Portaudio on Linux (shared lib only) And potentially on macOS too, but that is completely untested. --- Jamulus.pro | 58 ++++++++++++++++++------------- src/client.cpp | 12 ++++++- src/client.h | 42 +++++++++-------------- src/main.cpp | 7 ++-- src/portaudiosound.cpp | 78 +++++++++++++++++++++++++++++++++++------- src/portaudiosound.h | 30 +++++++++------- 6 files changed, 148 insertions(+), 79 deletions(-) diff --git a/Jamulus.pro b/Jamulus.pro index 159c8a44cb..a8fda28974 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -25,9 +25,7 @@ CONFIG += qt \ lrelease CONFIG (portaudio) { - message("driver WINDOWS portaudio") -} else { - message("driver WINDOWS standard") + message("driver portaudio") } QT += network \ @@ -156,8 +154,10 @@ win32 { } QT += macextras - HEADERS += mac/sound.h - SOURCES += mac/sound.cpp + !CONFIG(portaudio) { + HEADERS += mac/sound.h + SOURCES += mac/sound.cpp + } HEADERS += mac/activity.h OBJECTIVE_SOURCES += mac/activity.mm CONFIG += x86 @@ -199,6 +199,7 @@ win32 { } } else:ios { QMAKE_INFO_PLIST = ios/Info.plist + CONFIG(portaudio) { error( "Portaudio not supported on iOS" ) } QT += macextras OBJECTIVE_SOURCES += ios/ios_app_delegate.mm HEADERS += ios/ios_app_delegate.h @@ -224,6 +225,7 @@ win32 { target.path = /tmp/your_executable # path on device INSTALLS += target + CONFIG(portaudio) { error( "Portaudio not supported on Android" ) } HEADERS += android/sound.h \ android/ring_buffer.h @@ -360,8 +362,10 @@ win32 { # unnecessarily without this workaround (#741): QMAKE_LFLAGS += -Wl,--as-needed - HEADERS += linux/sound.h - SOURCES += linux/sound.cpp + !CONFIG(portaudio) { + HEADERS += linux/sound.h + SOURCES += linux/sound.cpp + } # we assume to have lrintf() one moderately modern linux distributions # would be better to have that tested, though @@ -374,7 +378,7 @@ win32 { contains(CONFIG, "nosound") { message(Restricting build to server-only due to CONFIG+=nosound.) DEFINES += SERVER_ONLY - } else { + } else:!CONFIG(portaudio) { message(Jack Audio Interface Enabled.) contains(CONFIG, "raspijamulus") { @@ -540,6 +544,9 @@ win32 { $$files(libs/portaudio/src/hostapi/wdmks/*.h) \ $$files(libs/portaudio/src/hostapi/wmme/*.h) \ $$files(libs/portaudio/src/hostapi/wasapi/*.h) +} else { + # Mac OS X uses the portaudio unix header files too. + HEADERS_PORTAUDIO += $$files(libs/portaudio/src/os/unix/*.h) } SOURCES += src/buffer.cpp \ @@ -755,8 +762,13 @@ win32 { SOURCES_CXX_PORTAUDIO -= libs/portaudio/src/hostapi/asio/iasiothiscallresolver.cpp } } else:unix { - # FIXME: also some hostapi files, probably. SOURCES_PORTAUDIO += $$files(libs/portaudio/src/os/unix/*.c) + SOURCES_PORTAUDIO += $$files(libs/portaudio/src/os/hostapi/alsa/*.c) \ + $$files(libs/portaudio/src/os/hostapi/jack/*.c) \ + $$files(libs/portaudio/src/os/hostapi/oss/*.c) +} else:macx { + SOURCES_PORTAUDIO += $$files(libs/portaudio/src/os/unix/*.c) + SOURCES_PORTAUDIO += $$files(libs/portaudio/src/os/hostapi/coreaudio/*.c) } # I can't figure out how to make the custom compiler stuff work for @@ -1217,25 +1229,23 @@ CONFIG(portaudio) { DEFINES += USE_PORTAUDIO HEADERS += src/portaudiosound.h SOURCES += src/portaudiosound.cpp - win32 { - CONFIG(portaudio_shared_lib) { - LIBS += $$libnames(portaudio) + CONFIG(portaudio_shared_lib) { + LIBS += $$libnames(portaudio) + } else:win32 { + DEFINES += PA_USE_ASIO=1 PA_USE_WASAPI=1 PA_USE_WDMKS=1 + INCLUDEPATH += $$INCLUDEPATH_PORTAUDIO + HEADERS += $$HEADERS_PORTAUDIO + mingw { + portaudiocxx.variable_out = OBJECTS + portaudiocc.variable_out = OBJECTS + QMAKE_EXTRA_COMPILERS += portaudiocc portaudiocxx } else { - DEFINES += PA_USE_ASIO=1 PA_USE_WASAPI=1 PA_USE_WDMKS=1 - INCLUDEPATH += $$INCLUDEPATH_PORTAUDIO - HEADERS += $$HEADERS_PORTAUDIO - mingw { - portaudiocxx.variable_out = OBJECTS - portaudiocc.variable_out = OBJECTS - QMAKE_EXTRA_COMPILERS += portaudiocc portaudiocxx - } else { - SOURCES += $$SOURCES_PORTAUDIO $$SOURCES_CXX_PORTAUDIO - } - DISTFILES += $$DISTFILES_PORTAUDIO + SOURCES += $$SOURCES_PORTAUDIO $$SOURCES_CXX_PORTAUDIO } + DISTFILES += $$DISTFILES_PORTAUDIO LIBS += $$libnames(winmm ole32 uuid setupapi) } else { - error( "portaudio only tested on win32 for now" ) + error ( "non-shared portaudio only implemented for win32" ) } } diff --git a/src/client.cpp b/src/client.cpp index 367972abdb..bd4c664944 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -31,6 +31,7 @@ CClient::CClient ( const quint16 iPortNumber, const QString& strMIDISetup, const bool bNoAutoJackConnect, const QString& strNClientName, + const QString& strApiName, const bool bNMuteMeInPersonalMix ) : ChannelInfo(), strClientName ( strNClientName ), @@ -47,7 +48,16 @@ CClient::CClient ( const quint16 iPortNumber, bMuteOutStream ( false ), fMuteOutStreamGain ( 1.0f ), Socket ( &Channel, iPortNumber, iQosNumber ), - Sound ( AudioCallback, this, strMIDISetup, bNoAutoJackConnect, strNClientName ), + Sound ( AudioCallback, + this, + strMIDISetup, + bNoAutoJackConnect, + strNClientName +#ifdef USE_PORTAUDIO + , + strApiName +#endif + ), iAudioInFader ( AUD_FADER_IN_MIDDLE ), bReverbOnLeftChan ( false ), iReverbLevel ( 0 ), diff --git a/src/client.h b/src/client.h index a0921a2ba5..c5ad325a91 100644 --- a/src/client.h +++ b/src/client.h @@ -42,34 +42,23 @@ #include "signalhandler.h" #ifdef LLCON_VST_PLUGIN # include "vstsound.h" +#elif defined( USE_PORTAUDIO ) +# include "portaudiosound.h" +#elif defined( _WIN32 ) && !defined( JACK_REPLACES_ASIO ) +# include "../windows/sound.h" +#elif defined( Q_OS_MACX ) && !defined( JACK_REPLACES_COREAUDIO ) +# include "../mac/sound.h" +#elif defined( Q_OS_IOS ) +# include "../ios/sound.h" +#elif defined( ANDROID ) +# include "../android/sound.h" #else -# if defined( _WIN32 ) && !defined( JACK_REPLACES_ASIO ) -# ifdef USE_PORTAUDIO -// FIXME: this shouldn't be windows specific -# include "portaudiosound.h" -# else -# include "../windows/sound.h" -# endif -# else -# if ( defined( Q_OS_MACX ) ) && !defined( JACK_REPLACES_COREAUDIO ) -# include "../mac/sound.h" -# else -# if defined( Q_OS_IOS ) -# include "../ios/sound.h" -# else -# ifdef ANDROID -# include "../android/sound.h" -# else -# include "../linux/sound.h" -# ifndef JACK_REPLACES_ASIO // these headers are not available in Windows OS -# include -# include -# endif -# include -# endif -# endif -# endif +# include "../linux/sound.h" +# ifndef JACK_REPLACES_ASIO // these headers are not available in Windows OS +# include +# include # endif +# include #endif /* Definitions ****************************************************************/ @@ -118,6 +107,7 @@ class CClient : public QObject const QString& strMIDISetup, const bool bNoAutoJackConnect, const QString& strNClientName, + const QString& strApiName, const bool bNMuteMeInPersonalMix ); virtual ~CClient(); diff --git a/src/main.cpp b/src/main.cpp index 04f3172972..46787a465d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -618,10 +618,11 @@ int main ( int argc, char** argv ) strConnOnStartupAddress, strMIDISetup, bNoAutoJackConnect, -#if defined (_WIN32) && defined (USE_PORTAUDIO) - strApiName, // TODO: don't abuse Jack client name for this. -#else strClientName, +#ifdef USE_PORTAUDIO + strApiName, +#else + "", #endif bMuteMeInPersonalMix ); diff --git a/src/portaudiosound.cpp b/src/portaudiosound.cpp index 820f51b5e4..b8b69569e4 100644 --- a/src/portaudiosound.cpp +++ b/src/portaudiosound.cpp @@ -26,31 +26,59 @@ \******************************************************************************/ #include "portaudiosound.h" +#include + #ifdef _WIN32 # include # include #endif // WIN32 +#ifdef Q_OS_MACX +# include +#endif // Q_OS_MACX + +#if defined( _WIN32 ) +# define DEFAULT_API "ASIO" +#elif defined( Q_OS_MACX ) +# define DEFAULT_API "CoreAudio" +#else +# define DEFAULT_API "Jack" +#endif + +#ifdef _WIN32 // Needed for reset request callback static CSound* pThisSound; static void resetRequestCallback() { pThisSound->EmitReinitRequestSignal ( RS_RELOAD_RESTART_AND_INIT ); } +#endif // _WIN32 +static QString NormalizeApiName ( const char* paName ) +{ + // "Windows " -> + // "JACK Audio Connection Kit" -> "JACK" + QString name = paName; + return name.replace ( QRegularExpression ( "^Windows | .*$" ), "" ); +} CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* arg ), void* arg, const QString& strMIDISetup, const bool, + const QString& strJackClientName, const QString& strApiName ) : CSoundBase ( "portaudio", fpNewProcessCallback, arg, strMIDISetup ), - strSelectApiName ( strApiName.isEmpty() ? "ASIO" : strApiName ), + strSelectApiName ( strApiName.isEmpty() ? DEFAULT_API : strApiName ), inDeviceIndex ( -1 ), outDeviceIndex ( -1 ), deviceStream ( NULL ), vSelectedInputChannels ( NUM_IN_OUT_CHANNELS ), vSelectedOutputChannels ( NUM_IN_OUT_CHANNELS ) { +#ifdef _WIN32 pThisSound = this; +#endif // _WIN32 + + (void) strJackClientName; // TODO: perhaps use this if strApiName == "Jack"... int numPortAudioDevices = std::min ( InitPa(), MAX_NUMBER_SOUND_CARDS ); lNumDevs = 0; @@ -65,7 +93,7 @@ CSound::CSound ( void ( *fpNewProcessCallback ) ( CVector& psData, void* paOutputDeviceIndices[lNumDevs] = devIndex; strDriverNames[lNumDevs++] = devInfo->name; } - else if ( devInfo->maxInputChannels > 0) + else if ( devInfo->maxInputChannels > 0 ) { // For each input device, construct virtual duplex devices by // combining it with every output device (i.e., Cartesian product of @@ -113,8 +141,7 @@ QString CSound::GetPaApiNames() for ( PaHostApiIndex i = 0; i < apiCount; i++ ) { const PaHostApiInfo* apiInfo = Pa_GetHostApiInfo ( i ); - QString name = apiInfo->name; - name = name.section ( ' ', -1 ); // Drop "Windows " from "Windows " names. + QString name = NormalizeApiName ( apiInfo->name ); apiNames << name; } @@ -140,8 +167,7 @@ int CSound::InitPa() for ( PaHostApiIndex i = 0; i < apiCount; i++ ) { apiInfo = Pa_GetHostApiInfo ( i ); - QString name = apiInfo->name; - name = name.section ( ' ', -1 ); // Drop "Windows " from "Windows " names. + QString name = NormalizeApiName ( apiInfo->name ); if ( strSelectApiName.compare ( name, Qt::CaseInsensitive ) == 0 ) { #ifdef _WIN32 @@ -167,10 +193,20 @@ int CSound::InitPa() int CSound::Init ( const int iNewPrefMonoBufferSize ) { const PaHostApiInfo* apiInfo = Pa_GetHostApiInfo ( selectedApiIndex ); - if ( inDeviceIndex >= 0 && apiInfo->type == paASIO ) + if ( inDeviceIndex >= 0 && ( apiInfo->type == paASIO || apiInfo->type == paCoreAudio ) ) { long minBufferSize, maxBufferSize, prefBufferSize, granularity; +#ifdef WIN32 PaAsio_GetAvailableBufferSizes ( inDeviceIndex, &minBufferSize, &maxBufferSize, &prefBufferSize, &granularity ); +#elif defined( Q_OS_MACX ) + PaMacCore_GetBufferSizeRange ( inDeviceIndex, &minBufferSize, &maxBufferSize ); + granularity = 1; +#else + granularity = 0; + prefBufferSize = iNewPrefMonoBufferSize; + minBufferSize = iNewPrefMonoBufferSize; + maxBufferSize = iNewPrefMonoBufferSize; +#endif if ( granularity == 0 ) // no options, just take the preferred one. { iPrefMonoBufferSize = prefBufferSize; @@ -266,14 +302,20 @@ int CSound::GetNumOutputChannels() return CSoundBase::GetNumOutputChannels(); } +#if defined( Q_OS_MACX ) || defined( _WIN32 ) QString CSound::GetInputChannelName ( const int channel ) { const PaHostApiInfo* apiInfo = Pa_GetHostApiInfo ( selectedApiIndex ); - if ( inDeviceIndex >= 0 && apiInfo->type == paASIO ) + if ( inDeviceIndex >= 0 && ( apiInfo->type == paASIO || apiInfo->type == paCoreAudio ) ) { const char* channelName; +# if defined( Q_OS_MACX ) + channelName = PaMacCore_GetChannelName ( inDeviceIndex, channel, true ); + if ( channelName ) +# elif defined( _WIN32 ) PaError err = PaAsio_GetInputChannelName ( inDeviceIndex, channel, &channelName ); if ( err == paNoError ) +# endif { return QString ( channelName ); } @@ -286,14 +328,20 @@ QString CSound::GetOutputChannelName ( const int channel ) if ( outDeviceIndex >= 0 && apiInfo->type == paASIO ) { const char* channelName; +# if defined( Q_OS_MACX ) + channelName = PaMacCore_GetChannelName ( inDeviceIndex, channel, false ); + if ( channelName ) +# elif defined( _WIN32 ) PaError err = PaAsio_GetOutputChannelName ( outDeviceIndex, channel, &channelName ); if ( err == paNoError ) +# endif { return QString ( channelName ); } } return CSoundBase::GetOutputChannelName ( channel ); } +#endif // defined( Q_OS_MACX ) || defined( _WIN32 ) void CSound::SetLeftInputChannel ( const int channel ) { @@ -343,6 +391,7 @@ QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDrive return err; } +#ifdef _WIN32 void CSound::SetWasapiMode ( PaWasapiMode mode ) { switch ( mode ) @@ -373,6 +422,7 @@ void CSound::SetWasapiMode ( PaWasapiMode mode ) } } } +#endif // _WIN32 QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outIndex ) { @@ -387,11 +437,13 @@ QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outInd outDeviceIndex = -1; } +#ifdef _WIN32 const PaHostApiInfo* apiInfo = Pa_GetHostApiInfo ( selectedApiIndex ); + PaAsioStreamInfo asioInputInfo, asioOutputInfo; + PaWasapiStreamInfo wasapiInputInfo, wasapiOutputInfo; +#endif // _WIN32 PaStreamParameters paInputParams; - PaAsioStreamInfo asioInputInfo; - PaWasapiStreamInfo wasapiInputInfo; paInputParams.device = inIndex; paInputParams.channelCount = std::min ( NUM_IN_OUT_CHANNELS, inDeviceInfo->maxInputChannels ); bMonoInput = ( paInputParams.channelCount == 1 ); @@ -402,6 +454,7 @@ QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outInd // WDM-KS devices. Put a latency value that corresponds to our intended. // buffer size. paInputParams.suggestedLatency = (PaTime) iPrefMonoBufferSize / SYSTEM_SAMPLE_RATE_HZ; +#ifdef _WIN32 if ( apiInfo->type == paASIO ) { paInputParams.hostApiSpecificStreamInfo = &asioInputInfo; @@ -421,18 +474,18 @@ QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outInd wasapiInputInfo.flags = wasapiFlag; } else +#endif // _WIN32 { paInputParams.hostApiSpecificStreamInfo = NULL; } PaStreamParameters paOutputParams; - PaAsioStreamInfo asioOutputInfo; - PaWasapiStreamInfo wasapiOutputInfo; paOutputParams.device = outIndex; paOutputParams.channelCount = std::min ( NUM_IN_OUT_CHANNELS, outDeviceInfo->maxOutputChannels ); bMonoOutput = ( paOutputParams.channelCount == 1 ); paOutputParams.sampleFormat = paInt16; paOutputParams.suggestedLatency = paInputParams.suggestedLatency; +#ifdef _WIN32 if ( apiInfo->type == paASIO ) { paOutputParams.hostApiSpecificStreamInfo = &asioOutputInfo; @@ -452,6 +505,7 @@ QString CSound::ReinitializeDriver ( PaDeviceIndex inIndex, PaDeviceIndex outInd wasapiOutputInfo.flags = wasapiFlag; } else +#endif // _WIN32 { paOutputParams.hostApiSpecificStreamInfo = NULL; } diff --git a/src/portaudiosound.h b/src/portaudiosound.h index dfd2c2d336..05e196c865 100644 --- a/src/portaudiosound.h +++ b/src/portaudiosound.h @@ -51,6 +51,7 @@ class CSound : public CSoundBase void* arg, const QString& strMIDISetup, const bool, + const QString& strJackClientName, const QString& apiName ); virtual ~CSound(); @@ -58,27 +59,30 @@ class CSound : public CSoundBase virtual void Start(); virtual void Stop(); - virtual int GetNumInputChannels(); + virtual int GetNumInputChannels(); +#if defined( Q_OS_MACX ) || defined( _WIN32 ) virtual QString GetInputChannelName ( const int ); - virtual void SetLeftInputChannel ( const int ); - virtual void SetRightInputChannel ( const int ); - virtual int GetLeftInputChannel() { return vSelectedInputChannels[0]; } - virtual int GetRightInputChannel() { return vSelectedInputChannels[1]; } - - virtual int GetNumOutputChannels(); +#endif + virtual void SetLeftInputChannel ( const int ); + virtual void SetRightInputChannel ( const int ); + virtual int GetLeftInputChannel() { return vSelectedInputChannels[0]; } + virtual int GetRightInputChannel() { return vSelectedInputChannels[1]; } + + virtual int GetNumOutputChannels(); +#if defined( Q_OS_MACX ) || defined( _WIN32 ) virtual QString GetOutputChannelName ( const int ); - virtual void SetLeftOutputChannel ( const int ); - virtual void SetRightOutputChannel ( const int ); - virtual int GetLeftOutputChannel() { return vSelectedOutputChannels[0]; } - virtual int GetRightOutputChannel() { return vSelectedOutputChannels[1]; } +#endif + virtual void SetLeftOutputChannel ( const int ); + virtual void SetRightOutputChannel ( const int ); + virtual int GetLeftOutputChannel() { return vSelectedOutputChannels[0]; } + virtual int GetRightOutputChannel() { return vSelectedOutputChannels[1]; } static QString GetPaApiNames(); virtual EPaApiSettings GetExtraSettings(); +#ifdef WIN32 virtual void SetWasapiMode ( PaWasapiMode mode ); - -#ifdef WIN32 virtual void OpenDriverSetup(); #endif // WIN32