New upstream version 2.2.2+ds
authorIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>
Sun, 11 Feb 2024 09:57:36 +0000 (10:57 +0100)
committerIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>
Sun, 11 Feb 2024 09:57:36 +0000 (10:57 +0100)
29 files changed:
LICENSE.md
docs/About/License.md
docs/Build/Linux.md
docs/changelog.yml
releases/edge/mac-manifests.json
releases/edge/win-manifests.json
releases/stable/linux-manifests.json
releases/stable/mac-manifests.json
releases/stable/win-manifests.json
src/AudioInterface.cpp
src/AudioInterface.h
src/JackAudioInterface.cpp
src/Regulator.cpp
src/RtAudioInterface.cpp
src/UdpDataProtocol.cpp
src/gui/Browse.qml
src/gui/Footer.qml
src/gui/Permissions.qml
src/gui/Recommendations.qml
src/gui/Setup.qml
src/gui/Studio.qml
src/gui/about.cpp
src/gui/virtualstudio.cpp
src/gui/virtualstudio.h
src/gui/vs.qml
src/gui/vsAudio.cpp
src/gui/vsAudio.h
src/gui/vsDeeplink.cpp
src/jacktrip_globals.h

index 513cb6c06b8bfbec527b484c34b4e12d3ad206c7..0169495062f0313a9f11c279c4274ad3b738ec88 100644 (file)
@@ -16,6 +16,9 @@ JackTrip uses Qt library throughout the project so the resulting binaries are
 also subject to Qt's license. The builds provided on GitHub's Releases page use
 open source distribution of Qt, licensed under LGPL.
 
+Windows builds of JackTrip may include support for ASIO.
+ASIO is a trademark and software of Steinberg Media Technologies GmbH.
+
 Using JackTrip to join Virtual Studios on Windows computers may use AVC (h264)
 video encoders and decoders subject to the AVC Patent Portfolio License.
 
index 9535e118d2fc4835560efcec457e67b96b6d9799..159323bfa86ab1e0cacd82df5666c9d22ec5a4cb 100644 (file)
@@ -11,4 +11,8 @@
 
 ---
 ### LGPL License
