diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 90a7b716fd..ee7c16716e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -12,7 +12,7 @@ assignees: '' **Describe the solution you'd like** - + **Describe alternatives that have been considered** diff --git a/.github/actions_scripts/analyse_git_reference.py b/.github/actions_scripts/analyse_git_reference.py index ca9599607c..5ea17c3c62 100755 --- a/.github/actions_scripts/analyse_git_reference.py +++ b/.github/actions_scripts/analyse_git_reference.py @@ -42,9 +42,9 @@ def get_git_hash(): print('Number of arguments:', len(sys.argv), 'arguments.') print('Argument List:', str(sys.argv)) raise Exception("wrong agruments") - + # derive workspace-path -repo_path_on_disk = os.environ['GITHUB_WORKSPACE'] +repo_path_on_disk = os.environ['GITHUB_WORKSPACE'] # derive git related variables version_from_changelog = get_jamulus_version(repo_path_on_disk) @@ -74,7 +74,7 @@ def get_git_hash(): print('this reference is a Tag') release_tag = pushed_name # tag already exists release_title="Release {} ({})".format(release_version_name, pushed_name) - + if pushed_name.startswith("r"): if re.match(r'^r\d+_\d+_\d+$', pushed_name): print('this reference is a Release-Tag') @@ -110,7 +110,7 @@ def set_github_variable(varname, varval): set_github_variable("PUBLISH_TO_RELEASE", str(publish_to_release).lower()) set_github_variable("IS_PRERELEASE", str(is_prerelease).lower()) set_github_variable("RELEASE_TITLE", release_title) -set_github_variable("RELEASE_TAG", release_tag) +set_github_variable("RELEASE_TAG", release_tag) set_github_variable("PUSHED_NAME", pushed_name) set_github_variable("JAMULUS_VERSION", release_version_name) set_github_variable("RELEASE_VERSION_NAME", release_version_name) diff --git a/COMPILING.md b/COMPILING.md index 78bd731a13..c3cdb46ec7 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -53,7 +53,7 @@ sudo make install ### “Headless” server build -Although not strictly necessary, we recommend using the headless flag to avoid having to install some of the dependent packages, save some disk space and/or speed up your build time. +Although not strictly necessary, we recommend using the headless flag to avoid having to install some of the dependent packages, save some disk space and/or speed up your build time. Note that you don’t need to install the JACK package(s) for a headless build. If you plan to run headless on Gentoo, or are compiling under Ubuntu for use on another Ubuntu machine, the only packages you should need for a headless build are `qtcore`, `qtnetwork`, `qtconcurrent` and `qtxml` (both for building and running the server). @@ -148,7 +148,7 @@ Will build the file and make it available in `./Release/Jamulus.app` 4. Open the generated .xcodeproject in Xcode 5. Go to the Signing & Capabilities tab and fix signing errors by setting a team. Xcode will tell you what you need to change. * If have a free Apple Developer Account, you can use it as a "Personal Team": -* Set it up under Xcode Menu->Preferences->Accounts. +* Set it up under Xcode Menu->Preferences->Accounts. * Then choose a Bundle Identifier at your choice in the relevant field in the "General" Tab (in section "Identity") * Now click on the "Signing & Capabilities" tab. In the section "Signing", the "Automatically manage signing" option should be selected. * You should now see Team: (Your Name) (Personal Team), Bundle identifier: (the same you modified on General Tab), Provisioning Profile: Xcode Management Profile, Signing Certificate: Apple Development (your e-mail used for signing in to Apple) below @@ -160,7 +160,7 @@ Will build the file and make it available in `./Release/Jamulus.app` ## Android * Install Qt, including the Android support from the Qt installer -* Follow Qt's [Getting Started with Qt for Android](https://doc.qt.io/qt-5/android-getting-started.html) instructions +* Follow Qt's [Getting Started with Qt for Android](https://doc.qt.io/qt-5/android-getting-started.html) instructions * Make sure Jamulus submodules are present, notably oboe: `git submodule update --init` * Open Jamulus.pro in Qt Creator diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index de2668273f..da0d2eaef4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,8 @@ ## We’d really appreciate your support! -- If the [Github issue](https://github.com/jamulussoftware/jamulus/issues) for your feature/bug fix already exists, write a message in that issue saying you want to work on it. -- Otherwise, please [post on the GitHub Discussions](https://github.com/jamulussoftware/jamulus/discussions) and say that you are planning to do some coding and explain why. Then we can discuss the specification. +- If the [Github issue](https://github.com/jamulussoftware/jamulus/issues) for your feature/bug fix already exists, write a message in that issue saying you want to work on it. +- Otherwise, please [post on the GitHub Discussions](https://github.com/jamulussoftware/jamulus/discussions) and say that you are planning to do some coding and explain why. Then we can discuss the specification. - Please only start coding after we have agreed to a specification to avoid putting lots of work into something which may not get accepted later. ## Jamulus project/source code general principles @@ -12,7 +12,7 @@ This has been, and must continue to be, the most important requirement. It is particularly important for use cases such as the Worldjam where instability during a live performance would be unacceptable. The following two principles are designed to support this principle. ### [Keep it Simple and Stupid](https://en.wikipedia.org/wiki/KISS_principle) and [Do One Thing and Do It Well](https://en.wikipedia.org/wiki/Unix_philosophy#Do_One_Thing_and_Do_It_Well) -If a feature or function can be achieved in another way by another system or method, then that is preferable to building that feature into Jamulus. +If a feature or function can be achieved in another way by another system or method, then that is preferable to building that feature into Jamulus. ### Source code consistency - Respect the existing code style: Tab size=4, insert spaces. @@ -53,7 +53,7 @@ The Jamulus project has a [style and tone guide](https://jamulus.io/contribute/S Features should be usable in the sense that they act as expected to somebody who does not have a technical background. ### Ownership -The submitter of an Issue or a PR is responsible for its care and feeding, answering all questions directed at them, and making agreed amends if needed. +The submitter of an Issue or a PR is responsible for its care and feeding, answering all questions directed at them, and making agreed amends if needed. So as to reduce effort for others in understanding a PR or an Issue, authors are strongly encouraged to update their initial posts/PR descriptions or title to reflect the current state of play, amends, enhancements, outstanding issues etc. Admins reserve the right to do this as they see fit. @@ -67,6 +67,6 @@ The git master branch is protected to require at least two reviews by the main d --- -## Want to get involved in other ways? +## Want to get involved in other ways? We always need help with documentation, translation and anything else. Have a look at our [overview for contributors](https://jamulus.io/wiki/Contribution). diff --git a/COPYING b/COPYING index 6408fdfe39..56ca5c8d12 100644 --- a/COPYING +++ b/COPYING @@ -300,7 +300,7 @@ notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- Neither the name of Internet Society, IETF or IETF Trust, nor the +- Neither the name of Internet Society, IETF or IETF Trust, nor the names of specific contributors, may be used to endorse or promote products derived from this software without specific prior written permission. @@ -371,4 +371,3 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/ChangeLog b/ChangeLog index c221f25672..c109cfa957 100644 --- a/ChangeLog +++ b/ChangeLog @@ -162,8 +162,8 @@ - Bug Fix: Fix ampersand not being shown correctly on mixer (#1886, #1893). (contributed by @HughePaul, @ann0see) - -- Bug Fix: A crash fix related to the way iOS handles sockets in idle mode (#1875). + +- Bug Fix: A crash fix related to the way iOS handles sockets in idle mode (#1875). (contributed by @ngocdh) - Server: A single click on the server application in the systemtray now opens the Jamulus Server main window (#1722, #1731). @@ -175,7 +175,7 @@ - Server: The list of servers registered on a directory can now be saved during directory restart. Use the new --directoryfile CLI option to use this feature (#1867). (contributed by @pljones) - + - Server: Add link to website if a new version is available (#1980). (contributed by: @ann0see) @@ -193,7 +193,7 @@ - Android: Add close button on Android to enhance UX (#1763, #1876). (contributed by @ngocdh) - + - iOS: Sound support, feature to allow switch between external device and the internal mic (#1875). (contributed by @ngocdh) @@ -217,14 +217,14 @@ - Documentation: Number range command line error messages reworded accurately (#1978). (contributed by @DavidSavinkoff) - + - Website: switch to .po file format to make updating translations easier (various PRs) (contributed by: @ignotus666) - Website: Removed edit button from wiki layout file (#576). (contributed by: @DevRish) -- Internal: Enable signing of macOS binaries (via build script and automatically via CI) (#1856, #1937). +- Internal: Enable signing of macOS binaries (via build script and automatically via CI) (#1856, #1937). (contributed by @emlynmac) - Internal: Added Apple Appstore licence waiver (#1874) @@ -235,7 +235,7 @@ - Internal: Document release process on contribute page (#1594, #592). (contributed by: @hoffie, @ann0see) - + ### 3.8.0 (2021-06-02) ### - The term "Central server" has been replaced with "Directory server" (#1407, #1715, #1629). diff --git a/Jamulus.pro b/Jamulus.pro index 7783099ae6..1a353a0c31 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -146,9 +146,9 @@ win32 { QMAKE_APPLICATION_BUNDLE_NAME. = $$TARGET OSX_ENTITLEMENTS.files = Jamulus.entitlements - OSX_ENTITLEMENTS.path = Contents/Resources + OSX_ENTITLEMENTS.path = Contents/Resources QMAKE_BUNDLE_DATA += OSX_ENTITLEMENTS - + macx-xcode { QMAKE_INFO_PLIST = mac/Info-xcode.plist XCODE_ENTITLEMENTS.name = CODE_SIGN_ENTITLEMENTS @@ -1168,4 +1168,3 @@ contains(CONFIG, "disable_version_check") { } ANDROID_ABIS = armeabi-v7a arm64-v8a x86 x86_64 - diff --git a/README.md b/README.md index 8865bab194..645823cdca 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Jamulus - Internet Jam Session Software Jamulus enables musicians to perform in real-time together over the internet. A Jamulus server collects the incoming audio data from each Jamulus client, mixes that data and then sends that mix back to each client. Jamulus can support large numbers of clients with minimal latency and modest bandwidth requirements. -Jamulus is [__free and open source software__](https://www.gnu.org/philosophy/free-sw.en.html) (FOSS) licensed under the [GPL](http://www.gnu.org/licenses/gpl-2.0.html) +Jamulus is [__free and open source software__](https://www.gnu.org/philosophy/free-sw.en.html) (FOSS) licensed under the [GPL](http://www.gnu.org/licenses/gpl-2.0.html) and runs under __Windows__ ([ASIO](https://www.steinberg.net) or [JACK](https://jackaudio.org)), __MacOS__ ([Core Audio](https://developer.apple.com/documentation/coreaudio)) and __Linux__ ([JACK](https://jackaudio.org)). diff --git a/autobuild/android/install-qt.sh b/autobuild/android/install-qt.sh index 2bae78a963..2fbb0133c0 100755 --- a/autobuild/android/install-qt.sh +++ b/autobuild/android/install-qt.sh @@ -352,4 +352,3 @@ for COMPONENT in ${COMPONENTS}; do fi done - diff --git a/autobuild/ensure_THIS_JAMULUS_PROJECT_PATH.sh b/autobuild/ensure_THIS_JAMULUS_PROJECT_PATH.sh index 45f707e17b..a1366340de 100755 --- a/autobuild/ensure_THIS_JAMULUS_PROJECT_PATH.sh +++ b/autobuild/ensure_THIS_JAMULUS_PROJECT_PATH.sh @@ -30,4 +30,3 @@ else echo "ERROR: THIS_JAMULUS_PROJECT_PATH must reference an existing directory: \"${THIS_JAMULUS_PROJECT_PATH}\"" exit 1 fi - diff --git a/autobuild/windows/autobuild_windowsinstaller_3_copy_files.ps1 b/autobuild/windows/autobuild_windowsinstaller_3_copy_files.ps1 index dabd6660c2..427a5e2c0c 100644 --- a/autobuild/windows/autobuild_windowsinstaller_3_copy_files.ps1 +++ b/autobuild/windows/autobuild_windowsinstaller_3_copy_files.ps1 @@ -68,7 +68,7 @@ Function github_output_value [Parameter(Mandatory=$true)] [string] $value ) - + echo "github_output_value() $name = $value" echo "::set-output name=$name::$value" } diff --git a/docs/README.md b/docs/README.md index 105eece0c1..640c585d8c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,4 +5,3 @@ Mostly documentation can be found on the Jamulus Website: https://jamulus.io ## Project related documentation 1. Release process: https://jamulus.io/contribute/Release-Process 2. Style and tone guide: https://jamulus.io/contribute/Style-and-Tone - diff --git a/docs/TRANSLATING.md b/docs/TRANSLATING.md index f0719615b3..ba879806e0 100644 --- a/docs/TRANSLATING.md +++ b/docs/TRANSLATING.md @@ -218,7 +218,7 @@ Select the current repository as `jamulus`, and the branch that was created abov The changed file(s) should be listed in the left-hand column as `src/res/translation/translation_xx_YY.ts`. When the file is selected, the differences will be displayed in the main panel. -Add a simple commit message in the first box below the file list, (e.g. change "Update filename" +Add a simple commit message in the first box below the file list, (e.g. change "Update filename" to something like "Update German translations for v3.7.0"), and add any extra description in the Description box (optional, probably not required). Commit the changes to the local git repo by clicking on **Commit to **. diff --git a/mac/activity.mm b/mac/activity.mm index c800489f36..5212437cad 100644 --- a/mac/activity.mm +++ b/mac/activity.mm @@ -41,13 +41,13 @@ void CActivity::BeginActivity() { NSActivityOptions options = NSActivityBackground | NSActivityIdleDisplaySleepDisabled | NSActivityIdleSystemSleepDisabled | NSActivityLatencyCritical; - + pActivity->activityId = [[NSProcessInfo processInfo] beginActivityWithOptions: options reason:@"Jamulus provides low latency audio processing and should not be inturrupted by system throttling."]; } void CActivity::EndActivity() { [[NSProcessInfo processInfo] endActivity: pActivity->activityId]; - + pActivity->activityId = nil; } diff --git a/mac/deploy_mac.sh b/mac/deploy_mac.sh index 438fc38a7b..b4266a12cc 100755 --- a/mac/deploy_mac.sh +++ b/mac/deploy_mac.sh @@ -12,21 +12,21 @@ cert_name="" while getopts 'hs:' flag; do case "${flag}" in - s) - cert_name=$OPTARG + s) + cert_name=$OPTARG if [[ -z "$cert_name" ]]; then echo "Please add the name of the certificate to use: -s \"\"" fi # shift 2 ;; - h) - echo "Usage: -s for signing mac build" - exit 0 + h) + echo "Usage: -s for signing mac build" + exit 0 ;; *) exit 1 ;; - + esac done diff --git a/src/vstmain.cpp b/src/vstmain.cpp index ddda3a68c4..d13db4b0ef 100644 --- a/src/vstmain.cpp +++ b/src/vstmain.cpp @@ -1,112 +1,112 @@ -/******************************************************************************\ +/******************************************************************************\ * Copyright (c) 2004-2022 - * - * Author(s): - * Volker Fischer - * - ****************************************************************************** - * - * 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 "vstmain.h" - -/* Implementation *************************************************************/ -// this function is required for host to get plugin -AudioEffect* createEffectInstance ( audioMasterCallback AudioMaster ) { return new CLlconVST ( AudioMaster ); } - -CLlconVST::CLlconVST ( audioMasterCallback AudioMaster ) : - AudioEffectX ( AudioMaster, 1, 0 ), // 1 program with no parameters (=0) - Client ( LLCON_DEFAULT_PORT_NUMBER ) -{ - // stereo input/output - setNumInputs ( 2 ); - setNumOutputs ( 2 ); - - setUniqueID ( 'Llco' ); - - // capabilities of llcon VST plugin - canProcessReplacing(); // supports replacing output - - // set default program name - GetName ( strProgName ); - - // we want a single shot timer to shut down the connection if no - // processing is done anymore (VST host has stopped the stream) - TimerOnOff.setSingleShot ( true ); - TimerOnOff.setInterval ( VST_STOP_TIMER_INTERVAL ); - - // connect timer event - connect ( &TimerOnOff, SIGNAL ( timeout() ), this, SLOT ( OnTimerOnOff() ) ); - - // clang-format off -// TODO settings -Client.SetServerAddr ( DEFAULT_SERVER_ADDRESS ); - // clang-format on -} - -bool CLlconVST::GetName ( char* cName ) -{ - // this name is used for program name, effect name, product string and - // vendor string - vst_strncpy ( cName, "Llcon", kVstMaxEffectNameLen ); - return true; -} - -void CLlconVST::OnTimerOnOff() -{ - // stop client since VST host seems to have stopped - Client.Stop(); -} - -void CLlconVST::processReplacing ( float** pvIn, float** pvOut, VstInt32 iNumSamples ) -{ - int i, j; - - // reset stop timer - TimerOnOff.start(); - - // check if client is running, if not, start it - if ( !Client.IsRunning() ) - { - // set buffer size and start - Client.GetSound()->SetMonoBufferSize ( iNumSamples ); - Client.Start(); - } - - // get pointers to actual buffers - float* pfIn0 = pvIn[0]; - float* pfIn1 = pvIn[1]; - float* pfOut0 = pvOut[0]; - float* pfOut1 = pvOut[1]; - - // copy input data - for ( i = 0, j = 0; i < iNumSamples; i++, j += 2 ) - { - Client.GetSound()->vecsTmpAudioSndCrdStereo[j] = pfIn0[i]; - Client.GetSound()->vecsTmpAudioSndCrdStereo[j + 1] = pfIn1[i]; - } - - // call processing callback function - Client.GetSound()->VSTProcessCallback(); - - // copy output data - for ( i = 0, j = 0; i < iNumSamples; i++, j += 2 ) - { - pfOut0[i] = Client.GetSound()->vecsTmpAudioSndCrdStereo[j]; - pfOut1[i] = Client.GetSound()->vecsTmpAudioSndCrdStereo[j + 1]; - } -} + * + * Author(s): + * Volker Fischer + * + ****************************************************************************** + * + * 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 "vstmain.h" + +/* Implementation *************************************************************/ +// this function is required for host to get plugin +AudioEffect* createEffectInstance ( audioMasterCallback AudioMaster ) { return new CLlconVST ( AudioMaster ); } + +CLlconVST::CLlconVST ( audioMasterCallback AudioMaster ) : + AudioEffectX ( AudioMaster, 1, 0 ), // 1 program with no parameters (=0) + Client ( LLCON_DEFAULT_PORT_NUMBER ) +{ + // stereo input/output + setNumInputs ( 2 ); + setNumOutputs ( 2 ); + + setUniqueID ( 'Llco' ); + + // capabilities of llcon VST plugin + canProcessReplacing(); // supports replacing output + + // set default program name + GetName ( strProgName ); + + // we want a single shot timer to shut down the connection if no + // processing is done anymore (VST host has stopped the stream) + TimerOnOff.setSingleShot ( true ); + TimerOnOff.setInterval ( VST_STOP_TIMER_INTERVAL ); + + // connect timer event + connect ( &TimerOnOff, SIGNAL ( timeout() ), this, SLOT ( OnTimerOnOff() ) ); + + // clang-format off +// TODO settings +Client.SetServerAddr ( DEFAULT_SERVER_ADDRESS ); + // clang-format on +} + +bool CLlconVST::GetName ( char* cName ) +{ + // this name is used for program name, effect name, product string and + // vendor string + vst_strncpy ( cName, "Llcon", kVstMaxEffectNameLen ); + return true; +} + +void CLlconVST::OnTimerOnOff() +{ + // stop client since VST host seems to have stopped + Client.Stop(); +} + +void CLlconVST::processReplacing ( float** pvIn, float** pvOut, VstInt32 iNumSamples ) +{ + int i, j; + + // reset stop timer + TimerOnOff.start(); + + // check if client is running, if not, start it + if ( !Client.IsRunning() ) + { + // set buffer size and start + Client.GetSound()->SetMonoBufferSize ( iNumSamples ); + Client.Start(); + } + + // get pointers to actual buffers + float* pfIn0 = pvIn[0]; + float* pfIn1 = pvIn[1]; + float* pfOut0 = pvOut[0]; + float* pfOut1 = pvOut[1]; + + // copy input data + for ( i = 0, j = 0; i < iNumSamples; i++, j += 2 ) + { + Client.GetSound()->vecsTmpAudioSndCrdStereo[j] = pfIn0[i]; + Client.GetSound()->vecsTmpAudioSndCrdStereo[j + 1] = pfIn1[i]; + } + + // call processing callback function + Client.GetSound()->VSTProcessCallback(); + + // copy output data + for ( i = 0, j = 0; i < iNumSamples; i++, j += 2 ) + { + pfOut0[i] = Client.GetSound()->vecsTmpAudioSndCrdStereo[j]; + pfOut1[i] = Client.GetSound()->vecsTmpAudioSndCrdStereo[j + 1]; + } +} diff --git a/src/vstmain.h b/src/vstmain.h index f68fb3590e..7afa9ec5c2 100644 --- a/src/vstmain.h +++ b/src/vstmain.h @@ -1,68 +1,68 @@ -/******************************************************************************\ +/******************************************************************************\ * Copyright (c) 2004-2022 - * - * Author(s): - * Volker Fischer - * - ****************************************************************************** - * - * 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 - * -\******************************************************************************/ - -#if !defined( LLCONVST_HOIHGE76G34528_3_434DFGUHF1912__INCLUDED_ ) -# define LLCONVST_HOIHGE76G34528_3_434DFGUHF1912__INCLUDED_ - -// copy the VST SDK in the llcon/windows directory: "llcon/windows/vstsdk2.4" to -// get it work -# include "audioeffectx.h" -# include -# include "global.h" -# include "client.h" - -/* Definitions ****************************************************************/ -// timeout after which the llcon client is stopped -# define VST_STOP_TIMER_INTERVAL 1000 - -/* Classes ********************************************************************/ -class CLlconVST : public QObject, public AudioEffectX -{ - Q_OBJECT - -public: - CLlconVST ( audioMasterCallback AudioMaster ); - - virtual void processReplacing ( float** pvIn, float** pvOut, VstInt32 iNumSamples ); - - virtual void setProgramName ( char* cName ) { vst_strncpy ( strProgName, cName, kVstMaxProgNameLen ); } - virtual void getProgramName ( char* cName ) { vst_strncpy ( cName, strProgName, kVstMaxProgNameLen ); } - - virtual bool getEffectName ( char* cString ) { return GetName ( cString ); } - virtual bool getVendorString ( char* cString ) { return GetName ( cString ); } - virtual bool getProductString ( char* cString ) { return GetName ( cString ); } - virtual VstInt32 getVendorVersion() { return 1000; } - -protected: - bool GetName ( char* cName ); - char strProgName[kVstMaxProgNameLen + 1]; - - CClient Client; - QTimer TimerOnOff; - -protected slots: - void OnTimerOnOff(); -}; - -#endif /* !defined ( LLCONVST_HOIHGE76G34528_3_434DFGUHF1912__INCLUDED_ ) */ + * + * Author(s): + * Volker Fischer + * + ****************************************************************************** + * + * 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 + * +\******************************************************************************/ + +#if !defined( LLCONVST_HOIHGE76G34528_3_434DFGUHF1912__INCLUDED_ ) +# define LLCONVST_HOIHGE76G34528_3_434DFGUHF1912__INCLUDED_ + +// copy the VST SDK in the llcon/windows directory: "llcon/windows/vstsdk2.4" to +// get it work +# include "audioeffectx.h" +# include +# include "global.h" +# include "client.h" + +/* Definitions ****************************************************************/ +// timeout after which the llcon client is stopped +# define VST_STOP_TIMER_INTERVAL 1000 + +/* Classes ********************************************************************/ +class CLlconVST : public QObject, public AudioEffectX +{ + Q_OBJECT + +public: + CLlconVST ( audioMasterCallback AudioMaster ); + + virtual void processReplacing ( float** pvIn, float** pvOut, VstInt32 iNumSamples ); + + virtual void setProgramName ( char* cName ) { vst_strncpy ( strProgName, cName, kVstMaxProgNameLen ); } + virtual void getProgramName ( char* cName ) { vst_strncpy ( cName, strProgName, kVstMaxProgNameLen ); } + + virtual bool getEffectName ( char* cString ) { return GetName ( cString ); } + virtual bool getVendorString ( char* cString ) { return GetName ( cString ); } + virtual bool getProductString ( char* cString ) { return GetName ( cString ); } + virtual VstInt32 getVendorVersion() { return 1000; } + +protected: + bool GetName ( char* cName ); + char strProgName[kVstMaxProgNameLen + 1]; + + CClient Client; + QTimer TimerOnOff; + +protected slots: + void OnTimerOnOff(); +}; + +#endif /* !defined ( LLCONVST_HOIHGE76G34528_3_434DFGUHF1912__INCLUDED_ ) */ diff --git a/src/vstsound.h b/src/vstsound.h index e3cd5c5c69..e93d370786 100644 --- a/src/vstsound.h +++ b/src/vstsound.h @@ -1,60 +1,60 @@ -/******************************************************************************\ +/******************************************************************************\ * Copyright (c) 2004-2022 - * - * Author(s): - * Volker Fischer - * - ****************************************************************************** - * - * 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 - * -\******************************************************************************/ - -#if !defined( _VSTSOUND_H__9518A346345768_11D3_8C0D_EEBF182CF549__INCLUDED_ ) -# define _VSTSOUND_H__9518A346345768_11D3_8C0D_EEBF182CF549__INCLUDED_ - -# include "../src/util.h" -# include "../src/global.h" -# include "../src/soundbase.h" - -/* Classes ********************************************************************/ -class CSound : public CSoundBase -{ -public: - CSound ( void ( *fpNewCallback ) ( CVector& psData, void* arg ), void* arg ) : - CSoundBase ( true, fpNewCallback, arg ), - iVSTMonoBufferSize ( 0 ) - {} - - // special VST functions - void SetMonoBufferSize ( const int iNVBS ) { iVSTMonoBufferSize = iNVBS; } - void VSTProcessCallback() { CSoundBase::ProcessCallback ( vecsTmpAudioSndCrdStereo ); } - - virtual int Init ( const int ) - { - // init base class - CSoundBase::Init ( iVSTMonoBufferSize ); - vecsTmpAudioSndCrdStereo.Init ( 2 * iVSTMonoBufferSize /* stereo */ ); - return iVSTMonoBufferSize; - } - - // this vector must be accessible from the outside (quick hack solution) - CVector vecsTmpAudioSndCrdStereo; - -protected: - int iVSTMonoBufferSize; -}; - -#endif // !defined ( _VSTSOUND_H__9518A346345768_11D3_8C0D_EEBF182CF549__INCLUDED_ ) + * + * Author(s): + * Volker Fischer + * + ****************************************************************************** + * + * 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 + * +\******************************************************************************/ + +#if !defined( _VSTSOUND_H__9518A346345768_11D3_8C0D_EEBF182CF549__INCLUDED_ ) +# define _VSTSOUND_H__9518A346345768_11D3_8C0D_EEBF182CF549__INCLUDED_ + +# include "../src/util.h" +# include "../src/global.h" +# include "../src/soundbase.h" + +/* Classes ********************************************************************/ +class CSound : public CSoundBase +{ +public: + CSound ( void ( *fpNewCallback ) ( CVector& psData, void* arg ), void* arg ) : + CSoundBase ( true, fpNewCallback, arg ), + iVSTMonoBufferSize ( 0 ) + {} + + // special VST functions + void SetMonoBufferSize ( const int iNVBS ) { iVSTMonoBufferSize = iNVBS; } + void VSTProcessCallback() { CSoundBase::ProcessCallback ( vecsTmpAudioSndCrdStereo ); } + + virtual int Init ( const int ) + { + // init base class + CSoundBase::Init ( iVSTMonoBufferSize ); + vecsTmpAudioSndCrdStereo.Init ( 2 * iVSTMonoBufferSize /* stereo */ ); + return iVSTMonoBufferSize; + } + + // this vector must be accessible from the outside (quick hack solution) + CVector vecsTmpAudioSndCrdStereo; + +protected: + int iVSTMonoBufferSize; +}; + +#endif // !defined ( _VSTSOUND_H__9518A346345768_11D3_8C0D_EEBF182CF549__INCLUDED_ ) diff --git a/windows/sound.cpp b/windows/sound.cpp index ef789893d2..58ad034e8a 100644 --- a/windows/sound.cpp +++ b/windows/sound.cpp @@ -1,1219 +1,1219 @@ -/******************************************************************************\ +/******************************************************************************\ * Copyright (c) 2004-2022 - * - * Author(s): - * Volker Fischer - * - * Description: - * Sound card interface for Windows operating systems - * - ****************************************************************************** - * - * 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 "sound.h" - -/* Implementation *************************************************************/ -// external references -extern AsioDrivers* asioDrivers; -bool loadAsioDriver ( char* name ); - -// pointer to our sound object -CSound* pSound; - -/******************************************************************************\ -* Common * -\******************************************************************************/ -QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDriverSetup ) -{ - // find and load driver - int iDriverIdx = INVALID_INDEX; // initialize with an invalid index - - for ( int i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ ) - { - if ( strDriverName.compare ( cDriverNames[i] ) == 0 ) - { - iDriverIdx = i; - } - } - - // if the selected driver was not found, return an error message - if ( iDriverIdx == INVALID_INDEX ) - { - return tr ( "The selected audio device is no longer present in the system. Please check your audio device." ); - } - - // Save number of channels from last driver - // Need to save these (but not the driver name) as CheckDeviceCapabilities() overwrites them - long lNumInChanPrev = lNumInChan; - long lNumOutChanPrev = lNumOutChan; - - loadAsioDriver ( cDriverNames[iDriverIdx] ); - - // According to the docs, driverInfo.asioVersion and driverInfo.sysRef - // should be set, but we haven't being doing that and it seems to work - // okay... - memset ( &driverInfo, 0, sizeof driverInfo ); - - if ( ASIOInit ( &driverInfo ) != ASE_OK ) - { - // clean up and return error string - asioDrivers->removeCurrentDriver(); - return tr ( "Couldn't initialise the audio driver. Check if your audio hardware is plugged in and verify your driver settings." ); - } - - // check device capabilities if it fulfills our requirements - const QString strStat = CheckDeviceCapabilities(); // also sets lNumInChan and lNumOutChan - - // check if device is capable - if ( strStat.isEmpty() ) - { - // Reset channel mapping if the sound card name has changed or the number of channels has changed - if ( ( strCurDevName.compare ( strDriverNames[iDriverIdx] ) != 0 ) || ( lNumInChanPrev != lNumInChan ) || ( lNumOutChanPrev != lNumOutChan ) ) - { - // In order to fix https://github.com/jamulussoftware/jamulus/issues/796 - // this code runs after a change in the ASIO driver (not when changing the ASIO input selection.) - - // mapping to the defaults (first two available channels) - ResetChannelMapping(); - - // store ID of selected driver if initialization was successful - strCurDevName = cDriverNames[iDriverIdx]; - } - } - else - { - // if requested, open ASIO driver setup in case of an error - if ( bOpenDriverSetup ) - { - OpenDriverSetup(); - QMessageBox::question ( nullptr, - APP_NAME, - "Are you done with your ASIO driver settings of " + GetDeviceName ( iDriverIdx ) + "?", - QMessageBox::Yes ); - } - - // driver cannot be used, clean up - asioDrivers->removeCurrentDriver(); - } - - return strStat; -} - -void CSound::UnloadCurrentDriver() -{ - // clean up ASIO stuff - if ( bRun ) - { - Stop(); - } - if ( bufferInfos[0].buffers[0] ) - { - ASIODisposeBuffers(); - bufferInfos[0].buffers[0] = NULL; - } - ASIOExit(); - asioDrivers->removeCurrentDriver(); -} - -QString CSound::CheckDeviceCapabilities() -{ - // This function checks if our required input/output channel - // properties are supported by the selected device. If the return - // string is empty, the device can be used, otherwise the error - // message is returned. - - // check the sample rate - const ASIOError CanSaRateReturn = ASIOCanSampleRate ( SYSTEM_SAMPLE_RATE_HZ ); - - if ( ( CanSaRateReturn == ASE_NoClock ) || ( CanSaRateReturn == ASE_NotPresent ) ) - { - // return error string - return QString ( tr ( "The selected audio device is incompatible " - "since it doesn't support a sample rate of %1 Hz. Please select another " - "device." ) ) - .arg ( SYSTEM_SAMPLE_RATE_HZ ); - } - - // check if sample rate can be set - const ASIOError SetSaRateReturn = ASIOSetSampleRate ( SYSTEM_SAMPLE_RATE_HZ ); - - if ( ( SetSaRateReturn == ASE_NoClock ) || ( SetSaRateReturn == ASE_InvalidMode ) || ( SetSaRateReturn == ASE_NotPresent ) ) - { - // return error string - return QString ( tr ( "The current audio device configuration is incompatible " - "because the sample rate couldn't be set to %2 Hz. Please check for a hardware switch or " - "driver setting to set the sample rate manually and restart %1." ) ) - .arg ( APP_NAME ) - .arg ( SYSTEM_SAMPLE_RATE_HZ ); - } - - // check the number of available channels - ASIOGetChannels ( &lNumInChan, &lNumOutChan ); - - if ( ( lNumInChan < NUM_IN_OUT_CHANNELS ) || ( lNumOutChan < NUM_IN_OUT_CHANNELS ) ) - { - // return error string - return QString ( tr ( "The selected audio device is incompatible since it doesn't support " - "%1 in/out channels. Please select another device or configuration." ) ) - .arg ( NUM_IN_OUT_CHANNELS ); - } - - // clip number of input/output channels to our maximum - if ( lNumInChan > MAX_NUM_IN_OUT_CHANNELS ) - { - lNumInChan = MAX_NUM_IN_OUT_CHANNELS; - } - if ( lNumOutChan > MAX_NUM_IN_OUT_CHANNELS ) - { - lNumOutChan = MAX_NUM_IN_OUT_CHANNELS; - } - - // query channel infos for all available input channels - bool bInputChMixingSupported = true; - - for ( int i = 0; i < lNumInChan; i++ ) - { - // setup for input channels - channelInfosInput[i].isInput = ASIOTrue; - channelInfosInput[i].channel = i; - - ASIOGetChannelInfo ( &channelInfosInput[i] ); - - // Check supported sample formats. - // Actually, it would be enough to have at least two channels which - // support the required sample format. But since we have support for - // all known sample types, the following check should always pass and - // therefore we throw the error message on any channel which does not - // fulfill the sample format requirement (quick hack solution). - if ( !CheckSampleTypeSupported ( channelInfosInput[i].type ) ) - { - // return error string - return tr ( "The selected audio device is incompatible since " - "the required audio sample format isn't available. Please use another device." ); - } - - // store the name of the channel and check if channel mixing is supported - channelInputName[i] = channelInfosInput[i].name; - - if ( !CheckSampleTypeSupportedForCHMixing ( channelInfosInput[i].type ) ) - { - bInputChMixingSupported = false; - } - } - - // query channel infos for all available output channels - for ( int i = 0; i < lNumOutChan; i++ ) - { - // setup for output channels - channelInfosOutput[i].isInput = ASIOFalse; - channelInfosOutput[i].channel = i; - - ASIOGetChannelInfo ( &channelInfosOutput[i] ); - - // Check supported sample formats. - // Actually, it would be enough to have at least two channels which - // support the required sample format. But since we have support for - // all known sample types, the following check should always pass and - // therefore we throw the error message on any channel which does not - // fulfill the sample format requirement (quick hack solution). - if ( !CheckSampleTypeSupported ( channelInfosOutput[i].type ) ) - { - // return error string - return tr ( "The selected audio device is incompatible since " - "the required audio sample format isn't available. Please use another device." ); - } - } - - // special case with 4 input channels: support adding channels - if ( ( lNumInChan == 4 ) && bInputChMixingSupported ) - { - // add four mixed channels (i.e. 4 normal, 4 mixed channels) - lNumInChanPlusAddChan = 8; - - for ( int iCh = 0; iCh < lNumInChanPlusAddChan; iCh++ ) - { - int iSelCH, iSelAddCH; - - GetSelCHAndAddCH ( iCh, lNumInChan, iSelCH, iSelAddCH ); - - if ( iSelAddCH >= 0 ) - { - // for mixed channels, show both audio channel names to be mixed - channelInputName[iCh] = channelInputName[iSelCH] + " + " + channelInputName[iSelAddCH]; - } - } - } - else - { - // regular case: no mixing input channels used - lNumInChanPlusAddChan = lNumInChan; - } - - // everything is ok, return empty string for "no error" case - return ""; -} - -void CSound::SetLeftInputChannel ( const int iNewChan ) -{ - // apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < lNumInChanPlusAddChan ) ) - { - vSelectedInputChannels[0] = iNewChan; - } -} - -void CSound::SetRightInputChannel ( const int iNewChan ) -{ - // apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < lNumInChanPlusAddChan ) ) - { - vSelectedInputChannels[1] = iNewChan; - } -} - -void CSound::SetLeftOutputChannel ( const int iNewChan ) -{ - // apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) ) - { - vSelectedOutputChannels[0] = iNewChan; - } -} - -void CSound::SetRightOutputChannel ( const int iNewChan ) -{ - // apply parameter after input parameter check - if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) ) - { - vSelectedOutputChannels[1] = iNewChan; - } -} - -int CSound::GetActualBufferSize ( const int iDesiredBufferSizeMono ) -{ - int iActualBufferSizeMono; - - // query the usable buffer sizes - ASIOGetBufferSize ( &HWBufferInfo.lMinSize, &HWBufferInfo.lMaxSize, &HWBufferInfo.lPreferredSize, &HWBufferInfo.lGranularity ); - - // clang-format off -/* -// TEST -#include -QMessageBox::information ( 0, "APP_NAME", QString("lMinSize: %1, lMaxSize: %2, lPreferredSize: %3, lGranularity: %4"). - arg(HWBufferInfo.lMinSize).arg(HWBufferInfo.lMaxSize).arg(HWBufferInfo.lPreferredSize).arg(HWBufferInfo.lGranularity) ); -_exit(1); -*/ - // clang-format on - - // clang-format off -// TODO see https://github.com/EddieRingle/portaudio/blob/master/src/hostapi/asio/pa_asio.cpp#L1654 (SelectHostBufferSizeForUnspecifiedUserFramesPerBuffer) - // clang-format on - - // calculate "nearest" buffer size and set internal parameter accordingly - // first check minimum and maximum values - if ( iDesiredBufferSizeMono <= HWBufferInfo.lMinSize ) - { - iActualBufferSizeMono = HWBufferInfo.lMinSize; - } - else - { - if ( iDesiredBufferSizeMono >= HWBufferInfo.lMaxSize ) - { - iActualBufferSizeMono = HWBufferInfo.lMaxSize; - } - else - { - // ASIO SDK 2.2: "Notes: When minimum and maximum buffer size are - // equal, the preferred buffer size has to be the same value as - // well; granularity should be 0 in this case." - if ( HWBufferInfo.lMinSize == HWBufferInfo.lMaxSize ) - { - iActualBufferSizeMono = HWBufferInfo.lMinSize; - } - else - { - if ( ( HWBufferInfo.lGranularity < -1 ) || ( HWBufferInfo.lGranularity == 0 ) ) - { - // Special case (seen for EMU audio cards): granularity is - // zero or less than zero (make sure to exclude the special - // case of -1). - // There is no definition of this case in the ASIO SDK - // document. We assume here that all buffer sizes in between - // minimum and maximum buffer sizes are allowed. - iActualBufferSizeMono = iDesiredBufferSizeMono; - } - else - { - // General case -------------------------------------------- - // initialization - int iTrialBufSize = HWBufferInfo.lMinSize; - int iLastTrialBufSize = HWBufferInfo.lMinSize; - bool bSizeFound = false; - - // test loop - while ( ( iTrialBufSize <= HWBufferInfo.lMaxSize ) && ( !bSizeFound ) ) - { - if ( iTrialBufSize >= iDesiredBufferSizeMono ) - { - // test which buffer size fits better: the old one or the - // current one - if ( ( iTrialBufSize - iDesiredBufferSizeMono ) > ( iDesiredBufferSizeMono - iLastTrialBufSize ) ) - { - iTrialBufSize = iLastTrialBufSize; - } - - // exit while loop - bSizeFound = true; - } - - if ( !bSizeFound ) - { - // store old trial buffer size - iLastTrialBufSize = iTrialBufSize; - - // increment trial buffer size (check for special - // case first) - if ( HWBufferInfo.lGranularity == -1 ) - { - // special case: buffer sizes are a power of 2 - iTrialBufSize *= 2; - } - else - { - iTrialBufSize += HWBufferInfo.lGranularity; - } - } - } - - // clip trial buffer size (it may happen in the while - // routine that "iTrialBufSize" is larger than "lMaxSize" in - // case "lMaxSize - lMinSize" is not divisible by the - // granularity) - if ( iTrialBufSize > HWBufferInfo.lMaxSize ) - { - iTrialBufSize = HWBufferInfo.lMaxSize; - } - - // set ASIO buffer size - iActualBufferSizeMono = iTrialBufSize; - } - } - } - } - - return iActualBufferSizeMono; -} - -int CSound::Init ( const int iNewPrefMonoBufferSize ) -{ - ASIOMutex.lock(); // get mutex lock - { - // get the actual sound card buffer size which is supported - // by the audio hardware - iASIOBufferSizeMono = GetActualBufferSize ( iNewPrefMonoBufferSize ); - - // init base class - CSoundBase::Init ( iASIOBufferSizeMono ); - - // set internal buffer size value and calculate stereo buffer size - iASIOBufferSizeStereo = 2 * iASIOBufferSizeMono; - - // set the sample rate - ASIOSetSampleRate ( SYSTEM_SAMPLE_RATE_HZ ); - - // create memory for intermediate audio buffer - vecsMultChanAudioSndCrd.Init ( iASIOBufferSizeStereo ); - - // create and activate ASIO buffers (buffer size in samples), - // dispose old buffers (if any) - ASIODisposeBuffers(); - - // prepare input channels - for ( int i = 0; i < lNumInChan; i++ ) - { - bufferInfos[i].isInput = ASIOTrue; - bufferInfos[i].channelNum = i; - bufferInfos[i].buffers[0] = 0; - bufferInfos[i].buffers[1] = 0; - } - - // prepare output channels - for ( int i = 0; i < lNumOutChan; i++ ) - { - bufferInfos[lNumInChan + i].isInput = ASIOFalse; - bufferInfos[lNumInChan + i].channelNum = i; - bufferInfos[lNumInChan + i].buffers[0] = 0; - bufferInfos[lNumInChan + i].buffers[1] = 0; - } - - ASIOCreateBuffers ( bufferInfos, lNumInChan + lNumOutChan, iASIOBufferSizeMono, &asioCallbacks ); - - // query the latency of the driver - long lInputLatency = 0; - long lOutputLatency = 0; - - if ( ASIOGetLatencies ( &lInputLatency, &lOutputLatency ) != ASE_NotPresent ) - { - // add the input and output latencies (returned in number of - // samples) and calculate the time in ms - fInOutLatencyMs = ( static_cast ( lInputLatency ) + lOutputLatency ) * 1000 / SYSTEM_SAMPLE_RATE_HZ; - } - else - { - // no latency available - fInOutLatencyMs = 0.0f; - } - - // check whether the driver requires the ASIOOutputReady() optimization - // (can be used by the driver to reduce output latency by one block) - bASIOPostOutput = ( ASIOOutputReady() == ASE_OK ); - } - ASIOMutex.unlock(); - - return iASIOBufferSizeMono; -} - -void CSound::Start() -{ - // start audio - ASIOStart(); - - // call base class - CSoundBase::Start(); -} - -void CSound::Stop() -{ - // stop audio - ASIOStop(); - - // call base class - CSoundBase::Stop(); - - // make sure the working thread is actually done - // (by checking the locked state) - if ( ASIOMutex.tryLock ( 5000 ) ) - { - ASIOMutex.unlock(); - } -} - -CSound::CSound ( void ( *fpNewCallback ) ( CVector& psData, void* arg ), - void* arg, - const QString& strMIDISetup, - const bool, - const QString& ) : - CSoundBase ( "ASIO", fpNewCallback, arg, strMIDISetup ), - lNumInChan ( 0 ), - lNumInChanPlusAddChan ( 0 ), - lNumOutChan ( 0 ), - fInOutLatencyMs ( 0.0f ), // "0.0" means that no latency value is available - vSelectedInputChannels ( NUM_IN_OUT_CHANNELS ), - vSelectedOutputChannels ( NUM_IN_OUT_CHANNELS ) -{ - int i; - - // init pointer to our sound object - pSound = this; - - // We assume NULL'd pointers in this structure indicate that buffers are not - // allocated yet (see UnloadCurrentDriver). - memset ( bufferInfos, 0, sizeof bufferInfos ); - - // get available ASIO driver names in system - for ( i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ ) - { - // allocate memory for driver names - cDriverNames[i] = new char[32]; - } - - char cDummyName[] = "dummy"; - loadAsioDriver ( cDummyName ); // to initialize external object - lNumDevs = asioDrivers->getDriverNames ( cDriverNames, MAX_NUMBER_SOUND_CARDS ); - - // in case we do not have a driver available, throw error - if ( lNumDevs == 0 ) - { - throw CGenErr ( "" + tr ( "No ASIO audio device driver found." ) + "

" + - QString ( tr ( "Please install an ASIO driver before running %1. " - "If you own a device with ASIO support, install its official ASIO driver. " - "If not, you'll need to install a universal driver like ASIO4ALL." ) ) - .arg ( APP_NAME ) ); - } - asioDrivers->removeCurrentDriver(); - - // copy driver names to base class but internally we still have to use - // the char* variable because of the ASIO API :-( - for ( i = 0; i < lNumDevs; i++ ) - { - strDriverNames[i] = cDriverNames[i]; - } - - // init device index as not initialized (invalid) - strCurDevName = ""; - - // init channel mapping - ResetChannelMapping(); - - // set up the asioCallback structure - asioCallbacks.bufferSwitch = &bufferSwitch; - asioCallbacks.sampleRateDidChange = &sampleRateChanged; - asioCallbacks.asioMessage = &asioMessages; - asioCallbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfo; -} - -void CSound::ResetChannelMapping() -{ - // init selected channel numbers with defaults: use first available - // channels for input and output - vSelectedInputChannels[0] = 0; - vSelectedInputChannels[1] = 1; - vSelectedOutputChannels[0] = 0; - vSelectedOutputChannels[1] = 1; -} - -// ASIO callbacks ------------------------------------------------------------- -ASIOTime* CSound::bufferSwitchTimeInfo ( ASIOTime*, long index, ASIOBool processNow ) -{ - bufferSwitch ( index, processNow ); - return 0L; -} - -bool CSound::CheckSampleTypeSupported ( const ASIOSampleType SamType ) -{ - // check for supported sample types - return ( ( SamType == ASIOSTInt16LSB ) || ( SamType == ASIOSTInt24LSB ) || ( SamType == ASIOSTInt32LSB ) || ( SamType == ASIOSTFloat32LSB ) || - ( SamType == ASIOSTFloat64LSB ) || ( SamType == ASIOSTInt32LSB16 ) || ( SamType == ASIOSTInt32LSB18 ) || - ( SamType == ASIOSTInt32LSB20 ) || ( SamType == ASIOSTInt32LSB24 ) || ( SamType == ASIOSTInt16MSB ) || ( SamType == ASIOSTInt24MSB ) || - ( SamType == ASIOSTInt32MSB ) || ( SamType == ASIOSTFloat32MSB ) || ( SamType == ASIOSTFloat64MSB ) || ( SamType == ASIOSTInt32MSB16 ) || - ( SamType == ASIOSTInt32MSB18 ) || ( SamType == ASIOSTInt32MSB20 ) || ( SamType == ASIOSTInt32MSB24 ) ); -} - -bool CSound::CheckSampleTypeSupportedForCHMixing ( const ASIOSampleType SamType ) -{ - // check for supported sample types for audio channel mixing (see bufferSwitch) - return ( ( SamType == ASIOSTInt16LSB ) || ( SamType == ASIOSTInt24LSB ) || ( SamType == ASIOSTInt32LSB ) ); -} - -void CSound::bufferSwitch ( long index, ASIOBool ) -{ - int iCurSample; - - // get references to class members - int& iASIOBufferSizeMono = pSound->iASIOBufferSizeMono; - CVector& vecsMultChanAudioSndCrd = pSound->vecsMultChanAudioSndCrd; - - // perform the processing for input and output - pSound->ASIOMutex.lock(); // get mutex lock - { - // CAPTURE ------------------------------------------------------------- - for ( int i = 0; i < NUM_IN_OUT_CHANNELS; i++ ) - { - int iSelCH, iSelAddCH; - - GetSelCHAndAddCH ( pSound->vSelectedInputChannels[i], pSound->lNumInChan, iSelCH, iSelAddCH ); - - // copy new captured block in thread transfer buffer (copy - // mono data interleaved in stereo buffer) - switch ( pSound->channelInfosInput[iSelCH].type ) - { - case ASIOSTInt16LSB: - { - // no type conversion required, just copy operation - int16_t* pASIOBuf = static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ); - - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = pASIOBuf[iCurSample]; - } - - if ( iSelAddCH >= 0 ) - { - // mix input channels case: - int16_t* pASIOBufAdd = static_cast ( pSound->bufferInfos[iSelAddCH].buffers[index] ); - - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - Float2Short ( (float) vecsMultChanAudioSndCrd[2 * iCurSample + i] + (float) pASIOBufAdd[iCurSample] ); - } - } - break; - } - - case ASIOSTInt24LSB: - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - int iCurSam = 0; - memcpy ( &iCurSam, ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, 3 ); - iCurSam >>= 8; - - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( iCurSam ); - } - - if ( iSelAddCH >= 0 ) - { - // mix input channels case: - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - int iCurSam = 0; - memcpy ( &iCurSam, ( (char*) pSound->bufferInfos[iSelAddCH].buffers[index] ) + iCurSample * 3, 3 ); - iCurSam >>= 8; - - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - Float2Short ( (float) vecsMultChanAudioSndCrd[2 * iCurSample + i] + (float) static_cast ( iCurSam ) ); - } - } - break; - - case ASIOSTInt32LSB: - { - int32_t* pASIOBuf = static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ); - - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( pASIOBuf[iCurSample] >> 16 ); - } - - if ( iSelAddCH >= 0 ) - { - // mix input channels case: - int32_t* pASIOBufAdd = static_cast ( pSound->bufferInfos[iSelAddCH].buffers[index] ); - - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = Float2Short ( (float) vecsMultChanAudioSndCrd[2 * iCurSample + i] + - (float) static_cast ( pASIOBufAdd[iCurSample] >> 16 ) ); - } - } - break; - } - - case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - static_cast ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] * _MAXSHORT ); - } - break; - - case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - static_cast ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] * _MAXSHORT ); - } - break; - - case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - static_cast ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0xFFFF ); - } - break; - - case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - static_cast ( ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0x3FFFF ) >> 2 ); - } - break; - - case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - static_cast ( ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0xFFFFF ) >> 4 ); - } - break; - - case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - static_cast ( ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0xFFFFFF ) >> 8 ); - } - break; - - case ASIOSTInt16MSB: - // clang-format off -// NOT YET TESTED - // clang-format on - // flip bits - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = - Flip16Bits ( ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ) )[iCurSample] ); - } - break; - - case ASIOSTInt24MSB: - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // because the bits are flipped, we do not have to perform the - // shift by 8 bits - int iCurSam = 0; - memcpy ( &iCurSam, ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, 3 ); - - vecsMultChanAudioSndCrd[2 * iCurSample + i] = Flip16Bits ( static_cast ( iCurSam ) ); - } - break; - - case ASIOSTInt32MSB: - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // flip bits and convert to 16 bit - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) >> 16 ); - } - break; - - case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - static_cast ( Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) ) * - _MAXSHORT ); - } - break; - - case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - static_cast ( Flip64Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) ) * - _MAXSHORT ); - } - break; - - case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0xFFFF ); - } - break; - - case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - ( Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0x3FFFF ) >> 2 ); - } - break; - - case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - ( Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0xFFFFF ) >> 4 ); - } - break; - - case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( - ( Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0xFFFFFF ) >> 8 ); - } - break; - } - } - - // call processing callback function - pSound->ProcessCallback ( vecsMultChanAudioSndCrd ); - - // PLAYBACK ------------------------------------------------------------ - for ( int i = 0; i < NUM_IN_OUT_CHANNELS; i++ ) - { - const int iSelCH = pSound->lNumInChan + pSound->vSelectedOutputChannels[i]; - - // copy data from sound card in output buffer (copy - // interleaved stereo data in mono sound card buffer) - switch ( pSound->channelInfosOutput[pSound->vSelectedOutputChannels[i]].type ) - { - case ASIOSTInt16LSB: - { - // no type conversion required, just copy operation - int16_t* pASIOBuf = static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ); - - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - pASIOBuf[iCurSample] = vecsMultChanAudioSndCrd[2 * iCurSample + i]; - } - break; - } - - case ASIOSTInt24LSB: - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert current sample in 24 bit format - int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - iCurSam <<= 8; - - memcpy ( ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, &iCurSam, 3 ); - } - break; - - case ASIOSTInt32LSB: - { - int32_t* pASIOBuf = static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ); - - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - pASIOBuf[iCurSample] = ( iCurSam << 16 ); - } - break; - } - - case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - const float fCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = fCurSam / _MAXSHORT; - } - break; - - case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - const double fCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = fCurSam / _MAXSHORT; - } - break; - - case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = iCurSam; - } - break; - - case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = ( iCurSam << 2 ); - } - break; - - case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = ( iCurSam << 4 ); - } - break; - - case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = ( iCurSam << 8 ); - } - break; - - case ASIOSTInt16MSB: - // clang-format off -// NOT YET TESTED - // clang-format on - // flip bits - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - ( (int16_t*) pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = - Flip16Bits ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - } - break; - - case ASIOSTInt24MSB: - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // because the bits are flipped, we do not have to perform the - // shift by 8 bits - int32_t iCurSam = static_cast ( Flip16Bits ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ) ); - - memcpy ( ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, &iCurSam, 3 ); - } - break; - - case ASIOSTInt32MSB: - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit and flip bits - int iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam << 16 ); - } - break; - - case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - const float fCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = - static_cast ( Flip32Bits ( static_cast ( fCurSam / _MAXSHORT ) ) ); - } - break; - - case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - const double fCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = - static_cast ( Flip64Bits ( static_cast ( fCurSam / _MAXSHORT ) ) ); - } - break; - - case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam ); - } - break; - - case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam << 2 ); - } - break; - - case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam << 4 ); - } - break; - - case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment - // clang-format off -// NOT YET TESTED - // clang-format on - for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) - { - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); - - static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam << 8 ); - } - break; - } - } - - // Finally if the driver supports the ASIOOutputReady() optimization, - // do it here, all data are in place ----------------------------------- - if ( pSound->bASIOPostOutput ) - { - ASIOOutputReady(); - } - } - pSound->ASIOMutex.unlock(); -} - -long CSound::asioMessages ( long selector, long, void*, double* ) -{ - long ret = 0; - - switch ( selector ) - { - case kAsioEngineVersion: - // return the supported ASIO version of the host application - ret = 2L; // Host ASIO implementation version, 2 or higher - break; - - // both messages might be send if the buffer size changes - case kAsioBufferSizeChange: - pSound->EmitReinitRequestSignal ( RS_ONLY_RESTART_AND_INIT ); - ret = 1L; // 1L if request is accepted or 0 otherwise - break; - - case kAsioResetRequest: - pSound->EmitReinitRequestSignal ( RS_RELOAD_RESTART_AND_INIT ); - ret = 1L; // 1L if request is accepted or 0 otherwise - break; - } - - return ret; -} - -int16_t CSound::Flip16Bits ( const int16_t iIn ) -{ - uint16_t iMask = ( 1 << 15 ); - int16_t iOut = 0; - - for ( unsigned int i = 0; i < 16; i++ ) - { - // copy current bit to correct position - iOut |= ( iIn & iMask ) ? 1 : 0; - - // shift out value and mask by one bit - iOut <<= 1; - iMask >>= 1; - } - - return iOut; -} - -int32_t CSound::Flip32Bits ( const int32_t iIn ) -{ - uint32_t iMask = ( static_cast ( 1 ) << 31 ); - int32_t iOut = 0; - - for ( unsigned int i = 0; i < 32; i++ ) - { - // copy current bit to correct position - iOut |= ( iIn & iMask ) ? 1 : 0; - - // shift out value and mask by one bit - iOut <<= 1; - iMask >>= 1; - } - - return iOut; -} - -int64_t CSound::Flip64Bits ( const int64_t iIn ) -{ - uint64_t iMask = ( static_cast ( 1 ) << 63 ); - int64_t iOut = 0; - - for ( unsigned int i = 0; i < 64; i++ ) - { - // copy current bit to correct position - iOut |= ( iIn & iMask ) ? 1 : 0; - - // shift out value and mask by one bit - iOut <<= 1; - iMask >>= 1; - } - - return iOut; -} + * + * Author(s): + * Volker Fischer + * + * Description: + * Sound card interface for Windows operating systems + * + ****************************************************************************** + * + * 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 "sound.h" + +/* Implementation *************************************************************/ +// external references +extern AsioDrivers* asioDrivers; +bool loadAsioDriver ( char* name ); + +// pointer to our sound object +CSound* pSound; + +/******************************************************************************\ +* Common * +\******************************************************************************/ +QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDriverSetup ) +{ + // find and load driver + int iDriverIdx = INVALID_INDEX; // initialize with an invalid index + + for ( int i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ ) + { + if ( strDriverName.compare ( cDriverNames[i] ) == 0 ) + { + iDriverIdx = i; + } + } + + // if the selected driver was not found, return an error message + if ( iDriverIdx == INVALID_INDEX ) + { + return tr ( "The selected audio device is no longer present in the system. Please check your audio device." ); + } + + // Save number of channels from last driver + // Need to save these (but not the driver name) as CheckDeviceCapabilities() overwrites them + long lNumInChanPrev = lNumInChan; + long lNumOutChanPrev = lNumOutChan; + + loadAsioDriver ( cDriverNames[iDriverIdx] ); + + // According to the docs, driverInfo.asioVersion and driverInfo.sysRef + // should be set, but we haven't being doing that and it seems to work + // okay... + memset ( &driverInfo, 0, sizeof driverInfo ); + + if ( ASIOInit ( &driverInfo ) != ASE_OK ) + { + // clean up and return error string + asioDrivers->removeCurrentDriver(); + return tr ( "Couldn't initialise the audio driver. Check if your audio hardware is plugged in and verify your driver settings." ); + } + + // check device capabilities if it fulfills our requirements + const QString strStat = CheckDeviceCapabilities(); // also sets lNumInChan and lNumOutChan + + // check if device is capable + if ( strStat.isEmpty() ) + { + // Reset channel mapping if the sound card name has changed or the number of channels has changed + if ( ( strCurDevName.compare ( strDriverNames[iDriverIdx] ) != 0 ) || ( lNumInChanPrev != lNumInChan ) || ( lNumOutChanPrev != lNumOutChan ) ) + { + // In order to fix https://github.com/jamulussoftware/jamulus/issues/796 + // this code runs after a change in the ASIO driver (not when changing the ASIO input selection.) + + // mapping to the defaults (first two available channels) + ResetChannelMapping(); + + // store ID of selected driver if initialization was successful + strCurDevName = cDriverNames[iDriverIdx]; + } + } + else + { + // if requested, open ASIO driver setup in case of an error + if ( bOpenDriverSetup ) + { + OpenDriverSetup(); + QMessageBox::question ( nullptr, + APP_NAME, + "Are you done with your ASIO driver settings of " + GetDeviceName ( iDriverIdx ) + "?", + QMessageBox::Yes ); + } + + // driver cannot be used, clean up + asioDrivers->removeCurrentDriver(); + } + + return strStat; +} + +void CSound::UnloadCurrentDriver() +{ + // clean up ASIO stuff + if ( bRun ) + { + Stop(); + } + if ( bufferInfos[0].buffers[0] ) + { + ASIODisposeBuffers(); + bufferInfos[0].buffers[0] = NULL; + } + ASIOExit(); + asioDrivers->removeCurrentDriver(); +} + +QString CSound::CheckDeviceCapabilities() +{ + // This function checks if our required input/output channel + // properties are supported by the selected device. If the return + // string is empty, the device can be used, otherwise the error + // message is returned. + + // check the sample rate + const ASIOError CanSaRateReturn = ASIOCanSampleRate ( SYSTEM_SAMPLE_RATE_HZ ); + + if ( ( CanSaRateReturn == ASE_NoClock ) || ( CanSaRateReturn == ASE_NotPresent ) ) + { + // return error string + return QString ( tr ( "The selected audio device is incompatible " + "since it doesn't support a sample rate of %1 Hz. Please select another " + "device." ) ) + .arg ( SYSTEM_SAMPLE_RATE_HZ ); + } + + // check if sample rate can be set + const ASIOError SetSaRateReturn = ASIOSetSampleRate ( SYSTEM_SAMPLE_RATE_HZ ); + + if ( ( SetSaRateReturn == ASE_NoClock ) || ( SetSaRateReturn == ASE_InvalidMode ) || ( SetSaRateReturn == ASE_NotPresent ) ) + { + // return error string + return QString ( tr ( "The current audio device configuration is incompatible " + "because the sample rate couldn't be set to %2 Hz. Please check for a hardware switch or " + "driver setting to set the sample rate manually and restart %1." ) ) + .arg ( APP_NAME ) + .arg ( SYSTEM_SAMPLE_RATE_HZ ); + } + + // check the number of available channels + ASIOGetChannels ( &lNumInChan, &lNumOutChan ); + + if ( ( lNumInChan < NUM_IN_OUT_CHANNELS ) || ( lNumOutChan < NUM_IN_OUT_CHANNELS ) ) + { + // return error string + return QString ( tr ( "The selected audio device is incompatible since it doesn't support " + "%1 in/out channels. Please select another device or configuration." ) ) + .arg ( NUM_IN_OUT_CHANNELS ); + } + + // clip number of input/output channels to our maximum + if ( lNumInChan > MAX_NUM_IN_OUT_CHANNELS ) + { + lNumInChan = MAX_NUM_IN_OUT_CHANNELS; + } + if ( lNumOutChan > MAX_NUM_IN_OUT_CHANNELS ) + { + lNumOutChan = MAX_NUM_IN_OUT_CHANNELS; + } + + // query channel infos for all available input channels + bool bInputChMixingSupported = true; + + for ( int i = 0; i < lNumInChan; i++ ) + { + // setup for input channels + channelInfosInput[i].isInput = ASIOTrue; + channelInfosInput[i].channel = i; + + ASIOGetChannelInfo ( &channelInfosInput[i] ); + + // Check supported sample formats. + // Actually, it would be enough to have at least two channels which + // support the required sample format. But since we have support for + // all known sample types, the following check should always pass and + // therefore we throw the error message on any channel which does not + // fulfill the sample format requirement (quick hack solution). + if ( !CheckSampleTypeSupported ( channelInfosInput[i].type ) ) + { + // return error string + return tr ( "The selected audio device is incompatible since " + "the required audio sample format isn't available. Please use another device." ); + } + + // store the name of the channel and check if channel mixing is supported + channelInputName[i] = channelInfosInput[i].name; + + if ( !CheckSampleTypeSupportedForCHMixing ( channelInfosInput[i].type ) ) + { + bInputChMixingSupported = false; + } + } + + // query channel infos for all available output channels + for ( int i = 0; i < lNumOutChan; i++ ) + { + // setup for output channels + channelInfosOutput[i].isInput = ASIOFalse; + channelInfosOutput[i].channel = i; + + ASIOGetChannelInfo ( &channelInfosOutput[i] ); + + // Check supported sample formats. + // Actually, it would be enough to have at least two channels which + // support the required sample format. But since we have support for + // all known sample types, the following check should always pass and + // therefore we throw the error message on any channel which does not + // fulfill the sample format requirement (quick hack solution). + if ( !CheckSampleTypeSupported ( channelInfosOutput[i].type ) ) + { + // return error string + return tr ( "The selected audio device is incompatible since " + "the required audio sample format isn't available. Please use another device." ); + } + } + + // special case with 4 input channels: support adding channels + if ( ( lNumInChan == 4 ) && bInputChMixingSupported ) + { + // add four mixed channels (i.e. 4 normal, 4 mixed channels) + lNumInChanPlusAddChan = 8; + + for ( int iCh = 0; iCh < lNumInChanPlusAddChan; iCh++ ) + { + int iSelCH, iSelAddCH; + + GetSelCHAndAddCH ( iCh, lNumInChan, iSelCH, iSelAddCH ); + + if ( iSelAddCH >= 0 ) + { + // for mixed channels, show both audio channel names to be mixed + channelInputName[iCh] = channelInputName[iSelCH] + " + " + channelInputName[iSelAddCH]; + } + } + } + else + { + // regular case: no mixing input channels used + lNumInChanPlusAddChan = lNumInChan; + } + + // everything is ok, return empty string for "no error" case + return ""; +} + +void CSound::SetLeftInputChannel ( const int iNewChan ) +{ + // apply parameter after input parameter check + if ( ( iNewChan >= 0 ) && ( iNewChan < lNumInChanPlusAddChan ) ) + { + vSelectedInputChannels[0] = iNewChan; + } +} + +void CSound::SetRightInputChannel ( const int iNewChan ) +{ + // apply parameter after input parameter check + if ( ( iNewChan >= 0 ) && ( iNewChan < lNumInChanPlusAddChan ) ) + { + vSelectedInputChannels[1] = iNewChan; + } +} + +void CSound::SetLeftOutputChannel ( const int iNewChan ) +{ + // apply parameter after input parameter check + if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) ) + { + vSelectedOutputChannels[0] = iNewChan; + } +} + +void CSound::SetRightOutputChannel ( const int iNewChan ) +{ + // apply parameter after input parameter check + if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) ) + { + vSelectedOutputChannels[1] = iNewChan; + } +} + +int CSound::GetActualBufferSize ( const int iDesiredBufferSizeMono ) +{ + int iActualBufferSizeMono; + + // query the usable buffer sizes + ASIOGetBufferSize ( &HWBufferInfo.lMinSize, &HWBufferInfo.lMaxSize, &HWBufferInfo.lPreferredSize, &HWBufferInfo.lGranularity ); + + // clang-format off +/* +// TEST +#include +QMessageBox::information ( 0, "APP_NAME", QString("lMinSize: %1, lMaxSize: %2, lPreferredSize: %3, lGranularity: %4"). + arg(HWBufferInfo.lMinSize).arg(HWBufferInfo.lMaxSize).arg(HWBufferInfo.lPreferredSize).arg(HWBufferInfo.lGranularity) ); +_exit(1); +*/ + // clang-format on + + // clang-format off +// TODO see https://github.com/EddieRingle/portaudio/blob/master/src/hostapi/asio/pa_asio.cpp#L1654 (SelectHostBufferSizeForUnspecifiedUserFramesPerBuffer) + // clang-format on + + // calculate "nearest" buffer size and set internal parameter accordingly + // first check minimum and maximum values + if ( iDesiredBufferSizeMono <= HWBufferInfo.lMinSize ) + { + iActualBufferSizeMono = HWBufferInfo.lMinSize; + } + else + { + if ( iDesiredBufferSizeMono >= HWBufferInfo.lMaxSize ) + { + iActualBufferSizeMono = HWBufferInfo.lMaxSize; + } + else + { + // ASIO SDK 2.2: "Notes: When minimum and maximum buffer size are + // equal, the preferred buffer size has to be the same value as + // well; granularity should be 0 in this case." + if ( HWBufferInfo.lMinSize == HWBufferInfo.lMaxSize ) + { + iActualBufferSizeMono = HWBufferInfo.lMinSize; + } + else + { + if ( ( HWBufferInfo.lGranularity < -1 ) || ( HWBufferInfo.lGranularity == 0 ) ) + { + // Special case (seen for EMU audio cards): granularity is + // zero or less than zero (make sure to exclude the special + // case of -1). + // There is no definition of this case in the ASIO SDK + // document. We assume here that all buffer sizes in between + // minimum and maximum buffer sizes are allowed. + iActualBufferSizeMono = iDesiredBufferSizeMono; + } + else + { + // General case -------------------------------------------- + // initialization + int iTrialBufSize = HWBufferInfo.lMinSize; + int iLastTrialBufSize = HWBufferInfo.lMinSize; + bool bSizeFound = false; + + // test loop + while ( ( iTrialBufSize <= HWBufferInfo.lMaxSize ) && ( !bSizeFound ) ) + { + if ( iTrialBufSize >= iDesiredBufferSizeMono ) + { + // test which buffer size fits better: the old one or the + // current one + if ( ( iTrialBufSize - iDesiredBufferSizeMono ) > ( iDesiredBufferSizeMono - iLastTrialBufSize ) ) + { + iTrialBufSize = iLastTrialBufSize; + } + + // exit while loop + bSizeFound = true; + } + + if ( !bSizeFound ) + { + // store old trial buffer size + iLastTrialBufSize = iTrialBufSize; + + // increment trial buffer size (check for special + // case first) + if ( HWBufferInfo.lGranularity == -1 ) + { + // special case: buffer sizes are a power of 2 + iTrialBufSize *= 2; + } + else + { + iTrialBufSize += HWBufferInfo.lGranularity; + } + } + } + + // clip trial buffer size (it may happen in the while + // routine that "iTrialBufSize" is larger than "lMaxSize" in + // case "lMaxSize - lMinSize" is not divisible by the + // granularity) + if ( iTrialBufSize > HWBufferInfo.lMaxSize ) + { + iTrialBufSize = HWBufferInfo.lMaxSize; + } + + // set ASIO buffer size + iActualBufferSizeMono = iTrialBufSize; + } + } + } + } + + return iActualBufferSizeMono; +} + +int CSound::Init ( const int iNewPrefMonoBufferSize ) +{ + ASIOMutex.lock(); // get mutex lock + { + // get the actual sound card buffer size which is supported + // by the audio hardware + iASIOBufferSizeMono = GetActualBufferSize ( iNewPrefMonoBufferSize ); + + // init base class + CSoundBase::Init ( iASIOBufferSizeMono ); + + // set internal buffer size value and calculate stereo buffer size + iASIOBufferSizeStereo = 2 * iASIOBufferSizeMono; + + // set the sample rate + ASIOSetSampleRate ( SYSTEM_SAMPLE_RATE_HZ ); + + // create memory for intermediate audio buffer + vecsMultChanAudioSndCrd.Init ( iASIOBufferSizeStereo ); + + // create and activate ASIO buffers (buffer size in samples), + // dispose old buffers (if any) + ASIODisposeBuffers(); + + // prepare input channels + for ( int i = 0; i < lNumInChan; i++ ) + { + bufferInfos[i].isInput = ASIOTrue; + bufferInfos[i].channelNum = i; + bufferInfos[i].buffers[0] = 0; + bufferInfos[i].buffers[1] = 0; + } + + // prepare output channels + for ( int i = 0; i < lNumOutChan; i++ ) + { + bufferInfos[lNumInChan + i].isInput = ASIOFalse; + bufferInfos[lNumInChan + i].channelNum = i; + bufferInfos[lNumInChan + i].buffers[0] = 0; + bufferInfos[lNumInChan + i].buffers[1] = 0; + } + + ASIOCreateBuffers ( bufferInfos, lNumInChan + lNumOutChan, iASIOBufferSizeMono, &asioCallbacks ); + + // query the latency of the driver + long lInputLatency = 0; + long lOutputLatency = 0; + + if ( ASIOGetLatencies ( &lInputLatency, &lOutputLatency ) != ASE_NotPresent ) + { + // add the input and output latencies (returned in number of + // samples) and calculate the time in ms + fInOutLatencyMs = ( static_cast ( lInputLatency ) + lOutputLatency ) * 1000 / SYSTEM_SAMPLE_RATE_HZ; + } + else + { + // no latency available + fInOutLatencyMs = 0.0f; + } + + // check whether the driver requires the ASIOOutputReady() optimization + // (can be used by the driver to reduce output latency by one block) + bASIOPostOutput = ( ASIOOutputReady() == ASE_OK ); + } + ASIOMutex.unlock(); + + return iASIOBufferSizeMono; +} + +void CSound::Start() +{ + // start audio + ASIOStart(); + + // call base class + CSoundBase::Start(); +} + +void CSound::Stop() +{ + // stop audio + ASIOStop(); + + // call base class + CSoundBase::Stop(); + + // make sure the working thread is actually done + // (by checking the locked state) + if ( ASIOMutex.tryLock ( 5000 ) ) + { + ASIOMutex.unlock(); + } +} + +CSound::CSound ( void ( *fpNewCallback ) ( CVector& psData, void* arg ), + void* arg, + const QString& strMIDISetup, + const bool, + const QString& ) : + CSoundBase ( "ASIO", fpNewCallback, arg, strMIDISetup ), + lNumInChan ( 0 ), + lNumInChanPlusAddChan ( 0 ), + lNumOutChan ( 0 ), + fInOutLatencyMs ( 0.0f ), // "0.0" means that no latency value is available + vSelectedInputChannels ( NUM_IN_OUT_CHANNELS ), + vSelectedOutputChannels ( NUM_IN_OUT_CHANNELS ) +{ + int i; + + // init pointer to our sound object + pSound = this; + + // We assume NULL'd pointers in this structure indicate that buffers are not + // allocated yet (see UnloadCurrentDriver). + memset ( bufferInfos, 0, sizeof bufferInfos ); + + // get available ASIO driver names in system + for ( i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ ) + { + // allocate memory for driver names + cDriverNames[i] = new char[32]; + } + + char cDummyName[] = "dummy"; + loadAsioDriver ( cDummyName ); // to initialize external object + lNumDevs = asioDrivers->getDriverNames ( cDriverNames, MAX_NUMBER_SOUND_CARDS ); + + // in case we do not have a driver available, throw error + if ( lNumDevs == 0 ) + { + throw CGenErr ( "" + tr ( "No ASIO audio device driver found." ) + "

" + + QString ( tr ( "Please install an ASIO driver before running %1. " + "If you own a device with ASIO support, install its official ASIO driver. " + "If not, you'll need to install a universal driver like ASIO4ALL." ) ) + .arg ( APP_NAME ) ); + } + asioDrivers->removeCurrentDriver(); + + // copy driver names to base class but internally we still have to use + // the char* variable because of the ASIO API :-( + for ( i = 0; i < lNumDevs; i++ ) + { + strDriverNames[i] = cDriverNames[i]; + } + + // init device index as not initialized (invalid) + strCurDevName = ""; + + // init channel mapping + ResetChannelMapping(); + + // set up the asioCallback structure + asioCallbacks.bufferSwitch = &bufferSwitch; + asioCallbacks.sampleRateDidChange = &sampleRateChanged; + asioCallbacks.asioMessage = &asioMessages; + asioCallbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfo; +} + +void CSound::ResetChannelMapping() +{ + // init selected channel numbers with defaults: use first available + // channels for input and output + vSelectedInputChannels[0] = 0; + vSelectedInputChannels[1] = 1; + vSelectedOutputChannels[0] = 0; + vSelectedOutputChannels[1] = 1; +} + +// ASIO callbacks ------------------------------------------------------------- +ASIOTime* CSound::bufferSwitchTimeInfo ( ASIOTime*, long index, ASIOBool processNow ) +{ + bufferSwitch ( index, processNow ); + return 0L; +} + +bool CSound::CheckSampleTypeSupported ( const ASIOSampleType SamType ) +{ + // check for supported sample types + return ( ( SamType == ASIOSTInt16LSB ) || ( SamType == ASIOSTInt24LSB ) || ( SamType == ASIOSTInt32LSB ) || ( SamType == ASIOSTFloat32LSB ) || + ( SamType == ASIOSTFloat64LSB ) || ( SamType == ASIOSTInt32LSB16 ) || ( SamType == ASIOSTInt32LSB18 ) || + ( SamType == ASIOSTInt32LSB20 ) || ( SamType == ASIOSTInt32LSB24 ) || ( SamType == ASIOSTInt16MSB ) || ( SamType == ASIOSTInt24MSB ) || + ( SamType == ASIOSTInt32MSB ) || ( SamType == ASIOSTFloat32MSB ) || ( SamType == ASIOSTFloat64MSB ) || ( SamType == ASIOSTInt32MSB16 ) || + ( SamType == ASIOSTInt32MSB18 ) || ( SamType == ASIOSTInt32MSB20 ) || ( SamType == ASIOSTInt32MSB24 ) ); +} + +bool CSound::CheckSampleTypeSupportedForCHMixing ( const ASIOSampleType SamType ) +{ + // check for supported sample types for audio channel mixing (see bufferSwitch) + return ( ( SamType == ASIOSTInt16LSB ) || ( SamType == ASIOSTInt24LSB ) || ( SamType == ASIOSTInt32LSB ) ); +} + +void CSound::bufferSwitch ( long index, ASIOBool ) +{ + int iCurSample; + + // get references to class members + int& iASIOBufferSizeMono = pSound->iASIOBufferSizeMono; + CVector& vecsMultChanAudioSndCrd = pSound->vecsMultChanAudioSndCrd; + + // perform the processing for input and output + pSound->ASIOMutex.lock(); // get mutex lock + { + // CAPTURE ------------------------------------------------------------- + for ( int i = 0; i < NUM_IN_OUT_CHANNELS; i++ ) + { + int iSelCH, iSelAddCH; + + GetSelCHAndAddCH ( pSound->vSelectedInputChannels[i], pSound->lNumInChan, iSelCH, iSelAddCH ); + + // copy new captured block in thread transfer buffer (copy + // mono data interleaved in stereo buffer) + switch ( pSound->channelInfosInput[iSelCH].type ) + { + case ASIOSTInt16LSB: + { + // no type conversion required, just copy operation + int16_t* pASIOBuf = static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ); + + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = pASIOBuf[iCurSample]; + } + + if ( iSelAddCH >= 0 ) + { + // mix input channels case: + int16_t* pASIOBufAdd = static_cast ( pSound->bufferInfos[iSelAddCH].buffers[index] ); + + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = + Float2Short ( (float) vecsMultChanAudioSndCrd[2 * iCurSample + i] + (float) pASIOBufAdd[iCurSample] ); + } + } + break; + } + + case ASIOSTInt24LSB: + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + int iCurSam = 0; + memcpy ( &iCurSam, ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, 3 ); + iCurSam >>= 8; + + vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( iCurSam ); + } + + if ( iSelAddCH >= 0 ) + { + // mix input channels case: + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + int iCurSam = 0; + memcpy ( &iCurSam, ( (char*) pSound->bufferInfos[iSelAddCH].buffers[index] ) + iCurSample * 3, 3 ); + iCurSam >>= 8; + + vecsMultChanAudioSndCrd[2 * iCurSample + i] = + Float2Short ( (float) vecsMultChanAudioSndCrd[2 * iCurSample + i] + (float) static_cast ( iCurSam ) ); + } + } + break; + + case ASIOSTInt32LSB: + { + int32_t* pASIOBuf = static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ); + + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( pASIOBuf[iCurSample] >> 16 ); + } + + if ( iSelAddCH >= 0 ) + { + // mix input channels case: + int32_t* pASIOBufAdd = static_cast ( pSound->bufferInfos[iSelAddCH].buffers[index] ); + + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = Float2Short ( (float) vecsMultChanAudioSndCrd[2 * iCurSample + i] + + (float) static_cast ( pASIOBufAdd[iCurSample] >> 16 ) ); + } + } + break; + } + + case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = + static_cast ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] * _MAXSHORT ); + } + break; + + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = + static_cast ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] * _MAXSHORT ); + } + break; + + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = + static_cast ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0xFFFF ); + } + break; + + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = + static_cast ( ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0x3FFFF ) >> 2 ); + } + break; + + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = + static_cast ( ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0xFFFFF ) >> 4 ); + } + break; + + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = + static_cast ( ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0xFFFFFF ) >> 8 ); + } + break; + + case ASIOSTInt16MSB: + // clang-format off +// NOT YET TESTED + // clang-format on + // flip bits + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = + Flip16Bits ( ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ) )[iCurSample] ); + } + break; + + case ASIOSTInt24MSB: + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // because the bits are flipped, we do not have to perform the + // shift by 8 bits + int iCurSam = 0; + memcpy ( &iCurSam, ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, 3 ); + + vecsMultChanAudioSndCrd[2 * iCurSample + i] = Flip16Bits ( static_cast ( iCurSam ) ); + } + break; + + case ASIOSTInt32MSB: + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // flip bits and convert to 16 bit + vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( + Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) >> 16 ); + } + break; + + case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( + static_cast ( Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) ) * + _MAXSHORT ); + } + break; + + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( + static_cast ( Flip64Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) ) * + _MAXSHORT ); + } + break; + + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( + Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0xFFFF ); + } + break; + + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( + ( Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0x3FFFF ) >> 2 ); + } + break; + + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( + ( Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0xFFFFF ) >> 4 ); + } + break; + + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast ( + ( Flip32Bits ( static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0xFFFFFF ) >> 8 ); + } + break; + } + } + + // call processing callback function + pSound->ProcessCallback ( vecsMultChanAudioSndCrd ); + + // PLAYBACK ------------------------------------------------------------ + for ( int i = 0; i < NUM_IN_OUT_CHANNELS; i++ ) + { + const int iSelCH = pSound->lNumInChan + pSound->vSelectedOutputChannels[i]; + + // copy data from sound card in output buffer (copy + // interleaved stereo data in mono sound card buffer) + switch ( pSound->channelInfosOutput[pSound->vSelectedOutputChannels[i]].type ) + { + case ASIOSTInt16LSB: + { + // no type conversion required, just copy operation + int16_t* pASIOBuf = static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ); + + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + pASIOBuf[iCurSample] = vecsMultChanAudioSndCrd[2 * iCurSample + i]; + } + break; + } + + case ASIOSTInt24LSB: + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // convert current sample in 24 bit format + int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + iCurSam <<= 8; + + memcpy ( ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, &iCurSam, 3 ); + } + break; + + case ASIOSTInt32LSB: + { + int32_t* pASIOBuf = static_cast ( pSound->bufferInfos[iSelCH].buffers[index] ); + + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + pASIOBuf[iCurSample] = ( iCurSam << 16 ); + } + break; + } + + case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + const float fCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = fCurSam / _MAXSHORT; + } + break; + + case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + const double fCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = fCurSam / _MAXSHORT; + } + break; + + case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = iCurSam; + } + break; + + case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = ( iCurSam << 2 ); + } + break; + + case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = ( iCurSam << 4 ); + } + break; + + case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = ( iCurSam << 8 ); + } + break; + + case ASIOSTInt16MSB: + // clang-format off +// NOT YET TESTED + // clang-format on + // flip bits + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + ( (int16_t*) pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = + Flip16Bits ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + } + break; + + case ASIOSTInt24MSB: + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // because the bits are flipped, we do not have to perform the + // shift by 8 bits + int32_t iCurSam = static_cast ( Flip16Bits ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ) ); + + memcpy ( ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, &iCurSam, 3 ); + } + break; + + case ASIOSTInt32MSB: + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // convert to 32 bit and flip bits + int iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam << 16 ); + } + break; + + case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + const float fCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = + static_cast ( Flip32Bits ( static_cast ( fCurSam / _MAXSHORT ) ) ); + } + break; + + case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + const double fCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = + static_cast ( Flip64Bits ( static_cast ( fCurSam / _MAXSHORT ) ) ); + } + break; + + case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam ); + } + break; + + case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam << 2 ); + } + break; + + case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam << 4 ); + } + break; + + case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment + // clang-format off +// NOT YET TESTED + // clang-format on + for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ ) + { + // convert to 32 bit + const int32_t iCurSam = static_cast ( vecsMultChanAudioSndCrd[2 * iCurSample + i] ); + + static_cast ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] = Flip32Bits ( iCurSam << 8 ); + } + break; + } + } + + // Finally if the driver supports the ASIOOutputReady() optimization, + // do it here, all data are in place ----------------------------------- + if ( pSound->bASIOPostOutput ) + { + ASIOOutputReady(); + } + } + pSound->ASIOMutex.unlock(); +} + +long CSound::asioMessages ( long selector, long, void*, double* ) +{ + long ret = 0; + + switch ( selector ) + { + case kAsioEngineVersion: + // return the supported ASIO version of the host application + ret = 2L; // Host ASIO implementation version, 2 or higher + break; + + // both messages might be send if the buffer size changes + case kAsioBufferSizeChange: + pSound->EmitReinitRequestSignal ( RS_ONLY_RESTART_AND_INIT ); + ret = 1L; // 1L if request is accepted or 0 otherwise + break; + + case kAsioResetRequest: + pSound->EmitReinitRequestSignal ( RS_RELOAD_RESTART_AND_INIT ); + ret = 1L; // 1L if request is accepted or 0 otherwise + break; + } + + return ret; +} + +int16_t CSound::Flip16Bits ( const int16_t iIn ) +{ + uint16_t iMask = ( 1 << 15 ); + int16_t iOut = 0; + + for ( unsigned int i = 0; i < 16; i++ ) + { + // copy current bit to correct position + iOut |= ( iIn & iMask ) ? 1 : 0; + + // shift out value and mask by one bit + iOut <<= 1; + iMask >>= 1; + } + + return iOut; +} + +int32_t CSound::Flip32Bits ( const int32_t iIn ) +{ + uint32_t iMask = ( static_cast ( 1 ) << 31 ); + int32_t iOut = 0; + + for ( unsigned int i = 0; i < 32; i++ ) + { + // copy current bit to correct position + iOut |= ( iIn & iMask ) ? 1 : 0; + + // shift out value and mask by one bit + iOut <<= 1; + iMask >>= 1; + } + + return iOut; +} + +int64_t CSound::Flip64Bits ( const int64_t iIn ) +{ + uint64_t iMask = ( static_cast ( 1 ) << 63 ); + int64_t iOut = 0; + + for ( unsigned int i = 0; i < 64; i++ ) + { + // copy current bit to correct position + iOut |= ( iIn & iMask ) ? 1 : 0; + + // shift out value and mask by one bit + iOut <<= 1; + iMask >>= 1; + } + + return iOut; +}