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