---8<-- "LICENSES/LGPL-3.0-only.txt"
\ No newline at end of file
+--8<-- "LICENSES/LGPL-3.0-only.txt"
+
+---
+### AVC License
+--8<-- "LICENSES/AVC.txt"
\ No newline at end of file
index c8b54b07744a1d8029931155174aca1578f89da6..985228dbda53fe1ee95eba2498456eb10dff3194 100644 (file)
@@ -43,6 +43,7 @@ apt install qtbase5-dev qtbase5-dev-tools qtchooser qt5-qmake qttools5-dev libqt
 ### Ubuntu and Debian/Raspbian (Qt6)
 ```sh
 apt install --no-install-recommends build-essential autoconf automake libtool make libjack-jackd2-dev git help2man libclang-dev libdbus-1-dev libdbus-1-dev python3-jinja2
+apt install -y libqt6core6 libqt6gui6 libqt6network6 libqt6widgets6 libqt6qml6 libqt6qmlcore6 libqt6quick6 libqt6quickcontrols2-6 libqt6svg6  libqt6webchannel6 libqt6webengine6-data libqt6webenginecore6 libqt6webenginecore6-bin libqt6webenginequick6 libqt6websockets6 libqt6shadertools6 qt6-qpa-plugins qml6-module-qtquick-controls qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qt5compat-graphicaleffects qml6-module-qtwebchannel qml6-module-qtwebengine qml6-module-qtquick-window
 apt install qt6-base-dev qt6-base-dev-tools qmake6 qt6-tools-dev qt6-declarative-dev qt6-webengine-dev qt6-webview-dev qt6-webview-plugins libqt6svg6-dev libqt6websockets6-dev libqt6core5compat6-dev libqt6shadertools6-dev libgl1-mesa-dev
 # for GUI builds
 apt install libfreetype6-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev libx11-xcb-dev libdrm-dev libglu1-mesa-dev libwayland-dev libwayland-egl1-mesa libgles2-mesa-dev libwayland-server0 libwayland-egl-backend-dev libxcb1-dev libxext-dev libfontconfig1-dev libxrender-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev '^libxcb.*-dev' libxcb-render-util0-dev libxcomposite-dev libgtk-3-dev
index 0d888b45ab95069dbe8db8e7de0506546fed3b1c..ff94aa1636bc3ffe91b43c2783650f8f347ac875 100644 (file)
@@ -1,3 +1,10 @@
+- Version: "2.2.2"
+  Date: 2024-02-09
+  Description:
+  - (updated) VS Mode updated network connection thresholds
+  - (updated) VS Mode improved sample rate flexibility for Windows
+  - (fixed) VS Mode inconsistent deep link handling on Windows
+  - (fixed) Throttle console errors for UDP waiting too long
 - Version: "2.2.1"
   Date: 2024-01-29
   Description:
index 09fa2557703c35d357b1decb2b8740cde88109de..618dcf970f76bc6862f78739ee225a1209b5d246 100644 (file)
@@ -1,6 +1,36 @@
 {
   "app_name": "JackTrip",
   "releases": [
+    {
+      "version": "2.2.1",
+      "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.2.1",
+      "download": {
+        "date": "2024-01-30T00:00:00Z",
+        "url": "https://files.jacktrip.org/app-builds/JackTrip-v2.2.1-macOS-x64-signed-installer.pkg",
+        "downloadSize": "177373978",
+        "sha256": "d02e5de0cee389ee39c789ad6fe8859823944cf2c6af15f8d80249f3134f4653"
+      }
+    },
+    {
+      "version": "2.2.0",
+      "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.2.0",
+      "download": {
+        "date": "2024-01-22T00:00:00Z",
+        "url": "https://files.jacktrip.org/app-builds/JackTrip-v2.2.0-macOS-x64-signed-installer.pkg",
+        "downloadSize": "177373618",
+        "sha256": "ecef1ac2ae1fd3f2da40017f5da1fca6d966946a016ba80116ca960d88f04a53"
+      }
+    },
+    {
+      "version": "2.2.0-beta1",
+      "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.2.0-beta1",
+      "download": {
+        "date": "2024-01-17T00:00:00Z",
+        "url": "https://files.jacktrip.org/app-builds/JackTrip-v2.2.0-beta1-macOS-x64-signed-installer.pkg",
+        "downloadSize": "177373175",
+        "sha256": "6512e524d022eebe5b2e928d008c0fdb85dbaa453179952ee232f0d73d0d68eb"
+      }
+    },
     {
       "version": "2.1.0",
       "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.1.0",
index 17eb017aef31367510005b6257fb2dc86dfabcdf..b44d3a13f5552e672026b5d867854b14b69dd6b5 100644 (file)
@@ -1,6 +1,36 @@
 {
   "app_name": "JackTrip",
   "releases": [
+    {
+      "version": "2.2.1",
+      "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.2.1",
+      "download": {
+        "date": "2024-01-30T00:00:00Z",
+        "url": "https://files.jacktrip.org/app-builds/JackTrip-v2.2.1-Windows-x64-signed-installer.msi",
+        "downloadSize": "108511232",
+        "sha256": "193825d24745cd5a052ae57f1345b02924fc269aa69324428e7a177b9c58aa05"
+      }
+    },
+    {
+      "version": "2.2.0",
+      "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.2.0",
+      "download": {
+        "date": "2024-01-22T00:00:00Z",
+        "url": "https://files.jacktrip.org/app-builds/JackTrip-v2.2.0-Windows-x64-signed-installer.msi",
+        "downloadSize": "108511232",
+        "sha256": "92aeb6ba74fcb5cade48962aa4696a77fb7b2434622c22097cfa9da037b32fb3"
+      }
+    },
+    {
+      "version": "2.2.0-beta1",
+      "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.2.0-beta1",
+      "download": {
+        "date": "2024-01-17T00:00:00Z",
+        "url": "https://files.jacktrip.org/app-builds/JackTrip-v2.2.0-beta1-Windows-x64-signed-installer.msi",
+        "downloadSize": "108511232",
+        "sha256": "24401a0adaf8753f68d4303bda0d08fed35032168254ab7445766216cfb73980"
+      }
+    },
     {
       "version": "2.1.0",
       "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.1.0",
index 26ec828e25d945edac058c141d5ebaae8d8c6c7e..13c1bd7420cf24560972641fac23b74e5fbbeeb8 100644 (file)
@@ -1,6 +1,26 @@
 {
   "app_name": "JackTrip",
   "releases": [
+    {
+      "version": "2.2.1",
+      "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.2.1",
+      "download": {
+        "date": "2024-01-30T00:00:00Z",
+        "url": "https://files.jacktrip.org/app-builds/JackTrip-v2.2.1-Linux-x64-binary.zip",
+        "downloadSize": "1239788",
+        "sha256": "bfd986377b54c1ab84f16e0c0fd5ca61ed50e6cec281f7505e95d2b663af32f7"
+      }
+    },
+    {
+      "version": "2.2.0",
+      "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.2.0",
+      "download": {
+        "date": "2024-01-22T00:00:00Z",
+        "url": "https://files.jacktrip.org/app-builds/JackTrip-v2.2.0-Linux-x64-binary.zip",
+        "downloadSize": "1239784",
+        "sha256": "c5ce96f64ea204f1a17a951e9d39fd247e925fd21f55ae48a8bc27d6767cf675"
+      }
+    },
     {
       "version": "2.1.0",
       "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.1.0",
index 78ddfd405ea148daad2d0641bc9455f717ad10e5..e62442bb546c91b3d93543ce1bc84d2da977150d 100644 (file)
@@ -1,6 +1,26 @@
 {
   "app_name": "JackTrip",
   "releases": [
+    {
+      "version": "2.2.1",
+      "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.2.1",
+      "download": {
+        "date": "2024-01-30T00:00:00Z",
+        "url": "https://files.jacktrip.org/app-builds/JackTrip-v2.2.1-macOS-x64-signed-installer.pkg",
+        "downloadSize": "177373978",
+        "sha256": "d02e5de0cee389ee39c789ad6fe8859823944cf2c6af15f8d80249f3134f4653"
+      }
+    },
+    {
+      "version": "2.2.0",
+      "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.2.0",
+      "download": {
+        "date": "2024-01-22T00:00:00Z",
+        "url": "https://files.jacktrip.org/app-builds/JackTrip-v2.2.0-macOS-x64-signed-installer.pkg",
+        "downloadSize": "177373618",
+        "sha256": "ecef1ac2ae1fd3f2da40017f5da1fca6d966946a016ba80116ca960d88f04a53"
+      }
+    },
     {
       "version": "2.1.0",
       "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.1.0",
index 3e34349c259325ac7fa8458ac52aa5a8939b633e..0656ed81f8965d9e8483acedb40d734df02de7bb 100644 (file)
@@ -1,6 +1,26 @@
 {
   "app_name": "JackTrip",
   "releases": [
+    {
+      "version": "2.2.1",
+      "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.2.1",
+      "download": {
+        "date": "2024-01-30T00:00:00Z",
+        "url": "https://files.jacktrip.org/app-builds/JackTrip-v2.2.1-Windows-x64-signed-installer.msi",
+        "downloadSize": "108511232",
+        "sha256": "193825d24745cd5a052ae57f1345b02924fc269aa69324428e7a177b9c58aa05"
+      }
+    },
+    {
+      "version": "2.2.0",
+      "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.2.0",
+      "download": {
+        "date": "2024-01-22T00:00:00Z",
+        "url": "https://files.jacktrip.org/app-builds/JackTrip-v2.2.0-Windows-x64-signed-installer.msi",
+        "downloadSize": "108511232",
+        "sha256": "92aeb6ba74fcb5cade48962aa4696a77fb7b2434622c22097cfa9da037b32fb3"
+      }
+    },
     {
       "version": "2.1.0",
       "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v2.1.0",
index 1d7dc81cf2eeff2e0177f6a033313b3891e11165..0f684f146af6a1c5ee64fc76547036b44032cbf3 100644 (file)
@@ -95,15 +95,15 @@ AudioInterface::~AudioInterface()
         delete[] mAPInBuffer[i];
     }
 #endif  // endwhere
-    for (auto* i : qAsConst(mProcessPluginsFromNetwork)) {
+    for (auto* i : std::as_const(mProcessPluginsFromNetwork)) {
         i->disconnect();
         delete i;
     }
-    for (auto* i : qAsConst(mProcessPluginsToNetwork)) {
+    for (auto* i : std::as_const(mProcessPluginsToNetwork)) {
         i->disconnect();
         delete i;
     }
-    for (auto* i : qAsConst(mProcessPluginsToMonitor)) {
+    for (auto* i : std::as_const(mProcessPluginsToMonitor)) {
         i->disconnect();
         delete i;
     }
@@ -206,7 +206,7 @@ void AudioInterface::audioInputCallback(QVarLengthArray<sample_t*>& in_buffer,
 #endif  // not WAIR
 
     // process incoming signal from audio interface using process plugins
-    for (auto* p : qAsConst(mProcessPluginsToNetwork)) {
+    for (auto* p : std::as_const(mProcessPluginsToNetwork)) {
         if (p->getInited()) {
             p->compute(n_frames, in_buffer.data(), in_buffer.data());
         }
@@ -268,7 +268,7 @@ void AudioInterface::audioOutputCallback(QVarLengthArray<sample_t*>& out_buffer,
     /// with one. do it chaining outputs to inputs in the buffers. May need a tempo buffer
 
 #ifndef WAIR  // NOT WAIR:
-    for (auto* p : qAsConst(mProcessPluginsFromNetwork)) {
+    for (auto* p : std::as_const(mProcessPluginsFromNetwork)) {
         if (p->getInited()) {
             p->compute(n_frames, out_buffer.data(), out_buffer.data());
         }
@@ -728,17 +728,17 @@ void AudioInterface::initPlugins(bool verbose)
                       << ") at sampling rate " << mSampleRate << "\n";
         }
 
-        for (ProcessPlugin* plugin : qAsConst(mProcessPluginsFromNetwork)) {
+        for (ProcessPlugin* plugin : std::as_const(mProcessPluginsFromNetwork)) {
             plugin->setOutgoingToNetwork(false);
             plugin->updateNumChannels(nChansIn, nChansOut);
             plugin->init(mSampleRate, mBufferSizeInSamples);
         }
-        for (ProcessPlugin* plugin : qAsConst(mProcessPluginsToNetwork)) {
+        for (ProcessPlugin* plugin : std::as_const(mProcessPluginsToNetwork)) {
             plugin->setOutgoingToNetwork(true);
             plugin->updateNumChannels(nChansIn, nChansOut);
             plugin->init(mSampleRate, mBufferSizeInSamples);
         }
-        for (ProcessPlugin* plugin : qAsConst(mProcessPluginsToMonitor)) {
+        for (ProcessPlugin* plugin : std::as_const(mProcessPluginsToMonitor)) {
             plugin->setOutgoingToNetwork(false);
             plugin->updateNumChannels(nChansMon, nChansMon);
             plugin->init(mSampleRate, mBufferSizeInSamples);
index a9ee255ad1c456c6ae8aaa5571995cfb50ffb419..6ed62be074fe55ef5cae76cd12d8d0cea7f5ce81 100644 (file)
@@ -40,6 +40,7 @@
 
 #include <QVarLengthArray>
 #include <QVector>
+#include <functional>
 
 #include "AudioTester.h"
 #include "ProcessPlugin.h"
@@ -51,6 +52,9 @@ class JackTrip;
 
 // using namespace JackTripNamespace;
 
+// callback function for audio interface errors
+typedef std::function<void(const std::string& errorText)> AudioErrorCallback;
+
 /** \brief Base Class that provides an interface with audio
  */
 class AudioInterface
@@ -265,6 +269,7 @@ class AudioInterface
     virtual void setLoopBack(bool b) { mLoopBack = b; }
     virtual void enableBroadcastOutput() {}
     virtual void setAudioTesterP(AudioTester* atp) { mAudioTesterP = atp; }
+    void setErrorCallback(AudioErrorCallback c) { mErrorCallback = c; }
     //------------------------------------------------------------------
 
     //--------------GETTERS---------------------------------------------
@@ -367,6 +372,7 @@ class AudioInterface
     std::string mWarningHelpUrl;
     std::string mErrorHelpUrl;
     bool mHighLatencyFlag;
+    AudioErrorCallback mErrorCallback;
 };
 
 #endif  // __AUDIOINTERFACE_H__
index f638de23ee51f3645e5f2d2188b07f0ef2f7ae50..72218276be62274736d7f729c9feaaf02eaa18ed 100644 (file)
@@ -283,7 +283,11 @@ void JackAudioInterface::jackShutdown(jack_status_t /*code*/, const char* reason
         errorMsg += reason;
     }
     if (arg != nullptr) {
-        static_cast<JackAudioInterface*>(arg)->mErrorMsg = errorMsg;
+        JackAudioInterface* ifPtr = static_cast<JackAudioInterface*>(arg);
+        ifPtr->mErrorMsg          = errorMsg;
+        if (ifPtr->mErrorCallback) {
+            ifPtr->mErrorCallback(errorMsg);
+        }
     }
     std::cerr << errorMsg << std::endl;
     JackTrip::sAudioStopped = true;
index efb9c70f9c2f51aed03b5706d91d85691be2ab1f..a58d6e6afe97eb156157675381009215a90ce28d 100644 (file)
@@ -97,7 +97,6 @@ constexpr double AutoMax  = 250.0;  // msec bounds on insane IPI, like ethernet
 constexpr double AutoInitDur = 3000.0;  // kick in auto after this many msec
 constexpr double AutoInitValFactor =
     0.5;  // scale for initial mMsecTolerance during init phase if unspecified
-constexpr double MaxWaitTime = 30;  // msec
 
 // tweak
 constexpr int WindowDivisor   = 8;     // for faster auto tracking
@@ -505,7 +504,7 @@ PACKETOK : {
 UNDERRUN : {
     pullStat->plcUnderruns++;  // count late
     if ((mLastSeqNumOut == lastSeqNumIn)
-        && ((now - mIncomingTiming[mLastSeqNumOut]) > MaxWaitTime)) {
+        && ((now - mIncomingTiming[mLastSeqNumOut]) > gUdpWaitTimeout)) {
         goto ZERO_OUTPUT;
     }
     // "good underrun", not a stuck client
@@ -827,7 +826,7 @@ bool StdDev::tick()
 
     // discard measurements that exceed the max wait time
     // this prevents temporary outages from skewing jitter metrics
-    if (msElapsed > MaxWaitTime)
+    if (msElapsed > gUdpWaitTimeout)
         return false;
 
     if (ctr != window) {
index 41268f8d6fe0e9423672411a2c86c05da32434dc..fc1753874e0d1eed62dc1c5e59f9527f4449d432 100644 (file)
@@ -610,7 +610,11 @@ void RtAudioInterface::errorCallback(RtAudioErrorType errorType,
         errorMsg += errorText;
     }
     if (arg != nullptr) {
-        static_cast<RtAudioInterface*>(arg)->mErrorMsg = errorMsg;
+        RtAudioInterface* ifPtr = static_cast<RtAudioInterface*>(arg);
+        ifPtr->mErrorMsg        = errorText;
+        if (ifPtr->mErrorCallback) {
+            ifPtr->mErrorCallback(errorText);
+        }
     }
     std::cerr << errorMsg << std::endl;
     JackTrip::sAudioStopped = true;
index 73f975aa331c6d5089eecba9a11ea1976a964168..656fc85a80b6988f92dd33fb22d3d132d16e4397 100644 (file)
@@ -672,7 +672,7 @@ void UdpDataProtocol::run()
             // This QT method gave me a lot of trouble, so I replaced it with my own 'waitForReady'
             // that uses signals and slots and can also report with packets have not
             // arrive for a longer time
-            //timeout = UdpSocket.waitForReadyRead(30);
+            //timeout = UdpSocket.waitForReadyRead(gUdpWaitTimeout);
             //        timeout = cc unused!
 #if defined (MANUAL_POLL)
             waitForReady(60000); //60 seconds
@@ -757,7 +757,7 @@ void UdpDataProtocol::run()
 
         // Send exit packet (with 1 redundant packet).
         cout << "sending exit packet" << endl;
-        QByteArray exitPacket = QByteArray(mControlPacketSize, 0xff);
+        QByteArray exitPacket = QByteArray(mControlPacketSize, static_cast<char>(0xff));
         sendPacket(exitPacket.constData(), mControlPacketSize);
         sendPacket(exitPacket.constData(), mControlPacketSize);
         emit signalCeaseTransmission();
@@ -800,10 +800,12 @@ void UdpDataProtocol::waitForReady(int timeout_msec)
 //*******************************************************************************
 void UdpDataProtocol::printUdpWaitedTooLong(int wait_msec)
 {
-    int wait_time = 30;  // msec
-    if (!(wait_msec % wait_time)) {
-        std::cerr << "UDP waiting too long (more than " << wait_time << "ms) for "
-                  << mPeerAddress.toString().toStdString() << "..." << endl;
+    if (!(wait_msec % gUdpWaitTimeout)) {
+        // only log error once per gap in audio, rather than every 30ms
+        if (wait_msec <= gUdpWaitTimeout) {
+            std::cerr << "UDP waiting too long (more than " << gUdpWaitTimeout << "ms) for "
+                    << mPeerAddress.toString().toStdString() << "..." << endl;
+        }
         emit signalUdpWaitingTooLong();
     }
 }
index 18db84437862e68f1bb42059d0a3b18c03889556..d5bb6dd204997d8b08fc0f0032787f6c338fc938 100644 (file)
@@ -86,6 +86,7 @@ Item {
             connected: false
             studioId: modelData.id ? modelData.id : ""
             inviteKeyString: modelData.inviteKey ? modelData.inviteKey : ""
+            sampleRate: modelData.sampleRate
         }
 
         section { property: "modelData.type"; criteria: ViewSection.FullString; delegate: SectionHeading {} }
index 6b7664350936df7b9b67e502840e251164545e1b..18f82923343332c491cc295afc62752c346ec137 100644 (file)
@@ -10,6 +10,7 @@ Rectangle {
     color: backgroundColour
     clip: true
 
+    property string statsOrange: "#b26a00"
     property string connectionStateColor: getConnectionStateColor()
     property variant networkStatsText: getNetworkStatsText()
 
@@ -44,20 +45,15 @@ Rectangle {
         texts[1] = "<b>" + minRtt + " ms - " + maxRtt + " ms</b>, avg " + avgRtt + " ms";
         let quality = "Poor";
         let color = meterRed;
-        if (avgRtt <= 25) {
-            if (maxRtt <= 30) {
-                quality = "Excellent";
-                color = meterGreen;
-            } else {
-                quality = "Good";
-                color = meterGreen;
-            }
-        } else if (avgRtt <= 30) {
-            quality = "Good";
+        if (avgRtt < 10 && maxRtt < 15) {
+            quality = "Excellent";
             color = meterGreen;
-        } else if (avgRtt <= 35) {
-            quality = "Fair";
+        } else if (avgRtt < 20 && maxRtt < 30) {
+            quality = "Good";
             color = meterYellow;
+        } else if (avgRtt < 30 && maxRtt < 40) {
+            quality = "Fair";
+            color = statsOrange;
         }
 
         texts[0] = quality
index 6c5331421d772abf3a147e619844ca575178f27f..da538c6025ec34d022edfe4280642b3115eb7c10 100644 (file)
@@ -189,13 +189,10 @@ Item {
 
         function onMicPermissionUpdated() {
             if (permissions.micPermission === "granted") {
-                if (virtualstudio.studioToJoin.toString() === "") {
+                if (virtualstudio.studioToJoin === "") {
                     virtualstudio.windowState = "browse";
-                } else if (virtualstudio.showDeviceSetup) {
-                    virtualstudio.windowState = "setup";
-                    audio.startAudio();
                 } else {
-                    virtualstudio.windowState = "connected";
+                    virtualstudio.windowState = virtualstudio.showDeviceSetup ? "setup" : "connected";
                     virtualstudio.joinStudio();
                 }
             }
index f6a3123366ef46c58b6794545d227203abbfdd82..b8de3a1f0ef376bf94907acb696be795f4f4f4d7 100644 (file)
@@ -471,13 +471,10 @@ Item {
                     virtualstudio.saveSettings();
                     if (permissions.micPermission !== "granted") {
                         virtualstudio.windowState = "permissions";
-                    } else if (virtualstudio.studioToJoin.toString() === "") {
+                    } else if (virtualstudio.studioToJoin === "") {
                         virtualstudio.windowState = "browse";
-                    } else if (virtualstudio.showDeviceSetup) {
-                        virtualstudio.windowState = "setup";
-                        audio.startAudio();
                     } else {
-                        virtualstudio.windowState = "connected";
+                        virtualstudio.windowState = virtualstudio.showDeviceSetup ? "setup" : "connected";
                         virtualstudio.joinStudio();
                     }
                 }
@@ -509,13 +506,10 @@ Item {
                     virtualstudio.saveSettings();
                     if (permissions.micPermission !== "granted") {
                         virtualstudio.windowState = "permissions";
-                    } else if (virtualstudio.studioToJoin.toString() === "") {
+                    } else if (virtualstudio.studioToJoin === "") {
                         virtualstudio.windowState = "browse";
-                    } else if (virtualstudio.showDeviceSetup) {
-                        virtualstudio.windowState = "setup";
-                        audio.startAudio();
                     } else {
-                        virtualstudio.windowState = "connected";
+                        virtualstudio.windowState = virtualstudio.showDeviceSetup ? "setup" : "connected";
                         virtualstudio.joinStudio();
                     }
                 }
index 16b566208d214b7395ad8a6b92b10cac7229c938..e0b9ef9d9426e2ba170700e2f28bc5098f85ac81 100644 (file)
@@ -136,7 +136,7 @@ Item {
                 }
                 enabled: !Boolean(audio.devicesError) && audio.backendAvailable && audio.audioReady
                 onClicked: {
-                    audio.stopAudio(true);
+                    virtualstudio.studioToJoin = virtualstudio.currentStudio.id;
                     virtualstudio.windowState = "connected";
                     virtualstudio.saveSettings();
                     virtualstudio.joinStudio();
index 91f8397e1cde743b6c48d8638dce7cddad9f27c5..e6eb55b05f3b3933283fa470e6a7325b8f1500fa 100644 (file)
@@ -14,6 +14,7 @@ Rectangle {
     property string studioName: "Test Studio"
     property string studioId: ""
     property string inviteKeyString: ""
+    property int sampleRate: 48000
     property bool publicStudio: false
     property bool admin: false
     property bool available: true
@@ -197,14 +198,9 @@ Rectangle {
         }
         visible: !connected
         onClicked: {
-            virtualstudio.studioToJoin = `jacktrip://join/${studioId}`
-            if (virtualstudio.showDeviceSetup) {
-                virtualstudio.windowState = "setup";
-                audio.startAudio();
-            } else {
-                virtualstudio.windowState = "connected";
-                virtualstudio.joinStudio();
-            }
+            virtualstudio.studioToJoin = studioId;
+            virtualstudio.windowState = virtualstudio.showDeviceSetup ? "setup" : "connected";
+            virtualstudio.joinStudio();
         }
         Image {
             id: join
index 02b982dcaa62445d3f62d2b74f66594722bcb4a5..5c259793340a4b233ebfe44432066fc304d39627 100644 (file)
@@ -50,19 +50,29 @@ About::About(QWidget* parent) : QDialog(parent), m_ui(new Ui::About)
         this->done(0);
     });
 
+    // Replace %VERSION% and %QTVERSION%
     m_ui->aboutLabel->setText(
         m_ui->aboutLabel->text().replace(QLatin1String("%VERSION%"), gVersion));
     m_ui->aboutLabel->setText(
         m_ui->aboutLabel->text().replace(QLatin1String("%QTVERSION%"), qVersion()));
+
+    // Replace %LICENSE%
+    QString licenseText;
+#if defined(_WIN32) && defined(RT_AUDIO)
+    licenseText = QLatin1String(
+        "This build of JackTrip includes support for ASIO. ASIO is a trademark and "
+        "software of Steinberg Media Technologies GmbH.</p><p></p><p>");
+#endif
 #ifdef QT_OPENSOURCE
-    m_ui->aboutLabel->setText(m_ui->aboutLabel->text().replace(
-        QLatin1String("%LICENSE%"),
-        QLatin1String("This build of JackTrip is subject to LGPL license. ")));
-#else
-    m_ui->aboutLabel->setText(m_ui->aboutLabel->text().replace("%LICENSE%", ""));
+    licenseText += QLatin1String("This build of JackTrip is subject to LGPL license. ");
 #endif
+    m_ui->aboutLabel->setText(
+        m_ui->aboutLabel->text().replace(QLatin1String("%LICENSE%"), licenseText));
+
+    // Replace %BUILD%
+    QString buildString;
     if (!s_buildType.isEmpty() || !s_buildID.isEmpty()) {
-        QString buildString = QStringLiteral("<br/>(");
+        buildString = QStringLiteral("<br/>(");
         if (!s_buildType.isEmpty()) {
             buildString.append(s_buildType);
             if (!s_buildID.isEmpty()) {
@@ -72,12 +82,10 @@ About::About(QWidget* parent) : QDialog(parent), m_ui(new Ui::About)
             buildString.append(QStringLiteral("Build %1").arg(s_buildID));
         }
         buildString.append(")");
-        m_ui->aboutLabel->setText(
-            m_ui->aboutLabel->text().replace(QLatin1String("%BUILD%"), buildString));
-    } else {
-        m_ui->aboutLabel->setText(m_ui->aboutLabel->text().replace(
-            QLatin1String("%BUILD%"), QLatin1String("")));
     }
+    m_ui->aboutLabel->setText(
+        m_ui->aboutLabel->text().replace(QLatin1String("%BUILD%"), buildString));
+
 #ifdef __APPLE__
     m_ui->aboutImage->setPixmap(QPixmap(":/qjacktrip/about@2x.png"));
 #endif
index 78c3fed14a55cc244231d92073cb60f084be6448..e11ac2113ccc05825ab4d60be4562a10c9babbf2 100644 (file)
@@ -582,16 +582,16 @@ void VirtualStudio::setTestMode(bool test)
     emit testModeChanged();
 }
 
-QUrl VirtualStudio::studioToJoin()
+QString VirtualStudio::studioToJoin()
 {
     return m_studioToJoin;
 }
 
-void VirtualStudio::setStudioToJoin(const QUrl& url)
+void VirtualStudio::setStudioToJoin(const QString& id)
 {
-    if (m_studioToJoin == url)
+    if (m_studioToJoin == id)
         return;
-    m_studioToJoin = url;
+    m_studioToJoin = id;
     emit studioToJoinChanged();
 }
 
@@ -620,36 +620,31 @@ QString VirtualStudio::failedMessage()
 
 void VirtualStudio::joinStudio()
 {
+    // nothing to do unless on setup or connected windows with studio to join
+    if ((m_windowState != "setup" && m_windowState != "connected")
+        || !m_auth->isAuthenticated() || m_studioToJoin.isEmpty())
+        return;
+
+    // make sure we've retrieved a list of servers
     QMutexLocker locker(&m_refreshMutex);
-    bool authenticated = m_auth->isAuthenticated();
-    if (!authenticated || m_studioToJoin.isEmpty() || m_servers.isEmpty()) {
+    if (m_servers.isEmpty()) {
         // No servers yet. Making sure we have them.
         // getServerList emits refreshFinished which
         // will come back to this function.
-        if (authenticated && !m_studioToJoin.isEmpty() && m_servers.isEmpty()) {
-            locker.unlock();
-            getServerList(true);
-        }
+        locker.unlock();
+        getServerList(true);
         return;
     }
-    if (m_windowState != "connected") {
-        return;  // on audio setup screen before joining the studio
-    }
 
-    QString scheme = m_studioToJoin.scheme();
-    QString path   = m_studioToJoin.path();
-    QString url    = m_studioToJoin.toString();
-    setStudioToJoin(QUrl(""));
+    // pop studioToJoin
+    const QString targetId = m_studioToJoin;
+    setStudioToJoin("");
+    emit studioToJoinChanged();
 
-    m_failedMessage = "";
-    if (scheme != "jacktrip" || path.length() <= 1) {
-        m_failedMessage = "Invalid join request received: " + url;
-        emit failedMessageChanged();
-        emit failed();
-        return;
-    }
-    QString targetId = path.remove(0, 1);
+    // stop audio if already running (settings or setup windows)
+    m_audioConfigPtr->stopAudio(true);
 
+    // find and populate data for current studio
     VsServerInfoPointer sPtr;
     for (const VsServerInfoPointer& s : m_servers) {
         if (s->id() == targetId) {
@@ -659,14 +654,24 @@ void VirtualStudio::joinStudio()
     }
     locker.unlock();
 
-    if (!sPtr.isNull()) {
-        connectToStudio(*sPtr);
+    if (sPtr.isNull()) {
+        m_failedMessage = "Unable to find studio " + targetId;
+        emit failedMessageChanged();
+        emit failed();
         return;
     }
 
-    m_failedMessage = "Unable to find studio " + targetId;
-    emit failedMessageChanged();
-    emit failed();
+    m_currentStudio = *sPtr;
+    emit currentStudioChanged();
+
+    if (m_windowState == "setup") {
+        m_audioConfigPtr->setSampleRate(m_currentStudio.sampleRate());
+        m_audioConfigPtr->startAudio();
+        return;
+    }
+
+    // m_windowState == "connected"
+    connectToStudio();
 }
 
 void VirtualStudio::toStandard()
@@ -806,15 +811,13 @@ void VirtualStudio::saveSettings()
     m_audioConfigPtr->saveSettings();
 }
 
-void VirtualStudio::connectToStudio(VsServerInfo& studio)
+void VirtualStudio::connectToStudio()
 {
     m_refreshTimer.stop();
 
     m_networkStats = QJsonObject();
     emit networkStatsChanged();
 
-    m_currentStudio = studio;
-    emit currentStudioChanged();
     m_onConnectedScreen = true;
 
     m_studioSocketPtr.reset(new VsWebSocket(
@@ -914,6 +917,7 @@ void VirtualStudio::completeConnection()
             return;
         }
         jackTrip->setIOStatTimeout(m_cliSettings->getIOStatTimeout());
+        m_audioConfigPtr->setSampleRate(jackTrip->getSampleRate());
 
         // this passes ownership to JackTrip
         jackTrip->setAudioInterface(m_audioConfigPtr->newAudioInterface(jackTrip));
@@ -1104,20 +1108,34 @@ void VirtualStudio::openLink(const QString& link)
 void VirtualStudio::handleDeeplinkRequest(const QUrl& link)
 {
     // check link is valid
-    if (link.scheme() != QLatin1String("jacktrip")
-        || link.host() != QLatin1String("join")) {
-        qDebug() << "Ignoring invalid deeplink to " << link;
+    QString studioId;
+    if (link.scheme() != QLatin1String("jacktrip") || link.path().length() <= 1) {
+        qDebug() << "Ignoring invalid deeplink to" << link;
+        return;
+    }
+    if (link.host() == QLatin1String("join")) {
+        studioId = link.path().remove(0, 1);
+    } else if (link.host().isEmpty() && link.path().startsWith("join/")) {
+        studioId = link.path().remove(0, 5);
+    } else {
+        qDebug() << "Ignoring invalid deeplink to" << link;
         return;
     }
 
     // check if already connected (ignore)
     if (m_windowState == "connected" || m_windowState == "change_devices") {
-        qDebug() << "Already connected; ignoring deeplink to " << link;
+        qDebug() << "Already connected; ignoring deeplink to" << link;
         return;
     }
 
-    qDebug() << "Handling deeplink to " << link;
-    setStudioToJoin(link);
+    if (m_windowState == "setup"
+        && (m_studioToJoin == studioId || m_currentStudio.id() == studioId)) {
+        qDebug() << "Already preparing to connect; ignoring deeplink to" << link;
+        return;
+    }
+
+    qDebug() << "Handling deeplink to" << link;
+    setStudioToJoin(studioId);
     raiseToTop();
 
     // Switch to virtual studio mode, if necessary
@@ -1132,21 +1150,7 @@ void VirtualStudio::handleDeeplinkRequest(const QUrl& link)
         }
     }
 
-    // special case if on settings screen
-    if (m_windowState == "settings") {
-        if (showDeviceSetup()) {
-            // audio is already active, so we can just flip screens
-            setWindowState("setup");
-        } else {
-            // we need to stop audio before connecting
-            setWindowState("connected");
-            m_audioConfigPtr->stopAudio(true);
-            joinStudio();
-        }
-        return;
-    }
-
-    // special case if on create_studio screen:
+    // automatically navigate if on certain window screens
     // note that the studio creation happens inside of the web view,
     // and the app doesn't really know anything about it. we depend
     // on the web app triggering a deep link join event, which is
@@ -1154,27 +1158,15 @@ void VirtualStudio::handleDeeplinkRequest(const QUrl& link)
     // noticed yet, so we don't join right away; otherwise we'd just
     // get an unknown studio error. instead, we trigger a refresh and
     // rely on it to kick off the join afterwards.
-    if (m_windowState == "create_studio") {
-        refreshStudios(0, true);
-        if (showDeviceSetup()) {
-            setWindowState("setup");
-            m_audioConfigPtr->startAudio();
-        } else {
-            setWindowState("connected");
-        }
-        return;
-    }
-
-    // special case if on browsing and failed screens
-    if (m_windowState == "browse" || m_windowState == "failed") {
+    if (m_windowState == "browse" || m_windowState == "create_studio"
+        || m_windowState == "settings" || m_windowState == "setup"
+        || m_windowState == "failed") {
         if (showDeviceSetup()) {
             setWindowState("setup");
-            m_audioConfigPtr->startAudio();
         } else {
             setWindowState("connected");
-            joinStudio();
         }
-        return;
+        refreshStudios(0, true);
     }
 
     // otherwise, assume we are on setup screens and can let the normal flow handle it
@@ -1267,7 +1259,7 @@ void VirtualStudio::connectionFinished()
             } else {
                 m_audioConfigPtr->validateDevices(true);
             }
-            connectToStudio(m_currentStudio);
+            connectToStudio();
         }
         return;
     }
@@ -1714,24 +1706,6 @@ void VirtualStudio::getUserMetadata()
     });
 }
 
-void VirtualStudio::stopStudio()
-{
-    if (m_currentStudio.id() == "") {
-        return;
-    }
-
-    QJsonObject json      = {{QLatin1String("enabled"), false}};
-    QJsonDocument request = QJsonDocument(json);
-    m_currentStudio.setHost(QLatin1String(""));
-    QNetworkReply* reply = m_api->updateServer(m_currentStudio.id(), request.toJson());
-    connect(reply, &QNetworkReply::finished, this, [=]() {
-        if (m_isExiting && !m_jackTripRunning) {
-            emit signalExit();
-        }
-        reply->deleteLater();
-    });
-}
-
 bool VirtualStudio::readyToJoin()
 {
     // FTUX shows warnings and device setup views
index d665166dd4a27ca5489346ce577dc5d1b13c44c4..f5d60e13ac730634c0a9c8d5fcf1ff13e886ca50 100644 (file)
@@ -86,7 +86,7 @@ class VirtualStudio : public QObject
     Q_PROPERTY(
         QVector<VsServerInfo*> serverModel READ getServerModel NOTIFY serverModelChanged)
     Q_PROPERTY(VsServerInfo* currentStudio READ currentStudio NOTIFY currentStudioChanged)
-    Q_PROPERTY(QUrl studioToJoin READ studioToJoin WRITE setStudioToJoin NOTIFY
+    Q_PROPERTY(QString studioToJoin READ studioToJoin WRITE setStudioToJoin NOTIFY
                    studioToJoinChanged)
     Q_PROPERTY(QJsonObject regions READ regions NOTIFY regionsChanged)
     Q_PROPERTY(QJsonObject userMetadata READ userMetadata NOTIFY userMetadataChanged)
@@ -163,8 +163,8 @@ class VirtualStudio : public QObject
     void setCollapseDeviceControls(bool collapseDeviceControls);
     bool testMode();
     void setTestMode(bool test);
-    QUrl studioToJoin();
-    void setStudioToJoin(const QUrl& url);
+    QString studioToJoin();
+    void setStudioToJoin(const QString& id);
     bool showDeviceSetup();
     void setShowDeviceSetup(bool show);
     bool showWarnings();
@@ -264,9 +264,8 @@ class VirtualStudio : public QObject
     void getSubscriptions();
     void getRegions();
     void getUserMetadata();
-    void stopStudio();
     bool readyToJoin();
-    void connectToStudio(VsServerInfo& studio);
+    void connectToStudio();
     void completeConnection();
 
    private:
@@ -299,7 +298,7 @@ class VirtualStudio : public QObject
     QTimer m_heartbeatTimer;
     QTimer m_networkOutageTimer;
     QMutex m_refreshMutex;
-    QUrl m_studioToJoin;
+    QString m_studioToJoin;
     QString m_updateChannel;
     QString m_refreshToken;
     QString m_userId;
index 4607c15466e18c20db7c004c2d6e74b244de30e2..9eee794be853a55a64c67026296584aacbf4cfab 100644 (file)
@@ -286,13 +286,10 @@ Rectangle {
             }
             if (virtualstudio.showWarnings) {
                 virtualstudio.windowState = "recommendations";
-            } else if (virtualstudio.studioToJoin.toString() === "") {
+            } else if (virtualstudio.studioToJoin === "") {
                 virtualstudio.windowState = "browse";
-            } else if (virtualstudio.showDeviceSetup) {
-                virtualstudio.windowState = "setup";
-                audio.startAudio();
             } else {
-                virtualstudio.windowState = "connected";
+                virtualstudio.windowState = virtualstudio.showDeviceSetup ? "setup" : "connected";
                 virtualstudio.joinStudio();
             }
         }
index ef4ebede57f09ad7d46e406d840d7e2ccf78f94f..3529d31c9808a3a1228fb072b1be1ad1b9d32a95 100644 (file)
@@ -96,6 +96,13 @@ VsAudio::VsAudio(QObject* parent)
           QJsonArray::fromStringList(QStringList(QLatin1String(""))))
     , m_inputMixModeComboModel(QJsonArray::fromStringList(QStringList(QLatin1String(""))))
     , m_audioWorkerPtr(new VsAudioWorker(this))
+    , m_workerThreadPtr(nullptr)
+    , m_inputMeterPluginPtr(nullptr)
+    , m_outputMeterPluginPtr(nullptr)
+    , m_inputVolumePluginPtr(nullptr)
+    , m_outputVolumePluginPtr(nullptr)
+    , m_monitorPluginPtr(nullptr)
+    , mHasErrors(false)
 {
     loadSettings();
 
@@ -261,6 +268,14 @@ void VsAudio::setFeedbackDetectionEnabled(bool enabled)
     emit feedbackDetectionEnabledChanged();
 }
 
+void VsAudio::setSampleRate(int sampleRate)
+{
+    if (m_audioSampleRate == sampleRate)
+        return;
+    m_audioSampleRate = sampleRate;
+    emit sampleRateChanged();
+}
+
 void VsAudio::setBufferSize(int bufSize)
 {
     if (m_audioBufferSize == bufSize)
@@ -841,6 +856,11 @@ AudioInterface* VsAudio::newAudioInterface(JackTrip* jackTripPtr)
     if (ifPtr == nullptr)
         return ifPtr;
 
+    mHasErrors = false;
+    ifPtr->setErrorCallback([this, jackTripPtr](const std::string& errorText) {
+        this->errorCallback(errorText, jackTripPtr);
+    });
+
     // AudioInterface::setup() can return a different buffer size
     // if the audio interface doesn't support the one that was requested
     if (ifPtr->getBufferSizeInSamples() != uint32_t(getBufferSize())) {
@@ -889,9 +909,7 @@ AudioInterface* VsAudio::newJackAudioInterface([[maybe_unused]] JackTrip* jackTr
                                        jackTripPtr != nullptr, jackTripPtr);
         ifPtr->setClientName(QStringLiteral("JackTrip"));
 #if defined(__unix__)
-        AudioInterface::setPipewireLatency(
-            getBufferSize(),
-            jackTripPtr == nullptr ? 48000 : jackTripPtr->getSampleRate());
+        AudioInterface::setPipewireLatency(getBufferSize(), getSampleRate());
 #endif
         ifPtr->setup(true);
     }
@@ -919,7 +937,7 @@ AudioInterface* VsAudio::newRtAudioInterface([[maybe_unused]] JackTrip* jackTrip
         inputChans, outputChans,
         static_cast<AudioInterface::inputMixModeT>(getInputMixMode()),
         m_audioBitResolution, jackTripPtr != nullptr, jackTripPtr);
-    ifPtr->setSampleRate(jackTripPtr == nullptr ? 48000 : jackTripPtr->getSampleRate());
+    ifPtr->setSampleRate(getSampleRate());
     ifPtr->setInputDevice(getInputDevice().toStdString());
     ifPtr->setOutputDevice(getOutputDevice().toStdString());
     ifPtr->setBufferSizeInSamples(getBufferSize());
@@ -944,6 +962,29 @@ AudioInterface* VsAudio::newRtAudioInterface([[maybe_unused]] JackTrip* jackTrip
     return ifPtr;
 }
 
+void VsAudio::errorCallback(const std::string& errorText,
+                            [[maybe_unused]] JackTrip* jackTripPtr)
+{
+    const QString errorMsg(QString::fromStdString(errorText));
+    setDevicesErrorMsg(errorMsg);
+#ifdef _WIN32
+    // handle special case for Windows ASIO drivers that trigger
+    // asynchronous errors shortly after you try opening the
+    // RtAudio stream with a different sample rate (only for audio tester)
+    if (jackTripPtr == nullptr && getUseRtAudio()
+        && errorMsg.contains("sample rate changed")) {
+        // only refresh devices once
+        if (mHasErrors)
+            return;
+        mHasErrors = true;
+        // asynchronously refresh devices
+        refreshDevices(false);
+    }
+#else
+    mHasErrors = true;
+#endif
+}
+
 // VsAudioWorker methods
 
 VsAudioWorker::VsAudioWorker(VsAudio* ptr) : m_parentPtr(ptr) {}
index e6e68ddd0046af097a751a68603ca9d7389663d3..c91beb5bdd3013af5532e190576eda890928b875 100644 (file)
@@ -78,6 +78,8 @@ class VsAudio : public QObject
     Q_PROPERTY(bool backendAvailable READ backendAvailable CONSTANT)
     Q_PROPERTY(QString audioBackend READ getAudioBackend WRITE setAudioBackend NOTIFY
                    audioBackendChanged)
+    Q_PROPERTY(
+        int sampleRate READ getSampleRate WRITE setSampleRate NOTIFY sampleRateChanged)
     Q_PROPERTY(
         int bufferSize READ getBufferSize WRITE setBufferSize NOTIFY bufferSizeChanged)
     Q_PROPERTY(int bufferStrategy READ getBufferStrategy WRITE setBufferStrategy NOTIFY
@@ -168,6 +170,7 @@ class VsAudio : public QObject
     {
         return getUseRtAudio() ? QStringLiteral("RtAudio") : QStringLiteral("JACK");
     }
+    int getSampleRate() const { return m_audioSampleRate; }
     int getBufferSize() const { return m_audioBufferSize; }
     int getBufferStrategy() const { return m_bufferStrategy; }
     int getNumInputChannels() const { return getUseRtAudio() ? m_numInputChannels : 2; }
@@ -222,6 +225,7 @@ class VsAudio : public QObject
     // setters for state shared with QML
     void setFeedbackDetectionEnabled(bool enabled);
     void setAudioBackend(const QString& backend);
+    void setSampleRate(int sampleRate);
     void setBufferSize(int bufSize);
     void setBufferStrategy(int bufStrategy);
     void setNumInputChannels(int numChannels);
@@ -258,6 +262,7 @@ class VsAudio : public QObject
     void signalScanningDevicesChanged();
     void deviceModelsInitializedChanged(bool initialized);
     void audioBackendChanged(bool useRtAudio);
+    void sampleRateChanged();
     void bufferSizeChanged();
     void bufferStrategyChanged();
     void numInputChannelsChanged(int numChannels);
@@ -314,6 +319,7 @@ class VsAudio : public QObject
     void updateDeviceMessages(AudioInterface& audioInterface);
     AudioInterface* newJackAudioInterface(JackTrip* jackTripPtr = nullptr);
     AudioInterface* newRtAudioInterface(JackTrip* jackTripPtr = nullptr);
+    void errorCallback(const std::string& errorText, JackTrip* jackTripPtr = nullptr);
 
     // range for volume meters
     static constexpr float m_meterMax = 0.0;
@@ -329,6 +335,7 @@ class VsAudio : public QObject
     bool m_scanningDevices          = false;
     bool m_feedbackDetectionEnabled = true;
     bool m_deviceModelsInitialized  = false;
+    int m_audioSampleRate           = gDefaultSampleRate;
     int m_audioBufferSize =
         gDefaultBufferSizeInSamples;  ///< Audio buffer size to process on each callback
     int m_bufferStrategy    = 0;
@@ -370,6 +377,7 @@ class VsAudio : public QObject
     Volume* m_inputVolumePluginPtr;
     Volume* m_outputVolumePluginPtr;
     Monitor* m_monitorPluginPtr;
+    bool mHasErrors;  ///< true if one or more error callbacks have been triggered
 
 #ifndef NO_FEEDBACK
     Analyzer* m_outputAnalyzerPluginPtr;
@@ -430,6 +438,7 @@ class VsAudioWorker : public QObject
     int getNumOutputChannels() const { return m_parentPtr->getNumOutputChannels(); }
     int getBaseInputChannel() const { return m_parentPtr->getBaseInputChannel(); }
     int getBaseOutputChannel() const { return m_parentPtr->getBaseOutputChannel(); }
+    int getSampleRate() const { return m_parentPtr->getSampleRate(); }
     int getBufferSize() const { return m_parentPtr->getBufferSize(); }
     int getInputMixMode() const { return m_parentPtr->getInputMixMode(); }
     const QString& getInputDevice() const { return m_parentPtr->getInputDevice(); }
index 53bb88dba5ae1ff6c8dac3ada37640a27363e732..d3cafd11f9497ee7c168628ec298490d3a566ba3 100644 (file)
@@ -132,7 +132,7 @@ void VsDeeplink::connectionReceived()
         m_readyToExit = true;
     }
 
-    m_instanceCheckSocket->flush();
+    m_instanceCheckSocket->waitForBytesWritten();
     m_instanceCheckSocket->disconnectFromServer();  // remove next
 
     // let main thread know we are finished
@@ -171,21 +171,20 @@ void VsDeeplink::handleDeeplinkRequest()
         // Receive URL from 2nd instance
         QLocalSocket* connectedSocket = m_instanceServer->nextPendingConnection();
 
-        if (!connectedSocket->waitForConnected()) {
-            qDebug() << "Never received connection";
+        if (connectedSocket == nullptr || !connectedSocket->waitForConnected()) {
+            qDebug() << "Deeplink socket: never received connection";
             return;
         }
 
-        if (!connectedSocket->waitForReadyRead()) {
-            qDebug() << "Never ready to read";
-            if (!(connectedSocket->bytesAvailable() > 0)) {
-                qDebug() << "Not ready and no bytes available";
-                return;
-            }
+        if (!connectedSocket->waitForReadyRead()
+            && connectedSocket->bytesAvailable() <= 0) {
+            qDebug() << "Deeplink socket: not ready and no bytes available: "
+                     << connectedSocket->errorString();
+            return;
         }
 
         if (connectedSocket->bytesAvailable() < (int)sizeof(quint16)) {
-            qDebug() << "no bytes available";
+            qDebug() << "Deeplink socket: ready but no bytes available";
             break;
         }
 
index 5e85b542279edf66c3ea4716d5885bb5fa6199be..400bafe9026130e342c2c95442e34e483f07412e 100644 (file)
@@ -40,7 +40,7 @@
 
 #include "AudioInterface.h"
 
-constexpr const char* const gVersion = "2.2.1";  ///< JackTrip version
+constexpr const char* const gVersion = "2.2.2";  ///< JackTrip version
 
 //*******************************************************************************
 /// \name Default Values
@@ -88,6 +88,7 @@ constexpr const char* gDefaultLocalAddress     = "";
 constexpr int gDefaultRedundancy               = 1;
 constexpr int gTimeOutMultiThreadedServer      = 10000;  // seconds
 constexpr int gWaitCounter                     = 60;
+constexpr int gUdpWaitTimeout                  = 30;  // milliseconds
 //@}
 
 //*******************************************************************************