New upstream version 1.6.7+ds0
authorIOhannes m zmölnig <zmoelnig@umlautS.umlaeute.mur.at>
Sun, 4 Dec 2022 21:03:20 +0000 (22:03 +0100)
committerIOhannes m zmölnig <zmoelnig@umlautS.umlaeute.mur.at>
Sun, 4 Dec 2022 21:03:20 +0000 (22:03 +0100)
59 files changed:
CMakeLists.txt
docs/Build/Linux.md
docs/changelog.yml
jacktrip.pro
linux/flatpak/org.jacktrip.JackTrip.Devel.yml
linux/flatpak/org.jacktrip.JackTrip.Devel.yml.j2
linux/flatpak/org.jacktrip.JackTrip.yml
macos/entitlements.plist
meson.build
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/JackAudioInterface.h
src/JackTrip.cpp
src/JackTrip.h
src/JitterBuffer.cpp
src/JitterBuffer.h
src/Regulator.cpp
src/Regulator.h
src/RingBuffer.cpp
src/RingBuffer.h
src/RtAudioInterface.cpp
src/RtAudioInterface.h
src/UdpDataProtocol.cpp
src/UdpDataProtocol.h
src/gui/Browse.qml
src/gui/Connected.qml
src/gui/Failed.qml
src/gui/FirstLaunch.qml
src/gui/Login.qml
src/gui/Meter.qml
src/gui/Prompt.svg [new file with mode: 0644]
src/gui/Settings.qml
src/gui/Setup.qml
src/gui/Studio.qml
src/gui/qjacktrip.cpp
src/gui/qjacktrip.h
src/gui/qjacktrip.qrc
src/gui/qjacktrip.ui
src/gui/qjacktrip_novs.qrc
src/gui/virtualstudio.cpp
src/gui/virtualstudio.h
src/gui/vs.qml
src/gui/vsAudioInterface.cpp
src/gui/vsAudioInterface.h
src/gui/vsDevice.cpp
src/gui/vsMacPermissions.h [new file with mode: 0644]
src/gui/vsMacPermissions.mm [new file with mode: 0644]
src/gui/vsPermissions.cpp [new file with mode: 0644]
src/gui/vsPermissions.h [new file with mode: 0644]
src/gui/vuMeter.cpp [new file with mode: 0644]
src/gui/vuMeter.h [new file with mode: 0644]
src/jacktrip_globals.h
win/jacktrip.wxs

index b33c4293d7eecf47413c681dbabb40834845c68f..456762b2ddf1850322c5045f7f768c1ede3a2b49 100644 (file)
@@ -25,15 +25,23 @@ if (nogui)
   set(noupdater TRUE)
 endif ()
 
+if (psi)
+  set(novs TRUE)
+endif ()
+
 if (novs)
+  add_compile_definitions(NO_VS)
   set(QRC_FILE "src/gui/qjacktrip_novs.qrc")
 else ()
   set(QRC_FILE "src/gui/qjacktrip.qrc")
 endif ()
 
+if (noupdater)
+  add_compile_definitions(NO_UPDATER)
+endif ()
+
 if (psi)
   add_compile_definitions(PSI)
-  set(novs TRUE)
   if (novs)
     add_compile_definitions(BUILD_TYPE="psi-borg.org NO_VS binary")
   else ()
@@ -57,14 +65,6 @@ else ()
   file(WRITE "${QRC_FILE}" "${QRC_CONTENTS}")
 endif ()
 
-if (novs)
-  add_compile_definitions(NO_VS)
-endif ()
-
-if (noupdater)
-  add_compile_definitions(NO_UPDATER)
-endif ()
-
 add_compile_definitions(QT_OPENSOURCE)
 
 if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
@@ -171,6 +171,8 @@ if (NOT nogui)
     src/gui/about.cpp
     src/gui/messageDialog.cpp
     src/gui/textbuf.cpp
+    src/gui/vuMeter.cpp
+    src/Meter.cpp
   )
   
   if (NOT novs)
@@ -185,7 +187,6 @@ if (NOT nogui)
       src/gui/vsUrlHandler.cpp
       src/gui/vsWebSocket.cpp
       src/gui/qjacktrip.qrc
-      src/Meter.cpp
       src/Volume.cpp
       src/Tone.cpp
       # Need to include this for AUTOMOC to do its thing
index b722e21fa7281de8bf6ab00ef4c57609ffcda28e..3b2df229ec144623566730783210060fb4897101 100644 (file)
@@ -16,7 +16,7 @@ Optional:
 
 ### Fedora
 ```sh
-dnf install qt5-qtbase-devel qt5-qtnetworkauth-devel qt5-qtquickcontrols2-devel qt5-qtsvg-devel
+dnf install qt5-qtbase-devel qt5-qtnetworkauth-devel qt5-qtwebsockets-devel qt5-qtquickcontrols2-devel qt5-qtsvg-devel
 dnf groupinstall "C Development Tools and Libraries"
 dnf groupinstall "Development Tools"
 dnf install "pkgconfig(jack)" alsa-lib-devel git help2man
@@ -29,7 +29,7 @@ directory or use QtCreator to compile.
 ### Ubuntu and Debian/Raspbian
 ```sh
 apt install --no-install-recommends build-essential qt5-default autoconf automake libtool make libjack-jackd2-dev git help2man
-apt install qjackctl qt5-qmake qttools5-dev libqt5svg5-dev libqt5networkauth5-dev qtdeclarative5-dev qml-module-qtquick-controls
+apt install qjackctl qt5-qmake qttools5-dev libqt5svg5-dev libqt5networkauth5-dev libqt5websockets5-dev qtdeclarative5-dev qml-module-qtquick-controls
 apt install librtaudio-dev # if building with RtAudio
 ```
 
index ce32d2b6015cb70e5285f728020d09ec3b237790..2a29c5c22fad325584c8a7b56fed8afed792ad8e 100644 (file)
@@ -1,3 +1,23 @@
+- Version: "1.6.7"
+  Date: 2022-11-03
+  Description:
+  - (added) volume meters in classic mode
+  - (added) release-acquire ordering for Regular
+  - (added) audio driver support article in VS mode
+  - (added) regulatorthread
+  - (added) studios page first time UI
+  - (added) non-asio audio devices can be used on windows
+  - (updated) dependency list in documentation
+  - (updated) windows opens jacktrip after install
+  - (updated) Move to overlapped I/O for Windows Networking
+  - (updated) New default device behavior in Virtual Studio mode
+  - (fixed) opening links from Virtual Studio
+  - (fixed) send capture volume as int
+  - (fixed) connection issues for servers without reverse dns
+  - (fixed) flatpak build errors
+  - (fixed) ventura updater crash
+  - (fixed) uninitialized delete issue
+  - (remvoed) extraneous call to readSlotNonBlocking
 - Version: "1.6.6"
   Date: 2022-11-01
   Description:
index 77cb80dac20ae7eccdf1c316d8cdba96092b4710..fca54203d1b64fc7ab59fa968630453f9efeb949 100644 (file)
@@ -108,6 +108,9 @@ macx {
   !nogui {
     LIBS += -framework Foundation
     CONFIG += objective_c
+    !novs {
+      LIBS += -framework AVFoundation
+    }
   }
 }
 
@@ -239,7 +242,8 @@ HEADERS += src/DataProtocol.h \
   HEADERS += src/gui/about.h \
              src/gui/messageDialog.h \
              src/gui/qjacktrip.h \
-             src/gui/textbuf.h
+             src/gui/textbuf.h \
+             src/gui/vuMeter.h
   !novs {
     HEADERS += src/gui/virtualstudio.h \
                src/gui/vsDevice.h \
@@ -247,6 +251,7 @@ HEADERS += src/DataProtocol.h \
                src/gui/vsServerInfo.h \
                src/gui/vsQuickView.h \
                src/gui/vsWebSocket.h \
+               src/gui/vsPermissions.h \
                src/gui/vsPinger.h \
                src/gui/vsPing.h \
                src/gui/vsUrlHandler.h \
@@ -300,7 +305,8 @@ SOURCES += src/DataProtocol.cpp \
   SOURCES += src/gui/messageDialog.cpp \
              src/gui/qjacktrip.cpp \
              src/gui/about.cpp \
-             src/gui/textbuf.cpp
+             src/gui/textbuf.cpp \
+             src/gui/vuMeter.cpp
   !novs {
     SOURCES += src/gui/virtualstudio.cpp \
                src/gui/vsDevice.cpp \
@@ -308,6 +314,7 @@ SOURCES += src/DataProtocol.cpp \
                src/gui/vsServerInfo.cpp \
                src/gui/vsQuickView.cpp \
                src/gui/vsWebSocket.cpp \
+               src/gui/vsPermissions.cpp \
                src/gui/vsPinger.cpp \
                src/gui/vsPing.cpp \
                src/gui/vsUrlHandler.cpp
@@ -324,8 +331,14 @@ SOURCES += src/DataProtocol.cpp \
   macx {
     HEADERS += src/gui/NoNap.h
     OBJECTIVE_SOURCES += src/gui/NoNap.mm
+    !novs {
+      HEADERS += src/gui/vsMacPermissions.h
+      OBJECTIVE_SOURCES += src/gui/vsMacPermissions.mm
+    }
   }
-  FORMS += src/gui/qjacktrip.ui src/gui/about.ui src/gui/messageDialog.ui
+  FORMS += src/gui/qjacktrip.ui \
+           src/gui/about.ui \
+           src/gui/messageDialog.ui
   novs {
     RESOURCES += src/gui/qjacktrip_novs.qrc
   } else {
index ef0cb43f4f475cd630a788f1c7be2f7923591ffa..45a3c75a18f62a8073be5510627153742ca8fc6d 100644 (file)
@@ -1,6 +1,6 @@
 app-id: org.jacktrip.JackTrip.Devel
 runtime: org.kde.Platform
-runtime-version: '5.15-21.08'
+runtime-version: '5.15-22.08'
 sdk: org.kde.Sdk
 command: jacktrip
 finish-args:
@@ -19,7 +19,6 @@ cleanup:
   - /lib/python3.8
   - /share/man
 modules:
-  - shared-modules/linux-audio/jack2.json
   - name: python3-pyyaml
     buildsystem: simple
     cleanup: [ "*" ]
@@ -47,5 +46,6 @@ modules:
       - -Dprofile=development
     sources:
       - type: git
+        disable-submodules: true
         url: https://github.com/jacktrip/jacktrip.git
         branch: dev
index b2d211c7eb32085e29ee3fd5a5b513ec9e93f15a..cc43a8370a9b3a19835db120db7ddca6269f81fe 100644 (file)
@@ -1,6 +1,6 @@
 app-id: org.jacktrip.JackTrip.Devel
 runtime: org.kde.Platform
-runtime-version: '5.15-21.08'
+runtime-version: '5.15-22.08'
 sdk: org.kde.Sdk
 command: jacktrip
 finish-args:
@@ -19,7 +19,6 @@ cleanup:
   - /lib/python3.8
   - /share/man
 modules:
-  - shared-modules/linux-audio/jack2.json
   - name: python3-pyyaml
     buildsystem: simple
     cleanup: [ "*" ]
@@ -47,5 +46,6 @@ modules:
       - -Dprofile=development
     sources:
       - type: git
+        disable-submodules: true
         url: {{ env['REPO'] }}
         branch: {{ env['REF'] }}
index 9bdfcc527b859fe6a186496a3697b9e0c54cf11c..be5c4144f41385dec55cf03062fd09bc9d5d31c0 100644 (file)
@@ -1,6 +1,6 @@
 app-id: org.jacktrip.JackTrip
 runtime: org.kde.Platform
-runtime-version: '5.15-21.08'
+runtime-version: '5.15-22.08'
 sdk: org.kde.Sdk
 command: jacktrip
 finish-args:
@@ -19,7 +19,6 @@ cleanup:
   - /lib/python3.8
   - /share/man
 modules:
-  - shared-modules/linux-audio/jack2.json
   - name: python3-pyyaml
     buildsystem: simple
     cleanup: [ "*" ]
@@ -45,5 +44,6 @@ modules:
     buildsystem: meson
     sources:
       - type: git
+        disable-submodules: true
         url: https://github.com/jacktrip/jacktrip.git
         branch: main
index 8f4b16dceef0b3608c6e2744aa1484f887e8b47b..88233d39f3e75899fe21a76006317f113b0365f5 100644 (file)
@@ -8,5 +8,7 @@
        <true/>
        <key>com.apple.security.device.audio-input</key>
        <true/>
+       <key>com.apple.security.cs.allow-jit</key>
+       <true/>
 </dict>
 </plist>
index 63830396c64ae194e04a710e7c3cc6f790f01cb5..864f9d605291947d65be39ba7b4ce9b4ec8efa45 100644 (file)
@@ -51,6 +51,7 @@ moc_h = ['src/DataProtocol.h',
        'src/Tone.h',
        'src/JackTripWorker.h',
        'src/PacketHeader.h',
+       'src/Regulator.h',
        'src/Settings.h',
        'src/UdpDataProtocol.h',
        'src/UdpHubListener.h',
@@ -92,13 +93,15 @@ else
                'src/gui/qjacktrip.cpp',
                'src/gui/about.cpp',
                'src/gui/messageDialog.cpp',
-               'src/gui/textbuf.cpp'
+               'src/gui/textbuf.cpp',
+               'src/gui/vuMeter.cpp'
        ]
        moc_h += [
                'src/gui/about.h',
                'src/gui/qjacktrip.h',
                'src/gui/messageDialog.h',
-               'src/gui/textbuf.h'
+               'src/gui/textbuf.h',
+               'src/gui/vuMeter.h'
        ]
        ui_h += [
                'src/gui/qjacktrip.ui',
@@ -119,6 +122,7 @@ else
                        'src/gui/vsQuickView.cpp',
                        'src/gui/vsWebSocket.cpp',
                        'src/gui/vsUrlHandler.cpp',
+                       'src/gui/vsPermissions.cpp',
                        'src/gui/vsPinger.cpp',
                        'src/gui/vsPing.cpp'
                ]
@@ -129,12 +133,18 @@ else
                        'src/gui/vsServerInfo.h',
                        'src/gui/vsQuickView.h',
                        'src/gui/vsWebSocket.h',
+                       'src/gui/vsPermissions.h',
                        'src/gui/vsPinger.h',
                        'src/gui/vsPing.h',
                        'src/gui/vsUrlHandler.h',
                        'src/gui/vsQmlClipboard.h',
                        'src/JTApplication.h'
                ]
+
+               if host_machine.system() == 'darwin'
+                       moc_h += ['src/gui/vsMacPermissions.h']
+               endif
+
                qt5_dep = dependency('qt5', modules: ['Core', 'Gui', 'Network', 'Widgets', 'Quick', 'Qml', 'Svg', 'NetworkAuth', 'WebSockets'], include_type: 'system')
                qres = ['src/gui/qjacktrip.qrc']
        endif
@@ -183,6 +193,12 @@ if host_machine.system() == 'darwin'
        add_languages('objcpp')
 endif
 
+if host_machine.system() == 'darwin' and get_option('novs') == false
+       src += ['src/gui/vsMacPermissions.mm']
+       apple_av_dep = dependency('appleframeworks', modules : ['avfoundation'])
+       deps += apple_av_dep
+endif
+
 jacktrip = executable('jacktrip', src, prepro_files, include_directories: incdirs, dependencies: deps, c_args: c_defines, cpp_args: defines, install: true )
 
 help2man = find_program('help2man', required: false)
index c38c9a588c1923b940aad64ebcc884b7cdcc0e81..30905ec44c139c6ac14fe3b9ca7af183e7e13286 100644 (file)
@@ -1,6 +1,36 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.6.7-rc.2",
+            "changelog": "Virtual Studio first time UI, Non-ASIO devices on Windows, Windows can't connect fix: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.7-rc.2",
+            "download": {
+                "date": "2022-11-29T00:00:00Z",
+                "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.7-rc.2/JackTrip-v1.6.7-rc.2-macOS-x64-installer.pkg",
+                "downloadSize": 11516784,
+                "sha256": "4fe2e4dbd67986926b6f738cd4d8a22b801fd3cd50aeebee13d2e1de0310b160"
+            }
+        },
+        {
+            "version": "1.6.7-rc.1",
+            "changelog": "New networking code on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.7-rc.1",
+            "download": {
+                "date": "2022-11-07T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.7-rc.1-macOS-x64-installer.pkg",
+                "downloadSize": 11481444,
+                "sha256": "aa07023d130129684dc8d1e395d04eabea9709db32459e203c4fb7cf9759976e"
+            }
+        },
+        {
+            "version": "1.6.6",
+            "changelog": "Adding volume controls in Virtual Studio, preliminary QT6 support, classic mode GUI updates, and fixes: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.6",
+            "download": {
+                "date": "2022-11-02T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.6-macOS-x64-installer.pkg",
+                "downloadSize": 11481324,
+                "sha256": "e99149ea9bbfb94a2000cfd3848013e44bb8d23e783198005681c2038bcbd313"
+            }
+        },
         {
             "version": "1.6.5-rc.1",
             "changelog": "Adding volume controls, mute, early Qt6 support, and bug fixes: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.5-rc1",
index 9b2f0b0e610e38b9da1d0cf2d20ebbca8c811b35..d1412cc8c7d313f5902df0651a50df34b96caea7 100644 (file)
@@ -1,6 +1,36 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.6.7-rc.2",
+            "changelog": "Virtual Studio first time UI, Non-ASIO devices on Windows, Windows can't connect fix: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.7-rc.2",
+            "download": {
+                "date": "2022-11-29T00:00:00Z",
+                "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.7-rc.2/JackTrip-v1.6.7-rc.2-Windows-x64-installer.msi",
+                "downloadSize": 44531712,
+                "sha256": "656716e1e665187474bda00c89348a405018a69830557b4722e382187ee367e1"
+            }
+        },
+        {
+            "version": "1.6.7-rc.1",
+            "changelog": "New networking code on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.7-rc.1",
+            "download": {
+                "date": "2022-11-07T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.7-rc.1-Windows-x64-installer.msi",
+                "downloadSize": 44412928,
+                "sha256": "a0485d45c615c435fc4d6b0840d0bf8a74f990001eeacfe26a74d61afbd72dfd"
+            }
+        },
+        {
+            "version": "1.6.6",
+            "changelog": "Adding volume controls in Virtual Studio, preliminary QT6 support, classic mode GUI updates, and fixes: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.6",
+            "download": {
+                "date": "2022-11-02T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.6-Windows-x64-installer.msi",
+                "downloadSize": 44408832,
+                "sha256": "828a1f43254db187a33601cc809551617db83e60a51dad3e992c30270967de0c"
+            }
+        },
         {
             "version": "1.6.5-rc.1",
             "changelog": "Adding volume controls, mute, early Qt6 support, and bug fixes: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.5-rc1",
index 9cb7d67aa36bf1c3ac14571083a695a6b82611c3..a3e43acc10258156a3c4ae329ae395a61ec092bc 100644 (file)
@@ -1,6 +1,16 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.6.6",
+            "changelog": "Adding volume controls in Virtual Studio, preliminary QT6 support, classic mode GUI updates, and fixes: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.6",
+            "download": {
+                "date": "2022-11-02T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.6-Linux-x64-binary.zip",
+                "downloadSize": 29837298,
+                "sha256": "25b15f3208521530b18e2096de722fefe5318149aa296610f8c5eec147586e0a"
+            }
+        },
         {
             "version": "1.6.4",
             "changelog": "Adding volume meters, bugfixes around Windows hanging, device disconnects, and PLC: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.4",
index febfe6aba976363d751940a5e66de6a9a54b0c5b..4de4ce3c6fd7ec2ee31a507eaeeec5188d362e5c 100644 (file)
@@ -1,6 +1,16 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.6.6",
+            "changelog": "Adding volume controls in Virtual Studio, preliminary QT6 support, classic mode GUI updates, and fixes: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.6",
+            "download": {
+                "date": "2022-11-02T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.6-macOS-x64-installer.pkg",
+                "downloadSize": 11481324,
+                "sha256": "e99149ea9bbfb94a2000cfd3848013e44bb8d23e783198005681c2038bcbd313"
+            }
+        },
         {
             "version": "1.6.4",
             "changelog": "Adding volume meters, bugfixes around Windows hanging, device disconnects, and PLC: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.4",
index d5d5454a99daf48e2eea626b4bee9b7de9073271..079d3a48480ccdfe24d83bd5bcf59773bb265db1 100644 (file)
@@ -1,6 +1,16 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.6.6",
+            "changelog": "Adding volume controls in Virtual Studio, preliminary QT6 support, classic mode GUI updates, and fixes: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.6",
+            "download": {
+                "date": "2022-11-02T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.6-Windows-x64-installer.msi",
+                "downloadSize": 44408832,
+                "sha256": "828a1f43254db187a33601cc809551617db83e60a51dad3e992c30270967de0c"
+            }
+        },
         {
             "version": "1.6.4",
             "changelog": "Adding volume meters, bugfixes around Windows hanging, device disconnects, and PLC: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.4",
index 8ee34b1fd7a1129ebe29bb012d3c2030b6b806a9..29bb3081bed0316f0f526a37235818b004f8bf05 100644 (file)
@@ -145,7 +145,7 @@ AudioInterface::~AudioInterface()
 }
 
 //*******************************************************************************
-void AudioInterface::setup()
+void AudioInterface::setup(bool /*verbose*/)
 {
     // Allocate buffer memory to read and write
     mSizeInBytesPerChannel = getSizeInBytesPerChannel();
@@ -683,12 +683,15 @@ void AudioInterface::appendProcessPluginFromNetwork(ProcessPlugin* plugin)
     mProcessPluginsFromNetwork.append(plugin);
 }
 
-void AudioInterface::initPlugins()
+void AudioInterface::initPlugins(bool verbose)
 {
     int nPlugins = mProcessPluginsFromNetwork.size() + mProcessPluginsToNetwork.size();
     if (nPlugins > 0) {
-        std::cout << "Initializing Faust plugins (have " << nPlugins
-                  << ") at sampling rate " << mSampleRate << "\n";
+        if (verbose) {
+            std::cout << "Initializing Faust plugins (have " << nPlugins
+                      << ") at sampling rate " << mSampleRate << "\n";
+        }
+
         for (ProcessPlugin* plugin : qAsConst(mProcessPluginsFromNetwork)) {
             plugin->setOutgoingToNetwork(false);
             plugin->updateNumChannels(mNumInChans, mNumOutChans);
@@ -766,3 +769,86 @@ int AudioInterface::getSampleRateFromType(samplingRateT rate_type)
 
     return sample_rate;
 }
+
+//*******************************************************************************
+void AudioInterface::setDevicesWarningMsg(warningMessageT msg)
+{
+    switch (msg) {
+    case DEVICE_WARN_LATENCY:
+        mWarningMsg =
+            "The selected devices don't support low latency. You can use them, but you "
+            "will experience audio delay. Make sure you have up to date drivers from the "
+            "manufacturer!";
+#ifdef _WIN32
+        mWarningHelpUrl = "https://help.jacktrip.org/hc/en-us/articles/4409919243155";
+#else
+        mWarningHelpUrl = "";
+#endif
+        break;
+    default:
+        mWarningMsg     = "";
+        mWarningHelpUrl = "";
+        break;
+    }
+
+    return;
+}
+
+//*******************************************************************************
+void AudioInterface::setDevicesErrorMsg(errorMessageT msg)
+{
+    mErrorMsg = msg;
+    switch (msg) {
+    case DEVICE_ERR_INCOMPATIBLE:
+        mErrorMsg =
+            "The two devices you have selected are not compatible. Please select a "
+            "different pair of devices.";
+#ifdef _WIN32
+        mErrorHelpUrl = "https://help.jacktrip.org/hc/en-us/articles/4409919243155";
+#else
+        mErrorHelpUrl   = "";
+#endif
+        break;
+    case DEVICE_ERR_NO_INPUTS:
+        mErrorMsg     = "JackTrip couldn't find any input devices!";
+        mErrorHelpUrl = "";
+        break;
+    case DEVICE_ERR_NO_OUTPUTS:
+        mErrorMsg     = "JackTrip couldn't find any output devices!";
+        mErrorHelpUrl = "";
+        break;
+    case DEVICE_ERR_NO_DEVICES:
+        mErrorMsg     = "JackTrip couldn't find any audio devices!";
+        mErrorHelpUrl = "";
+        break;
+    default:
+        mErrorMsg     = "";
+        mErrorHelpUrl = "";
+        break;
+    }
+    return;
+}
+
+//*******************************************************************************
+std::string AudioInterface::getDevicesWarningMsg()
+{
+    return mWarningMsg;
+}
+
+//*******************************************************************************
+std::string AudioInterface::getDevicesErrorMsg()
+{
+    return mErrorMsg;
+}
+
+//*******************************************************************************
+std::string AudioInterface::getDevicesWarningHelpUrl()
+{
+    return mWarningHelpUrl;
+}
+
+//*******************************************************************************
+std::string AudioInterface::getDevicesErrorHelpUrl()
+{
+    return mErrorHelpUrl;
+}
\ No newline at end of file
index ba1753995ccc8cd721bb7b259e79a25260099031..20967d6f774042fda493e4b4985df041d44a0114 100644 (file)
@@ -76,6 +76,16 @@ class AudioInterface
         UNDEF   ///< Undefined
     };
 
+    enum warningMessageT { DEVICE_WARN_NONE, DEVICE_WARN_LATENCY };
+
+    enum errorMessageT {
+        DEVICE_ERR_NONE,
+        DEVICE_ERR_INCOMPATIBLE,
+        DEVICE_ERR_NO_INPUTS,
+        DEVICE_ERR_NO_OUTPUTS,
+        DEVICE_ERR_NO_DEVICES
+    };
+
     /** \brief The class constructor
      * \param jacktrip Pointer to the JackTrip class that connects all classes (mediator)
      * \param NumInChans Number of Input Channels
@@ -99,14 +109,14 @@ class AudioInterface
      * Packet Size, Bit Resolution, etc... Sub-classes should also call the parent
      * method to ensure correct inizialization.
      */
-    virtual void setup();
+    virtual void setup(bool verbose = true);
     /// \brief Tell the audio server that we are ready to roll. The
     /// process-callback will start running. This runs on its own thread.
     /// \return 0 on success, otherwise a non-zero error code
-    virtual int startProcess() const = 0;
+    virtual int startProcess() = 0;
     /// \brief Stops the process-callback thread
     /// \return 0 on success, otherwise a non-zero error code
-    virtual int stopProcess() const = 0;
+    virtual int stopProcess() = 0;
     /** \brief Process callback. Subclass should call this callback after obtaining the
     in_buffer and out_buffer pointers.
     * \param in_buffer Array of input audio samplers for each channel. The user
@@ -139,7 +149,7 @@ class AudioInterface
      * Initialize all ProcessPlugin modules.
      * The audio sampling rate (mSampleRate) must be set at this time.
      */
-    void initPlugins();
+    void initPlugins(bool verbose = true);
     virtual void connectDefaultPorts() = 0;
     /** \brief Convert a 32bit number (sample_t) into one of the bit resolution
      * supported (audioBitResolutionT).
@@ -213,6 +223,11 @@ class AudioInterface
      * \return Sample Rate in Hz
      */
     static int getSampleRateFromType(samplingRateT rate_type);
+    std::string getDevicesWarningMsg();
+    std::string getDevicesErrorMsg();
+    std::string getDevicesWarningHelpUrl();
+    std::string getDevicesErrorHelpUrl();
+
     //------------------------------------------------------------------
 
    private:
@@ -260,8 +275,16 @@ class AudioInterface
     AudioTester* mAudioTesterP{nullptr};
 
    protected:
+    void setDevicesWarningMsg(warningMessageT msg);
+    void setDevicesErrorMsg(errorMessageT msg);
+
     bool mProcessingAudio;  ///< Set when processing an audio callback buffer pair
     const uint32_t MAX_AUDIO_BUFFER_SIZE = 8192;
+
+    std::string mWarningMsg;
+    std::string mErrorMsg;
+    std::string mWarningHelpUrl;
+    std::string mErrorHelpUrl;
 };
 
 #endif  // __AUDIOINTERFACE_H__
index 1df61d3fc0491941b2d545e82dc54efebe363790..fd02495008798d54238511851bfb09340f3daec7 100644 (file)
@@ -117,10 +117,10 @@ JackAudioInterface::JackAudioInterface(
 JackAudioInterface::~JackAudioInterface() {}
 
 //*******************************************************************************
-void JackAudioInterface::setup()
+void JackAudioInterface::setup(bool verbose)
 {
     setupClient();
-    AudioInterface::setup();
+    AudioInterface::setup(verbose);
     setProcessCallback();
 }
 
@@ -271,7 +271,7 @@ void JackAudioInterface::setProcessCallback()
 }
 
 //*******************************************************************************
-int JackAudioInterface::startProcess() const
+int JackAudioInterface::startProcess()
 {
     // Tell the JACK server that we are ready to roll.  Our
     // process() callback will start running now.
@@ -283,7 +283,7 @@ int JackAudioInterface::startProcess() const
 }
 
 //*******************************************************************************
-int JackAudioInterface::stopProcess() const
+int JackAudioInterface::stopProcess()
 {
     QMutexLocker locker(&sJackMutex);
     int code = (jack_deactivate(mClient));
index b85d4ec452e881dd6477a298b9baf986d36d14f9..7a5501ac0a9e20632f6236eb0ad59dadf713dcf2 100644 (file)
@@ -92,16 +92,16 @@ class JackAudioInterface : public AudioInterface
     virtual ~JackAudioInterface();
 
     /// \brief Setup the client
-    virtual void setup();
+    virtual void setup(bool verbose = true);
     /** \brief Tell the JACK server that we are ready to roll. The
      * process-callback will start running. This runs on its own thread.
      * \return 0 on success, otherwise a non-zero error code
      */
-    virtual int startProcess() const;
+    virtual int startProcess();
     /** \brief Stops the process-callback thread
      * \return 0 on success, otherwise a non-zero error code
      */
-    virtual int stopProcess() const;
+    virtual int stopProcess();
     /// \brief Connect the default ports, capture to sends, and receives to playback
     void connectDefaultPorts();
 
index 9d21357ddd922d288f8f89f81e9558a6db164d56..5fa1f5aba016f04ba60cc05d2ffde0560fc21a02 100644 (file)
@@ -116,6 +116,8 @@ JackTrip::JackTrip(jacktripModeT JacktripMode, dataProtocolT DataProtocolType,
     , mStopOnTimeout(false)
     , mSendRingBuffer(NULL)
     , mReceiveRingBuffer(NULL)
+    , mRegulatorThreadPtr(NULL)
+    , mRegulatorWorkerPtr(NULL)
     , mReceiverBindPort(receiver_bind_port)
     , mSenderPeerPort(sender_peer_port)
     , mSenderBindPort(sender_bind_port)
@@ -155,6 +157,8 @@ JackTrip::~JackTrip()
     delete mDataProtocolReceiver;
     delete mAudioInterface;
     delete mPacketHeader;
+    delete mRegulatorWorkerPtr;
+    delete mRegulatorThreadPtr;
     delete mSendRingBuffer;
     delete mReceiveRingBuffer;
 }
@@ -211,7 +215,7 @@ void JackTrip::setupAudio(
         if (gVerboseFlag)
             std::cout << "  JackTrip:setupAudio before mAudioInterface->setup"
                       << std::endl;
-        mAudioInterface->setup();
+        mAudioInterface->setup(true);
         if (gVerboseFlag)
             std::cout << "  JackTrip:setupAudio before mAudioInterface->getSampleRate"
                       << std::endl;
@@ -236,7 +240,7 @@ void JackTrip::setupAudio(
         mAudioInterface->setInputDevice(mInputDeviceName);
         mAudioInterface->setOutputDevice(mOutputDeviceName);
         mAudioInterface->setBufferSizeInSamples(mAudioBufferSize);
-        mAudioInterface->setup();
+        mAudioInterface->setup(true);
         // Setup might have reduced number of channels
         mNumAudioChansIn  = mAudioInterface->getNumInputChannels();
         mNumAudioChansOut = mAudioInterface->getNumOutputChannels();
@@ -253,7 +257,7 @@ void JackTrip::setupAudio(
         mAudioInterface->setInputDevice(mInputDeviceName);
         mAudioInterface->setOutputDevice(mOutputDeviceName);
         mAudioInterface->setBufferSizeInSamples(mAudioBufferSize);
-        mAudioInterface->setup();
+        mAudioInterface->setup(true);
         // Setup might have reduced number of channels
         mNumAudioChansIn  = mAudioInterface->getNumInputChannels();
         mNumAudioChansOut = mAudioInterface->getNumOutputChannels();
@@ -473,6 +477,12 @@ void JackTrip::startProcess(
         ID
 #endif  // endwhere
     );
+
+    if (mAudioInterface->getDevicesErrorMsg() != "") {
+        stop();
+        return;
+    }
+
     // cc redundant with instance creator  createHeader(mPacketHeaderType); next line
     // fixme
     createHeader(mPacketHeaderType);
@@ -593,7 +603,19 @@ void JackTrip::completeConnection()
         mAudioInterface->appendProcessPluginToNetwork(i);
     }
 
-    mAudioInterface->initPlugins();   // mSampleRate known now, which plugins require
+    if (mBufferStrategy == 3) {
+        mRegulatorThreadPtr = new QThread();
+        mRegulatorThreadPtr->setObjectName("RegulatorThread");
+        Regulator* regulatorPtr    = reinterpret_cast<Regulator*>(mReceiveRingBuffer);
+        RegulatorWorker* workerPtr = new RegulatorWorker(regulatorPtr);
+        workerPtr->moveToThread(mRegulatorThreadPtr);
+        QObject::connect(this, &JackTrip::signalReceivedNetworkPacket, workerPtr,
+                         &RegulatorWorker::pullPacket, Qt::QueuedConnection);
+        mRegulatorThreadPtr->start();
+        mRegulatorWorkerPtr = workerPtr;
+    }
+
+    mAudioInterface->initPlugins(true);  // mSampleRate known now, which plugins require
     mAudioInterface->startProcess();  // Tell JACK server we are ready for audio flow now
 
     if (mConnectDefaultAudioPorts) {
@@ -1052,6 +1074,12 @@ void JackTrip::stop(const QString& errorMessage)
     mHasShutdown = true;
     std::cout << "Stopping JackTrip..." << std::endl;
 
+    if (mRegulatorThreadPtr != nullptr) {
+        // Stop the Regulator thread
+        mRegulatorThreadPtr->quit();
+        mRegulatorThreadPtr->wait();
+    }
+
     if (mDataProtocolSender != nullptr) {
         // Stop The Sender
         mDataProtocolSender->stop();
@@ -1086,6 +1114,9 @@ void JackTrip::waitThreads()
 {
     mDataProtocolSender->wait();
     mDataProtocolReceiver->wait();
+    if (mRegulatorThreadPtr != nullptr) {
+        mRegulatorThreadPtr->wait();
+    }
 }
 
 //*******************************************************************************
index 99cb23abdca2ae73997bb8a17452458f70ab88e2..9393ffc405299411583ad8e1c8fb0bf20d746c8c 100644 (file)
@@ -381,7 +381,7 @@ class JackTrip : public QObject
     int getReceivePacketSizeInBytes() const;
     virtual void sendNetworkPacket(const int8_t* ptrToSlot)
     {
-        mSendRingBuffer->insertSlotNonBlocking(ptrToSlot, 0, 0);
+        mSendRingBuffer->insertSlotNonBlocking(ptrToSlot, 0, 0, 0);
     }
     virtual void receiveBroadcastPacket(int8_t* ptrToReadSlot)
     {
@@ -390,20 +390,18 @@ class JackTrip : public QObject
     virtual void receiveNetworkPacket(int8_t* ptrToReadSlot)
     {
         mReceiveRingBuffer->readSlotNonBlocking(ptrToReadSlot);
+        if (mBufferStrategy == 3) {
+            // trigger next packet using RegulatorThread
+            emit signalReceivedNetworkPacket();
+        }
     }
     virtual void readAudioBuffer(int8_t* ptrToReadSlot)
     {
         mSendRingBuffer->readSlotBlocking(ptrToReadSlot);
     }
-    virtual bool writeAudioBuffer(const int8_t* ptrToSlot, int len, int lostLen)
-    {
-        return mReceiveRingBuffer->insertSlotNonBlocking(ptrToSlot, len, lostLen);
-    }
-    virtual bool writeAudioBufferRegulator(const int8_t* ptrToSlot, int len, int seq,
-                                           int lostLen)
+    virtual bool writeAudioBuffer(const int8_t* ptrToSlot, int len, int lostLen, int seq)
     {
-        return mReceiveRingBuffer->insertSlotNonBlockingRegulator(ptrToSlot, len, seq,
-                                                                  lostLen);
+        return mReceiveRingBuffer->insertSlotNonBlocking(ptrToSlot, len, lostLen, seq);
     }
     uint32_t getBufferSizeInSamples() const
     {
@@ -586,6 +584,7 @@ class JackTrip : public QObject
     void signalUdpWaitingTooLong();
     void signalQueueLengthChanged(int queueLength);
     void signalAudioStarted();
+    void signalReceivedNetworkPacket();
 
    public:
     /// \brief Set the AudioInteface object
@@ -654,6 +653,10 @@ class JackTrip : public QObject
     RingBuffer* mSendRingBuffer;
     /// Pointer for the Receive RingBuffer
     RingBuffer* mReceiveRingBuffer;
+    /// thread used to pull packets from Regulator (if mBufferStrategy==3)
+    QThread* mRegulatorThreadPtr;
+    /// worker used to pull packets from Regulator (if mBufferStrategy==3)
+    QObject* mRegulatorWorkerPtr;
 
     int mReceiverBindPort;  ///< Incoming (receiving) port for local machine
     int mSenderPeerPort;    ///< Incoming (receiving) port for peer machine
index 003a0f93534af4423ba6347c3385e081ab10684a..d37479dc224113149572c09f426edb9c80a9e352 100644 (file)
@@ -117,7 +117,8 @@ JitterBuffer::JitterBuffer(int buf_samples, int qlen, int sample_rate, int strat
 }
 
 //*******************************************************************************
-bool JitterBuffer::insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int lostLen)
+bool JitterBuffer::insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int lostLen,
+                                         [[maybe_unused]] int seq_num)
 {
     if (0 == len) {
         len = mSlotSize;
index c61a02eaa09ae5f42da41baefa3e42dfecef1ded..7414420f6f8c8103bb22e5e89f61e356a244054a 100644 (file)
@@ -48,7 +48,8 @@ class JitterBuffer : public RingBuffer
                  int channels, int bit_res);
     virtual ~JitterBuffer() {}
 
-    virtual bool insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int lostLen);
+    virtual bool insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int lostLen,
+                                       int seq_num);
     virtual void readSlotNonBlocking(int8_t* ptrToReadSlot);
     virtual void readBroadcastSlot(int8_t* ptrToReadSlot);
 
index 56a950a9863fc9977e1d48d81df762fd12c21db8..ea6dc2cfe687277008d3b19e752e4346fca02002 100644 (file)
@@ -156,8 +156,10 @@ Regulator::Regulator(int rcvChannels, int bit_res, int FPP, int qLen, int bqLen)
     if (gVerboseFlag)
         cout << "mHist = " << mHist << " at " << mFPP << "\n";
     mBytes     = mFPP * mNumChannels * mBitResolutionMode;
-    mXfrBuffer = new int8_t[mBytes];
+    mPullQueue = new int8_t[mBytes * 2];
+    mXfrBuffer = mPullQueue;
     mPacketCnt = 0;  // burg initialization
+    mNextPacket.store(mPullQueue + mBytes, std::memory_order_release);
     mFadeUp.resize(mFPP, 0.0);
     mFadeDown.resize(mFPP, 0.0);
     for (int i = 0; i < mFPP; i++) {
@@ -203,7 +205,7 @@ Regulator::Regulator(int rcvChannels, int bit_res, int FPP, int qLen, int bqLen)
     changeGlobal_3(LostWindowMax);
     changeGlobal_2(NumSlotsMax);  // need hg if running GUI
     if (m_b_BroadcastQueueLength) {
-        m_b_ReceiveRingBuffer = new JitterBuffer(
+        m_b_BroadcastRingBuffer = new JitterBuffer(
             mFPP, qLen, 48000, 1, m_b_BroadcastQueueLength, mNumChannels, mAudioBitRes);
         qDebug() << "Broadcast started in Regulator with packet queue of"
                  << m_b_BroadcastQueueLength;
@@ -241,7 +243,7 @@ void Regulator::printParams(){
 
 Regulator::~Regulator()
 {
-    delete[] mXfrBuffer;
+    delete[] mPullQueue;
     delete[] mZeros;
     delete[] mAssembledPacket;
     delete pushStat;
@@ -252,7 +254,7 @@ Regulator::~Regulator()
         delete[] slot;
     };
     if (m_b_BroadcastQueueLength)
-        delete m_b_ReceiveRingBuffer;
+        delete m_b_BroadcastRingBuffer;
 }
 
 void Regulator::setFPPratio()
@@ -357,7 +359,7 @@ void Regulator::pushPacket(const int8_t* buf, int seq_num)
 };
 
 //*******************************************************************************
-void Regulator::pullPacket(int8_t* buf)
+void Regulator::pullPacket()
 {
     QMutexLocker locker(&mMutex);
     mSkip = 0;
@@ -419,7 +421,13 @@ ZERO_OUTPUT:
     memcpy(mXfrBuffer, mZeros, mBytes);
 
 OUTPUT:
-    memcpy(buf, mXfrBuffer, mBytes);
+    // swap positions of mXfrBuffer and mNextPacket
+    mNextPacket.store(mXfrBuffer, std::memory_order_release);
+    if (mXfrBuffer == mPullQueue) {
+        mXfrBuffer = mPullQueue + mBytes;
+    } else {
+        mXfrBuffer = mPullQueue;
+    }
 };
 
 //*******************************************************************************
index 9f66afc5087eb132117f7ff02ac00dfbd9f15867..cfe631e03b20bb553ca89adecc83a238c51a0ca8 100644 (file)
@@ -46,6 +46,8 @@
 
 #include <QDebug>
 #include <QElapsedTimer>
+#include <atomic>
+#include <cstring>
 
 #include "AudioInterface.h"
 #include "RingBuffer.h"
@@ -131,23 +133,27 @@ class Regulator : public RingBuffer
     // if (!mJackTrip->writeAudioBuffer(src, host_buf_size, last_seq_num))
     // instead of
     // if (!mJackTrip->writeAudioBuffer(src, host_buf_size, gap_size))
-    virtual bool insertSlotNonBlockingRegulator(const int8_t* ptrToSlot,
-                                                [[maybe_unused]] int len,
-                                                [[maybe_unused]] int seq_num, int lostLen)
+    virtual bool insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int lostLen,
+                                       int seq_num)
     {
         shimFPP(ptrToSlot, len, seq_num);
         if (m_b_BroadcastQueueLength)
-            m_b_ReceiveRingBuffer->insertSlotNonBlocking(ptrToSlot, len, lostLen);
+            m_b_BroadcastRingBuffer->insertSlotNonBlocking(ptrToSlot, len, lostLen,
+                                                           seq_num);
         return (true);
     }
 
-    void pullPacket(int8_t* buf);
+    // called by RegulatorWorker after each audio callback, to prep next packet
+    void pullPacket();
+
+    virtual void readSlotNonBlocking(int8_t* ptrToReadSlot)
+    {
+        ::memcpy(ptrToReadSlot, mNextPacket.load(std::memory_order_acquire), mBytes);
+    }
 
-    virtual void readSlotNonBlocking(int8_t* ptrToReadSlot) { pullPacket(ptrToReadSlot); }
     virtual void readBroadcastSlot(int8_t* ptrToReadSlot)
     {
-        m_b_ReceiveRingBuffer->readSlotNonBlocking(ptrToReadSlot);
-        m_b_ReceiveRingBuffer->readBroadcastSlot(ptrToReadSlot);
+        m_b_BroadcastRingBuffer->readBroadcastSlot(ptrToReadSlot);
     }
 
     //    virtual QString getStats(uint32_t statCount, uint32_t lostCount);
@@ -169,7 +175,9 @@ class Regulator : public RingBuffer
     BurgAlgorithm ba;
     int mBytes;
     int mBytesPeerPacket;
+    int8_t* mPullQueue;
     int8_t* mXfrBuffer;
+    std::atomic<const void*> mNextPacket;
     int8_t* mAssembledPacket;
     int mPacketCnt;
     sample_t bitsToSample(int ch, int frame);
@@ -203,8 +211,30 @@ class Regulator : public RingBuffer
     void changeGlobal_2(int);
     void changeGlobal_3(int);
     void printParams();
-    /// Pointer for the Receive RingBuffer
-    RingBuffer* m_b_ReceiveRingBuffer;
+
+    /// Pointer for the Broadcast RingBuffer
+    RingBuffer* m_b_BroadcastRingBuffer;
     int m_b_BroadcastQueueLength;
 };
+
+class RegulatorWorker : public QObject
+{
+    Q_OBJECT;
+
+   public:
+    RegulatorWorker(Regulator* rPtr) : mRegulatorPtr(rPtr) {}
+    virtual ~RegulatorWorker() {}
+
+   public slots:
+    void pullPacket()
+    {
+        if (mRegulatorPtr != nullptr) {
+            mRegulatorPtr->pullPacket();
+        }
+    }
+
+   private:
+    Regulator* mRegulatorPtr;
+};
+
 #endif  //__REGULATOR_H__
index 8e362bb5e4970a8bc42990f7f1bb6bd6a61c986f..b2fc0e47de6b38c799a2aaa0e18edda67d2d47fe 100644 (file)
@@ -147,7 +147,8 @@ void RingBuffer::readSlotBlocking(int8_t* ptrToReadSlot)
 }
 
 //*******************************************************************************
-bool RingBuffer::insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int lostLen)
+bool RingBuffer::insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int lostLen,
+                                       [[maybe_unused]] int seq_num)
 {
     if (len != mSlotSize && 0 != len) {
         // RingBuffer does not support mixed buf sizes
@@ -183,15 +184,6 @@ bool RingBuffer::insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int los
     return true;
 }
 
-//*******************************************************************************
-bool RingBuffer::insertSlotNonBlockingRegulator([[maybe_unused]] const int8_t* ptrToSlot,
-                                                [[maybe_unused]] int len,
-                                                [[maybe_unused]] int seq_num,
-                                                [[maybe_unused]] int lostLen)
-{
-    return true;
-}
-
 //*******************************************************************************
 void RingBuffer::readSlotNonBlocking(int8_t* ptrToReadSlot)
 {
index f7415cba59fc24885b6d9d8e703e6f6add1c37fd..a35f0b28ebbf18f225bf88aab4b26f715f6f97e8 100644 (file)
@@ -92,13 +92,8 @@ class RingBuffer
     /** \brief Same as insertSlotBlocking but non-blocking (asynchronous)
      * \param ptrToSlot Pointer to slot to insert into the RingBuffer
      */
-    virtual bool insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int lostLen);
-
-    /** \brief Same as insertSlotNonBlocking but seq_num for Regulator
-     * \param ptrToSlot Pointer to slot to insert into the RingBuffer
-     */
-    virtual bool insertSlotNonBlockingRegulator(const int8_t* ptrToSlot, int len,
-                                                int seq_num, int lostLen);
+    virtual bool insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int lostLen,
+                                       int seq_num);
 
     /** \brief Same as readSlotBlocking but non-blocking (asynchronous)
      * \param ptrToReadSlot Pointer to read slot from the RingBuffer
index 5f89eb5306b45660e0d865381757a04e419ecbe6..8bd1217e3008643e9e4e0e0c865983b51338608c 100644 (file)
@@ -37,6 +37,7 @@
 
 #include "RtAudioInterface.h"
 
+#include <QString>
 #include <cstdlib>
 
 #include "JackTrip.h"
@@ -65,11 +66,13 @@ RtAudioInterface::RtAudioInterface(int NumInChans, int NumOutChans,
 //*******************************************************************************
 RtAudioInterface::~RtAudioInterface()
 {
-    delete mRtAudio;
+    if (mRtAudio != NULL) {
+        delete mRtAudio;
+    }
 }
 
 //*******************************************************************************
-void RtAudioInterface::setup()
+void RtAudioInterface::setup(bool verbose)
 {
     // Initialize Buffer array to read and write audio and members
     mNumInChans  = getNumInputChannels();
@@ -79,54 +82,93 @@ void RtAudioInterface::setup()
 
     cout << "Setting Up RtAudio Interface" << endl;
     cout << gPrintSeparator << endl;
-    mRtAudio = new RtAudio;
 
-    int deviceId_input;
-    int deviceId_output;
-    unsigned int n_devices = mRtAudio->getDeviceCount();
-    if (n_devices < 1) {
+    AudioInterface::setDevicesWarningMsg(AudioInterface::DEVICE_WARN_NONE);
+    AudioInterface::setDevicesErrorMsg(AudioInterface::DEVICE_ERR_NONE);
+
+    int index_in  = -1;
+    int index_out = -1;
+    std::string api_in;
+    std::string api_out;
+
+    QStringList all_input_devices;
+    QStringList all_output_devices;
+    getDeviceList(&all_input_devices, NULL, true);
+    getDeviceList(&all_output_devices, NULL, false);
+
+    unsigned int n_devices_input  = all_input_devices.size();
+    unsigned int n_devices_output = all_output_devices.size();
+    unsigned int n_devices_total  = n_devices_input + n_devices_output;
+
+    RtAudio* rtAudioIn;
+    RtAudio* rtAudioOut;
+
+    // unsigned int n_devices = mRtAudio->getDeviceCount();
+    if (n_devices_total < 1) {
+        AudioInterface::setDevicesErrorMsg(AudioInterface::DEVICE_ERR_NO_DEVICES);
         cout << "No audio devices found!" << endl;
         std::exit(0);
     } else {
-        deviceId_input = getDeviceID();
-        if (deviceId_input < 0) {
-            auto inName    = getInputDevice();
-            deviceId_input = getDeviceIdFromName(inName, true);
-            if (!inName.empty() && (deviceId_input < 0)) {
-                throw std::runtime_error("Requested input device \"" + inName
-                                         + "\" not found.");
-            }
+        // Locate the selected input audio device
+        auto inName = getInputDevice();
+        getDeviceInfoFromName(inName, &index_in, &api_in, true);
+        if (!inName.empty() && (index_in < 0)) {
+            throw std::runtime_error("Requested input device \"" + inName
+                                     + "\" not found.");
         }
-        if (deviceId_input < 0) {
-            cout << "Selecting default INPUT device" << endl;
-            if (mRtAudio->getCurrentApi() == RtAudio::LINUX_PULSE) {
-                deviceId_input = getDefaultDevice(true);
+        rtAudioIn = new RtAudio(RtAudio::getCompiledApiByName(api_in));
+
+        // The selected input audio device is not available, so select the default device
+        if (index_in < 0) {
+            // reset rtAudioIn using the system default
+            delete rtAudioIn;
+            rtAudioIn = new RtAudio;
+            api_in    = RtAudio::getApiName(rtAudioIn->getCurrentApi());
+
+            // Edge case for Linux Pulse Audio
+            if (rtAudioIn->getCurrentApi() == RtAudio::LINUX_PULSE) {
+                index_in = getDefaultDeviceForLinuxPulseAudio(true);
             } else {
-                deviceId_input = mRtAudio->getDefaultInputDevice();
+                index_in = rtAudioIn->getDefaultInputDevice();
             }
+
+            cout << "Selected default INPUT device" << endl;
+        } else {
+            cout << "Selected INPUT device " << inName << endl;
         }
 
-        deviceId_output = getDeviceID();
-        if (deviceId_output < 0) {
-            auto outName    = getOutputDevice();
-            deviceId_output = getDeviceIdFromName(outName, false);
-            if (!outName.empty() && (deviceId_output < 0)) {
-                throw std::runtime_error("Requested output device \"" + outName
-                                         + "\" not found.");
-            }
+        // Locate the selected output audio device
+        auto outName = getOutputDevice();
+        getDeviceInfoFromName(outName, &index_out, &api_out, false);
+        if (!outName.empty() && (index_out < 0)) {
+            throw std::runtime_error("Requested output device \"" + outName
+                                     + "\" not found.");
         }
-        if (deviceId_output < 0) {
-            cout << "Selecting default OUTPUT device" << endl;
-            if (mRtAudio->getCurrentApi() == RtAudio::LINUX_PULSE) {
-                deviceId_output = getDefaultDevice(false);
+        rtAudioOut = new RtAudio(RtAudio::getCompiledApiByName(api_out));
+
+        // The selected output audio device is not available, so select the default device
+        if (index_out < 0) {
+            // reset rtAudioIn using the system default
+            delete rtAudioOut;
+            rtAudioOut = new RtAudio;
+            api_out    = RtAudio::getApiName(rtAudioOut->getCurrentApi());
+
+            // Edge case for Linux Pulse Audio
+            if (rtAudioOut->getCurrentApi() == RtAudio::LINUX_PULSE) {
+                index_out = getDefaultDeviceForLinuxPulseAudio(false);
             } else {
-                deviceId_output = mRtAudio->getDefaultOutputDevice();
+                index_out = rtAudioOut->getDefaultOutputDevice();
             }
+
+            cout << "Selected default OUTPUT device" << endl;
+
+        } else {
+            cout << "Selected OUTPUT device " << outName << endl;
         }
     }
 
-    auto dev_info_input  = mRtAudio->getDeviceInfo(deviceId_input);
-    auto dev_info_output = mRtAudio->getDeviceInfo(deviceId_output);
+    auto dev_info_input  = rtAudioIn->getDeviceInfo(index_in);
+    auto dev_info_output = rtAudioOut->getDeviceInfo(index_out);
 
     if (static_cast<unsigned int>(getNumInputChannels()) > dev_info_input.inputChannels) {
         setNumInputChannels(dev_info_input.inputChannels);
@@ -136,16 +178,42 @@ void RtAudioInterface::setup()
         setNumOutputChannels(dev_info_output.outputChannels);
     }
 
-    cout << "INPUT DEVICE:" << endl;
-    printDeviceInfo(deviceId_input);
-    cout << gPrintSeparator << endl;
-    cout << "OUTPUT DEVICE:" << endl;
-    printDeviceInfo(deviceId_output);
-    cout << gPrintSeparator << endl;
+    if (verbose) {
+        cout << "INPUT DEVICE:" << endl;
+        printDeviceInfo(api_in, index_in);
+
+        cout << gPrintSeparator << endl;
+        cout << "OUTPUT DEVICE:" << endl;
+
+        printDeviceInfo(api_out, index_out);
+        cout << gPrintSeparator << endl;
+    }
+
+    if (n_devices_input == 0) {
+        AudioInterface::setDevicesErrorMsg(AudioInterface::DEVICE_ERR_NO_INPUTS);
+    } else if (n_devices_output == 0) {
+        AudioInterface::setDevicesErrorMsg(AudioInterface::DEVICE_ERR_NO_OUTPUTS);
+    }
+
+    delete rtAudioIn;
+    delete rtAudioOut;
+    if (api_in == api_out) {
+        mRtAudio = new RtAudio(RtAudio::getCompiledApiByName(api_in));
+#ifdef _WIN32
+        if (api_in != "asio") {
+            AudioInterface::setDevicesWarningMsg(AudioInterface::DEVICE_WARN_LATENCY);
+            AudioInterface::setDevicesErrorMsg(AudioInterface::DEVICE_ERR_NONE);
+        }
+#endif
+    } else {
+        AudioInterface::setDevicesWarningMsg(AudioInterface::DEVICE_WARN_NONE);
+        AudioInterface::setDevicesErrorMsg(AudioInterface::DEVICE_ERR_INCOMPATIBLE);
+        mRtAudio = NULL;
+    }
 
     RtAudio::StreamParameters in_params, out_params;
-    in_params.deviceId   = deviceId_input;
-    out_params.deviceId  = deviceId_output;
+    in_params.deviceId   = index_in;
+    out_params.deviceId  = index_out;
     in_params.nChannels  = getNumInputChannels();
     out_params.nChannels = getNumOutputChannels();
 
@@ -163,72 +231,46 @@ void RtAudioInterface::setup()
     unsigned int bufferFrames = getBufferSizeInSamples();  // mBufferSize;
 
     try {
-        // IMPORTANT NOTE: It's VERY important to remember to pass this
-        // as the user data in the process callback, otherwise member won't
+        // IMPORTANT NOTE: It's VERY important to remember to pass "this"
+        // to the user data in the process callback, otherwise member won't
         // be accessible
-        mRtAudio->openStream(&out_params, &in_params, RTAUDIO_FLOAT32, sampleRate,
-                             &bufferFrames, &RtAudioInterface::wrapperRtAudioCallback,
-                             this, &options, &RtAudioInterface::RtAudioErrorCallback);
+        if (mRtAudio != NULL) {
+            mRtAudio->openStream(&out_params, &in_params, RTAUDIO_FLOAT32, sampleRate,
+                                 &bufferFrames, &RtAudioInterface::wrapperRtAudioCallback,
+                                 this, &options, &RtAudioInterface::RtAudioErrorCallback);
+        }
+
         setBufferSize(bufferFrames);
     } catch (RtAudioError& e) {
-        std::cout << '\n' << e.getMessage() << '\n' << std::endl;
+        std::cout << e.getMessage() << '\n' << std::endl;
         throw std::runtime_error(e.getMessage());
     }
 
     // Setup parent class
-    AudioInterface::setup();
-}
-
-//*******************************************************************************
-void RtAudioInterface::listAllInterfaces()
-{
-    RtAudio rtaudio;
-    if (rtaudio.getDeviceCount() < 1) {
-        cout << "No audio devices found!" << endl;
-    } else {
-        for (unsigned int i = 0; i < rtaudio.getDeviceCount(); i++) {
-            printDeviceInfo(i);
-            cout << gPrintSeparator << endl;
-        }
-    }
+    AudioInterface::setup(verbose);
 }
 
 //*******************************************************************************
 void RtAudioInterface::printDevices()
 {
-    // TODO: evenntually list devices for all RtAudio-compiled backends
-    RtAudio audio;
-    audio.showWarnings(false);
-    cout << "Available audio devices: " << endl;
-    unsigned int devices = audio.getDeviceCount();
-    RtAudio::DeviceInfo info;
-    for (unsigned int i = 0; i < devices; i++) {
-        info = audio.getDeviceInfo(i);
-        if (info.probed == true) {
-            std::cout << i << ": \"" << info.name << "\" ";
-            std::cout << "(" << info.inputChannels << " ins, " << info.outputChannels
-                      << " outs)" << endl;
-        }
-    }
-}
-
-//*******************************************************************************
-int RtAudioInterface::getDeviceIdFromName(std::string deviceName, bool isInput)
-{
-    RtAudio rtaudio;
-    for (unsigned int i = 0; i < rtaudio.getDeviceCount(); i++) {
-        auto info = rtaudio.getDeviceInfo(i);
-        if (info.probed == true) {
-            if (info.name == deviceName) {
-                if (isInput && info.inputChannels > 0) {
-                    return i;
-                } else if (!isInput && info.outputChannels > 0) {
-                    return i;
-                }
+    std::vector<RtAudio::Api> apis;
+    RtAudio::getCompiledApi(apis);
+
+    for (uint32_t i = 0; i < apis.size(); i++) {
+        RtAudio rtaudio(apis.at(i));
+        unsigned int devices = rtaudio.getDeviceCount();
+        for (unsigned int j = 0; j < devices; j++) {
+            RtAudio::DeviceInfo info = rtaudio.getDeviceInfo(j);
+            if (info.probed == true) {
+                std::cout << "[" << RtAudio::getApiDisplayName(rtaudio.getCurrentApi())
+                          << " - " << j << "]"
+                          << ": \"";
+                std::cout << info.name << "\" ";
+                std::cout << "(" << info.inputChannels << " ins, " << info.outputChannels
+                          << " outs)" << endl;
             }
         }
     }
-    return -1;
 }
 
 //*******************************************************************************
@@ -238,7 +280,7 @@ int RtAudioInterface::getDeviceIdFromName(std::string deviceName, bool isInput)
 // Once this functinoality is provided upstream and in the distributions'
 // package managers, the following function can be removed and the default device
 // can be obtained by calls to getDefaultInputDevice() / getDefaultOutputDevice()
-unsigned int RtAudioInterface::getDefaultDevice(bool isInput)
+unsigned int RtAudioInterface::getDefaultDeviceForLinuxPulseAudio(bool isInput)
 {
     RtAudio rtaudio;
     for (unsigned int i = 0; i < rtaudio.getDeviceCount(); i++) {
@@ -257,20 +299,19 @@ unsigned int RtAudioInterface::getDefaultDevice(bool isInput)
 }
 
 //*******************************************************************************
-void RtAudioInterface::printDeviceInfo(unsigned int deviceId)
+void RtAudioInterface::printDeviceInfo(std::string api, unsigned int deviceIndex)
 {
-    RtAudio rtaudio;
-    RtAudio::DeviceInfo info;
-    int i = deviceId;
-    info  = rtaudio.getDeviceInfo(i);
-    std::vector<unsigned int> sampleRates;
-    cout << "Audio Device  [" << i << "] : " << info.name << endl;
+    RtAudio rtaudio(RtAudio::getCompiledApiByName(api));
+    RtAudio::DeviceInfo info              = rtaudio.getDeviceInfo(deviceIndex);
+    std::vector<unsigned int> sampleRates = info.sampleRates;
+
+    cout << "Audio Device  [" << RtAudio::getApiDisplayName(rtaudio.getCurrentApi())
+         << " - " << deviceIndex << "] : " << info.name << endl;
     cout << "  Output Channels : " << info.outputChannels << endl;
     cout << "  Input Channels  : " << info.inputChannels << endl;
-    sampleRates = info.sampleRates;
     cout << "  Supported Sampling Rates: ";
-    for (unsigned int ii = 0; ii < sampleRates.size(); ii++) {
-        cout << sampleRates[ii] << " ";
+    for (unsigned int i = 0; i < sampleRates.size(); i++) {
+        cout << sampleRates[i] << " ";
     }
     cout << endl;
     if (info.isDefaultOutput) {
@@ -289,21 +330,29 @@ int RtAudioInterface::RtAudioCallback(void* outputBuffer, void* inputBuffer,
                                       unsigned int nFrames, double /*streamTime*/,
                                       RtAudioStreamStatus /*status*/)
 {
-    sample_t* inputBuffer_sample  = (sample_t*)inputBuffer;
-    sample_t* outputBuffer_sample = (sample_t*)outputBuffer;
-
-    // Get input and output buffers
-    //-------------------------------------------------------------------
-    for (int i = 0; i < mNumInChans; i++) {
-        // Input Ports are READ ONLY
-        mInBuffer[i] = inputBuffer_sample + (nFrames * i);
-    }
-    for (int i = 0; i < mNumOutChans; i++) {
-        // Output Ports are WRITABLE
-        mOutBuffer[i] = outputBuffer_sample + (nFrames * i);
+    // TODO: this function may need more changes. As-is I'm not sure this will work
+
+    sample_t* inputBuffer_sample  = NULL;
+    sample_t* outputBuffer_sample = NULL;
+
+    inputBuffer_sample  = (sample_t*)inputBuffer;
+    outputBuffer_sample = (sample_t*)outputBuffer;
+
+    if (inputBuffer_sample != NULL && outputBuffer_sample != NULL) {
+        // Get input and output buffers
+        //-------------------------------------------------------------------
+        for (int i = 0; i < mNumInChans; i++) {
+            // Input Ports are READ ONLY
+            mInBuffer[i] = inputBuffer_sample + (nFrames * i);
+        }
+        for (int i = 0; i < mNumOutChans; i++) {
+            // Output Ports are WRITABLE
+            mOutBuffer[i] = outputBuffer_sample + (nFrames * i);
+        }
+
+        AudioInterface::callback(mInBuffer, mOutBuffer, nFrames);
     }
 
-    AudioInterface::callback(mInBuffer, mOutBuffer, nFrames);
     return 0;
 }
 
@@ -312,8 +361,9 @@ int RtAudioInterface::wrapperRtAudioCallback(void* outputBuffer, void* inputBuff
                                              unsigned int nFrames, double streamTime,
                                              RtAudioStreamStatus status, void* userData)
 {
-    return static_cast<RtAudioInterface*>(userData)->RtAudioCallback(
-        outputBuffer, inputBuffer, nFrames, streamTime, status);
+    RtAudioInterface* interface = static_cast<RtAudioInterface*>(userData);
+    return interface->RtAudioCallback(outputBuffer, inputBuffer, nFrames, streamTime,
+                                      status);
 }
 
 //*******************************************************************************
@@ -321,31 +371,192 @@ void RtAudioInterface::RtAudioErrorCallback(RtAudioError::Type type,
                                             const std::string& errorText)
 {
     if ((type != RtAudioError::WARNING) && (type != RtAudioError::DEBUG_WARNING)) {
-        std::cout << '\n' << errorText << '\n' << std::endl;
+        std::cout << errorText << '\n' << std::endl;
         throw std::runtime_error(errorText);
+    } else if (type == RtAudioError::WARNING) {
+        std::cout << errorText << '\n' << std::endl;
+    } else if (type == RtAudioError::DEBUG_WARNING) {
+        std::cout << errorText << '\n' << std::endl;
     }
 }
 
 //*******************************************************************************
-int RtAudioInterface::startProcess() const
+int RtAudioInterface::startProcess()
 {
     try {
-        mRtAudio->startStream();
+        if (mRtAudio != NULL) {
+            mRtAudio->startStream();
+        }
     } catch (RtAudioError& e) {
-        std::cout << '\n' << e.getMessage() << '\n' << std::endl;
+        std::cout << e.getMessage() << '\n' << std::endl;
         return (-1);
     }
     return (0);
 }
 
 //*******************************************************************************
-int RtAudioInterface::stopProcess() const
+int RtAudioInterface::stopProcess()
 {
     try {
-        mRtAudio->closeStream();
+        if (mRtAudio != NULL) {
+            mRtAudio->closeStream();
+            AudioInterface::setDevicesWarningMsg(AudioInterface::DEVICE_WARN_NONE);
+            AudioInterface::setDevicesErrorMsg(AudioInterface::DEVICE_ERR_NONE);
+        }
     } catch (RtAudioError& e) {
-        std::cout << '\n' << e.getMessage() << '\n' << std::endl;
+        std::cout << e.getMessage() << '\n' << std::endl;
         return (-1);
     }
     return 0;
 }
+
+//*******************************************************************************
+void RtAudioInterface::getDeviceList(QStringList* list, QStringList* categories,
+                                     bool isInput)
+{
+    RtAudio baseRtAudio;
+    RtAudio::Api baseRtAudioApi = baseRtAudio.getCurrentApi();
+
+    // Add (default)
+    list->clear();
+    list->append(QStringLiteral("(default)"));
+    if (categories != NULL) {
+#ifdef _WIN32
+        switch (baseRtAudioApi) {
+        case RtAudio::WINDOWS_ASIO:
+            categories->append(QStringLiteral("Low-Latency (ASIO)"));
+            break;
+        case RtAudio::WINDOWS_WASAPI:
+            categories->append(QStringLiteral("All Devices (Non-ASIO)"));
+            break;
+        case RtAudio::WINDOWS_DS:
+            categories->append(QStringLiteral("All Devices (Non-ASIO)"));
+            break;
+        default:
+            categories->append(QStringLiteral(""));
+            break;
+        }
+#else
+        categories->append(QStringLiteral(""));
+#endif
+    }
+
+    // Explicitly add default device
+    QString defaultDeviceName = "";
+    uint32_t defaultDeviceIdx;
+    if (isInput) {
+        defaultDeviceIdx = baseRtAudio.getDefaultInputDevice();
+    } else {
+        defaultDeviceIdx = baseRtAudio.getDefaultOutputDevice();
+    }
+
+    if (defaultDeviceIdx != 0) {
+        RtAudio::DeviceInfo info = baseRtAudio.getDeviceInfo(defaultDeviceIdx);
+        defaultDeviceName        = QString::fromStdString(info.name);
+    }
+
+    if (defaultDeviceName != "") {
+        list->append(defaultDeviceName);
+        if (categories != NULL) {
+#ifdef _WIN32
+            switch (baseRtAudioApi) {
+            case RtAudio::WINDOWS_ASIO:
+                categories->append(QStringLiteral("Low-Latency (ASIO)"));
+                break;
+            case RtAudio::WINDOWS_WASAPI:
+                categories->append(QStringLiteral("All Devices (Non-ASIO)"));
+                break;
+            case RtAudio::WINDOWS_DS:
+                categories->append(QStringLiteral("All Devices (Non-ASIO)"));
+                break;
+            default:
+                categories->append(QStringLiteral(""));
+                break;
+            }
+#else
+            categories->append(QStringLiteral(""));
+#endif
+        }
+    }
+
+    std::vector<RtAudio::Api> apis;
+    RtAudio::getCompiledApi(apis);
+
+    for (uint32_t i = 0; i < apis.size(); i++) {
+        RtAudio::Api api = apis.at(i);
+        RtAudio rtaudio(api);
+        unsigned int devices = rtaudio.getDeviceCount();
+        for (unsigned int j = 0; j < devices; j++) {
+            RtAudio::DeviceInfo info = rtaudio.getDeviceInfo(j);
+            if (info.probed == true) {
+                // Don't include duplicate entries
+                if (list->contains(QString::fromStdString(info.name))) {
+                    continue;
+                }
+
+                // Skip the default device, since we already added it
+                if (QString::fromStdString(info.name) == defaultDeviceName
+                    && api == baseRtAudioApi) {
+                    continue;
+                }
+
+                if (isInput && info.inputChannels > 0) {
+                    list->append(QString::fromStdString(info.name));
+                } else if (!isInput && info.outputChannels > 0) {
+                    list->append(QString::fromStdString(info.name));
+                }
+
+                if (categories == NULL) {
+                    continue;
+                }
+
+#ifdef _WIN32
+                switch (api) {
+                case RtAudio::WINDOWS_ASIO:
+                    categories->append("Low-Latency (ASIO)");
+                    break;
+                case RtAudio::WINDOWS_WASAPI:
+                    categories->append("All Devices (Non-ASIO)");
+                    break;
+                case RtAudio::WINDOWS_DS:
+                    categories->append("All Devices (Non-ASIO)");
+                    break;
+                default:
+                    categories->append("");
+                    break;
+                }
+#else
+                categories->append("");
+#endif
+            }
+        }
+    }
+}
+
+//*******************************************************************************
+void RtAudioInterface::getDeviceInfoFromName(std::string deviceName, int* index,
+                                             std::string* api, bool isInput)
+{
+    std::vector<RtAudio::Api> apis;
+    RtAudio::getCompiledApi(apis);
+
+    for (uint32_t i = 0; i < apis.size(); i++) {
+        RtAudio rtaudio(apis.at(i));
+        unsigned int devices = rtaudio.getDeviceCount();
+        for (unsigned int j = 0; j < devices; j++) {
+            RtAudio::DeviceInfo info = rtaudio.getDeviceInfo(j);
+            if (info.probed == true && deviceName == info.name) {
+                if ((isInput && info.inputChannels > 0)
+                    || (!isInput && info.outputChannels > 0)) {
+                    *index = j;
+                    *api   = RtAudio::getApiName(rtaudio.getCurrentApi());
+                    return;
+                }
+            }
+        }
+    }
+
+    *index = -1;
+    *api   = "";
+    return;
+}
\ No newline at end of file
index afcfba0050bc6084ad06db266fb0e98a8ebbc915..bb1b7462f555ab9e80f22d701cc1fe06226d798a 100644 (file)
@@ -40,6 +40,8 @@
 
 #include <RtAudio.h>
 
+#include <QQueue>
+
 #include "AudioInterface.h"
 #include "jacktrip_globals.h"
 class JackTrip;  // Forward declaration
@@ -65,15 +67,17 @@ class RtAudioInterface : public AudioInterface
     virtual ~RtAudioInterface();
 
     /// \brief List all available audio interfaces, with its properties
-    virtual void listAllInterfaces();
     static void printDevices();
-    virtual int getDeviceIdFromName(std::string deviceName, bool isInput);
-    virtual void setup();
-    virtual int startProcess() const;
-    virtual int stopProcess() const;
+    virtual void setup(bool verbose = true);
+    virtual int startProcess();
+    virtual int stopProcess();
     /// \brief This has no effect in RtAudio
     virtual void connectDefaultPorts() {}
 
+    static void getDeviceList(QStringList* list, QStringList* categories, bool isInput);
+    static void getDeviceInfoFromName(std::string deviceName, int* index,
+                                      std::string* api, bool isInput);
+
     //--------------SETTERS---------------------------------------------
     /// \brief This has no effect in RtAudio
     virtual void setClientName(const QString& /*ClientName*/) {}
@@ -90,7 +94,7 @@ class RtAudioInterface : public AudioInterface
                                       RtAudioStreamStatus status, void* userData);
     static void RtAudioErrorCallback(RtAudioError::Type type,
                                      const std::string& errorText);
-    void printDeviceInfo(unsigned int deviceId);
+    void printDeviceInfo(std::string api, unsigned int deviceId);
 
     int mNumInChans;   ///< Number of Input Channels
     int mNumOutChans;  ///<  Number of Output Channels
@@ -98,8 +102,8 @@ class RtAudioInterface : public AudioInterface
         mInBuffer;  ///< Vector of Input buffers/channel read from JACK
     QVarLengthArray<float*>
         mOutBuffer;     ///< Vector of Output buffer/channel to write to JACK
-    RtAudio* mRtAudio;  ///< RtAudio class
-    unsigned int getDefaultDevice(bool isInput);
+    RtAudio* mRtAudio;  ///< RtAudio class if the input and output device are the same
+    unsigned int getDefaultDeviceForLinuxPulseAudio(bool isInput);
 };
 
 #endif  // __RTAUDIOINTERFACE_H__
index bf1e4c3dde2d50ae27f309195f1ab38230ad75a4..bfd862cb858139f6c28da79342061c48f5e7a7df 100644 (file)
@@ -127,11 +127,7 @@ UdpDataProtocol::~UdpDataProtocol()
 void UdpDataProtocol::setPeerAddress(const char* peerHostOrIP)
 {
     // Get DNS Address
-#ifndef _WIN32
-    // Don't make the following code conditional on windows
-    //(Addresses a weird timing bug when in hub client mode)
     if (!mPeerAddress.setAddress(peerHostOrIP)) {
-#endif
         QHostInfo info = QHostInfo::fromName(peerHostOrIP);
         if (!info.addresses().isEmpty()) {
             // use the first IP address
@@ -139,9 +135,7 @@ void UdpDataProtocol::setPeerAddress(const char* peerHostOrIP)
         }
         // cout << "UdpDataProtocol::setPeerAddress IP Address Number: "
         //    << mPeerAddress.toString().toStdString() << endl;
-#ifndef _WIN32
     }
-#endif
 
     // check if the ip address is valid
     if (mPeerAddress.protocol() == QAbstractSocket::IPv6Protocol) {
@@ -324,24 +318,29 @@ int UdpDataProtocol::bindSocket()
     return sock_fd;
 }
 
+void UdpDataProtocol::processControlPacket(const char* buf)
+{
+    // Control signal (currently just check for exit packet);
+    bool exit = true;
+    for (int i = 0; i < mControlPacketSize; i++) {
+        if (buf[i] != char(0xff)) {
+            exit = false;
+            i    = mControlPacketSize;
+        }
+    }
+    if (exit && !mStopSignalSent) {
+        mStopSignalSent = true;
+        emit signalCeaseTransmission(QStringLiteral("Peer Stopped"));
+        std::cout << "Peer Stopped" << std::endl;
+    }
+}
+
 //*******************************************************************************
 int UdpDataProtocol::receivePacket(char* buf, const size_t n)
 {
     int n_bytes = ::recv(mSocket, buf, n, 0);
     if (n_bytes == mControlPacketSize) {
-        // Control signal (currently just check for exit packet);
-        bool exit = true;
-        for (int i = 0; i < mControlPacketSize; i++) {
-            if (buf[i] != char(0xff)) {
-                exit = false;
-                i    = mControlPacketSize;
-            }
-        }
-        if (exit && !mStopSignalSent) {
-            mStopSignalSent = true;
-            emit signalCeaseTransmission(QStringLiteral("Peer Stopped"));
-            std::cout << "Peer Stopped" << std::endl;
-        }
+        processControlPacket(buf);
         return 0;
     }
     return n_bytes;
@@ -621,14 +620,39 @@ void UdpDataProtocol::run()
         mRevivedCount            = 0;
         mStatCount               = 0;
 
-        //Set up our platform specific polling mechanism. (kqueue, epoll)
-#if !defined (MANUAL_POLL) && !defined (_WIN32)
-#ifdef __linux__
+        //Set up our platform specific polling mechanism. (kqueue, epoll, overlapped I/O)
+#if !defined (MANUAL_POLL)
+#if defined (__linux__)
         int epollfd = epoll_create1(0);
         struct epoll_event change, event;
         change.events = EPOLLIN;
         change.data.fd = mSocket;
         epoll_ctl(epollfd, EPOLL_CTL_ADD, mSocket, &change);
+#elif defined (_WIN32)
+        WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];
+        WSAOVERLAPPED socketOverlapped;
+        WSABUF dataBuf;
+        dataBuf.len = full_redundant_packet_size;
+        dataBuf.buf = reinterpret_cast<char *>(full_redundant_packet);
+        DWORD recvBytes = 0, flags = 0, index, bytesTransferred = 0;
+        
+        eventArray[0] = WSACreateEvent();
+        if (eventArray == WSA_INVALID_EVENT) {
+            emit signalError("Unable to set up network event monitoring");
+            cout << "ERROR: Unable to set up network event monitoring" << endl;
+            mStopped = true;
+        }
+        ZeroMemory(&socketOverlapped, sizeof(WSAOVERLAPPED));
+        socketOverlapped.hEvent = eventArray[0];
+        
+        if (WSARecv(mSocket, &dataBuf, 1, &recvBytes, &flags, &socketOverlapped, NULL) == SOCKET_ERROR) {
+            int result = WSAGetLastError();
+            if (result != WSA_IO_PENDING) {
+                emit signalError("Unable to listen for incoming network packets");
+                cout << "ERROR: Unable to listen for incoming network packets" << endl;
+                mStopped = true;
+            }
+        }
 #else
         int kq = kqueue();
         struct kevent change;
@@ -649,12 +673,14 @@ void UdpDataProtocol::run()
             // arrive for a longer time
             //timeout = UdpSocket.waitForReadyRead(30);
             //        timeout = cc unused!
-#if defined (_WIN32) || defined (MANUAL_POLL)
+#if defined (MANUAL_POLL)
             waitForReady(60000); //60 seconds
-            receivePacketRedundancy(full_redundant_packet, full_redundant_packet_size,
-                                    full_packet_size, current_seq_num, last_seq_num,
-                                    newer_seq_num);
-       }
+            if (receivePacket(reinterpret_cast<char *>(full_redundant_packet), full_redundant_packet_size) > 0) {
+                receivePacketRedundancy(full_redundant_packet, full_redundant_packet_size,
+                                        full_packet_size, current_seq_num, last_seq_num,
+                                        newer_seq_num);
+            }
+        }
 #else
             
             // OLD CODE WITHOUT REDUNDANCY----------------------------------------------------
@@ -671,27 +697,51 @@ void UdpDataProtocol::run()
         */
             //----------------------------------------------------------------------------------
 
-#ifdef __linux__
+#if defined(_WIN32)
+            index = WSAWaitForMultipleEvents(1, eventArray, FALSE, 10, FALSE);
+            if (index == WSA_WAIT_TIMEOUT) {
+                waitTime += 10;
+                emit signalWaitingTooLong(waitTime);
+            } else {
+                waitTime = 0;
+                WSAResetEvent(eventArray[index - WSA_WAIT_EVENT_0]);
+                WSAGetOverlappedResult(mSocket, &socketOverlapped, &bytesTransferred, FALSE, &flags);
+                if (bytesTransferred == mControlPacketSize) {
+                    processControlPacket(reinterpret_cast<char *>(full_redundant_packet));
+                } else if (bytesTransferred > 0 ){
+                    receivePacketRedundancy(full_redundant_packet, full_redundant_packet_size,
+                                        full_packet_size, current_seq_num, last_seq_num,
+                                        newer_seq_num);
+                }
+                WSARecv(mSocket, &dataBuf, 1, &recvBytes, &flags, &socketOverlapped, NULL);
+            }
+#else
+#if defined(__linux__)
             int n = epoll_wait(epollfd, &event, 1, 10);
 #else
             int n = kevent(kq, &change, 1, &event, 1, &timeout);
 #endif
             if (n > 0) {
                 waitTime = 0;
-                receivePacketRedundancy(full_redundant_packet, full_redundant_packet_size,
-                                        full_packet_size, current_seq_num, last_seq_num,
-                                        newer_seq_num);
+                if (receivePacket(reinterpret_cast<char *>(full_redundant_packet), full_redundant_packet_size) > 0) {
+                    receivePacketRedundancy(full_redundant_packet, full_redundant_packet_size,
+                                            full_packet_size, current_seq_num, last_seq_num,
+                                            newer_seq_num);
+                }
             } else {
                 waitTime += 10;
                 emit signalWaitingTooLong(waitTime);
             }
+#endif
         }
-#ifdef __linux__
+#if defined(__linux__)
         close(epollfd);
+#elif defined(_WIN32)
+        WSACloseEvent(eventArray);
 #else
         close(kq);
 #endif
-#endif // _WIN32 || MANUAL_POLL
+#endif // MANUAL_POLL
         break; }
 
     case SENDER : {
@@ -759,16 +809,9 @@ void UdpDataProtocol::printUdpWaitedTooLong(int wait_msec)
 
 //*******************************************************************************
 void UdpDataProtocol::receivePacketRedundancy(
-    int8_t* full_redundant_packet, int full_redundant_packet_size, int full_packet_size,
+    int8_t* full_redundant_packet, [[maybe_unused]] int full_redundant_packet_size, int full_packet_size,
     uint16_t& current_seq_num, uint16_t& last_seq_num, uint16_t& newer_seq_num)
 {
-    // This is blocking until we get a packet...
-    if (receivePacket(reinterpret_cast<char*>(full_redundant_packet),
-                      full_redundant_packet_size)
-        <= 0) {
-        return;
-    }
-
     if (0.0 < mSimulatedLossRate || 0.0 < mSimulatedJitterRate) {
         double x = mUniformDist(mRndEngine);
         // Drop packets
@@ -845,9 +888,7 @@ void UdpDataProtocol::receivePacketRedundancy(
             src = dst;
         }
         int ok = true; // send audio buf to
-        ok = (mJackTrip->getBufferStrategy() !=3) ? // ring or jitter
-                    mJackTrip->writeAudioBuffer(src, host_buf_size, gap_size)
-                  : mJackTrip->writeAudioBufferRegulator(src, host_buf_size, last_seq_num, gap_size);
+        ok = mJackTrip->writeAudioBuffer(src, host_buf_size, gap_size, last_seq_num);
         if (!ok) {
             emit signalError("Local and Peer buffer settings are incompatible");
             cout << "ERROR: Local and Peer buffer settings are incompatible" << endl;
@@ -974,7 +1015,6 @@ void UdpDataProtocol::sendPacketRedundancy(int8_t* full_redundant_packet,
 bool UdpDataProtocol::datagramAvailable()
 {
     //Currently using a simplified version of the way QUdpSocket checks for datagrams.
-    //TODO: Consider changing to use poll() or select().
     char c;
 #if defined(_WIN32)
     //Need to use the winsock version of the function for MSG_PEEK
index a49fda12cf5d28d9edb532db724c93ed8c658267..77b84f25cece8d0d28b905f1e4b7797a222139c5 100644 (file)
@@ -96,6 +96,8 @@ class UdpDataProtocol : public DataProtocol
     void setSocket(int& socket);
 #endif
 
+    void processControlPacket(const char* buf);
+
     /** \brief Receives a packet. It blocks until a packet is received
      *
      * This function makes sure we receive a complete packet
@@ -104,7 +106,6 @@ class UdpDataProtocol : public DataProtocol
      * \param n size of packet to receive
      * \return number of bytes read, -1 on error
      */
-    // virtual int receivePacket(char* buf, const size_t n);
     virtual int receivePacket(char* buf, const size_t n);
 
     /** \brief Sends a packet
index d36870c1157ee0a7594d6fbe748b328e31b55506..4dcf4fe203921980e7175f538d07fcb8705e7813 100644 (file)
@@ -18,7 +18,7 @@ Item {
     property int extraSettingsButtonWidth: 16
     property int emptyListMessageWidth: 450
     property int createMessageTopMargin: 16
-    property int createButtonTopMargin: 48
+    property int createButtonTopMargin: 24
     property int fontBig: 28
     property int fontMedium: 11
     
@@ -100,7 +100,7 @@ Item {
         Text {
             id: emptyListMessage
             visible: parent.count == 0 && !virtualstudio.showCreateStudio
-            text: "No studios found that match your filter criteria"
+            text: "No studios found that match your filter criteria."
             font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
             color: textColour
             width: emptyListMessageWidth
@@ -110,6 +110,33 @@ Item {
             anchors.verticalCenter: parent.verticalCenter
         }
 
+        Button {
+            id: resetFiltersButton
+            background: Rectangle {
+                radius: 6 * virtualstudio.uiScale
+                color: resetFiltersButton.down ? buttonPressedColour : (resetFiltersButton.hovered ? buttonHoverColour : buttonColour)
+                border.width: 1
+                border.color: resetFiltersButton.down ? buttonPressedStroke : (resetFiltersButton.hovered ? buttonHoverStroke : buttonStroke)
+            }
+            visible: parent.count == 0 && !virtualstudio.showCreateStudio
+            onClicked: {
+                virtualstudio.showSelfHosted = false;
+                virtualstudio.showInactive = true;
+                refreshing = true;
+                refresh();
+            }
+            anchors.top: emptyListMessage.bottom
+            anchors.topMargin: createButtonTopMargin
+            anchors.horizontalCenter: emptyListMessage.horizontalCenter
+            width: 120 * virtualstudio.uiScale; height: 32 * virtualstudio.uiScale
+            Text {
+                text: "Reset Filters"
+                font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+                anchors {horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
+                color: textColour
+            }
+        }
+
         Rectangle {
             id: newUserEmptyList
             anchors.fill: parent
@@ -137,7 +164,7 @@ Item {
 
                 Text {
                     id: createStudioMessage
-                    text: "JackTrip works by connecting your computer's audio to a Virtual Studio. Create your first Studio to get started!"
+                    text: "Looks like you're not a member of any studios!\nHave the studio owner send you an invite link, or create your own studio to invite others."
                     font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
                     color: textColour
                     width: emptyListMessageWidth
@@ -267,7 +294,7 @@ Item {
                 border.width: 1
                 border.color: settingsButton.down ? buttonPressedStroke : (settingsButton.hovered ? buttonHoverStroke : buttonStroke)
             }
-            onClicked: window.state = "settings"
+            onClicked: virtualstudio.windowState = "settings"
             display: AbstractButton.TextBesideIcon
             font {
                 family: "Poppins"; 
index 3cd5209e0c7bd5d8566d56b7f6144bd9058bc396..80e13178548141d2724e56ae2e50ebb05ff843a3 100644 (file)
@@ -70,9 +70,13 @@ Item {
     }
 
     Image {
+        id: jtlogo
         x: parent.width - (49 * virtualstudio.uiScale); y: 16 * virtualstudio.uiScale
         width: 32 * virtualstudio.uiScale; height: 59 * virtualstudio.uiScale
         source: "logo.svg"
+        sourceSize: Qt.size(jtlogo.width,jtlogo.height)
+        fillMode: Image.PreserveAspectFit
+        smooth: true
     }
     
     Text {
@@ -109,6 +113,9 @@ Item {
             source: "mic.svg"
             x: 0; y: 0
             width: 18 * virtualstudio.uiScale; height: 28 * virtualstudio.uiScale
+            sourceSize: Qt.size(mic.width,mic.height)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
         }
 
         Colorize {
@@ -136,7 +143,7 @@ Item {
             anchors.top: inputDeviceHeader.bottom
             anchors.left: inputDeviceHeader.left
             text: virtualstudio.audioBackend == "JACK" ?
-                virtualstudio.audioBackend : inputComboModel[virtualstudio.inputDevice]
+                virtualstudio.audioBackend : inputComboModel.filter(item => item.type === "element")[virtualstudio.inputDevice].text
             font {family: "Poppins"; pixelSize: fontTiny * virtualstudio.fontScale * virtualstudio.uiScale }
             color: textColour
             elide: Text.ElideRight
@@ -155,6 +162,9 @@ Item {
             source: "headphones.svg"
             x: 0; y: 0
             width: 28 * virtualstudio.uiScale; height: 28 * virtualstudio.uiScale
+            sourceSize: Qt.size(headphones.width,headphones.height)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
         }
 
         Colorize {
@@ -182,7 +192,7 @@ Item {
             anchors.top: outputDeviceHeader.bottom
             anchors.left: outputDeviceHeader.left
             text: virtualstudio.audioBackend == "JACK" ?
-                virtualstudio.audioBackend : outputComboModel[virtualstudio.outputDevice]
+                virtualstudio.audioBackend : outputComboModel.filter(item => item.type === "element")[virtualstudio.outputDevice].text
             font {family: "Poppins"; pixelSize: fontTiny * virtualstudio.fontScale * virtualstudio.uiScale }
             color: textColour
             elide: Text.ElideRight
@@ -245,6 +255,9 @@ Item {
                 width: 11.57 * virtualstudio.uiScale; height: 18 * virtualstudio.uiScale
                 anchors { verticalCenter: parent.verticalCenter; horizontalCenter: parent.horizontalCenter }
                 source: virtualstudio.inputMuted ? "micoff.svg" : "mic.svg"
+                sourceSize: Qt.size(micMute.width,micMute.height)
+                fillMode: Image.PreserveAspectFit
+                smooth: true
             }
             Colorize {
                 anchors.fill: micMute
@@ -335,6 +348,9 @@ Item {
             source: "network.svg"
             x: 0; y: 0
             width: 28 * virtualstudio.uiScale; height: 28 * virtualstudio.uiScale
+            sourceSize: Qt.size(network.width,network.height)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
         }
 
         Colorize {
index 8eb4a61c09d1b2a044b0eeff22813b648376cdec..28bafcf2e945ad5fda251d8c7b326f0e50781c90 100644 (file)
@@ -71,7 +71,7 @@ Item {
             border.color: buttonStroke
             layer.enabled: !backButton.down
         }
-        onClicked: { window.state = "browse" }
+        onClicked: { virtualstudio.windowState = "browse" }
         width: 256 * virtualstudio.uiScale
         height: 42 * virtualstudio.uiScale
         anchors.horizontalCenter: parent.horizontalCenter
index 7356df284db30a9b9ae455b0851b26b2c096b169..2cba8ac0dfab6709990e0928de63bda5e4ad6030 100644 (file)
@@ -16,10 +16,14 @@ Item {
     property string buttonPressedStroke: virtualstudio.darkMode ? "#6F6C6C" : "#B0B5B5"
     
     Image {
+        id: jtlogo
         source: "logo.svg"
         anchors.horizontalCenter: parent.horizontalCenter
         y: 35 * virtualstudio.uiScale
         width: 50 * virtualstudio.uiScale; height: 92 * virtualstudio.uiScale
+        sourceSize: Qt.size(jtlogo.width,jtlogo.height)
+        fillMode: Image.PreserveAspectFit
+        smooth: true
     }
 
     Text {
@@ -56,7 +60,7 @@ Item {
                 color: shadowColour
             }
         }
-        onClicked: { window.state = "login"; virtualstudio.toVirtualStudio(); }
+        onClicked: { virtualstudio.windowState = "login"; virtualstudio.toVirtualStudio(); }
         x: parent.width / 2 - (265 * virtualstudio.uiScale); y: 290 * virtualstudio.uiScale
         width: 234 * virtualstudio.uiScale; height: 49 * virtualstudio.uiScale
         Text {
@@ -104,7 +108,7 @@ Item {
                 color: shadowColour
             }
         }
-        onClicked: { window.state = "login"; virtualstudio.toStandard(); }
+        onClicked: { virtualstudio.windowState = "login"; virtualstudio.toStandard(); }
         x: parent.width / 2 + (32 * virtualstudio.uiScale); y: 290 * virtualstudio.uiScale
         width: 234 * virtualstudio.uiScale; height: 49 * virtualstudio.uiScale
         Text {
index 0ed3fc0229b20474d7c92b10fcc33886331563ce..955cfb1de58641aec6114d922642300d516f257e 100644 (file)
@@ -36,6 +36,9 @@ Item {
         source: "logo.svg"
         x: parent.width / 2 - (150 * virtualstudio.uiScale); y: 110 * virtualstudio.uiScale
         width: 42 * virtualstudio.uiScale; height: 76 * virtualstudio.uiScale
+        sourceSize: Qt.size(loginLogo.width,loginLogo.height)
+        fillMode: Image.PreserveAspectFit
+        smooth: true
     }
 
     Image {
@@ -124,7 +127,7 @@ Item {
                 color: shadowColour
             }
         }
-        onClicked: { window.state = "start" }
+        onClicked: { virtualstudio.windowState = "start" }
         anchors.horizontalCenter: parent.horizontalCenter
         y: 401 * virtualstudio.uiScale
         width: 263 * virtualstudio.uiScale; height: 64 * virtualstudio.uiScale
index f3a3a3d0b46e548ee81a1471636be53249f824b8..64b4450cc6acd067014eee2637f26b58d82dfc6d 100644 (file)
@@ -10,7 +10,8 @@ Item {
     property int clipWidth: 10 * virtualstudio.uiScale
     required property bool clipped
 
-    property string meterColor: virtualstudio.darkMode ? "#5B5858" : "#D3D4D4"
+    property bool enabled: true
+    property string meterColor: enabled ? (virtualstudio.darkMode ? "#5B5858" : "#D3D4D4") : "#EAECEC"
 
     property string meterGreen: "#61C554"
     property string meterYellow: "#F5BF4F"
@@ -18,6 +19,10 @@ Item {
 
     function getBoxColor (idx, level) {
 
+        if (!enabled) {
+            return meterColor;
+        }
+
         // Case where the meter should be filled
         if (level > (idx / bins)) {
             let fillColor = meterGreen;
diff --git a/src/gui/Prompt.svg b/src/gui/Prompt.svg
new file mode 100644 (file)
index 0000000..110d116
--- /dev/null
@@ -0,0 +1,9 @@
+<svg width="260" height="250" viewBox="0 0 260 250" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="1" y="1" width="258" height="248" rx="9" stroke="#0F0D0D" stroke-width="2"/>
+<rect x="18" y="205" width="108" height="26" rx="5" stroke="#0F0D0D" stroke-width="2"/>
+<path d="M42.1189 222H44.9021C47.4861 222 48.9158 220.4 48.9158 217.775V217.764C48.9158 215.145 47.4802 213.545 44.9021 213.545H42.1189V222ZM42.9216 221.273V214.271H44.8552C46.9177 214.271 48.0955 215.59 48.0955 217.77V217.781C48.0955 219.961 46.9236 221.273 44.8552 221.273H42.9216ZM53.151 222.105C54.9147 222.105 56.0221 220.857 56.0221 218.848V218.836C56.0221 216.826 54.9147 215.584 53.151 215.584C51.3873 215.584 50.2857 216.826 50.2857 218.836V218.848C50.2857 220.857 51.3873 222.105 53.151 222.105ZM53.151 221.414C51.8502 221.414 51.0768 220.441 51.0768 218.848V218.836C51.0768 217.242 51.8502 216.275 53.151 216.275C54.4518 216.275 55.2252 217.242 55.2252 218.836V218.848C55.2252 220.441 54.4518 221.414 53.151 221.414ZM57.6323 222H58.4057V218.25C58.4057 217.061 59.1616 216.275 60.2749 216.275C61.3706 216.275 61.9155 216.891 61.9155 218.092V222H62.6889V217.898C62.6889 216.428 61.8862 215.584 60.4799 215.584C59.4956 215.584 58.8159 216.035 58.4819 216.797H58.4057V215.689H57.6323V222ZM64.598 216.662H65.1898L66.0335 213.545H65.1312L64.598 216.662ZM70.0871 222.041C70.2746 222.041 70.4503 222.023 70.6261 221.994V221.326C70.4679 221.344 70.3507 221.35 70.175 221.35C69.4601 221.35 69.1847 221.027 69.1847 220.254V216.34H70.6261V215.689H69.1847V214.049H68.382V215.689H67.3683V216.34H68.382V220.43C68.382 221.578 68.88 222.041 70.0871 222.041ZM74.8251 222H75.6688L76.6063 219.41H80.1629L81.1004 222H81.9442L78.7801 213.545H77.9833L74.8251 222ZM78.3465 214.594H78.4168L79.911 218.719H76.8524L78.3465 214.594ZM83.3845 222H84.1579V213.176H83.3845V222ZM86.2076 222H86.981V213.176H86.2076V222ZM91.5209 222.105C93.2846 222.105 94.392 220.857 94.392 218.848V218.836C94.392 216.826 93.2846 215.584 91.5209 215.584C89.7572 215.584 88.6557 216.826 88.6557 218.836V218.848C88.6557 220.857 89.7572 222.105 91.5209 222.105ZM91.5209 221.414C90.2201 221.414 89.4467 220.441 89.4467 218.848V218.836C89.4467 217.242 90.2201 216.275 91.5209 216.275C92.8217 216.275 93.5951 217.242 93.5951 218.836V218.848C93.5951 220.441 92.8217 221.414 91.5209 221.414ZM96.969 222H97.7776L99.3303 216.762H99.4065L100.959 222H101.774L103.573 215.689H102.799L101.387 221.045H101.317L99.758 215.689H98.9905L97.4319 221.045H97.3616L95.9612 215.689H95.1819L96.969 222Z" fill="#272525"/>
+<path d="M141.5 205H239.5C242.261 205 244.5 207.239 244.5 210V226C244.5 228.761 242.261 231 239.5 231H141.5C138.739 231 136.5 228.761 136.5 226V210C136.5 207.239 138.739 205 141.5 205Z" fill="white" stroke="#0F0D0D" stroke-width="2"/>
+<path d="M185.77 213.328C183.286 213.328 181.698 215.027 181.698 217.77C181.698 220.512 183.262 222.217 185.77 222.217C188.272 222.217 189.831 220.512 189.831 217.77C189.831 215.033 188.266 213.328 185.77 213.328ZM185.77 214.881C187.147 214.881 188.026 216 188.026 217.77C188.026 219.533 187.147 220.664 185.77 220.664C184.381 220.664 183.508 219.533 183.508 217.77C183.508 216 184.399 214.881 185.77 214.881ZM193.228 222V219.451L194.066 218.461L196.521 222H198.642L195.343 217.254L198.425 213.545H196.456L193.333 217.312H193.228V213.545H191.458V222H193.228Z" fill="#272525"/>
+<path d="M20.1581 104.84L18.9647 108.814H20.7801L21.5164 104.84H20.1581ZM23.0716 104.84L21.9735 108.814H23.7889L24.43 104.84H23.0716ZM24.6637 111.309C24.6637 113.105 25.8951 114.235 27.774 114.235C29.7164 114.235 30.8844 113.13 30.8844 111.156V104.84H28.9674V111.144C28.9674 112.045 28.5357 112.521 27.7486 112.521C27.0123 112.521 26.5299 112.045 26.5172 111.309H24.6637ZM35.3011 112.718C34.66 112.718 34.2093 112.4 34.2093 111.88C34.2093 111.378 34.5965 111.093 35.39 111.036L36.8055 110.947V111.461C36.8055 112.172 36.1581 112.718 35.3011 112.718ZM34.6917 114.108C35.5995 114.108 36.3612 113.727 36.723 113.086H36.8373V114H38.6273V109.22C38.6273 107.722 37.5799 106.846 35.7264 106.846C33.9681 106.846 32.7811 107.659 32.6605 108.954H34.3617C34.514 108.535 34.9583 108.306 35.6249 108.306C36.3866 108.306 36.8055 108.636 36.8055 109.22V109.792L35.1107 109.893C33.3714 109.995 32.4002 110.731 32.4002 112C32.4002 113.283 33.346 114.108 34.6917 114.108ZM46.586 109.468C46.4337 107.875 45.3229 106.846 43.4693 106.846C41.2794 106.846 40.0479 108.16 40.0479 110.483C40.0479 112.832 41.2857 114.152 43.4693 114.152C45.2911 114.152 46.4337 113.143 46.586 111.575H44.8595C44.7198 112.267 44.2247 112.635 43.4693 112.635C42.4791 112.635 41.9142 111.88 41.9142 110.483C41.9142 109.106 42.4728 108.363 43.4693 108.363C44.2564 108.363 44.7325 108.801 44.8595 109.468H46.586ZM50.0062 109.734H49.8919V104.326H48.0448V114H49.8919V111.677L50.3998 111.156L52.4437 114H54.6336L51.7835 110.001L54.4496 106.999H52.3485L50.0062 109.734ZM59.7169 114V106.478H62.4654V104.84H55.0514V106.478H57.7999V114H59.7169ZM63.1878 114H65.035V110.122C65.035 109.138 65.7269 108.541 66.7362 108.541C67.0345 108.541 67.4662 108.598 67.6122 108.655V106.973C67.4535 106.916 67.1424 106.884 66.8885 106.884C65.9999 106.884 65.2762 107.417 65.0921 108.116H64.9779V106.999H63.1878V114ZM68.8107 114H70.6578V106.999H68.8107V114ZM69.7374 106.021C70.3976 106.021 70.8292 105.634 70.8292 105.088C70.8292 104.536 70.3976 104.155 69.7374 104.155C69.0709 104.155 68.6456 104.536 68.6456 105.088C68.6456 105.634 69.0709 106.021 69.7374 106.021ZM76.5218 106.884C75.557 106.884 74.7635 107.36 74.3827 108.147H74.2684V106.999H72.4784V116.323H74.3255V112.927H74.4398C74.7826 113.67 75.5506 114.108 76.5536 114.108C78.3055 114.108 79.3783 112.756 79.3783 110.496C79.3783 108.23 78.2928 106.884 76.5218 106.884ZM75.8934 112.565C74.9159 112.565 74.3065 111.785 74.3065 110.502C74.3065 109.22 74.9159 108.433 75.8998 108.433C76.8837 108.433 77.4803 109.214 77.4803 110.496C77.4803 111.791 76.89 112.565 75.8934 112.565ZM82.2145 108.814L83.3063 104.84H81.4972L80.8497 108.814H82.2145ZM85.128 108.814L86.3151 104.84H84.506L83.7633 108.814H85.128ZM100.95 106.999H99.1158L98.0748 112.02H97.9606L96.7291 106.999H94.9645L93.7394 112.02H93.6251L92.5841 106.999H90.7115L92.5778 114H94.5201L95.7579 109.169H95.8722L97.1227 114H99.0904L100.95 106.999ZM105.031 114.152C107.182 114.152 108.477 112.788 108.477 110.496C108.477 108.224 107.163 106.846 105.031 106.846C102.898 106.846 101.584 108.23 101.584 110.496C101.584 112.788 102.879 114.152 105.031 114.152ZM105.031 112.642C104.04 112.642 103.482 111.861 103.482 110.496C103.482 109.15 104.047 108.357 105.031 108.357C106.008 108.357 106.579 109.15 106.579 110.496C106.579 111.854 106.015 112.642 105.031 112.642ZM116.271 106.999H114.424V111.036C114.424 111.969 113.929 112.546 113.008 112.546C112.158 112.546 111.72 112.051 111.72 111.086V106.999H109.873V111.562C109.873 113.188 110.812 114.152 112.323 114.152C113.383 114.152 114.037 113.689 114.367 112.876H114.481V114H116.271V106.999ZM118.136 114H119.983V104.326H118.136V114ZM124.362 114.108C125.333 114.108 126.127 113.657 126.501 112.902H126.615V114H128.405V104.326H126.558V108.116H126.45C126.089 107.341 125.308 106.884 124.362 106.884C122.604 106.884 121.512 108.262 121.512 110.49C121.512 112.724 122.597 114.108 124.362 114.108ZM124.99 108.433C125.974 108.433 126.577 109.227 126.577 110.502C126.577 111.785 125.981 112.565 124.99 112.565C124 112.565 123.41 111.791 123.41 110.496C123.41 109.214 124.006 108.433 124.99 108.433ZM133.633 114H135.481V104.326H133.633V114ZM137.365 114H139.212V106.999H137.365V114ZM138.291 106.021C138.952 106.021 139.383 105.634 139.383 105.088C139.383 104.536 138.952 104.155 138.291 104.155C137.625 104.155 137.2 104.536 137.2 105.088C137.2 105.634 137.625 106.021 138.291 106.021ZM143.057 109.734H142.943V104.326H141.096V114H142.943V111.677L143.451 111.156L145.495 114H147.685L144.835 110.001L147.501 106.999H145.4L143.057 109.734ZM151.41 108.262C152.273 108.262 152.831 108.839 152.87 109.766H149.886C149.95 108.858 150.553 108.262 151.41 108.262ZM152.908 112.007C152.711 112.47 152.209 112.73 151.479 112.73C150.515 112.73 149.905 112.083 149.88 111.042V110.947H154.685V110.382C154.685 108.16 153.466 106.846 151.403 106.846C149.321 106.846 148.039 108.255 148.039 110.534C148.039 112.807 149.296 114.152 151.429 114.152C153.142 114.152 154.349 113.327 154.628 112.007H152.908ZM159.976 105.335V107.068H158.885V108.522H159.976V112.108C159.976 113.473 160.649 114.025 162.35 114.025C162.706 114.025 163.049 113.987 163.277 113.943V112.534C163.1 112.553 162.973 112.559 162.731 112.559C162.103 112.559 161.824 112.28 161.824 111.67V108.522H163.277V107.068H161.824V105.335H159.976ZM167.821 114.152C169.973 114.152 171.268 112.788 171.268 110.496C171.268 108.224 169.954 106.846 167.821 106.846C165.688 106.846 164.374 108.23 164.374 110.496C164.374 112.788 165.669 114.152 167.821 114.152ZM167.821 112.642C166.831 112.642 166.272 111.861 166.272 110.496C166.272 109.15 166.837 108.357 167.821 108.357C168.798 108.357 169.37 109.15 169.37 110.496C169.37 111.854 168.805 112.642 167.821 112.642ZM178.552 112.718C177.911 112.718 177.461 112.4 177.461 111.88C177.461 111.378 177.848 111.093 178.641 111.036L180.057 110.947V111.461C180.057 112.172 179.409 112.718 178.552 112.718ZM177.943 114.108C178.851 114.108 179.612 113.727 179.974 113.086H180.089V114H181.879V109.22C181.879 107.722 180.831 106.846 178.978 106.846C177.219 106.846 176.032 107.659 175.912 108.954H177.613C177.765 108.535 178.21 108.306 178.876 108.306C179.638 108.306 180.057 108.636 180.057 109.22V109.792L178.362 109.893C176.623 109.995 175.652 110.731 175.652 112C175.652 113.283 176.597 114.108 177.943 114.108ZM189.837 109.468C189.685 107.875 188.574 106.846 186.721 106.846C184.531 106.846 183.299 108.16 183.299 110.483C183.299 112.832 184.537 114.152 186.721 114.152C188.542 114.152 189.685 113.143 189.837 111.575H188.111C187.971 112.267 187.476 112.635 186.721 112.635C185.73 112.635 185.165 111.88 185.165 110.483C185.165 109.106 185.724 108.363 186.721 108.363C187.508 108.363 187.984 108.801 188.111 109.468H189.837ZM197.428 109.468C197.276 107.875 196.165 106.846 194.311 106.846C192.121 106.846 190.89 108.16 190.89 110.483C190.89 112.832 192.128 114.152 194.311 114.152C196.133 114.152 197.276 113.143 197.428 111.575H195.701C195.562 112.267 195.067 112.635 194.311 112.635C193.321 112.635 192.756 111.88 192.756 110.483C192.756 109.106 193.315 108.363 194.311 108.363C195.098 108.363 195.574 108.801 195.701 109.468H197.428ZM201.851 108.262C202.714 108.262 203.273 108.839 203.311 109.766H200.328C200.391 108.858 200.994 108.262 201.851 108.262ZM203.349 112.007C203.152 112.47 202.651 112.73 201.921 112.73C200.956 112.73 200.347 112.083 200.321 111.042V110.947H205.126V110.382C205.126 108.16 203.908 106.846 201.845 106.846C199.763 106.846 198.48 108.255 198.48 110.534C198.48 112.807 199.737 114.152 201.87 114.152C203.584 114.152 204.79 113.327 205.069 112.007H203.349ZM206.433 109.055C206.433 110.134 207.093 110.782 208.451 111.08L209.721 111.366C210.337 111.499 210.603 111.708 210.603 112.051C210.603 112.502 210.108 112.8 209.391 112.8C208.654 112.8 208.204 112.527 208.064 112.051H206.261C206.388 113.397 207.506 114.152 209.353 114.152C211.187 114.152 212.438 113.238 212.438 111.842C212.438 110.794 211.828 110.223 210.47 109.925L209.156 109.639C208.508 109.493 208.21 109.284 208.21 108.941C208.21 108.497 208.699 108.198 209.359 108.198C210.045 108.198 210.47 108.478 210.565 108.928H212.273C212.171 107.589 211.124 106.846 209.346 106.846C207.601 106.846 206.433 107.729 206.433 109.055ZM213.617 109.055C213.617 110.134 214.277 110.782 215.636 111.08L216.905 111.366C217.521 111.499 217.788 111.708 217.788 112.051C217.788 112.502 217.292 112.8 216.575 112.8C215.839 112.8 215.388 112.527 215.248 112.051H213.446C213.573 113.397 214.69 114.152 216.537 114.152C218.371 114.152 219.622 113.238 219.622 111.842C219.622 110.794 219.013 110.223 217.654 109.925L216.34 109.639C215.693 109.493 215.394 109.284 215.394 108.941C215.394 108.497 215.883 108.198 216.543 108.198C217.229 108.198 217.654 108.478 217.749 108.928H219.457C219.355 107.589 218.308 106.846 216.531 106.846C214.785 106.846 213.617 107.729 213.617 109.055ZM224.913 105.335V107.068H223.822V108.522H224.913V112.108C224.913 113.473 225.586 114.025 227.288 114.025C227.643 114.025 227.986 113.987 228.214 113.943V112.534C228.037 112.553 227.91 112.559 227.668 112.559C227.04 112.559 226.761 112.28 226.761 111.67V108.522H228.214V107.068H226.761V105.335H224.913ZM229.768 114H231.615V109.982C231.615 109.074 232.149 108.458 233.075 108.458C233.913 108.458 234.364 108.96 234.364 109.931V114H236.211V109.493C236.211 107.811 235.316 106.865 233.812 106.865C232.783 106.865 232.022 107.348 231.698 108.147H231.584V104.326H229.768V114ZM240.983 108.262C241.847 108.262 242.405 108.839 242.443 109.766H239.46C239.523 108.858 240.126 108.262 240.983 108.262ZM242.481 112.007C242.285 112.47 241.783 112.73 241.053 112.73C240.088 112.73 239.479 112.083 239.454 111.042V110.947H244.259V110.382C244.259 108.16 243.04 106.846 240.977 106.846C238.895 106.846 237.613 108.255 237.613 110.534C237.613 112.807 238.87 114.152 241.002 114.152C242.716 114.152 243.922 113.327 244.202 112.007H242.481ZM91.5539 130H93.4011V125.785C93.4011 125.004 93.9153 124.439 94.6389 124.439C95.3625 124.439 95.7942 124.865 95.7942 125.607V130H97.5715V125.715C97.5715 124.973 98.0539 124.439 98.803 124.439C99.5837 124.439 99.9709 124.852 99.9709 125.684V130H101.818V125.195C101.818 123.754 100.948 122.846 99.552 122.846C98.5744 122.846 97.7683 123.36 97.4446 124.141H97.3303C97.051 123.329 96.3782 122.846 95.3943 122.846C94.4739 122.846 93.7439 123.341 93.4582 124.141H93.344V122.999H91.5539V130ZM103.575 130H105.422V122.999H103.575V130ZM104.502 122.021C105.162 122.021 105.594 121.634 105.594 121.088C105.594 120.536 105.162 120.155 104.502 120.155C103.835 120.155 103.41 120.536 103.41 121.088C103.41 121.634 103.835 122.021 104.502 122.021ZM113.438 125.468C113.286 123.875 112.175 122.846 110.322 122.846C108.132 122.846 106.9 124.16 106.9 126.483C106.9 128.832 108.138 130.152 110.322 130.152C112.143 130.152 113.286 129.143 113.438 127.575H111.712C111.572 128.267 111.077 128.635 110.322 128.635C109.331 128.635 108.766 127.88 108.766 126.483C108.766 125.106 109.325 124.363 110.322 124.363C111.109 124.363 111.585 124.801 111.712 125.468H113.438ZM114.833 130H116.681V126.122C116.681 125.138 117.373 124.541 118.382 124.541C118.68 124.541 119.112 124.598 119.258 124.655V122.973C119.099 122.916 118.788 122.884 118.534 122.884C117.646 122.884 116.922 123.417 116.738 124.116H116.624V122.999H114.833V130ZM123.383 130.152C125.534 130.152 126.829 128.788 126.829 126.496C126.829 124.224 125.515 122.846 123.383 122.846C121.25 122.846 119.936 124.23 119.936 126.496C119.936 128.788 121.231 130.152 123.383 130.152ZM123.383 128.642C122.392 128.642 121.834 127.861 121.834 126.496C121.834 125.15 122.399 124.357 123.383 124.357C124.36 124.357 124.931 125.15 124.931 126.496C124.931 127.854 124.366 128.642 123.383 128.642ZM132.332 122.884C131.367 122.884 130.573 123.36 130.192 124.147H130.078V122.999H128.288V132.323H130.135V128.927H130.25C130.592 129.67 131.36 130.108 132.363 130.108C134.115 130.108 135.188 128.756 135.188 126.496C135.188 124.23 134.103 122.884 132.332 122.884ZM131.703 128.565C130.726 128.565 130.116 127.785 130.116 126.502C130.116 125.22 130.726 124.433 131.709 124.433C132.693 124.433 133.29 125.214 133.29 126.496C133.29 127.791 132.7 128.565 131.703 128.565ZM136.704 130H138.551V125.982C138.551 125.074 139.084 124.458 140.011 124.458C140.849 124.458 141.3 124.96 141.3 125.931V130H143.147V125.493C143.147 123.811 142.252 122.865 140.747 122.865C139.719 122.865 138.957 123.348 138.634 124.147H138.519V120.326H136.704V130ZM147.995 130.152C150.147 130.152 151.442 128.788 151.442 126.496C151.442 124.224 150.128 122.846 147.995 122.846C145.862 122.846 144.548 124.23 144.548 126.496C144.548 128.788 145.843 130.152 147.995 130.152ZM147.995 128.642C147.005 128.642 146.446 127.861 146.446 126.496C146.446 125.15 147.011 124.357 147.995 124.357C148.973 124.357 149.544 125.15 149.544 126.496C149.544 127.854 148.979 128.642 147.995 128.642ZM152.901 130H154.748V125.963C154.748 125.042 155.275 124.439 156.132 124.439C157.008 124.439 157.42 124.947 157.42 125.912V130H159.267V125.474C159.267 123.798 158.429 122.846 156.874 122.846C155.84 122.846 155.129 123.335 154.805 124.122H154.691V122.999H152.901V130ZM164.04 124.262C164.903 124.262 165.461 124.839 165.5 125.766H162.516C162.58 124.858 163.183 124.262 164.04 124.262ZM165.538 128.007C165.341 128.47 164.839 128.73 164.109 128.73C163.145 128.73 162.535 128.083 162.51 127.042V126.947H167.315V126.382C167.315 124.16 166.096 122.846 164.033 122.846C161.951 122.846 160.669 124.255 160.669 126.534C160.669 128.807 161.926 130.152 164.059 130.152C165.772 130.152 166.979 129.327 167.258 128.007H165.538ZM170.113 130.171C170.799 130.171 171.237 129.708 171.237 129.067C171.237 128.426 170.799 127.969 170.113 127.969C169.434 127.969 168.99 128.426 168.99 129.067C168.99 129.708 169.434 130.171 170.113 130.171Z" fill="#272525"/>
+<path d="M23.7968 160V152.494H26.5214V151.545H20.0175V152.494H22.7421V160H23.7968ZM28.032 160H29.0398V156.262C29.0398 155.195 29.6609 154.48 30.7917 154.48C31.7468 154.48 32.2507 155.037 32.2507 156.156V160H33.2585V155.91C33.2585 154.428 32.4148 153.572 31.0788 153.572C30.112 153.572 29.4499 153.982 29.1335 154.68H29.0398V151.176H28.032V160ZM35.0738 160H36.0816V153.684H35.0738V160ZM35.5777 152.4C35.9644 152.4 36.2808 152.084 36.2808 151.697C36.2808 151.311 35.9644 150.994 35.5777 150.994C35.191 150.994 34.8746 151.311 34.8746 151.697C34.8746 152.084 35.191 152.4 35.5777 152.4ZM37.809 155.412C37.809 156.326 38.3481 156.836 39.5317 157.123L40.6157 157.387C41.2895 157.551 41.6176 157.844 41.6176 158.277C41.6176 158.857 41.0082 159.262 40.1586 159.262C39.35 159.262 38.8461 158.922 38.6762 158.389H37.6391C37.7504 159.438 38.7172 160.111 40.1235 160.111C41.559 160.111 42.6547 159.332 42.6547 158.201C42.6547 157.293 42.0805 156.777 40.8911 156.49L39.9184 156.256C39.1743 156.074 38.8227 155.805 38.8227 155.371C38.8227 154.809 39.4086 154.428 40.1586 154.428C40.9203 154.428 41.4125 154.762 41.5473 155.266H42.5434C42.4086 154.229 41.4887 153.572 40.1645 153.572C38.8227 153.572 37.809 154.363 37.809 155.412ZM49.4728 159.227C48.7404 159.227 48.1954 158.852 48.1954 158.207C48.1954 157.574 48.6173 157.24 49.5783 157.176L51.2775 157.064V157.645C51.2775 158.547 50.5099 159.227 49.4728 159.227ZM49.2853 160.111C50.129 160.111 50.8204 159.742 51.2306 159.068H51.3243V160H52.2853V155.676C52.2853 154.363 51.424 153.572 49.8829 153.572C48.5353 153.572 47.5392 154.24 47.4044 155.254H48.424C48.5646 154.756 49.0919 154.469 49.8478 154.469C50.7911 154.469 51.2775 154.896 51.2775 155.676V156.25L49.4552 156.361C47.9845 156.449 47.1525 157.1 47.1525 158.23C47.1525 159.385 48.0607 160.111 49.2853 160.111ZM57.1767 153.572C56.3154 153.572 55.5596 154.012 55.1553 154.738H55.0615V153.684H54.1006V162.109H55.1084V159.051H55.2021C55.5478 159.719 56.2744 160.111 57.1767 160.111C58.7822 160.111 59.831 158.816 59.831 156.842C59.831 154.855 58.7881 153.572 57.1767 153.572ZM56.9365 159.203C55.7998 159.203 55.0791 158.289 55.0791 156.842C55.0791 155.389 55.7998 154.48 56.9424 154.48C58.0967 154.48 58.7881 155.365 58.7881 156.842C58.7881 158.318 58.0967 159.203 56.9365 159.203ZM64.4412 153.572C63.5799 153.572 62.8241 154.012 62.4198 154.738H62.326V153.684H61.3651V162.109H62.3729V159.051H62.4666C62.8123 159.719 63.5389 160.111 64.4412 160.111C66.0467 160.111 67.0955 158.816 67.0955 156.842C67.0955 154.855 66.0526 153.572 64.4412 153.572ZM64.201 159.203C63.0643 159.203 62.3436 158.289 62.3436 156.842C62.3436 155.389 63.0643 154.48 64.2069 154.48C65.3612 154.48 66.0526 155.365 66.0526 156.842C66.0526 158.318 65.3612 159.203 64.201 159.203ZM71.9683 160H72.9761V156.086C72.9761 155.195 73.6734 154.551 74.6343 154.551C74.8335 154.551 75.1968 154.586 75.2788 154.609V153.602C75.1499 153.584 74.939 153.572 74.7749 153.572C73.937 153.572 73.2105 154.006 73.023 154.621H72.9292V153.684H71.9683V160ZM78.8343 154.463C79.8363 154.463 80.5043 155.201 80.5277 156.32H77.0472C77.1234 155.201 77.8265 154.463 78.8343 154.463ZM80.4984 158.365C80.2347 158.922 79.684 159.221 78.8695 159.221C77.7972 159.221 77.1 158.43 77.0472 157.182V157.135H81.5883V156.748C81.5883 154.785 80.5511 153.572 78.8461 153.572C77.1117 153.572 75.9984 154.861 75.9984 156.848C75.9984 158.846 77.0941 160.111 78.8461 160.111C80.2289 160.111 81.2015 159.449 81.5062 158.365H80.4984ZM85.4485 153.572C83.8488 153.572 82.8059 154.861 82.8059 156.842C82.8059 158.834 83.8371 160.111 85.4485 160.111C86.3391 160.111 87.0188 159.742 87.4231 159.051H87.5168V162.109H88.5363V153.684H87.5637V154.738H87.4699C87.0949 154.029 86.3098 153.572 85.4485 153.572ZM85.677 159.203C84.5285 159.203 83.8488 158.324 83.8488 156.842C83.8488 155.365 84.5344 154.48 85.6828 154.48C86.8254 154.48 87.5461 155.395 87.5461 156.842C87.5461 158.295 86.8313 159.203 85.677 159.203ZM95.4962 153.684H94.4883V157.422C94.4883 158.529 93.879 159.191 92.7657 159.191C91.7579 159.191 91.336 158.664 91.336 157.527V153.684H90.3282V157.773C90.3282 159.268 91.0665 160.111 92.4844 160.111C93.4512 160.111 94.1251 159.713 94.4415 159.01H94.5352V160H95.4962V153.684ZM97.37 160H98.3778V153.684H97.37V160ZM97.8739 152.4C98.2607 152.4 98.5771 152.084 98.5771 151.697C98.5771 151.311 98.2607 150.994 97.8739 150.994C97.4872 150.994 97.1708 151.311 97.1708 151.697C97.1708 152.084 97.4872 152.4 97.8739 152.4ZM100.252 160H101.26V156.086C101.26 155.195 101.957 154.551 102.918 154.551C103.117 154.551 103.48 154.586 103.562 154.609V153.602C103.433 153.584 103.222 153.572 103.058 153.572C102.22 153.572 101.494 154.006 101.306 154.621H101.213V153.684H100.252V160ZM107.118 154.463C108.12 154.463 108.788 155.201 108.811 156.32H105.331C105.407 155.201 106.11 154.463 107.118 154.463ZM108.782 158.365C108.518 158.922 107.967 159.221 107.153 159.221C106.081 159.221 105.383 158.43 105.331 157.182V157.135H109.872V156.748C109.872 154.785 108.835 153.572 107.13 153.572C105.395 153.572 104.282 154.861 104.282 156.848C104.282 158.846 105.378 160.111 107.13 160.111C108.512 160.111 109.485 159.449 109.79 158.365H108.782ZM111.259 155.412C111.259 156.326 111.798 156.836 112.982 157.123L114.066 157.387C114.74 157.551 115.068 157.844 115.068 158.277C115.068 158.857 114.458 159.262 113.609 159.262C112.8 159.262 112.296 158.922 112.126 158.389H111.089C111.201 159.438 112.167 160.111 113.574 160.111C115.009 160.111 116.105 159.332 116.105 158.201C116.105 157.293 115.531 156.777 114.341 156.49L113.369 156.256C112.624 156.074 112.273 155.805 112.273 155.371C112.273 154.809 112.859 154.428 113.609 154.428C114.371 154.428 114.863 154.762 114.998 155.266H115.994C115.859 154.229 114.939 153.572 113.615 153.572C112.273 153.572 111.259 154.363 111.259 155.412ZM120.978 160H121.986V156.086C121.986 155.195 122.624 154.48 123.45 154.48C124.247 154.48 124.769 154.961 124.769 155.711V160H125.777V155.939C125.777 155.137 126.362 154.48 127.241 154.48C128.132 154.48 128.571 154.938 128.571 155.869V160H129.579V155.635C129.579 154.311 128.859 153.572 127.569 153.572C126.696 153.572 125.976 154.012 125.636 154.68H125.542C125.249 154.023 124.652 153.572 123.796 153.572C122.952 153.572 122.319 153.977 122.032 154.68H121.939V153.684H120.978V160ZM131.395 160H132.402V153.684H131.395V160ZM131.898 152.4C132.285 152.4 132.602 152.084 132.602 151.697C132.602 151.311 132.285 150.994 131.898 150.994C131.512 150.994 131.195 151.311 131.195 151.697C131.195 152.084 131.512 152.4 131.898 152.4ZM139.503 155.617C139.327 154.492 138.39 153.572 136.854 153.572C135.085 153.572 133.96 154.85 133.96 156.818C133.96 158.828 135.091 160.111 136.86 160.111C138.378 160.111 139.321 159.256 139.503 158.096H138.483C138.296 158.811 137.704 159.203 136.854 159.203C135.729 159.203 135.003 158.277 135.003 156.818C135.003 155.389 135.718 154.48 136.854 154.48C137.763 154.48 138.319 154.99 138.483 155.617H139.503ZM140.943 160H141.951V156.086C141.951 155.195 142.648 154.551 143.609 154.551C143.808 154.551 144.172 154.586 144.254 154.609V153.602C144.125 153.584 143.914 153.572 143.75 153.572C142.912 153.572 142.185 154.006 141.998 154.621H141.904V153.684H140.943V160ZM147.885 160.111C149.684 160.111 150.797 158.869 150.797 156.842C150.797 154.809 149.684 153.572 147.885 153.572C146.086 153.572 144.973 154.809 144.973 156.842C144.973 158.869 146.086 160.111 147.885 160.111ZM147.885 159.203C146.69 159.203 146.016 158.336 146.016 156.842C146.016 155.342 146.69 154.48 147.885 154.48C149.081 154.48 149.754 155.342 149.754 156.842C149.754 158.336 149.081 159.203 147.885 159.203ZM155.408 153.572C154.546 153.572 153.79 154.012 153.386 154.738H153.292V153.684H152.331V162.109H153.339V159.051H153.433C153.779 159.719 154.505 160.111 155.408 160.111C157.013 160.111 158.062 158.816 158.062 156.842C158.062 154.855 157.019 153.572 155.408 153.572ZM155.167 159.203C154.031 159.203 153.31 158.289 153.31 156.842C153.31 155.389 154.031 154.48 155.173 154.48C156.328 154.48 157.019 155.365 157.019 156.842C157.019 158.318 156.328 159.203 155.167 159.203ZM159.655 160H160.662V156.262C160.662 155.195 161.283 154.48 162.414 154.48C163.369 154.48 163.873 155.037 163.873 156.156V160H164.881V155.91C164.881 154.428 164.037 153.572 162.701 153.572C161.735 153.572 161.073 153.982 160.756 154.68H160.662V151.176H159.655V160ZM169.269 160.111C171.067 160.111 172.181 158.869 172.181 156.842C172.181 154.809 171.067 153.572 169.269 153.572C167.47 153.572 166.357 154.809 166.357 156.842C166.357 158.869 167.47 160.111 169.269 160.111ZM169.269 159.203C168.073 159.203 167.4 158.336 167.4 156.842C167.4 155.342 168.073 154.48 169.269 154.48C170.464 154.48 171.138 155.342 171.138 156.842C171.138 158.336 170.464 159.203 169.269 159.203ZM173.715 160H174.723V156.262C174.723 155.154 175.373 154.48 176.381 154.48C177.389 154.48 177.869 155.02 177.869 156.156V160H178.877V155.91C178.877 154.41 178.086 153.572 176.668 153.572C175.701 153.572 175.086 153.982 174.769 154.68H174.676V153.684H173.715V160ZM183.194 154.463C184.196 154.463 184.864 155.201 184.887 156.32H181.407C181.483 155.201 182.186 154.463 183.194 154.463ZM184.858 158.365C184.595 158.922 184.044 159.221 183.229 159.221C182.157 159.221 181.46 158.43 181.407 157.182V157.135H185.948V156.748C185.948 154.785 184.911 153.572 183.206 153.572C181.471 153.572 180.358 154.861 180.358 156.848C180.358 158.846 181.454 160.111 183.206 160.111C184.589 160.111 185.561 159.449 185.866 158.365H184.858ZM192.766 159.227C192.034 159.227 191.489 158.852 191.489 158.207C191.489 157.574 191.911 157.24 192.872 157.176L194.571 157.064V157.645C194.571 158.547 193.803 159.227 192.766 159.227ZM192.579 160.111C193.422 160.111 194.114 159.742 194.524 159.068H194.618V160H195.579V155.676C195.579 154.363 194.717 153.572 193.176 153.572C191.829 153.572 190.833 154.24 190.698 155.254H191.717C191.858 154.756 192.385 154.469 193.141 154.469C194.084 154.469 194.571 154.896 194.571 155.676V156.25L192.749 156.361C191.278 156.449 190.446 157.1 190.446 158.23C190.446 159.385 191.354 160.111 192.579 160.111ZM202.62 155.617C202.445 154.492 201.507 153.572 199.972 153.572C198.202 153.572 197.077 154.85 197.077 156.818C197.077 158.828 198.208 160.111 199.978 160.111C201.495 160.111 202.439 159.256 202.62 158.096H201.601C201.413 158.811 200.822 159.203 199.972 159.203C198.847 159.203 198.12 158.277 198.12 156.818C198.12 155.389 198.835 154.48 199.972 154.48C200.88 154.48 201.437 154.99 201.601 155.617H202.62ZM209.287 155.617C209.111 154.492 208.174 153.572 206.639 153.572C204.869 153.572 203.744 154.85 203.744 156.818C203.744 158.828 204.875 160.111 206.645 160.111C208.162 160.111 209.106 159.256 209.287 158.096H208.268C208.08 158.811 207.488 159.203 206.639 159.203C205.514 159.203 204.787 158.277 204.787 156.818C204.787 155.389 205.502 154.48 206.639 154.48C207.547 154.48 208.104 154.99 208.268 155.617H209.287ZM213.247 154.463C214.249 154.463 214.917 155.201 214.94 156.32H211.46C211.536 155.201 212.239 154.463 213.247 154.463ZM214.911 158.365C214.647 158.922 214.097 159.221 213.282 159.221C212.21 159.221 211.513 158.43 211.46 157.182V157.135H216.001V156.748C216.001 154.785 214.964 153.572 213.259 153.572C211.524 153.572 210.411 154.861 210.411 156.848C210.411 158.846 211.507 160.111 213.259 160.111C214.642 160.111 215.614 159.449 215.919 158.365H214.911ZM217.389 155.412C217.389 156.326 217.928 156.836 219.111 157.123L220.195 157.387C220.869 157.551 221.197 157.844 221.197 158.277C221.197 158.857 220.588 159.262 219.738 159.262C218.93 159.262 218.426 158.922 218.256 158.389H217.219C217.33 159.438 218.297 160.111 219.703 160.111C221.139 160.111 222.234 159.332 222.234 158.201C222.234 157.293 221.66 156.777 220.471 156.49L219.498 156.256C218.754 156.074 218.402 155.805 218.402 155.371C218.402 154.809 218.988 154.428 219.738 154.428C220.5 154.428 220.992 154.762 221.127 155.266H222.123C221.988 154.229 221.068 153.572 219.744 153.572C218.402 153.572 217.389 154.363 217.389 155.412ZM223.505 155.412C223.505 156.326 224.044 156.836 225.227 157.123L226.311 157.387C226.985 157.551 227.313 157.844 227.313 158.277C227.313 158.857 226.704 159.262 225.854 159.262C225.046 159.262 224.542 158.922 224.372 158.389H223.335C223.446 159.438 224.413 160.111 225.819 160.111C227.255 160.111 228.35 159.332 228.35 158.201C228.35 157.293 227.776 156.777 226.587 156.49L225.614 156.256C224.87 156.074 224.518 155.805 224.518 155.371C224.518 154.809 225.104 154.428 225.854 154.428C226.616 154.428 227.108 154.762 227.243 155.266H228.239C228.104 154.229 227.184 153.572 225.86 153.572C224.518 153.572 223.505 154.363 223.505 155.412ZM233.645 152.049V153.684H232.625V154.527H233.645V158.359C233.645 159.566 234.166 160.047 235.467 160.047C235.666 160.047 235.86 160.023 236.059 159.988V159.139C235.872 159.156 235.772 159.162 235.59 159.162C234.934 159.162 234.653 158.846 234.653 158.102V154.527H236.059V153.684H234.653V152.049H233.645ZM240.036 160.111C241.835 160.111 242.949 158.869 242.949 156.842C242.949 154.809 241.835 153.572 240.036 153.572C238.238 153.572 237.124 154.809 237.124 156.842C237.124 158.869 238.238 160.111 240.036 160.111ZM240.036 159.203C238.841 159.203 238.167 158.336 238.167 156.842C238.167 155.342 238.841 154.48 240.036 154.48C241.232 154.48 241.906 155.342 241.906 156.842C241.906 158.336 241.232 159.203 240.036 159.203ZM27.91 173.227C27.1776 173.227 26.6327 172.852 26.6327 172.207C26.6327 171.574 27.0546 171.24 28.0155 171.176L29.7147 171.064V171.645C29.7147 172.547 28.9472 173.227 27.91 173.227ZM27.7225 174.111C28.5663 174.111 29.2577 173.742 29.6679 173.068H29.7616V174H30.7225V169.676C30.7225 168.363 29.8612 167.572 28.3202 167.572C26.9725 167.572 25.9765 168.24 25.8417 169.254H26.8612C27.0018 168.756 27.5292 168.469 28.285 168.469C29.2284 168.469 29.7147 168.896 29.7147 169.676V170.25L27.8925 170.361C26.4218 170.449 25.5897 171.1 25.5897 172.23C25.5897 173.385 26.4979 174.111 27.7225 174.111ZM32.5964 174H33.6042V165.176H32.5964V174ZM35.5719 174H36.5797V165.176H35.5719V174ZM41.0844 174.111C42.8832 174.111 43.9965 172.869 43.9965 170.842C43.9965 168.809 42.8832 167.572 41.0844 167.572C39.2856 167.572 38.1723 168.809 38.1723 170.842C38.1723 172.869 39.2856 174.111 41.0844 174.111ZM41.0844 173.203C39.8891 173.203 39.2153 172.336 39.2153 170.842C39.2153 169.342 39.8891 168.48 41.0844 168.48C42.2797 168.48 42.9535 169.342 42.9535 170.842C42.9535 172.336 42.2797 173.203 41.0844 173.203ZM53.2415 167.684H52.2278L50.9856 172.734H50.8919L49.4798 167.684H48.513L47.1009 172.734H47.0071L45.7649 167.684H44.7454L46.5149 174H47.5345L48.9407 169.113H49.0345L50.4466 174H51.472L53.2415 167.684ZM58.3017 166.049V167.684H57.2822V168.527H58.3017V172.359C58.3017 173.566 58.8232 174.047 60.124 174.047C60.3232 174.047 60.5166 174.023 60.7158 173.988V173.139C60.5283 173.156 60.4287 173.162 60.247 173.162C59.5908 173.162 59.3095 172.846 59.3095 172.102V168.527H60.7158V167.684H59.3095V166.049H58.3017ZM62.2498 174H63.2576V170.262C63.2576 169.195 63.8787 168.48 65.0096 168.48C65.9647 168.48 66.4686 169.037 66.4686 170.156V174H67.4764V169.91C67.4764 168.428 66.6326 167.572 65.2967 167.572C64.3299 167.572 63.6678 167.982 63.3514 168.68H63.2576V165.176H62.2498V174ZM71.7878 168.463C72.7897 168.463 73.4577 169.201 73.4811 170.32H70.0007C70.0768 169.201 70.78 168.463 71.7878 168.463ZM73.4518 172.365C73.1882 172.922 72.6374 173.221 71.8229 173.221C70.7507 173.221 70.0534 172.43 70.0007 171.182V171.135H74.5417V170.748C74.5417 168.785 73.5046 167.572 71.7995 167.572C70.0651 167.572 68.9518 168.861 68.9518 170.848C68.9518 172.846 70.0475 174.111 71.7995 174.111C73.1823 174.111 74.155 173.449 74.4596 172.365H73.4518ZM80.4457 167.684H79.4379V174.328C79.4379 175.025 79.2035 175.307 78.4886 175.307H78.3363V176.168H78.5121C79.8363 176.168 80.4457 175.611 80.4457 174.311V167.684ZM79.9418 166.4C80.3285 166.4 80.6449 166.084 80.6449 165.697C80.6449 165.311 80.3285 164.994 79.9418 164.994C79.555 164.994 79.2386 165.311 79.2386 165.697C79.2386 166.084 79.555 166.4 79.9418 166.4ZM84.2649 173.227C83.5324 173.227 82.9875 172.852 82.9875 172.207C82.9875 171.574 83.4094 171.24 84.3703 171.176L86.0695 171.064V171.645C86.0695 172.547 85.302 173.227 84.2649 173.227ZM84.0774 174.111C84.9211 174.111 85.6125 173.742 86.0227 173.068H86.1164V174H87.0774V169.676C87.0774 168.363 86.216 167.572 84.675 167.572C83.3274 167.572 82.3313 168.24 82.1965 169.254H83.216C83.3567 168.756 83.884 168.469 84.6399 168.469C85.5832 168.469 86.0695 168.896 86.0695 169.676V170.25L84.2473 170.361C82.7766 170.449 81.9445 171.1 81.9445 172.23C81.9445 173.385 82.8528 174.111 84.0774 174.111ZM94.1192 169.617C93.9434 168.492 93.0059 167.572 91.4708 167.572C89.7012 167.572 88.5762 168.85 88.5762 170.818C88.5762 172.828 89.7071 174.111 91.4766 174.111C92.9942 174.111 93.9376 173.256 94.1192 172.096H93.0997C92.9122 172.811 92.3204 173.203 91.4708 173.203C90.3458 173.203 89.6192 172.277 89.6192 170.818C89.6192 169.389 90.334 168.48 91.4708 168.48C92.379 168.48 92.9356 168.99 93.0997 169.617H94.1192ZM96.7196 170.443H96.6259V165.176H95.6181V174H96.6259V171.604L97.2294 171.041L99.5966 174H100.88L97.9794 170.391L100.698 167.684H99.4618L96.7196 170.443ZM105.219 169.412C105.219 170.326 105.758 170.836 106.942 171.123L108.026 171.387C108.7 171.551 109.028 171.844 109.028 172.277C109.028 172.857 108.419 173.262 107.569 173.262C106.76 173.262 106.256 172.922 106.087 172.389H105.049C105.161 173.438 106.128 174.111 107.534 174.111C108.969 174.111 110.065 173.332 110.065 172.201C110.065 171.293 109.491 170.777 108.301 170.49L107.329 170.256C106.585 170.074 106.233 169.805 106.233 169.371C106.233 168.809 106.819 168.428 107.569 168.428C108.331 168.428 108.823 168.762 108.958 169.266H109.954C109.819 168.229 108.899 167.572 107.575 167.572C106.233 167.572 105.219 168.363 105.219 169.412ZM114.119 168.463C115.121 168.463 115.789 169.201 115.812 170.32H112.332C112.408 169.201 113.111 168.463 114.119 168.463ZM115.783 172.365C115.519 172.922 114.968 173.221 114.154 173.221C113.082 173.221 112.384 172.43 112.332 171.182V171.135H116.873V170.748C116.873 168.785 115.835 167.572 114.13 167.572C112.396 167.572 111.283 168.861 111.283 170.848C111.283 172.846 112.378 174.111 114.13 174.111C115.513 174.111 116.486 173.449 116.79 172.365H115.783ZM118.407 174H119.414V170.086C119.414 169.195 120.112 168.551 121.073 168.551C121.272 168.551 121.635 168.586 121.717 168.609V167.602C121.588 167.584 121.377 167.572 121.213 167.572C120.375 167.572 119.649 168.006 119.461 168.621H119.367V167.684H118.407V174ZM128.085 167.684H127.007L125.278 172.887H125.185L123.456 167.684H122.378L124.716 174H125.747L128.085 167.684ZM131.67 168.463C132.672 168.463 133.34 169.201 133.363 170.32H129.883C129.959 169.201 130.662 168.463 131.67 168.463ZM133.334 172.365C133.07 172.922 132.52 173.221 131.705 173.221C130.633 173.221 129.936 172.43 129.883 171.182V171.135H134.424V170.748C134.424 168.785 133.387 167.572 131.682 167.572C129.947 167.572 128.834 168.861 128.834 170.848C128.834 172.846 129.93 174.111 131.682 174.111C133.064 174.111 134.037 173.449 134.342 172.365H133.334ZM135.958 174H136.966V170.086C136.966 169.195 137.663 168.551 138.624 168.551C138.823 168.551 139.186 168.586 139.268 168.609V167.602C139.14 167.584 138.929 167.572 138.765 167.572C137.927 167.572 137.2 168.006 137.013 168.621H136.919V167.684H135.958V174ZM144.241 166.049V167.684H143.221V168.527H144.241V172.359C144.241 173.566 144.762 174.047 146.063 174.047C146.262 174.047 146.456 174.023 146.655 173.988V173.139C146.467 173.156 146.368 173.162 146.186 173.162C145.53 173.162 145.249 172.846 145.249 172.102V168.527H146.655V167.684H145.249V166.049H144.241ZM150.632 174.111C152.431 174.111 153.544 172.869 153.544 170.842C153.544 168.809 152.431 167.572 150.632 167.572C148.833 167.572 147.72 168.809 147.72 170.842C147.72 172.869 148.833 174.111 150.632 174.111ZM150.632 173.203C149.437 173.203 148.763 172.336 148.763 170.842C148.763 169.342 149.437 168.48 150.632 168.48C151.828 168.48 152.501 169.342 152.501 170.842C152.501 172.336 151.828 173.203 150.632 173.203ZM163.644 169.617C163.468 168.492 162.53 167.572 160.995 167.572C159.226 167.572 158.101 168.85 158.101 170.818C158.101 172.828 159.232 174.111 161.001 174.111C162.519 174.111 163.462 173.256 163.644 172.096H162.624C162.437 172.811 161.845 173.203 160.995 173.203C159.87 173.203 159.144 172.277 159.144 170.818C159.144 169.389 159.858 168.48 160.995 168.48C161.903 168.48 162.46 168.99 162.624 169.617H163.644ZM167.029 173.227C166.297 173.227 165.752 172.852 165.752 172.207C165.752 171.574 166.174 171.24 167.135 171.176L168.834 171.064V171.645C168.834 172.547 168.066 173.227 167.029 173.227ZM166.842 174.111C167.685 174.111 168.377 173.742 168.787 173.068H168.881V174H169.842V169.676C169.842 168.363 168.98 167.572 167.439 167.572C166.092 167.572 165.096 168.24 164.961 169.254H165.98C166.121 168.756 166.648 168.469 167.404 168.469C168.348 168.469 168.834 168.896 168.834 169.676V170.25L167.012 170.361C165.541 170.449 164.709 171.1 164.709 172.23C164.709 173.385 165.617 174.111 166.842 174.111ZM174.733 167.572C173.872 167.572 173.116 168.012 172.712 168.738H172.618V167.684H171.657V176.109H172.665V173.051H172.759C173.104 173.719 173.831 174.111 174.733 174.111C176.339 174.111 177.387 172.816 177.387 170.842C177.387 168.855 176.345 167.572 174.733 167.572ZM174.493 173.203C173.356 173.203 172.636 172.289 172.636 170.842C172.636 169.389 173.356 168.48 174.499 168.48C175.653 168.48 176.345 169.365 176.345 170.842C176.345 172.318 175.653 173.203 174.493 173.203ZM179.343 166.049V167.684H178.324V168.527H179.343V172.359C179.343 173.566 179.865 174.047 181.166 174.047C181.365 174.047 181.558 174.023 181.757 173.988V173.139C181.57 173.156 181.47 173.162 181.289 173.162C180.632 173.162 180.351 172.846 180.351 172.102V168.527H181.757V167.684H180.351V166.049H179.343ZM188.342 167.684H187.334V171.422C187.334 172.529 186.725 173.191 185.612 173.191C184.604 173.191 184.182 172.664 184.182 171.527V167.684H183.174V171.773C183.174 173.268 183.913 174.111 185.331 174.111C186.297 174.111 186.971 173.713 187.288 173.01H187.381V174H188.342V167.684ZM190.193 174H191.201V170.086C191.201 169.195 191.898 168.551 192.859 168.551C193.058 168.551 193.421 168.586 193.503 168.609V167.602C193.374 167.584 193.163 167.572 192.999 167.572C192.161 167.572 191.435 168.006 191.247 168.621H191.154V167.684H190.193V174ZM197.059 168.463C198.061 168.463 198.729 169.201 198.752 170.32H195.272C195.348 169.201 196.051 168.463 197.059 168.463ZM198.723 172.365C198.459 172.922 197.908 173.221 197.094 173.221C196.022 173.221 195.324 172.43 195.272 171.182V171.135H199.813V170.748C199.813 168.785 198.776 167.572 197.07 167.572C195.336 167.572 194.223 168.861 194.223 170.848C194.223 172.846 195.319 174.111 197.07 174.111C198.453 174.111 199.426 173.449 199.731 172.365H198.723ZM206.631 173.227C205.898 173.227 205.353 172.852 205.353 172.207C205.353 171.574 205.775 171.24 206.736 171.176L208.435 171.064V171.645C208.435 172.547 207.668 173.227 206.631 173.227ZM206.443 174.111C207.287 174.111 207.978 173.742 208.389 173.068H208.482V174H209.443V169.676C209.443 168.363 208.582 167.572 207.041 167.572C205.693 167.572 204.697 168.24 204.562 169.254H205.582C205.723 168.756 206.25 168.469 207.006 168.469C207.949 168.469 208.435 168.896 208.435 169.676V170.25L206.613 170.361C205.142 170.449 204.31 171.1 204.31 172.23C204.31 173.385 205.219 174.111 206.443 174.111ZM216.368 167.684H215.36V171.422C215.36 172.529 214.751 173.191 213.637 173.191C212.63 173.191 212.208 172.664 212.208 171.527V167.684H211.2V171.773C211.2 173.268 211.938 174.111 213.356 174.111C214.323 174.111 214.997 173.713 215.313 173.01H215.407V174H216.368V167.684ZM220.556 174.111C221.429 174.111 222.179 173.695 222.578 172.992H222.671V174H223.632V165.176H222.625V168.68H222.537C222.179 167.988 221.435 167.572 220.556 167.572C218.951 167.572 217.902 168.861 217.902 170.842C217.902 172.828 218.939 174.111 220.556 174.111ZM220.791 168.48C221.933 168.48 222.648 169.395 222.648 170.842C222.648 172.301 221.939 173.203 220.791 173.203C219.636 173.203 218.945 172.318 218.945 170.842C218.945 169.371 219.642 168.48 220.791 168.48ZM225.565 174H226.573V167.684H225.565V174ZM226.069 166.4C226.455 166.4 226.772 166.084 226.772 165.697C226.772 165.311 226.455 164.994 226.069 164.994C225.682 164.994 225.366 165.311 225.366 165.697C225.366 166.084 225.682 166.4 226.069 166.4ZM231.042 174.111C232.841 174.111 233.954 172.869 233.954 170.842C233.954 168.809 232.841 167.572 231.042 167.572C229.243 167.572 228.13 168.809 228.13 170.842C228.13 172.869 229.243 174.111 231.042 174.111ZM231.042 173.203C229.847 173.203 229.173 172.336 229.173 170.842C229.173 169.342 229.847 168.48 231.042 168.48C232.238 168.48 232.911 169.342 232.911 170.842C232.911 172.336 232.238 173.203 231.042 173.203ZM236.203 174.059C236.625 174.059 236.965 173.713 236.965 173.297C236.965 172.875 236.625 172.535 236.203 172.535C235.787 172.535 235.442 172.875 235.442 173.297C235.442 173.713 235.787 174.059 236.203 174.059Z" fill="#272525"/>
+</svg>
index 7e478af43ab9a728e47f284ae11007cc25a33c90..76c4d0b83c987cbdbc4cf5f9405a0fbfc96b3cad 100644 (file)
@@ -13,6 +13,7 @@ Item {
     property int fontBig: 28
     property int fontMedium: 13
     property int fontSmall: 11
+    property int fontExtraSmall: 8
 
     property int leftMargin: 48
     property int rightMargin: 16
@@ -27,6 +28,12 @@ Item {
     property string buttonStroke: virtualstudio.darkMode ? "#80827D7D" : "#40979797"
     property string buttonHoverStroke: virtualstudio.darkMode ? "#7B7777" : "#BABCBC"
     property string buttonPressedStroke: virtualstudio.darkMode ? "#827D7D" : "#BABCBC"
+    property string warningTextColour: "#DB0A0A"
+    property string linkText: virtualstudio.darkMode ? "#8B8D8D" : "#272525"
+
+    property string errorFlagColour: "#DB0A0A"
+
+    property string disabledButtonTextColour: virtualstudio.darkMode ? "#827D7D" : "#BABCBC"
 
     property string settingsGroupView: "Audio"
 
@@ -57,7 +64,7 @@ Item {
         y: header.height-1
         modal: false
         interactive: false
-        visible: window.state == "settings"
+        visible: virtualstudio.windowState == "settings"
 
         background: Rectangle {
             border.color: "#33979797"
@@ -78,12 +85,31 @@ Item {
                 id: audioBtn
                 text: "Audio"
                 width: parent.width
-                contentItem: Label {
-                    text: audioBtn.text
-                    font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
-                    horizontalAlignment: Text.AlignHCenter
-                    verticalAlignment: Text.AlignVCenter
-                    color: textColour
+                contentItem: Item {
+                    implicitWidth: audioButtonText.implicitWidth
+                    implicitHeight: audioButtonText.implicitHeight
+
+                    Label {
+                        id: audioButtonText
+                        text: audioBtn.text
+                        width: Boolean(virtualstudio.devicesError) ? parent.width - 16 * virtualstudio.uiScale : parent.width
+                        font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+                        horizontalAlignment: Text.AlignHCenter
+                        verticalAlignment: Text.AlignVCenter
+                        color: textColour
+                    }
+
+                    Rectangle {
+                        id: audioDevicesErrorFlag
+                        anchors.left: audioButtonText.right
+                        anchors.verticalCenter: audioButtonText.verticalCenter
+                        anchors.rightMargin: 16 * virtualstudio.uiScale
+                        width: 8 * virtualstudio.uiScale 
+                        height: 8 * virtualstudio.uiScale 
+                        color: errorFlagColour
+                        radius: 4 * virtualstudio.uiScale
+                        visible: Boolean(virtualstudio.devicesError)
+                    }
                 }
                 background: Rectangle {
                     width: parent.width
@@ -94,13 +120,22 @@ Item {
                 id: appearanceBtn
                 text: "Appearance"
                 width: parent.width
-                contentItem: Label {
-                    text: appearanceBtn.text
-                    font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
-                    horizontalAlignment: Text.AlignHCenter
-                    verticalAlignment: Text.AlignVCenter
-                    color: textColour
+                contentItem: Item {
+                    implicitWidth: appearanceButtonText.implicitWidth
+                    implicitHeight: appearanceButtonText.implicitHeight
+
+                    Label {
+                        id: appearanceButtonText
+                        text: appearanceBtn.text
+                        width: parent.width
+                        font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+                        horizontalAlignment: Text.AlignHCenter
+                        verticalAlignment: Text.AlignVCenter
+                        color: textColour
+                    }
                 }
+                
+
                 background: Rectangle {
                     width: parent.width
                     color: appearanceBtn.down ? buttonPressedColour : (appearanceBtn.hovered || settingsGroupView == "Appearance" ? buttonHoverColour : backgroundColour)
@@ -110,12 +145,19 @@ Item {
                 id: advancedBtn
                 text: "Advanced"
                 width: parent.width
-                contentItem: Label {
-                    text: advancedBtn.text
-                    font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
-                    horizontalAlignment: Text.AlignHCenter
-                    verticalAlignment: Text.AlignVCenter
-                    color: textColour
+                contentItem: Item {
+                    implicitWidth: advancedButtonText.implicitWidth
+                    implicitHeight: advancedButtonText.implicitHeight
+
+                    Label {
+                        id: advancedButtonText
+                        text: advancedBtn.text
+                        width: parent.width
+                        font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+                        horizontalAlignment: Text.AlignHCenter
+                        verticalAlignment: Text.AlignVCenter
+                        color: textColour
+                    }
                 }
                 background: Rectangle {
                     width: parent.width
@@ -126,12 +168,20 @@ Item {
                 id: profileBtn
                 text: "Profile"
                 width: parent.width
-                contentItem: Label {
-                    text: profileBtn.text
-                    font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
-                    horizontalAlignment: Text.AlignHCenter
-                    verticalAlignment: Text.AlignVCenter
-                    color: textColour
+                contentItem: Item {
+
+                    implicitWidth: profileButtonText.implicitWidth
+                    implicitHeight: profileButtonText.implicitHeight
+
+                    Label {
+                        id: profileButtonText
+                        text: profileBtn.text
+                        width: parent.width
+                        font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+                        horizontalAlignment: Text.AlignHCenter
+                        verticalAlignment: Text.AlignVCenter
+                        color: textColour
+                    }
                 }
                 background: Rectangle {
                     width: parent.width
@@ -173,7 +223,7 @@ Item {
             model: backendComboModel
             currentIndex: virtualstudio.audioBackend == "JACK" ? 0 : 1
             onActivated: { virtualstudio.audioBackend = currentText }
-            x: 234 * virtualstudio.uiScale; y: 100 * virtualstudio.uiScale
+            x: 234 * virtualstudio.uiScale; y: 48 * virtualstudio.uiScale
             width: parent.width - x - (16 * virtualstudio.uiScale); height: 36 * virtualstudio.uiScale
             visible: virtualstudio.selectableBackend
         }
@@ -199,53 +249,67 @@ Item {
             color: textColour
         }
 
-        ComboBox {
-            id: inputCombo
-            model: inputComboModel
-            currentIndex: virtualstudio.inputDevice
-            onActivated: { virtualstudio.inputDevice = currentIndex }
-            x: 234 * virtualstudio.uiScale; y: virtualstudio.uiScale * (virtualstudio.selectableBackend ? 148 : 100)
-            width: parent.width - x - (16 * virtualstudio.uiScale); height: 36 * virtualstudio.uiScale
+        Text {
+            anchors.verticalCenter: outputCombo.verticalCenter
+            x: leftMargin * virtualstudio.uiScale
+            text: "Output Device"
+            font { family: "Poppins"; pixelSize: 13 * virtualstudio.fontScale * virtualstudio.uiScale }
             visible: virtualstudio.audioBackend != "JACK"
-        }
-
-        Meter {
-            id: inputDeviceMeters
-            anchors.left: backendCombo.left
-            anchors.right: parent.right
-            anchors.rightMargin: rightMargin * virtualstudio.uiScale
-            y: virtualstudio.audioBackend != "JACK" ?  inputCombo.y + 48 * virtualstudio.uiScale : virtualstudio.uiScale * (virtualstudio.selectableBackend ? 148 : 100)
-            height: 100 * virtualstudio.uiScale
-            model: inputMeterModel
-            clipped: inputClipped
+            color: textColour
         }
 
         ComboBox {
             id: outputCombo
             model: outputComboModel
-            currentIndex: virtualstudio.outputDevice
-            onActivated: { virtualstudio.outputDevice = currentIndex }
-            x: backendCombo.x; y: inputDeviceMeters.y + (48 * virtualstudio.uiScale)
+            currentIndex: (() => {
+                let count = 0;
+                for (let i = 0; i < outputCombo.model.length; i++) {
+                    if (outputCombo.model[i].type === "element") {
+                        count++;
+                    }
+
+                    if (count > virtualstudio.outputDevice) {
+                        return i;
+                    }
+                }
+
+                return 0;
+            })()
+            x: 234 * virtualstudio.uiScale; y: virtualstudio.uiScale * (virtualstudio.selectableBackend ? 96 : 48)
             width: backendCombo.width; height: backendCombo.height
             visible: virtualstudio.audioBackend != "JACK"
-        }
+            delegate: ItemDelegate {
+                required property var modelData
+                required property int index
 
-        Text {
-            anchors.verticalCenter: inputCombo.verticalCenter
-            x: leftMargin * virtualstudio.uiScale
-            text: "Input Device"
-            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-            visible: virtualstudio.audioBackend != "JACK"
-            color: textColour
-        }
+                leftPadding: 0
 
-        Text {
-            anchors.verticalCenter: outputCombo.verticalCenter
-            x: leftMargin * virtualstudio.uiScale
-            text: "Output Device"
-            font { family: "Poppins"; pixelSize: 13 * virtualstudio.fontScale * virtualstudio.uiScale }
-            visible: virtualstudio.audioBackend != "JACK"
-            color: textColour
+                width: parent.width
+                contentItem: Text {
+                    leftPadding: modelData.type === "element" && outputCombo.model.filter(it => it.type === "header").length > 0 ? 24 : 12
+                    text: modelData.text
+                    font.bold: modelData.type === "header"
+                }
+                highlighted: outputCombo.highlightedIndex === index
+                MouseArea {
+                    anchors.fill: parent
+                    onClicked: {
+                        if (modelData.type == "element") {
+                            outputCombo.currentIndex = index
+                            outputCombo.popup.close()
+                            virtualstudio.outputDevice = index - outputCombo.model.filter((elem, idx) => idx < index && elem.type === "header").length
+                        }
+                    }
+                }
+            }
+            contentItem: Text {
+                leftPadding: 12
+                font: outputCombo.font
+                horizontalAlignment: Text.AlignHLeft
+                verticalAlignment: Text.AlignVCenter
+                elide: Text.ElideRight
+                text: outputCombo.model[outputCombo.currentIndex].text
+            }
         }
 
         Button {
@@ -259,7 +323,7 @@ Item {
             onClicked: { virtualstudio.playOutputAudio() }
             width: 216 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
             x: parent.width - (232 * virtualstudio.uiScale)
-            y: virtualstudio.audioBackend != "JACK" ? outputCombo.y + (60 * virtualstudio.uiScale) : inputDeviceMeters.y + (48 * virtualstudio.uiScale)
+            y: virtualstudio.audioBackend != "JACK" ? outputCombo.y + (48 * virtualstudio.uiScale) : outputCombo.y + (48 * virtualstudio.uiScale)
             Text {
                 text: "Test Output Audio"
                 font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
@@ -268,6 +332,81 @@ Item {
             }
         }
 
+        Text {
+            anchors.verticalCenter: inputCombo.verticalCenter
+            x: leftMargin * virtualstudio.uiScale; y: testOutputAudioButton.y + (48 * virtualstudio.uiScale)
+            text: "Input Device"
+            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+            visible: virtualstudio.audioBackend != "JACK"
+            color: textColour
+        }
+
+        ComboBox {
+            id: inputCombo
+            model: inputComboModel
+            currentIndex: (() => {
+                let count = 0;
+                for (let i = 0; i < inputCombo.model.length; i++) {
+                    if (inputCombo.model[i].type === "element") {
+                        count++;
+                    }
+
+                    if (count > virtualstudio.inputDevice) {
+                        return i;
+                    }
+                }
+
+                return 0;
+            })()
+            x: backendCombo.x; y: testOutputAudioButton.y + (48 * virtualstudio.uiScale)
+            width: parent.width - x - (16 * virtualstudio.uiScale); height: 36 * virtualstudio.uiScale
+            visible: virtualstudio.audioBackend != "JACK"
+                        delegate: ItemDelegate {
+                required property var modelData
+                required property int index
+
+                leftPadding: 0
+
+                width: parent.width
+                contentItem: Text {
+                    leftPadding: modelData.type === "element" && inputCombo.model.filter(it => it.type === "header").length > 0 ? 24 : 12
+                    text: modelData.text
+                    font.bold: modelData.type === "header"
+                }
+                highlighted: inputCombo.highlightedIndex === index
+                MouseArea {
+                    anchors.fill: parent
+                    onClicked: {
+                        if (modelData.type == "element") {
+                            inputCombo.currentIndex = index
+                            inputCombo.popup.close()
+                            virtualstudio.inputDevice = index - inputCombo.model.filter((elem, idx) => idx < index && elem.type === "header").length
+                        }
+                    }
+                }
+            }
+            contentItem: Text {
+                leftPadding: 12
+                font: inputCombo.font
+                horizontalAlignment: Text.AlignHLeft
+                verticalAlignment: Text.AlignVCenter
+                elide: Text.ElideRight
+                text: inputCombo.model[inputCombo.currentIndex].text
+            }
+        }
+
+        Meter {
+            id: inputDeviceMeters
+            anchors.left: backendCombo.left
+            anchors.right: parent.right
+            anchors.rightMargin: rightMargin * virtualstudio.uiScale
+            y: virtualstudio.audioBackend != "JACK" ?  inputCombo.y + 48 * virtualstudio.uiScale : virtualstudio.uiScale * (virtualstudio.selectableBackend ? 112 : 64)
+            height: 100 * virtualstudio.uiScale
+            model: inputMeterModel
+            clipped: inputClipped
+            enabled: !Boolean(virtualstudio.devicesError)
+        }
+
         Button {
             id: refreshButton
             background: Rectangle {
@@ -277,7 +416,7 @@ Item {
                 border.color: refreshButton.down ? buttonPressedStroke : (refreshButton.hovered ? buttonHoverStroke : buttonStroke)
             }
             onClicked: { virtualstudio.refreshDevices() }
-            x: parent.width - (232 * virtualstudio.uiScale); y: testOutputAudioButton.y + (48 * virtualstudio.uiScale)
+            x: parent.width - (232 * virtualstudio.uiScale); y: inputDeviceMeters.y + (48 * virtualstudio.uiScale)
             width: 216 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
             visible: virtualstudio.audioBackend != "JACK"
             Text {
@@ -288,10 +427,31 @@ Item {
             }
         }
 
+        Text {
+            id: devicesWarningOrError
+            x: leftMargin * virtualstudio.uiScale
+            y: virtualstudio.audioBackend != "JACK" ? refreshButton.y + (48 * virtualstudio.uiScale) : testOutputAudioButton.y + (48 * virtualstudio.uiScale)
+            width: parent.width - (64 * virtualstudio.uiScale)
+            textFormat: Text.RichText
+            text: (virtualstudio.devicesError || virtualstudio.devicesWarning)
+                + ((virtualstudio.devicesErrorHelpUrl || virtualstudio.devicesWarningHelpUrl)
+                    ? `&nbsp;<a style="color: ${linkText};" href=${virtualstudio.devicesErrorHelpUrl || virtualstudio.devicesWarningHelpUrl}>Learn More.</a>`
+                    : ""
+                )
+            onLinkActivated: link => {
+                virtualstudio.openLink(link)
+            }
+            horizontalAlignment: Text.AlignHLeft
+            wrapMode: Text.WordWrap
+            color: warningTextColour
+            font { family: "Poppins"; pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            visible: Boolean(virtualstudio.devicesError || virtualstudio.devicesWarning);
+        }
+
         Rectangle {
             id: divider
             x: leftMargin * virtualstudio.uiScale
-            y: virtualstudio.audioBackend != "JACK" ? refreshButton.y + (48 * virtualstudio.uiScale) : testOutputAudioButton.y + (48 * virtualstudio.uiScale)
+            y: Boolean(virtualstudio.devicesError || virtualstudio.devicesWarning) ? devicesWarningOrError.y + (60 * virtualstudio.uiScale) : refreshButton.y + (60 * virtualstudio.uiScale)
             width: parent.width - x - (16 * virtualstudio.uiScale); height: 1 * virtualstudio.uiScale
             color: textColour
             visible: virtualstudio.audioBackend != "JACK"
@@ -388,7 +548,7 @@ Item {
                 border.width: 1
                 border.color: modeButton.down ? buttonPressedStroke : (modeButton.hovered ? buttonHoverStroke : buttonStroke)
             }
-            onClicked: { window.state = "login"; virtualstudio.toStandard(); }
+            onClicked: { virtualstudio.windowState = "login"; virtualstudio.toStandard(); }
             x: 234 * virtualstudio.uiScale; y: 100 * virtualstudio.uiScale
             width: 216 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
             Text {
@@ -512,7 +672,7 @@ Item {
                 border.width: 1
                 border.color: logoutButton.down ? buttonPressedStroke : (logoutButton.hovered ? buttonHoverStroke : buttonStroke)
             }
-            onClicked: { window.state = "login"; virtualstudio.logout() }
+            onClicked: { virtualstudio.windowState = "login"; virtualstudio.logout() }
             anchors.horizontalCenter: parent.horizontalCenter
             y: editButton.y + (48 * virtualstudio.uiScale)
             width: 260 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
@@ -532,7 +692,7 @@ Item {
                 border.width: 1
                 border.color: testModeButton.down ? buttonPressedStroke : (testModeButton.hovered ? buttonHoverStroke : buttonStroke)
             }
-            onClicked: { virtualstudio.testMode = !virtualstudio.testMode; window.state = "login"; virtualstudio.logout() }
+            onClicked: { virtualstudio.testMode = !virtualstudio.testMode; virtualstudio.windowState = "login"; virtualstudio.logout() }
             anchors.horizontalCenter: parent.horizontalCenter
             y: logoutButton.y + (48 * virtualstudio.uiScale)
             width: 260 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
@@ -560,7 +720,7 @@ Item {
                 border.width: 1
                 border.color: cancelButton.down ? buttonPressedStroke : (cancelButton.hovered ? buttonHoverStroke : buttonStroke)
             }
-            onClicked: { window.state = "browse"; virtualstudio.revertSettings() }
+            onClicked: { virtualstudio.windowState = "browse"; virtualstudio.revertSettings() }
             anchors.verticalCenter: parent.verticalCenter
             x: parent.width - (230 * virtualstudio.uiScale)
             width: buttonWidth * virtualstudio.uiScale; height: buttonHeight * virtualstudio.uiScale
@@ -574,13 +734,14 @@ Item {
 
         Button {
             id: saveButton
+            enabled: !Boolean(virtualstudio.devicesError)
             background: Rectangle {
                 radius: 6 * virtualstudio.uiScale
                 color: saveButton.down ? buttonPressedColour : (saveButton.hovered ? buttonHoverColour : buttonColour)
                 border.width: 1
                 border.color: saveButton.down ? buttonPressedStroke : (saveButton.hovered ? buttonHoverStroke : buttonStroke)
             }
-            onClicked: { window.state = "browse"; virtualstudio.applySettings() }
+            onClicked: { virtualstudio.windowState = "browse"; virtualstudio.applySettings() }
             anchors.verticalCenter: parent.verticalCenter
             x: parent.width - (119 * virtualstudio.uiScale)
             width: buttonWidth * virtualstudio.uiScale; height: buttonHeight * virtualstudio.uiScale
@@ -588,7 +749,7 @@ Item {
                 text: "Save"
                 font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
                 anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
-                color: textColour
+                color: Boolean(virtualstudio.devicesError) ? disabledButtonTextColour : textColour
             }
         }
     }
index b8e835847ff5113c00bd2fc4ade99dd5e26a73bb..65e5f716b936b4cdb53e8a961df6d401dfd453e2 100644 (file)
@@ -37,10 +37,12 @@ Item {
     property string saveButtonText: "#DB0A0A"
     property string checkboxStroke: "#0062cc"
     property string checkboxPressedStroke: "#007AFF"
+    property string disabledButtonText: "#D3D4D4"
+    property string linkText: virtualstudio.darkMode ? "#8B8D8D" : "#272525"
 
     property bool currShowWarnings: virtualstudio.showWarnings
-    property string warningScreen: virtualstudio.showWarnings ? "ethernet" : "acknowledged"
-
+    property string warningScreen: virtualstudio.showWarnings ? "ethernet" : ( permissions.micPermission == "unknown" ? "microphone" : "acknowledged")
     Item {
         id: ethernetWarningItem
         width: parent.width; height: parent.height
@@ -264,7 +266,13 @@ Item {
                     color: saveButtonShadow
                 }
             }
-            onClicked: { virtualstudio.showWarnings = currShowWarnings; warningScreen = "acknowledged" }
+            onClicked: {
+                if (permissions.micPermission == "unknown") {
+                    virtualstudio.showWarnings = currShowWarnings; warningScreen = "microphone"
+                } else {
+                    virtualstudio.showWarnings = currShowWarnings; warningScreen = "acknowledged"
+                }
+            }
             anchors.right: parent.right
             anchors.rightMargin: 16 * virtualstudio.uiScale
             anchors.bottomMargin: 16 * virtualstudio.uiScale
@@ -319,10 +327,216 @@ Item {
         }
     }
 
+    Item {
+        id: requestMicPermissionsItem
+        width: parent.width; height: parent.height
+        visible: warningScreen == "microphone" && permissions.micPermission == "unknown"
+
+        Image {
+            id: microphonePrompt
+            source: "Prompt.svg"
+            width: 260
+            height: 250
+            y: 60
+            anchors.horizontalCenter: parent.horizontalCenter
+            sourceSize: Qt.size(microphonePrompt.width,microphonePrompt.height)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+        }
+
+        Image {
+            id: micLogo
+            source: "logo.svg"
+            anchors.horizontalCenter: parent.horizontalCenter
+            anchors.top: microphonePrompt.top
+            anchors.topMargin: 18 * virtualstudio.uiScale
+            width: 32 * virtualstudio.uiScale; height: 59 * virtualstudio.uiScale
+            sourceSize: Qt.size(micLogo.width,micLogo.height)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+        }
+
+        Colorize {
+            anchors.fill: microphonePrompt
+            source: microphonePrompt
+            hue: 0
+            saturation: 0
+            lightness: imageLightnessValue
+        }
+
+        Button {
+            id: showPromptButton
+            width: 112 * virtualstudio.uiScale
+            height: 30 * virtualstudio.uiScale
+            background: Rectangle {
+                radius: 6 * virtualstudio.uiScale
+                color: showPromptButton.down ? saveButtonPressedColour : saveButtonBackgroundColour
+                border.width: 2
+                border.color: showPromptButton.down ? saveButtonPressedStroke : saveButtonStroke
+                layer.enabled: showPromptButton.hovered && !showPromptButton.down
+                layer.effect: DropShadow {
+                    horizontalOffset: 1 * virtualstudio.uiScale
+                    verticalOffset: 1 * virtualstudio.uiScale
+                    radius: 8.0 * virtualstudio.uiScale
+                    samples: 17
+                    color: saveButtonShadow
+                }
+            }
+            onClicked: { 
+                permissions.getMicPermission();
+            }
+            anchors.right: microphonePrompt.right
+            anchors.rightMargin: 13.5 * virtualstudio.uiScale
+            anchors.bottomMargin: 17 * virtualstudio.uiScale
+            anchors.bottom: microphonePrompt.bottom
+            Text {
+                text: "OK"
+                font.pixelSize: 11 * virtualstudio.fontScale * virtualstudio.uiScale
+                font.weight: Font.Bold
+                color: saveButtonText
+                anchors.horizontalCenter: parent.horizontalCenter
+                anchors.verticalCenter: parent.verticalCenter
+            }
+        }
+
+        Text {
+            id: micPermissionsHeader
+            text: "JackTrip needs your sounds!"
+            font { family: "Poppins"; weight: Font.Bold; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+            anchors.horizontalCenter: parent.horizontalCenter
+            anchors.top: microphonePrompt.bottom
+            anchors.topMargin: 48 * virtualstudio.uiScale
+        }
+
+        Text {
+            id: micPermissionsSubheader1
+            text: "JackTrip requires permission to use your microphone."
+            font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+            width: 400
+            wrapMode: Text.Wrap
+            horizontalAlignment: Text.AlignHCenter
+            anchors.horizontalCenter: parent.horizontalCenter
+            anchors.top: micPermissionsHeader.bottom
+            anchors.topMargin: 32 * virtualstudio.uiScale
+        }
+
+        Text {
+            id: micPermissionsSubheader2
+            text: "Click ‘OK’ to give JackTrip access to your microphone, instrument, or other audio device."
+            font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+            width: 400
+            wrapMode: Text.Wrap
+            horizontalAlignment: Text.AlignHCenter
+            anchors.horizontalCenter: parent.horizontalCenter
+            anchors.top: micPermissionsSubheader1.bottom
+            anchors.topMargin: 24 * virtualstudio.uiScale
+        }
+    }
+
+    Item {
+        id: noMicItem
+        width: parent.width; height: parent.height
+        visible: (warningScreen == "acknowledged" || warningScreen == "microphone") && permissions.micPermission == "denied"
+
+        Image {
+            id: noMic
+            source: "micoff.svg"
+            width: 109.27
+            height: 170
+            y: 60
+            anchors.horizontalCenter: parent.horizontalCenter
+            sourceSize: Qt.size(noMic.width,noMic.height)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+        }
+
+        Colorize {
+            anchors.fill: noMic
+            source: noMic
+            hue: 0
+            saturation: 0
+            lightness: imageLightnessValue
+        }
+
+        Button {
+            id: openSettingsButton
+            background: Rectangle {
+                radius: 6 * virtualstudio.uiScale
+                color: openSettingsButton.down ? saveButtonPressedColour : saveButtonBackgroundColour
+                border.width: 1
+                border.color: openSettingsButton.down ? saveButtonPressedStroke : saveButtonStroke
+                layer.enabled: openSettingsButton.hovered && !openSettingsButton.down
+                layer.effect: DropShadow {
+                    horizontalOffset: 1 * virtualstudio.uiScale
+                    verticalOffset: 1 * virtualstudio.uiScale
+                    radius: 8.0 * virtualstudio.uiScale
+                    samples: 17
+                    color: saveButtonShadow
+                }
+            }
+            onClicked: { 
+                permissions.openSystemPrivacy();
+            }
+            anchors.right: parent.right
+            anchors.rightMargin: 16 * virtualstudio.uiScale
+            anchors.bottomMargin: 16 * virtualstudio.uiScale
+            anchors.bottom: parent.bottom
+            width: 200 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
+            Text {
+                text: "Open Privacy Settings"
+                font.family: "Poppins"
+                font.pixelSize: 11 * virtualstudio.fontScale * virtualstudio.uiScale
+                font.weight: Font.Bold
+                color: saveButtonText
+                anchors.horizontalCenter: parent.horizontalCenter
+                anchors.verticalCenter: parent.verticalCenter
+            }
+        }
+
+        Text {
+            id: noMicHeader
+            text: "JackTrip can't hear you!"
+            font { family: "Poppins"; weight: Font.Bold; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+            anchors.horizontalCenter: parent.horizontalCenter
+            anchors.top: noMic.bottom
+            anchors.topMargin: 48 * virtualstudio.uiScale
+        }
+
+        Text {
+            id: noMicSubheader1
+            text: "JackTrip requires permission to use your microphone."
+            font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+            width: 400
+            wrapMode: Text.Wrap
+            horizontalAlignment: Text.AlignHCenter
+            anchors.horizontalCenter: parent.horizontalCenter
+            anchors.top: noMicHeader.bottom
+            anchors.topMargin: 32 * virtualstudio.uiScale
+        }
+
+        Text {
+            id: noMicSubheader2
+            text: "Click 'Open Privacy Settings' to give JackTrip permission to access your microphone, instrument, or other audio device."
+            font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+            width: 400
+            wrapMode: Text.Wrap
+            horizontalAlignment: Text.AlignHCenter
+            anchors.horizontalCenter: parent.horizontalCenter
+            anchors.top: noMicSubheader1.bottom
+            anchors.topMargin: 24 * virtualstudio.uiScale
+        }
+    }
+
     Item {
         id: setupItem
         width: parent.width; height: parent.height
-        visible: warningScreen == "acknowledged"
+        visible: (warningScreen == "acknowledged" || warningScreen == "microphone") && permissions.micPermission == "granted"
 
         Text {
             id: pageTitle
@@ -368,11 +582,55 @@ Item {
         ComboBox {
             id: outputCombo
             model: outputComboModel
-            currentIndex: virtualstudio.outputDevice
-            onActivated: { virtualstudio.outputDevice = currentIndex }
+            currentIndex: (() => {
+                let count = 0;
+                for (let i = 0; i < outputCombo.model.length; i++) {
+                    if (outputCombo.model[i].type === "element") {
+                        count++;
+                    }
+
+                    if (count > virtualstudio.outputDevice) {
+                        return i;
+                    }
+                }
+
+                return 0;
+            })()
             x: backendCombo.x; y: backendCombo.y + virtualstudio.uiScale * (virtualstudio.selectableBackend ? 48 : 0)
             width: backendCombo.width; height: backendCombo.height
             visible: virtualstudio.audioBackend != "JACK"
+            delegate: ItemDelegate {
+                required property var modelData
+                required property int index
+
+                leftPadding: 0
+
+                width: parent.width
+                contentItem: Text {
+                    leftPadding: modelData.type === "element" && outputCombo.model.filter(it => it.type === "header").length > 0 ? 24 : 12
+                    text: modelData.text
+                    font.bold: modelData.type === "header"
+                }
+                highlighted: outputCombo.highlightedIndex === index
+                MouseArea {
+                    anchors.fill: parent
+                    onClicked: {
+                        if (modelData.type == "element") {
+                            outputCombo.currentIndex = index
+                            outputCombo.popup.close()
+                            virtualstudio.outputDevice = index - outputCombo.model.filter((elem, idx) => idx < index && elem.type === "header").length
+                        }
+                    }
+                }
+            }
+            contentItem: Text {
+                leftPadding: 12
+                font: outputCombo.font
+                horizontalAlignment: Text.AlignHLeft
+                verticalAlignment: Text.AlignVCenter
+                elide: Text.ElideRight
+                text: outputCombo.model[outputCombo.currentIndex].text
+            }
         }
 
         Text {
@@ -431,13 +689,57 @@ Item {
         ComboBox {
             id: inputCombo
             model: inputComboModel
-            currentIndex: virtualstudio.inputDevice
-            onActivated: { virtualstudio.inputDevice = currentIndex }
+            currentIndex: (() => {
+                let count = 0;
+                for (let i = 0; i < inputCombo.model.length; i++) {
+                    if (inputCombo.model[i].type === "element") {
+                        count++;
+                    }
+
+                    if (count > virtualstudio.inputDevice) {
+                        return i;
+                    }
+                }
+
+                return 0;
+            })()
             anchors.right: parent.right
             anchors.rightMargin: rightMargin * virtualstudio.uiScale
             y: testOutputAudioButton.y + (48 * virtualstudio.uiScale)
             width: parent.width - (234 * virtualstudio.uiScale); height: 36 * virtualstudio.uiScale
             visible: virtualstudio.audioBackend != "JACK"
+            delegate: ItemDelegate {
+                required property var modelData
+                required property int index
+
+                leftPadding: 0
+
+                width: parent.width
+                contentItem: Text {
+                    leftPadding: modelData.type === "element" && inputCombo.model.filter(it => it.type === "header").length > 0 ? 24 : 12
+                    text: modelData.text
+                    font.bold: modelData.type === "header"
+                }
+                highlighted: inputCombo.highlightedIndex === index
+                MouseArea {
+                    anchors.fill: parent
+                    onClicked: {
+                        if (modelData.type == "element") {
+                            inputCombo.currentIndex = index
+                            inputCombo.popup.close()
+                            virtualstudio.inputDevice = index - inputCombo.model.filter((elem, idx) => idx < index && elem.type === "header").length
+                        }
+                    }
+                }
+            }
+            contentItem: Text {
+                leftPadding: 12
+                font: inputCombo.font
+                horizontalAlignment: Text.AlignHLeft
+                verticalAlignment: Text.AlignVCenter
+                elide: Text.ElideRight
+                text: inputCombo.model[inputCombo.currentIndex].text
+            }
         }
 
         Text {
@@ -459,6 +761,7 @@ Item {
             height: 100 * virtualstudio.uiScale
             model: inputMeterModel
             clipped: inputClipped
+            enabled: !Boolean(virtualstudio.devicesError)
         }
 
         Slider {
@@ -507,18 +810,25 @@ Item {
         }
 
         Text {
-            anchors.left: outputLabel.left
-            anchors.right: outputCombo.right
-            anchors.leftMargin: 16 * virtualstudio.uiScale
+            anchors.left: inputLabel.left
+            anchors.right: refreshButton.left
             anchors.rightMargin: 16 * virtualstudio.uiScale
-            anchors.bottom: parent.bottom
+            anchors.top: refreshButton.top
             anchors.bottomMargin: 60 * virtualstudio.uiScale
-            text: "JackTrip on Windows requires use of an audio device with ASIO drivers. If you do not see your device, you may need to install drivers from your manufacturer."
-            horizontalAlignment: Text.AlignHCenter
+            textFormat: Text.RichText
+            text: (virtualstudio.devicesError || virtualstudio.devicesWarning)
+                + ((virtualstudio.devicesErrorHelpUrl || virtualstudio.devicesWarningHelpUrl)
+                    ? `&nbsp;<a style="color: ${linkText};" href=${virtualstudio.devicesErrorHelpUrl || virtualstudio.devicesWarningHelpUrl}>Learn More.</a>`
+                    : ""
+                )
+            onLinkActivated: link => {
+                virtualstudio.openLink(link)
+            }
+            horizontalAlignment: Text.AlignHLeft
             wrapMode: Text.WordWrap
             color: warningText
             font { family: "Poppins"; pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale }
-            visible: Qt.platform.os == "windows" && virtualstudio.audioBackend != "JACK"
+            visible: Boolean(virtualstudio.devicesError) || Boolean(virtualstudio.devicesWarning);
         }
 
         Button {
@@ -537,7 +847,8 @@ Item {
                     color: saveButtonShadow
                 }
             }
-            onClicked: { window.state = "browse"; virtualstudio.applySettings() }
+            enabled: !Boolean(virtualstudio.devicesError)
+            onClicked: { virtualstudio.windowState = "browse"; virtualstudio.applySettings() }
             anchors.right: parent.right
             anchors.rightMargin: rightMargin * virtualstudio.uiScale
             anchors.bottomMargin: rightMargin * virtualstudio.uiScale
@@ -548,7 +859,7 @@ Item {
                 font.family: "Poppins"
                 font.pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale
                 font.weight: Font.Bold
-                color: saveButtonText
+                color: !Boolean(virtualstudio.devicesError) ? saveButtonText : disabledButtonText
                 anchors.horizontalCenter: parent.horizontalCenter
                 anchors.verticalCenter: parent.verticalCenter
             }
index fffc304f6609809a1d0eae75b3715ec9abc4fb99..f7f06b9e40b0203e15b0d47e080f1bcbf624ac35 100644 (file)
@@ -81,13 +81,21 @@ Rectangle {
     }
 
     Image {
+        id: wedge
         source: available ? "wedge.svg" : "wedge_inactive.svg"
         x: 6; y: 0; width: 52 * virtualstudio.uiScale; height: 83 * virtualstudio.uiScale
+        sourceSize: Qt.size(wedge.width,wedge.height)
+        fillMode: Image.PreserveAspectFit
+        smooth: true
     }
 
     Image {
+        id: studioLogo
         source: "logo.svg"
         x: 8; y: 11; width: 32 * virtualstudio.uiScale; height: 59 * virtualstudio.uiScale
+        sourceSize: Qt.size(studioLogo.width,studioLogo.height)
+        fillMode: Image.PreserveAspectFit
+        smooth: true
     }
     
     Rectangle {
@@ -139,8 +147,12 @@ Rectangle {
         radius: 2 * virtualstudio.uiScale
         color: publicStudio ? "#0095FF" : "#FF9800"
         Image {
+            id: pubPriv
             source: publicStudio ? "public.svg" : "private.svg"
             x: 1 * virtualstudio.uiScale; y: x; width: 12 * virtualstudio.uiScale; height: width
+            sourceSize: Qt.size(pubPriv.width,pubPriv.height)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
         }
     }
     
@@ -167,16 +179,20 @@ Rectangle {
         visible: connected || canConnect || canStart
         onClicked: {
             if (!connected) {
-                window.state = "connected";
+                virtualstudio.windowState = "connected";
                 virtualstudio.connectToStudio(index);
             } else {
                 virtualstudio.disconnect();
             }
         }
         Image {
+            id: joinLeave
             width: 22 * virtualstudio.uiScale; height: 20 * virtualstudio.uiScale
             anchors { verticalCenter: parent.verticalCenter; horizontalCenter: parent.horizontalCenter }
             source: connected ? "leave.svg" : "join.svg"
+            sourceSize: Qt.size(joinLeave.width,joinLeave.height)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
         }
     }
     
@@ -218,9 +234,13 @@ Rectangle {
         }
         visible: true
         Image {
+            id: shareImg
             width: 20 * virtualstudio.uiScale; height: width
             anchors { verticalCenter: parent.verticalCenter; horizontalCenter: parent.horizontalCenter }
             source: "share.svg"
+            sourceSize: Qt.size(shareImg.width,shareImg.height)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
         }
         ToolTip {
             parent: inviteButton
@@ -284,9 +304,13 @@ Rectangle {
         }
         visible: manageable
         Image {
+            id: manageImg
             width: 20 * virtualstudio.uiScale; height: width
             anchors { verticalCenter: parent.verticalCenter; horizontalCenter: parent.horizontalCenter }
             source: "manage.svg"
+            sourceSize: Qt.size(manageImg.width,manageImg.height)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
         }
     }
     
index 191d544fc27166866a653b4cd5de6d1f6b679602..ad5ebb284c93794fdf13a6f09191589baffee3d2 100644 (file)
@@ -49,6 +49,7 @@
 #include "../Compressor.h"
 #include "../CompressorPresets.h"
 #include "../Limiter.h"
+#include "../Meter.h"
 #include "../Reverb.h"
 
 QJackTrip::QJackTrip(int argc, bool suppressCommandlineWarning, QWidget* parent)
@@ -236,6 +237,11 @@ QJackTrip::QJackTrip(int argc, bool suppressCommandlineWarning, QWidget* parent)
     m_ui->requireAuthGroupBox->setVisible(false);
     m_ui->backendWarningLabel->setVisible(false);
     m_ui->vsModeButton->setVisible(false);
+    m_ui->inputGroupBox->setVisible(false);
+    m_ui->outputGroupBox->setVisible(false);
+
+    m_inputLayout.reset(new QGridLayout(m_ui->inputGroupBox));
+    m_outputLayout.reset(new QGridLayout(m_ui->outputGroupBox));
 
 #ifdef RT_AUDIO
     connect(m_ui->backendComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
@@ -915,6 +921,9 @@ void QJackTrip::start()
             // Append any plugins
             appendPlugins(m_jackTrip.data(), m_ui->channelSendSpinBox->value(),
                           m_ui->channelRecvSpinBox->value());
+            // Setup meters (also currently using faust plugins).
+            createMeters(m_ui->channelSendSpinBox->value(),
+                         (m_ui->channelRecvSpinBox->value()));
 
             QObject::connect(m_jackTrip.data(), &JackTrip::signalProcessesStopped, this,
                              &QJackTrip::processFinished, Qt::QueuedConnection);
@@ -927,6 +936,7 @@ void QJackTrip::start()
                              &QJackTrip::udpWaitingTooLong, Qt::QueuedConnection);
             QObject::connect(m_jackTrip.data(), &JackTrip::signalQueueLengthChanged, this,
                              &QJackTrip::queueLengthChanged, Qt::QueuedConnection);
+
             m_ui->statusBar->showMessage(QStringLiteral("Waiting for Peer..."));
             m_ui->disconnectButton->setEnabled(true);
 #ifdef WAIRTOHUB  // WAIR
@@ -993,6 +1003,36 @@ void QJackTrip::exit()
     }
 }
 
+void QJackTrip::updatedInputMeasurements(const QVector<float> valuesInDb)
+{
+    for (int i = 0; i < m_inputMeters.count(); i++) {
+        // Determine decibel reading
+        qreal dB = m_meterMin;
+        if (i < valuesInDb.size()) {
+            dB = std::max(m_meterMin, valuesInDb.at(i));
+        }
+
+        // Produce a normalized value from 0 to 1
+        float level = (dB - m_meterMin) / (m_meterMax - m_meterMin);
+        m_inputMeters.at(i)->setLevel(level);
+    }
+}
+
+void QJackTrip::updatedOutputMeasurements(const QVector<float> valuesInDb)
+{
+    for (int i = 0; i < m_outputMeters.count(); i++) {
+        // Determine decibel reading
+        qreal dB = m_meterMin;
+        if (i < valuesInDb.size()) {
+            dB = std::max(m_meterMin, valuesInDb.at(i));
+        }
+
+        // Produce a normalized value from 0 to 1
+        float level = (dB - m_meterMin) / (m_meterMax - m_meterMin);
+        m_outputMeters.at(i)->setLevel(level);
+    }
+}
+
 #ifndef NO_VS
 void QJackTrip::virtualStudioMode()
 {
@@ -1013,7 +1053,20 @@ int QJackTrip::findTab(const QString& tabName)
 
 void QJackTrip::enableUi(bool enabled)
 {
-    m_ui->optionsTabWidget->setEnabled(enabled);
+    if (m_ui->typeComboBox->currentIndex() == HUB_SERVER) {
+        m_ui->optionsTabWidget->setEnabled(enabled);
+    } else {
+        if (enabled) {
+            m_ui->inputGroupBox->setVisible(false);
+            m_ui->outputGroupBox->setVisible(false);
+            removeMeters();
+            m_ui->optionsTabWidget->setVisible(true);
+        } else {
+            m_ui->optionsTabWidget->setVisible(false);
+            m_ui->inputGroupBox->setVisible(true);
+            m_ui->outputGroupBox->setVisible(true);
+        }
+    }
     m_ui->typeLabel->setEnabled(enabled);
     m_ui->typeComboBox->setEnabled(enabled);
     m_ui->addressLabel->setEnabled(
@@ -1394,6 +1447,66 @@ void QJackTrip::appendPlugins(JackTrip* jackTrip, int numSendChannels,
     }
 }
 
+void QJackTrip::createMeters(quint32 inputChannels, quint32 outputChannels)
+{
+    // These pointers are also deleted by AudioInterface.
+    Meter* inputMeter  = new Meter(inputChannels);
+    Meter* outputMeter = new Meter(outputChannels);
+    m_jackTrip->appendProcessPluginToNetwork(inputMeter);
+    m_jackTrip->appendProcessPluginFromNetwork(outputMeter);
+
+    // Create our widgets.
+    for (quint32 i = 0; i < inputChannels; i++) {
+        VuMeter* meter = new VuMeter(this);
+        m_inputMeters.append(meter);
+        QLabel* label = new QLabel(QString::number(i + 1), this);
+        m_inputLabels.append(label);
+        label->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
+        m_inputLayout->addWidget(label, i, 0, 1, 1);
+        m_inputLayout->addWidget(meter, i, 1, 1, 1);
+    }
+    // Effectively add a spacer at the bottom.
+    m_inputLayout->setRowStretch(inputChannels, 100);
+
+    for (quint32 i = 0; i < outputChannels; i++) {
+        VuMeter* meter = new VuMeter(this);
+        m_outputMeters.append(meter);
+        QLabel* label = new QLabel(QString::number(i + 1), this);
+        m_outputLabels.append(label);
+        label->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
+        m_outputLayout->addWidget(label, i, 0, 1, 1);
+        m_outputLayout->addWidget(meter, i, 1, 1, 1);
+    }
+    m_outputLayout->setRowStretch(outputChannels, 100);
+
+    QObject::connect(inputMeter, &Meter::onComputedVolumeMeasurements, this,
+                     &QJackTrip::updatedInputMeasurements);
+    QObject::connect(outputMeter, &Meter::onComputedVolumeMeasurements, this,
+                     &QJackTrip::updatedOutputMeasurements);
+}
+
+void QJackTrip::removeMeters()
+{
+    m_inputLayout->setRowStretch(m_inputMeters.count(), 0);
+    m_outputLayout->setRowStretch(m_outputMeters.count(), 0);
+    for (int i = 0; i < m_inputLabels.count(); i++) {
+        delete m_inputLabels.at(i);
+    }
+    for (int i = 0; i < m_inputMeters.count(); i++) {
+        delete m_inputMeters.at(i);
+    }
+    for (int i = 0; i < m_outputLabels.count(); i++) {
+        delete m_outputLabels.at(i);
+    }
+    for (int i = 0; i < m_outputMeters.count(); i++) {
+        delete m_outputMeters.at(i);
+    }
+    m_inputLabels.clear();
+    m_inputMeters.clear();
+    m_outputLabels.clear();
+    m_outputMeters.clear();
+}
+
 QString QJackTrip::commandLineFromCurrentOptions()
 {
     QString commandLine = QStringLiteral("jacktrip");
@@ -1633,23 +1746,33 @@ QString QJackTrip::commandLineFromCurrentOptions()
 #ifdef RT_AUDIO
 void QJackTrip::populateDeviceMenu(QComboBox* menu, bool isInput)
 {
-    RtAudio audio;
     QString previousString = menu->currentText();
     menu->clear();
-    // std::cout << "previousString: " << previousString.toStdString() << std::endl;
     menu->addItem(QStringLiteral("(default)"));
-    unsigned int devices = audio.getDeviceCount();
-    RtAudio::DeviceInfo info;
-    for (unsigned int i = 0; i < devices; i++) {
-        info = audio.getDeviceInfo(i);
-        if (info.probed == true) {
-            if (isInput && info.inputChannels > 0) {
-                menu->addItem(QString::fromStdString(info.name));
-            } else if (!isInput && info.outputChannels > 0) {
-                menu->addItem(QString::fromStdString(info.name));
+
+    std::vector<RtAudio::Api> apis;
+    RtAudio::getCompiledApi(apis);
+
+    for (uint32_t i = 0; i < apis.size(); i++) {
+        RtAudio rtaudio(apis.at(i));
+        unsigned int devices = rtaudio.getDeviceCount();
+        for (unsigned int j = 0; j < devices; j++) {
+            RtAudio::DeviceInfo info = rtaudio.getDeviceInfo(j);
+            if (info.probed == true) {
+                // Don't include duplicate entries
+                if (menu->findText(QString::fromStdString(info.name)) != -1) {
+                    continue;
+                }
+
+                if (isInput && info.inputChannels > 0) {
+                    menu->addItem(QString::fromStdString(info.name));
+                } else if (!isInput && info.outputChannels > 0) {
+                    menu->addItem(QString::fromStdString(info.name));
+                }
             }
         }
     }
+
     // set the previous value
     menu->setCurrentText(previousString);
 }
index 4359b31dc91c929591a7ac4c689fcb2d28cce466..ff0a75eb50d9366c5f1451ec9ccfcd9fba58d05c 100644 (file)
@@ -28,6 +28,7 @@
 
 #include <QByteArray>
 #include <QCloseEvent>
+#include <QGridLayout>
 #include <QLabel>
 #include <QMainWindow>
 #include <QMutex>
@@ -40,6 +41,7 @@
 #include "../JackTrip.h"
 #include "../UdpHubListener.h"
 #include "messageDialog.h"
+#include "vuMeter.h"
 
 #ifdef __APPLE__
 #include "NoNap.h"
@@ -95,6 +97,8 @@ class QJackTrip : public QMainWindow
     void start();
     void stop();
     void exit();
+    void updatedInputMeasurements(const QVector<float> valuesInDb);
+    void updatedOutputMeasurements(const QVector<float> valuesInDb);
 #ifndef NO_VS
     void virtualStudioMode();
 #endif
@@ -115,6 +119,8 @@ class QJackTrip : public QMainWindow
 #endif
 
     void appendPlugins(JackTrip* jackTrip, int numSendChannels, int numRecvChannels);
+    void createMeters(quint32 inputChannels, quint32 outputChannels);
+    void removeMeters();
 
     QString commandLineFromCurrentOptions();
     void showCommandLineMessageBox();
@@ -125,12 +131,22 @@ class QJackTrip : public QMainWindow
     QScopedPointer<QNetworkAccessManager> m_netManager;
     QScopedPointer<MessageDialog> m_statsDialog;
     QScopedPointer<MessageDialog> m_debugDialog;
+    QScopedPointer<QGridLayout> m_inputLayout;
+    QScopedPointer<QGridLayout> m_outputLayout;
     std::ostream m_realCout;
     std::ostream m_realCerr;
     bool m_jackTripRunning;
     bool m_isExiting;
     bool m_exitSent;
 
+    float m_meterMax = 0.0;
+    float m_meterMin = -64.0;
+
+    QList<VuMeter*> m_inputMeters;
+    QList<QLabel*> m_inputLabels;
+    QList<VuMeter*> m_outputMeters;
+    QList<QLabel*> m_outputLabels;
+
     QMutex m_requestMutex;
     QString m_IPv6Address;
     bool m_hasIPv4Reply;
index c34366f23dc6431de6855de8b9e0b5abc7278bc0..9aca57fa00301a3a485eff56b7c579a1397daa4e 100644 (file)
@@ -31,6 +31,7 @@
     <file>ethernet.png</file>
     <file>ohno.png</file>
     <file>headphones.svg</file>
+    <file>Prompt.svg</file>
     <file>network.svg</file>
     <file>jacktrip.png</file>
     <file>jacktrip white.png</file>
index 9ead504e69fa9964407f0a2a83d9c5c21869e5ac..11a8fb28f0d740d7b4ce7faf770328f8f0dc62e8 100644 (file)
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>409</width>
-    <height>913</height>
+    <height>961</height>
    </rect>
   </property>
   <property name="windowTitle">
   </property>
   <widget class="QWidget" name="centralWidget">
    <layout class="QGridLayout" name="gridLayout">
-    <item row="0" column="1">
-     <widget class="QComboBox" name="typeComboBox">
+    <item row="3" column="1">
+     <widget class="QLabel" name="ipLabel">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
       <property name="toolTip">
-       <string>To connect to a p2p (peer to peer) server you need to run as a p2p client.
-To connect to a hub server you need to run as a hub client.</string>
+       <string>If running as a server, this is the address you should supply to the other clients.
+(You will need to enable any port forwarding on your router manually.)</string>
       </property>
-      <property name="currentIndex">
-       <number>2</number>
+      <property name="text">
+       <string>Looking up external IP address...</string>
       </property>
-      <item>
-       <property name="text">
-        <string>P2P Client</string>
-       </property>
-      </item>
-      <item>
-       <property name="text">
-        <string>P2P Server</string>
-       </property>
-      </item>
-      <item>
-       <property name="text">
-        <string>Hub Client</string>
-       </property>
-      </item>
-      <item>
-       <property name="text">
-        <string>Hub Server</string>
-       </property>
-      </item>
      </widget>
     </item>
-    <item row="6" column="0" colspan="2">
+    <item row="8" column="0" colspan="2">
      <layout class="QHBoxLayout" name="buttonLayout">
       <item>
        <widget class="QPushButton" name="connectButton">
@@ -81,36 +67,10 @@ To connect to a hub server you need to run as a hub client.</string>
       </item>
      </layout>
     </item>
-    <item row="3" column="1">
-     <widget class="QLabel" name="ipLabel">
-      <property name="toolTip">
-       <string>If running as a server, this is the address you should supply to the other clients.
-(You will need to enable any port forwarding on your router manually.)</string>
-      </property>
-      <property name="text">
-       <string>Looking up external IP address...</string>
-      </property>
-     </widget>
-    </item>
-    <item row="1" column="1">
-     <widget class="QComboBox" name="addressComboBox">
-      <property name="sizePolicy">
-       <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-        <horstretch>0</horstretch>
-        <verstretch>0</verstretch>
-       </sizepolicy>
-      </property>
-      <property name="toolTip">
-       <string>Enter the IP address or the hostname of the server you want to connect to.</string>
-      </property>
-      <property name="editable">
-       <bool>true</bool>
-      </property>
-      <property name="maxCount">
-       <number>5</number>
-      </property>
-      <property name="insertPolicy">
-       <enum>QComboBox::NoInsert</enum>
+    <item row="6" column="0" colspan="2">
+     <widget class="QGroupBox" name="inputGroupBox">
+      <property name="title">
+       <string>Input Channels</string>
       </property>
      </widget>
     </item>
@@ -252,23 +212,7 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st
         <item row="2" column="0" colspan="2">
          <widget class="QGroupBox" name="channelGroupBox">
           <layout class="QGridLayout" name="gridLayout_9">
-           <item row="3" column="0">
-            <widget class="QLabel" name="channelRecvLabel">
-             <property name="sizePolicy">
-              <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
-               <horstretch>0</horstretch>
-               <verstretch>0</verstretch>
-              </sizepolicy>
-             </property>
-             <property name="text">
-              <string>&amp;Received from network:</string>
-             </property>
-             <property name="buddy">
-              <cstring>channelRecvSpinBox</cstring>
-             </property>
-            </widget>
-           </item>
-           <item row="3" column="1">
+           <item row="4" column="1">
             <widget class="QSpinBox" name="channelRecvSpinBox">
              <property name="sizePolicy">
               <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
@@ -287,26 +231,19 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st
              </property>
             </widget>
            </item>
-           <item row="4" column="1">
-            <widget class="QSpinBox" name="channelSendSpinBox">
-             <property name="toolTip">
-              <string>Number of audio channels to send to the network.</string>
-             </property>
-             <property name="minimum">
-              <number>1</number>
-             </property>
-             <property name="value">
-              <number>2</number>
-             </property>
-            </widget>
-           </item>
            <item row="4" column="0">
-            <widget class="QLabel" name="channelSendLabel">
+            <widget class="QLabel" name="channelRecvLabel">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
              <property name="text">
-              <string>&amp;Sent to network:</string>
+              <string>&amp;Received from network:</string>
              </property>
              <property name="buddy">
-              <cstring>channelSendSpinBox</cstring>
+              <cstring>channelRecvSpinBox</cstring>
              </property>
             </widget>
            </item>
@@ -322,7 +259,30 @@ play from this machine. (Available in client fan out/in and full mix modes.)</st
               <string>&amp;Number of channels</string>
              </property>
              <property name="buddy">
-              <cstring>channelRecvSpinBox</cstring>
+              <cstring>channelSendSpinBox</cstring>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="0">
+            <widget class="QLabel" name="channelSendLabel">
+             <property name="text">
+              <string>&amp;Sent to network:</string>
+             </property>
+             <property name="buddy">
+              <cstring>channelSendSpinBox</cstring>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="1">
+            <widget class="QSpinBox" name="channelSendSpinBox">
+             <property name="toolTip">
+              <string>Number of audio channels to send to the network.</string>
+             </property>
+             <property name="minimum">
+              <number>1</number>
+             </property>
+             <property name="value">
+              <number>2</number>
              </property>
             </widget>
            </item>
@@ -1805,6 +1765,66 @@ and wetness is the essence of beauty.</string>
       </widget>
      </widget>
     </item>
+    <item row="0" column="1">
+     <widget class="QComboBox" name="typeComboBox">
+      <property name="toolTip">
+       <string>To connect to a p2p (peer to peer) server you need to run as a p2p client.
+To connect to a hub server you need to run as a hub client.</string>
+      </property>
+      <property name="currentIndex">
+       <number>2</number>
+      </property>
+      <item>
+       <property name="text">
+        <string>P2P Client</string>
+       </property>
+      </item>
+      <item>
+       <property name="text">
+        <string>P2P Server</string>
+       </property>
+      </item>
+      <item>
+       <property name="text">
+        <string>Hub Client</string>
+       </property>
+      </item>
+      <item>
+       <property name="text">
+        <string>Hub Server</string>
+       </property>
+      </item>
+     </widget>
+    </item>
+    <item row="1" column="1">
+     <widget class="QComboBox" name="addressComboBox">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="toolTip">
+       <string>Enter the IP address or the hostname of the server you want to connect to.</string>
+      </property>
+      <property name="editable">
+       <bool>true</bool>
+      </property>
+      <property name="maxCount">
+       <number>5</number>
+      </property>
+      <property name="insertPolicy">
+       <enum>QComboBox::NoInsert</enum>
+      </property>
+     </widget>
+    </item>
+    <item row="7" column="0" colspan="2">
+     <widget class="QGroupBox" name="outputGroupBox">
+      <property name="title">
+       <string>Output Channels</string>
+      </property>
+     </widget>
+    </item>
    </layout>
   </widget>
   <widget class="QMenuBar" name="menuBar">
@@ -1826,8 +1846,8 @@ and wetness is the essence of beauty.</string>
   <tabstop>disconnectButton</tabstop>
   <tabstop>exitButton</tabstop>
   <tabstop>optionsTabWidget</tabstop>
-  <tabstop>channelRecvSpinBox</tabstop>
   <tabstop>channelSendSpinBox</tabstop>
+  <tabstop>channelRecvSpinBox</tabstop>
   <tabstop>autoPatchComboBox</tabstop>
   <tabstop>patchServerCheckBox</tabstop>
   <tabstop>upmixCheckBox</tabstop>
index 179c85a0338656107e5fbea5fe12d4335cec3e66..5fe08a894b3a5806b36a18a7ad6beec11c0245cc 100644 (file)
@@ -1,7 +1,7 @@
 <RCC>
   <qresource prefix="qjacktrip">
-    <file>about@2x.png</file>
-    <file>about.png</file>
-    <file>icon.png</file>
+    <file alias="about@2x.png">alt/about@2x.png</file>
+    <file alias="about.png">alt/about.png</file>
+    <file alias="icon.png">alt/icon.png</file>
   </qresource>
 </RCC>
index 6b133ace6d5e37ec73d6a95f6e7129836af1abf4..d869d977307a18724d733a10d2f7d536e9508632 100644 (file)
@@ -113,7 +113,7 @@ VirtualStudio::VirtualStudio(bool firstRun, QObject* parent)
     m_inMuted       = settings.value(QStringLiteral("InMuted"), false).toBool();
     m_outMuted      = settings.value(QStringLiteral("OutMuted"), false).toBool();
 #ifdef RT_AUDIO
-    m_useRtAudio     = settings.value(QStringLiteral("Backend"), 0).toInt() == 1;
+    m_useRtAudio     = settings.value(QStringLiteral("Backend"), 1).toInt() == 1;
     m_inputDevice    = settings.value(QStringLiteral("InputDevice"), "").toString();
     m_outputDevice   = settings.value(QStringLiteral("OutputDevice"), "").toString();
     m_bufferSize     = settings.value(QStringLiteral("BufferSize"), 128).toInt();
@@ -166,6 +166,22 @@ VirtualStudio::VirtualStudio(bool firstRun, QObject* parent)
                                                        QVariant::fromValue(m_servers));
     m_view.engine()->rootContext()->setContextProperty(QStringLiteral("audioInterface"),
                                                        m_vsAudioInterface.data());
+    // Add permissions for Mac
+#ifdef __APPLE__
+    m_permissions.reset(new VsMacPermissions());
+    m_view.engine()->rootContext()->setContextProperty(
+        QStringLiteral("permissions"), QVariant::fromValue(m_permissions.data()));
+    if (m_permissions->micPermissionChecked()
+        && m_permissions->micPermission() == "unknown") {
+        m_permissions->getMicPermission();
+    }
+    connect(m_permissions.data(), &VsMacPermissions::micPermissionUpdated, this,
+            &VirtualStudio::startAudio);
+#else
+    m_permissions.reset(new VsPermissions());
+    m_view.engine()->rootContext()->setContextProperty(
+        QStringLiteral("permissions"), QVariant::fromValue(m_permissions.data()));
+#endif
 
     m_view.engine()->rootContext()->setContextProperty(
         QStringLiteral("inputMeterModel"), QVariant::fromValue(QVector<float>()));
@@ -206,6 +222,17 @@ VirtualStudio::VirtualStudio(bool firstRun, QObject* parent)
     // thread
     connect(this, &VirtualStudio::refreshFinished, this, &VirtualStudio::joinStudio,
             Qt::QueuedConnection);
+    connect(
+        this, &VirtualStudio::studioToJoinChanged, this,
+        [&]() {
+            if (!m_studioToJoin.isEmpty()) {
+                // join studio when studio to join changes
+                if (readyToJoin()) {
+                    joinStudio();
+                }
+            }
+        },
+        Qt::QueuedConnection);
 }
 
 void VirtualStudio::setStandardWindow(QSharedPointer<QJackTrip> window)
@@ -285,7 +312,14 @@ int VirtualStudio::inputDevice()
 {
 #ifdef RT_AUDIO
     if (m_useRtAudio) {
-        int index = m_inputDeviceList.indexOf(m_inputDevice);
+        QStringList filteredInputDeviceList;
+        for (int i = 0; i < m_inputDeviceList.size(); i++) {
+            if (m_inputDeviceList.at(i) != "(default)") {
+                filteredInputDeviceList += m_inputDeviceList.at(i);
+            }
+        }
+
+        int index = filteredInputDeviceList.indexOf(m_inputDevice);
         return index >= 0 ? index : 0;
     }
 #endif
@@ -298,7 +332,15 @@ void VirtualStudio::setInputDevice([[maybe_unused]] int device)
         return;
     }
 #ifdef RT_AUDIO
-    m_inputDevice = m_inputDeviceList.at(device);
+    std::cout << "Setting Input Device: " << device << std::endl;
+    QStringList filteredInputDeviceList;
+    for (int i = 0; i < m_inputDeviceList.size(); i++) {
+        if (m_inputDeviceList.at(i) != "(default)") {
+            filteredInputDeviceList += m_inputDeviceList.at(i);
+        }
+    }
+
+    m_inputDevice = filteredInputDeviceList.at(device);
     emit inputDeviceSelected(m_inputDevice);
 #endif
 }
@@ -307,7 +349,14 @@ int VirtualStudio::outputDevice()
 {
 #ifdef RT_AUDIO
     if (m_useRtAudio) {
-        int index = m_outputDeviceList.indexOf(m_outputDevice);
+        QStringList filteredOutputDeviceList;
+        for (int i = 0; i < m_outputDeviceList.size(); i++) {
+            if (m_outputDeviceList.at(i) != "(default)") {
+                filteredOutputDeviceList += m_outputDeviceList.at(i);
+            }
+        }
+
+        int index = filteredOutputDeviceList.indexOf(m_outputDevice);
         return index >= 0 ? index : 0;
     }
 #endif
@@ -320,11 +369,38 @@ void VirtualStudio::setOutputDevice([[maybe_unused]] int device)
         return;
     }
 #ifdef RT_AUDIO
-    m_outputDevice = m_outputDeviceList.at(device);
+    QStringList filteredOutputDeviceList;
+    for (int i = 0; i < m_outputDeviceList.size(); i++) {
+        if (m_outputDeviceList.at(i) != "(default)") {
+            filteredOutputDeviceList += m_outputDeviceList.at(i);
+        }
+    }
+
+    m_outputDevice = filteredOutputDeviceList.at(device);
     emit outputDeviceSelected(m_outputDevice);
 #endif
 }
 
+QString VirtualStudio::devicesWarning()
+{
+    return m_devicesWarningMsg;
+}
+
+QString VirtualStudio::devicesError()
+{
+    return m_devicesErrorMsg;
+}
+
+QString VirtualStudio::devicesWarningHelpUrl()
+{
+    return m_devicesWarningHelpUrl;
+}
+
+QString VirtualStudio::devicesErrorHelpUrl()
+{
+    return m_devicesErrorHelpUrl;
+}
+
 float VirtualStudio::inputVolume()
 {
     return m_inMultiplier;
@@ -511,6 +587,17 @@ void VirtualStudio::setShowDeviceSetup(bool show)
     m_showDeviceSetup = show;
 }
 
+QString VirtualStudio::windowState()
+{
+    return m_windowState;
+}
+
+void VirtualStudio::setWindowState(QString state)
+{
+    m_windowState = state;
+    emit windowStateUpdated();
+}
+
 bool VirtualStudio::showWarnings()
 {
     return m_showWarnings;
@@ -528,9 +615,8 @@ void VirtualStudio::setShowWarnings(bool show)
     if (!m_studioToJoin.isEmpty()) {
         // device setup view proceeds warning view
         // if device setup is shown, do not immediately join
-        if (!m_showDeviceSetup) {
+        if (readyToJoin()) {
             // We're done waiting to be on the browse page
-            m_shouldJoin = true;
             joinStudio();
         }
     }
@@ -596,6 +682,7 @@ QUrl VirtualStudio::studioToJoin()
 void VirtualStudio::setStudioToJoin(const QUrl& url)
 {
     m_studioToJoin = url;
+    emit studioToJoinChanged();
 }
 
 bool VirtualStudio::noUpdater()
@@ -621,16 +708,6 @@ QString VirtualStudio::failedMessage()
     return m_failedMessage;
 }
 
-bool VirtualStudio::shouldJoin()
-{
-    return m_shouldJoin;
-}
-
-void VirtualStudio::setShouldJoin(bool join)
-{
-    m_shouldJoin = join;
-}
-
 void VirtualStudio::joinStudio()
 {
     if (!m_authenticated || m_studioToJoin.isEmpty() || m_servers.isEmpty()) {
@@ -643,12 +720,6 @@ void VirtualStudio::joinStudio()
         return;
     }
 
-    if (!m_shouldJoin) {
-        // Not time to join yet.
-        // Waiting until joinStudio is called and m_shouldJoin is true.
-        return;
-    }
-
     QString scheme = m_studioToJoin.scheme();
     QString path   = m_studioToJoin.path();
     QString url    = m_studioToJoin.toString();
@@ -760,12 +831,18 @@ void VirtualStudio::refreshStudios(int index, bool signalRefresh)
 void VirtualStudio::refreshDevices()
 {
 #ifdef RT_AUDIO
-    getDeviceList(&m_inputDeviceList, true);
-    getDeviceList(&m_outputDeviceList, false);
-    m_view.engine()->rootContext()->setContextProperty(
-        QStringLiteral("inputComboModel"), QVariant::fromValue(m_inputDeviceList));
-    m_view.engine()->rootContext()->setContextProperty(
-        QStringLiteral("outputComboModel"), QVariant::fromValue(m_outputDeviceList));
+    RtAudioInterface::getDeviceList(&m_inputDeviceList, &m_inputDeviceCategories, true);
+    RtAudioInterface::getDeviceList(&m_outputDeviceList, &m_outputDeviceCategories,
+                                    false);
+
+    QVariant inputComboModel =
+        formatDeviceList(m_inputDeviceList, m_inputDeviceCategories);
+    QVariant outputComboModel =
+        formatDeviceList(m_outputDeviceList, m_outputDeviceCategories);
+    m_view.engine()->rootContext()->setContextProperty(QStringLiteral("inputComboModel"),
+                                                       inputComboModel);
+    m_view.engine()->rootContext()->setContextProperty(QStringLiteral("outputComboModel"),
+                                                       outputComboModel);
 
     // Make sure we keep our current settings if the device still exists
     if (!m_inputDeviceList.contains(m_inputDevice)) {
@@ -833,7 +910,6 @@ void VirtualStudio::applySettings()
     // which can display upon opening the app from join link
     if (!m_studioToJoin.isEmpty()) {
         // We're done waiting to be on the browse page
-        m_shouldJoin = true;
         joinStudio();
     }
 }
@@ -1079,6 +1155,9 @@ void VirtualStudio::disconnect()
 
         m_vsAudioInterface->startProcess();
     }
+
+    m_connectionState = QStringLiteral("Disconnected");
+    emit connectionStateChanged();
 }
 
 void VirtualStudio::manageStudio(int studioIndex)
@@ -1111,6 +1190,12 @@ void VirtualStudio::showAbout()
     about.exec();
 }
 
+void VirtualStudio::openLink(const QString& link)
+{
+    QUrl url = QUrl(link);
+    QDesktopServices::openUrl(url);
+}
+
 void VirtualStudio::exit()
 {
     m_refreshTimer.stop();
@@ -1149,42 +1234,13 @@ void VirtualStudio::slotAuthSucceded()
     m_device = new VsDevice(m_authenticator.data(), m_testMode);
     m_device->registerApp();
 
-    if (m_vsAudioInterface.isNull()) {
-        m_vsAudioInterface.reset(new VsAudioInterface());
-        m_view.engine()->rootContext()->setContextProperty(
-            QStringLiteral("audioInterface"), m_vsAudioInterface.data());
+#ifdef __APPLE__
+    if (m_permissions->micPermission() == "granted") {
+        startAudio();
     }
-#ifdef RT_AUDIO
-    m_vsAudioInterface->setInputDevice(m_inputDevice);
-    m_vsAudioInterface->setOutputDevice(m_outputDevice);
-    m_vsAudioInterface->setAudioInterfaceMode(m_useRtAudio);
+#else
+    startAudio();
 #endif
-    m_vsAudioInterface->setupAudio();
-
-    connect(this, &VirtualStudio::inputDeviceChanged, m_vsAudioInterface.data(),
-            &VsAudioInterface::setInputDevice);
-    connect(this, &VirtualStudio::inputDeviceSelected, m_vsAudioInterface.data(),
-            &VsAudioInterface::setInputDevice);
-    connect(this, &VirtualStudio::outputDeviceChanged, m_vsAudioInterface.data(),
-            &VsAudioInterface::setOutputDevice);
-    connect(this, &VirtualStudio::outputDeviceSelected, m_vsAudioInterface.data(),
-            &VsAudioInterface::setOutputDevice);
-    connect(this, &VirtualStudio::audioBackendChanged, m_vsAudioInterface.data(),
-            &VsAudioInterface::setAudioInterfaceMode);
-    connect(this, &VirtualStudio::triggerPlayOutputAudio, m_vsAudioInterface.data(),
-            &VsAudioInterface::triggerPlayback);
-    connect(m_vsAudioInterface.data(), &VsAudioInterface::newVolumeMeterMeasurements,
-            this, &VirtualStudio::updatedInputVuMeasurements);
-    connect(m_vsAudioInterface.data(), &VsAudioInterface::errorToProcess, this,
-            &VirtualStudio::processError);
-
-    m_vsAudioInterface->setupPlugins();
-
-    m_view.engine()->rootContext()->setContextProperty(
-        QStringLiteral("inputMeterModel"),
-        QVariant::fromValue(QVector<float>(m_vsAudioInterface->getNumInputChannels())));
-
-    m_vsAudioInterface->startProcess();
 
     if (m_userId.isEmpty()) {
         getUserId();
@@ -1203,8 +1259,8 @@ void VirtualStudio::slotAuthSucceded()
     if (!m_studioToJoin.isEmpty()) {
         // FTUX shows warnings and device setup views
         // if any of these enabled, do not immediately join
-        if (!m_showDeviceSetup) {
-            // Don't need to set m_shouldJoin because it's default true
+        if (readyToJoin()) {
+            // We should join in this case
             joinStudio();
         }
     }
@@ -1361,6 +1417,34 @@ void VirtualStudio::updatedStats(const QJsonObject& stats)
     return;
 }
 
+void VirtualStudio::updatedDevicesErrorMsg(const QString& msg)
+{
+    m_devicesErrorMsg = msg;
+    emit devicesErrorChanged();
+    return;
+}
+
+void VirtualStudio::updatedDevicesWarningMsg(const QString& msg)
+{
+    m_devicesWarningMsg = msg;
+    emit devicesWarningChanged();
+    return;
+}
+
+void VirtualStudio::updatedDevicesErrorHelpUrl(const QString& url)
+{
+    m_devicesErrorHelpUrl = url;
+    emit devicesErrorHelpUrlChanged();
+    return;
+}
+
+void VirtualStudio::updatedDevicesWarningHelpUrl(const QString& url)
+{
+    m_devicesWarningHelpUrl = url;
+    emit devicesWarningHelpUrlChanged();
+    return;
+}
+
 void VirtualStudio::updatedInputVuMeasurements(const QVector<float>& valuesInDecibels)
 {
     QJsonArray uiValues;
@@ -1764,27 +1848,58 @@ void VirtualStudio::getUserMetadata()
     });
 }
 
-#ifdef RT_AUDIO
-void VirtualStudio::getDeviceList(QStringList* list, bool isInput)
-{
-    RtAudio audio;
-    list->clear();
-    list->append(QStringLiteral("(default)"));
-
-    unsigned int devices = audio.getDeviceCount();
-    RtAudio::DeviceInfo info;
-    for (unsigned int i = 0; i < devices; i++) {
-        info = audio.getDeviceInfo(i);
-        if (info.probed == true) {
-            if (isInput && info.inputChannels > 0) {
-                list->append(QString::fromStdString(info.name));
-            } else if (!isInput && info.outputChannels > 0) {
-                list->append(QString::fromStdString(info.name));
-            }
-        }
+void VirtualStudio::startAudio()
+{
+#ifdef __APPLE__
+    if (m_permissions->micPermission() != "granted") {
+        return;
     }
-}
 #endif
+    if (m_vsAudioInterface.isNull()) {
+        m_vsAudioInterface.reset(new VsAudioInterface());
+        m_view.engine()->rootContext()->setContextProperty(
+            QStringLiteral("audioInterface"), m_vsAudioInterface.data());
+    }
+#ifdef RT_AUDIO
+    m_vsAudioInterface->setInputDevice(m_inputDevice);
+    m_vsAudioInterface->setOutputDevice(m_outputDevice);
+    m_vsAudioInterface->setAudioInterfaceMode(m_useRtAudio);
+#endif
+    connect(m_vsAudioInterface.data(), &VsAudioInterface::devicesErrorMsgChanged, this,
+            &VirtualStudio::updatedDevicesErrorMsg);
+    connect(m_vsAudioInterface.data(), &VsAudioInterface::devicesWarningMsgChanged, this,
+            &VirtualStudio::updatedDevicesWarningMsg);
+    connect(m_vsAudioInterface.data(), &VsAudioInterface::devicesErrorHelpUrlChanged,
+            this, &VirtualStudio::updatedDevicesErrorHelpUrl);
+    connect(m_vsAudioInterface.data(), &VsAudioInterface::devicesWarningHelpUrlChanged,
+            this, &VirtualStudio::updatedDevicesWarningHelpUrl);
+    m_vsAudioInterface->setupAudio();
+
+    connect(this, &VirtualStudio::inputDeviceChanged, m_vsAudioInterface.data(),
+            &VsAudioInterface::setInputDevice);
+    connect(this, &VirtualStudio::inputDeviceSelected, m_vsAudioInterface.data(),
+            &VsAudioInterface::setInputDevice);
+    connect(this, &VirtualStudio::outputDeviceChanged, m_vsAudioInterface.data(),
+            &VsAudioInterface::setOutputDevice);
+    connect(this, &VirtualStudio::outputDeviceSelected, m_vsAudioInterface.data(),
+            &VsAudioInterface::setOutputDevice);
+    connect(this, &VirtualStudio::audioBackendChanged, m_vsAudioInterface.data(),
+            &VsAudioInterface::setAudioInterfaceMode);
+    connect(this, &VirtualStudio::triggerPlayOutputAudio, m_vsAudioInterface.data(),
+            &VsAudioInterface::triggerPlayback);
+    connect(m_vsAudioInterface.data(), &VsAudioInterface::newVolumeMeterMeasurements,
+            this, &VirtualStudio::updatedInputVuMeasurements);
+    connect(m_vsAudioInterface.data(), &VsAudioInterface::errorToProcess, this,
+            &VirtualStudio::processError);
+
+    m_vsAudioInterface->setupPlugins();
+
+    m_view.engine()->rootContext()->setContextProperty(
+        QStringLiteral("inputMeterModel"),
+        QVariant::fromValue(QVector<float>(m_vsAudioInterface->getNumInputChannels())));
+
+    m_vsAudioInterface->startProcess();
+}
 
 void VirtualStudio::stopStudio()
 {
@@ -1807,6 +1922,64 @@ void VirtualStudio::stopStudio()
     });
 }
 
+bool VirtualStudio::readyToJoin()
+{
+    return m_windowState == "browse"
+           && (m_connectionState == QStringLiteral("Waiting")
+               || m_connectionState == QStringLiteral("Disconnected"));
+}
+
+#ifdef RT_AUDIO
+QVariant VirtualStudio::formatDeviceList(const QStringList& devices,
+                                         const QStringList& categories)
+{
+    QStringList filteredDevices;
+    QStringList filteredCategories;
+
+    for (int i = 0; i < devices.size(); i++) {
+        if (!devices[i].contains("(default)")) {
+            filteredDevices += devices[i];
+            filteredCategories += categories[i];
+        }
+    }
+
+    QStringList uniqueCategories = QStringList(filteredCategories);
+    uniqueCategories.removeDuplicates();
+
+    bool containsCategories = true;
+    if (uniqueCategories.size() == 0) {
+        containsCategories = false;
+    } else if (uniqueCategories.size() == 1 && uniqueCategories.at(0) == "") {
+        containsCategories = false;
+    }
+
+    QVariantList items = QVariantList();
+    for (int i = 0; i < uniqueCategories.size(); i++) {
+        QString category = uniqueCategories.at(i);
+
+        if (containsCategories) {
+            QJsonObject header = QJsonObject();
+            header.insert(QString::fromStdString("text"), uniqueCategories.at(i));
+            header.insert(QString::fromStdString("type"),
+                          QString::fromStdString("header"));
+            items.push_back(QVariant(QJsonValue(header)));
+        }
+
+        for (int j = 0; j < filteredDevices.size(); j++) {
+            if (filteredCategories.at(j).toStdString() == category.toStdString()) {
+                QJsonObject element = QJsonObject();
+                element.insert(QString::fromStdString("text"), filteredDevices.at(j));
+                element.insert(QString::fromStdString("type"),
+                               QString::fromStdString("element"));
+                items.push_back(QVariant(QJsonValue(element)));
+            }
+        }
+    }
+
+    return QVariant(items);
+}
+#endif
+
 VirtualStudio::~VirtualStudio()
 {
     for (int i = 0; i < m_servers.count(); i++) {
index 693b9110c593d2793d626446eb2792644e45530f..0866c2d67d59d485f03074edd417cb1dc4b510ce 100644 (file)
@@ -60,6 +60,9 @@
 
 #ifdef __APPLE__
 #include "NoNap.h"
+#include "vsMacPermissions.h"
+#else
+#include "vsPermissions.h"
 #endif
 
 class QJackTrip;
@@ -78,6 +81,14 @@ class VirtualStudio : public QObject
         int inputDevice READ inputDevice WRITE setInputDevice NOTIFY inputDeviceChanged)
     Q_PROPERTY(int outputDevice READ outputDevice WRITE setOutputDevice NOTIFY
                    outputDeviceChanged)
+
+    Q_PROPERTY(QString devicesWarning READ devicesWarning NOTIFY devicesWarningChanged)
+    Q_PROPERTY(QString devicesError READ devicesError NOTIFY devicesErrorChanged)
+    Q_PROPERTY(QString devicesWarningHelpUrl READ devicesWarningHelpUrl NOTIFY
+                   devicesWarningHelpUrlChanged)
+    Q_PROPERTY(QString devicesErrorHelpUrl READ devicesErrorHelpUrl NOTIFY
+                   devicesErrorHelpUrlChanged)
+
     Q_PROPERTY(
         int bufferSize READ bufferSize WRITE setBufferSize NOTIFY bufferSizeChanged)
     Q_PROPERTY(int bufferStrategy READ bufferStrategy WRITE setBufferStrategy NOTIFY
@@ -107,14 +118,14 @@ class VirtualStudio : public QObject
     Q_PROPERTY(bool noUpdater READ noUpdater CONSTANT)
     Q_PROPERTY(bool psiBuild READ psiBuild CONSTANT)
     Q_PROPERTY(QString failedMessage READ failedMessage NOTIFY failedMessageChanged)
-    Q_PROPERTY(
-        bool shouldJoin READ shouldJoin WRITE setShouldJoin NOTIFY shouldJoinChanged)
     Q_PROPERTY(
         float inputVolume READ inputVolume WRITE setInputVolume NOTIFY updatedInputVolume)
     Q_PROPERTY(float outputVolume READ outputVolume WRITE setOutputVolume NOTIFY
                    updatedOutputVolume)
     Q_PROPERTY(
         bool inputMuted READ inputMuted WRITE setInputMuted NOTIFY updatedInputMuted)
+    Q_PROPERTY(QString windowState READ windowState WRITE setWindowState NOTIFY
+                   windowStateUpdated)
 
    public:
     explicit VirtualStudio(bool firstRun = false, QObject* parent = nullptr);
@@ -135,6 +146,10 @@ class VirtualStudio : public QObject
     void setInputDevice(int device);
     int outputDevice();
     void setOutputDevice(int device);
+    QString devicesWarning();
+    QString devicesError();
+    QString devicesWarningHelpUrl();
+    QString devicesErrorHelpUrl();
     int bufferSize();
     void setBufferSize(int index);
     int bufferStrategy();
@@ -170,12 +185,11 @@ class VirtualStudio : public QObject
     bool noUpdater();
     bool psiBuild();
     QString failedMessage();
-    bool shouldJoin();
-    void setShouldJoin(bool join);
     float inputVolume();
     float outputVolume();
     bool inputMuted();
     bool outputMuted();
+    QString windowState();
 
    public slots:
     void toStandard();
@@ -194,12 +208,14 @@ class VirtualStudio : public QObject
     void createStudio();
     void editProfile();
     void showAbout();
+    void openLink(const QString& url);
     void updatedInputVuMeasurements(const QVector<float>& valuesInDecibels);
     void updatedOutputVuMeasurements(const QVector<float>& valuesInDecibels);
     void setInputVolume(float multiplier);
     void setOutputVolume(float multiplier);
     void setInputMuted(bool muted);
     void setOutputMuted(bool muted);
+    void setWindowState(QString state);
     void exit();
 
    signals:
@@ -217,6 +233,10 @@ class VirtualStudio : public QObject
     void outputDeviceChanged(QString device);
     void inputDeviceSelected(QString device);
     void outputDeviceSelected(QString device);
+    void devicesWarningChanged();
+    void devicesErrorChanged();
+    void devicesWarningHelpUrlChanged();
+    void devicesErrorHelpUrlChanged();
     void triggerPlayOutputAudio();
     void bufferSizeChanged();
     void bufferStrategyChanged();
@@ -238,11 +258,12 @@ class VirtualStudio : public QObject
     void signalExit();
     void periodicRefresh();
     void failedMessageChanged();
-    void shouldJoinChanged();
+    void studioToJoinChanged();
     void updatedInputVolume(float multiplier);
     void updatedOutputVolume(float multiplier);
     void updatedInputMuted(bool muted);
     void updatedOutputMuted(bool muted);
+    void windowStateUpdated();
 
    private slots:
     void slotAuthSucceded();
@@ -255,6 +276,11 @@ class VirtualStudio : public QObject
     void launchBrowser(const QUrl& url);
     void joinStudio();
     void updatedStats(const QJsonObject& stats);
+    void startAudio();
+    void updatedDevicesErrorMsg(const QString& msg);
+    void updatedDevicesWarningMsg(const QString& msg);
+    void updatedDevicesErrorHelpUrl(const QString& url);
+    void updatedDevicesWarningHelpUrl(const QString& url);
 
    private:
     void setupAuthenticator();
@@ -266,14 +292,14 @@ class VirtualStudio : public QObject
     void getSubscriptions();
     void getRegions();
     void getUserMetadata();
+    void stopStudio();
+    bool readyToJoin();
 #ifdef RT_AUDIO
-    void getDeviceList(QStringList* list, bool isInput);
+    QVariant formatDeviceList(const QStringList& devices, const QStringList& categories);
 #endif
-    void stopStudio();
 
     bool m_showFirstRun = false;
     bool m_checkSsl     = true;
-    bool m_shouldJoin   = true;
     QString m_updateChannel;
     QString m_refreshToken;
     QString m_userId;
@@ -334,6 +360,12 @@ class VirtualStudio : public QObject
     QTimer m_inputClipTimer;
     QTimer m_outputClipTimer;
 
+    QString m_devicesWarningMsg     = QStringLiteral("");
+    QString m_devicesErrorMsg       = QStringLiteral("");
+    QString m_devicesWarningHelpUrl = QStringLiteral("");
+    QString m_devicesErrorHelpUrl   = QStringLiteral("");
+    QString m_windowState           = QStringLiteral("login");
+
     float m_meterMax = 0.0;
     float m_meterMin = -64.0;
 
@@ -347,6 +379,8 @@ class VirtualStudio : public QObject
 #ifdef RT_AUDIO
     QStringList m_inputDeviceList;
     QStringList m_outputDeviceList;
+    QStringList m_inputDeviceCategories;
+    QStringList m_outputDeviceCategories;
     QString m_inputDevice;
     QString m_outputDevice;
     quint16 m_bufferSize;
@@ -371,6 +405,8 @@ class VirtualStudio : public QObject
 #ifdef __APPLE__
     NoNap m_noNap;
 #endif
+
+    QSharedPointer<VsPermissions> m_permissions;
 };
 
 #endif  // VIRTUALSTUDIO_H
index fec620bca7e8af917fbd840a42d1e78a7a4c9751..b694d5669baa62040e3a7473889569b792260eae 100644 (file)
@@ -8,7 +8,7 @@ Rectangle {
     width: 696
     height: 577
     color: backgroundColour
-    state: virtualstudio.showFirstRun ? "start" : "login"
+    state: virtualstudio.showFirstRun ? "start" : virtualstudio.windowState
     anchors.fill: parent
 
     id: window
@@ -127,24 +127,22 @@ Rectangle {
         target: virtualstudio
         onAuthSucceeded: { 
             if (virtualstudio.showDeviceSetup) {
-                virtualstudio.shouldJoin = false;
-                window.state = "setup";
+                virtualstudio.windowState = "setup";
             } else {
-                virtualstudio.shouldJoin = true;
-                window.state = "browse";
+                virtualstudio.windowState = "browse";
             }
         }
         onAuthFailed: {
             loginScreen.failTextVisible = true;
         }
         onConnected: {
-            window.state = "connected";
+            virtualstudio.windowState = "connected";
         }
         onFailed: {
-            window.state = "failed";
+            virtualstudio.windowState = "failed";
         }
         onDisconnected: {
-            window.state = "browse";
+            virtualstudio.windowState = "browse";
         }
     }
 }
index b21df2dc1666d99ec77a7a5bd0845afd69bece69..df0c7a7600ec732a7daa4c2b4941ec110677b318 100644 (file)
@@ -111,7 +111,23 @@ void VsAudioInterface::setupAudio()
             if (gVerboseFlag)
                 std::cout << "  JackTrip:setupAudio before m_audioInterface->setup"
                           << std::endl;
-            m_audioInterface->setup();
+            m_audioInterface->setup(true);
+
+            std::string devicesWarningMsg = m_audioInterface->getDevicesWarningMsg();
+            std::string devicesErrorMsg   = m_audioInterface->getDevicesErrorMsg();
+
+            if (devicesWarningMsg != "") {
+                qDebug() << "Devices Warning: "
+                         << QString::fromStdString(devicesWarningMsg);
+            }
+
+            if (devicesErrorMsg != "") {
+                qDebug() << "Devices Error: " << QString::fromStdString(devicesErrorMsg);
+            }
+
+            updateDevicesWarningMsg(QString::fromStdString(devicesWarningMsg));
+            updateDevicesErrorMsg(QString::fromStdString(devicesErrorMsg));
+
             if (gVerboseFlag)
                 std::cout
                     << "  JackTrip:setupAudio before m_audioInterface->getSampleRate"
@@ -138,12 +154,29 @@ void VsAudioInterface::setupAudio()
             m_audioInterface->setInputDevice(m_inputDeviceName);
             m_audioInterface->setOutputDevice(m_outputDeviceName);
             m_audioInterface->setBufferSizeInSamples(m_audioBufferSize);
-            m_audioInterface->setup();
+
+            m_audioInterface->setup(true);
             // Setup might have reduced number of channels
             m_numAudioChansIn  = m_audioInterface->getNumInputChannels();
             m_numAudioChansOut = m_audioInterface->getNumOutputChannels();
             // Setup might have changed buffer size
             m_audioBufferSize = m_audioInterface->getBufferSizeInSamples();
+
+            std::string devicesWarningMsg = m_audioInterface->getDevicesWarningMsg();
+            std::string devicesErrorMsg   = m_audioInterface->getDevicesErrorMsg();
+
+            if (devicesWarningMsg != "") {
+                qDebug() << "Devices Warning: "
+                         << QString::fromStdString(devicesWarningMsg);
+            }
+
+            if (devicesErrorMsg != "") {
+                qDebug() << "Devices Error: " << QString::fromStdString(devicesErrorMsg);
+            }
+
+            updateDevicesWarningMsg(QString::fromStdString(devicesWarningMsg));
+            updateDevicesErrorMsg(QString::fromStdString(devicesErrorMsg));
+
 #endif
 #endif
         } else if (m_audioInterfaceMode == VsAudioInterface::RTAUDIO) {
@@ -155,12 +188,41 @@ void VsAudioInterface::setupAudio()
             m_audioInterface->setInputDevice(m_inputDeviceName);
             m_audioInterface->setOutputDevice(m_outputDeviceName);
             m_audioInterface->setBufferSizeInSamples(m_audioBufferSize);
-            m_audioInterface->setup();
+
+            m_audioInterface->setup(true);
             // Setup might have reduced number of channels
             m_numAudioChansIn  = m_audioInterface->getNumInputChannels();
             m_numAudioChansOut = m_audioInterface->getNumOutputChannels();
             // Setup might have changed buffer size
             m_audioBufferSize = m_audioInterface->getBufferSizeInSamples();
+
+            std::string devicesWarningMsg = m_audioInterface->getDevicesWarningMsg();
+            std::string devicesErrorMsg   = m_audioInterface->getDevicesErrorMsg();
+            std::string devicesWarningHelpUrl =
+                m_audioInterface->getDevicesWarningHelpUrl();
+            std::string devicesErrorHelpUrl = m_audioInterface->getDevicesErrorHelpUrl();
+
+            if (devicesWarningMsg != "") {
+                qDebug() << "Devices Warning: "
+                         << QString::fromStdString(devicesWarningMsg);
+                if (devicesWarningHelpUrl != "") {
+                    qDebug() << "Learn More: "
+                             << QString::fromStdString(devicesWarningHelpUrl);
+                }
+            }
+
+            if (devicesErrorMsg != "") {
+                qDebug() << "Devices Error: " << QString::fromStdString(devicesErrorMsg);
+                if (devicesErrorHelpUrl != "") {
+                    qDebug() << "Learn More: "
+                             << QString::fromStdString(devicesErrorHelpUrl);
+                }
+            }
+
+            updateDevicesWarningMsg(QString::fromStdString(devicesWarningMsg));
+            updateDevicesErrorMsg(QString::fromStdString(devicesErrorMsg));
+            updateDevicesWarningHelpUrl(QString::fromStdString(devicesWarningHelpUrl));
+            updateDevicesErrorHelpUrl(QString::fromStdString(devicesErrorHelpUrl));
 #endif
         }
 
@@ -309,7 +371,7 @@ void VsAudioInterface::startProcess()
 {
     if (!m_audioInterface.isNull() && !m_audioActive) {
         try {
-            m_audioInterface->initPlugins();
+            m_audioInterface->initPlugins(false);
             m_audioInterface->startProcess();
             if (m_audioInterfaceMode == VsAudioInterface::JACK) {
                 m_audioInterface->connectDefaultPorts();
@@ -381,3 +443,27 @@ void VsAudioInterface::setOutputMuted(bool muted)
     settings.endGroup();
     emit updatedOutputMuted(muted);
 }
+
+void VsAudioInterface::updateDevicesErrorMsg(const QString& msg)
+{
+    emit devicesErrorMsgChanged(msg);
+    return;
+}
+
+void VsAudioInterface::updateDevicesWarningMsg(const QString& msg)
+{
+    emit devicesWarningMsgChanged(msg);
+    return;
+}
+
+void VsAudioInterface::updateDevicesWarningHelpUrl(const QString& url)
+{
+    emit devicesWarningHelpUrlChanged(url);
+    return;
+}
+
+void VsAudioInterface::updateDevicesErrorHelpUrl(const QString& url)
+{
+    emit devicesErrorHelpUrlChanged(url);
+    return;
+}
index 507bd16560809e73fb59e02770bda7796f8e12a1..f4660d14b6ca7787e14b14404451befb6d1d425c 100644 (file)
@@ -109,6 +109,10 @@ class VsAudioInterface : public QObject
     void modeUpdated();
     void newVolumeMeterMeasurements(QVector<float> values);
     void errorToProcess(const QString& errorMessage);
+    void devicesErrorMsgChanged(const QString& msg);
+    void devicesWarningMsgChanged(const QString& msg);
+    void devicesErrorHelpUrlChanged(const QString& url);
+    void devicesWarningHelpUrlChanged(const QString& url);
 
    private slots:
     // void refreshAudioStream();
@@ -138,6 +142,11 @@ class VsAudioInterface : public QObject
     Volume* m_inputVolumePlugin;
     Volume* m_outputVolumePlugin;
     Tone* m_outputTonePlugin;
+
+    void updateDevicesErrorMsg(const QString& msg);
+    void updateDevicesWarningMsg(const QString& msg);
+    void updateDevicesErrorHelpUrl(const QString& url);
+    void updateDevicesWarningHelpUrl(const QString& url);
 };
 
 #endif  // VSDAUDIOINTERFACE_H
index 9b39725b787f46498ed01adcd49e6c1d302e9d66..3bc63b5844e33493fb152aad6d56eeff98d82c36 100644 (file)
@@ -69,7 +69,7 @@ VsDevice::VsDevice(QOAuth2AuthorizationCodeFlow* authenticator, bool testMode,
 
     // Set server levels to stored versions
     QJsonObject json = {
-        {QLatin1String("captureVolume"), m_captureVolume * 100.0},
+        {QLatin1String("captureVolume"), (int)(m_captureVolume * 100.0)},
         {QLatin1String("captureMute"), m_captureMute},
     };
     QJsonDocument request = QJsonDocument(json);
@@ -320,7 +320,7 @@ void VsDevice::sendLevels()
 {
     // Add latest volume and mute values to heartbeat body
     QJsonObject json = {
-        {QLatin1String("captureVolume"), (int)(m_captureVolume * 100)},
+        {QLatin1String("captureVolume"), (int)(m_captureVolume * 100.0)},
         {QLatin1String("captureMute"), m_captureMute},
     };
     QJsonDocument request = QJsonDocument(json);
diff --git a/src/gui/vsMacPermissions.h b/src/gui/vsMacPermissions.h
new file mode 100644 (file)
index 0000000..1360fa5
--- /dev/null
@@ -0,0 +1,64 @@
+//*****************************************************************
+/*
+  JackTrip: A System for High-Quality Audio Network Performance
+  over the Internet
+
+  Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe.
+  SoundWIRE group at CCRMA, Stanford University.
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation
+  files (the "Software"), to deal in the Software without
+  restriction, including without limitation the rights to use,
+  copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the
+  Software is furnished to do so, subject to the following
+  conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. 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.
+*/
+//*****************************************************************
+/**
+ * \file vsMacPermissions.h
+ * \author Matt Horton
+ * \date Oct 2022
+ */
+
+#ifndef __VSMACPERMISSIONS_H__
+#define __VSMACPERMISSIONS_H__
+
+#include <objc/objc.h>
+
+#include <QDebug>
+#include <QObject>
+#include <QString>
+
+#include "vsPermissions.h"
+
+class VsMacPermissions : public VsPermissions
+{
+    Q_OBJECT
+
+   public:
+    explicit VsMacPermissions();
+
+    bool micPermissionChecked() override;
+    Q_INVOKABLE void getMicPermission() override;
+    Q_INVOKABLE void openSystemPrivacy();
+
+   private:
+    QString m_micPermission     = "unknown";
+    bool m_micPermissionChecked = false;
+};
+
+#endif  // __VSMACPERMISSIONS_H__
diff --git a/src/gui/vsMacPermissions.mm b/src/gui/vsMacPermissions.mm
new file mode 100644 (file)
index 0000000..a29c4ba
--- /dev/null
@@ -0,0 +1,104 @@
+//*****************************************************************
+/*
+  JackTrip: A System for High-Quality Audio Network Performance
+  over the Internet
+
+  Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe.
+  SoundWIRE group at CCRMA, Stanford University.
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation
+  files (the "Software"), to deal in the Software without
+  restriction, including without limitation the rights to use,
+  copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the
+  Software is furnished to do so, subject to the following
+  conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. 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.
+*/
+//*****************************************************************
+/**
+ * \file vsMacPermissions.mm
+ * \author Matt Horton
+ * \date Oct 2022
+ */
+
+#include "vsMacPermissions.h"
+#include <Foundation/Foundation.h>
+#include <AVFoundation/AVFoundation.h>
+#include <QDesktopServices>
+#include <QSettings>
+#include <QUrl>
+
+VsMacPermissions::VsMacPermissions()
+{
+    QSettings settings;
+    settings.beginGroup(QStringLiteral("VirtualStudio"));
+    m_micPermissionChecked = settings.value(QStringLiteral("MicPermissionChecked"), false).toBool();
+    settings.endGroup();
+}
+
+bool VsMacPermissions::micPermissionChecked()
+{
+    if (m_micPermissionChecked) {
+        getMicPermission();
+    }
+    return m_micPermissionChecked;
+}
+
+void VsMacPermissions::getMicPermission()
+{
+    if (@available(macOS 10.14, *)) {
+        // Request permission to access.
+        switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio])
+        {
+            case AVAuthorizationStatusAuthorized:
+            {
+                // The user has previously granted access.
+                setMicPermission(QStringLiteral("granted"));
+                break;
+            }
+            case AVAuthorizationStatusNotDetermined:
+            {
+                // The app hasn't yet asked the user for access.
+                [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
+                    if (granted) {
+                        setMicPermission(QStringLiteral("granted"));
+                    } else {
+                        setMicPermission(QStringLiteral("denied"));
+                    }
+                }];
+                setMicPermission(QStringLiteral("unknown"));
+                break;
+            }
+            case AVAuthorizationStatusDenied:
+            {
+                // The user has previously denied access.
+                setMicPermission(QStringLiteral("denied"));
+            }
+            case AVAuthorizationStatusRestricted:
+            {
+                // The user can't grant access due to restrictions.
+                setMicPermission(QStringLiteral("denied"));
+            }
+        }
+    } else {
+        setMicPermission(QStringLiteral("granted"));
+    }
+}
+
+void VsMacPermissions::openSystemPrivacy()
+{
+    QDesktopServices::openUrl(QUrl("x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"));
+}
diff --git a/src/gui/vsPermissions.cpp b/src/gui/vsPermissions.cpp
new file mode 100644 (file)
index 0000000..a978a5a
--- /dev/null
@@ -0,0 +1,68 @@
+//*****************************************************************
+/*
+  JackTrip: A System for High-Quality Audio Network Performance
+  over the Internet
+
+  Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe.
+  SoundWIRE group at CCRMA, Stanford University.
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation
+  files (the "Software"), to deal in the Software without
+  restriction, including without limitation the rights to use,
+  copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the
+  Software is furnished to do so, subject to the following
+  conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. 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.
+*/
+//*****************************************************************
+/**
+ * \file vsPermissions.mm
+ * \author Matt Horton
+ * \date Oct 2022
+ */
+
+#include "vsPermissions.h"
+
+#include <QDesktopServices>
+#include <QSettings>
+#include <QUrl>
+
+QString VsPermissions::micPermission()
+{
+    return m_micPermission;
+}
+
+bool VsPermissions::micPermissionChecked()
+{
+    return m_micPermissionChecked;
+}
+
+void VsPermissions::getMicPermission()
+{
+    setMicPermission("granted");
+}
+
+void VsPermissions::setMicPermission(QString status)
+{
+    m_micPermission        = status;
+    m_micPermissionChecked = true;
+    emit micPermissionUpdated();
+
+    QSettings settings;
+    settings.beginGroup(QStringLiteral("VirtualStudio"));
+    settings.setValue(QStringLiteral("MicPermissionChecked"), m_micPermissionChecked);
+    settings.endGroup();
+}
diff --git a/src/gui/vsPermissions.h b/src/gui/vsPermissions.h
new file mode 100644 (file)
index 0000000..cec2d97
--- /dev/null
@@ -0,0 +1,70 @@
+//*****************************************************************
+/*
+  JackTrip: A System for High-Quality Audio Network Performance
+  over the Internet
+
+  Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe.
+  SoundWIRE group at CCRMA, Stanford University.
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation
+  files (the "Software"), to deal in the Software without
+  restriction, including without limitation the rights to use,
+  copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the
+  Software is furnished to do so, subject to the following
+  conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. 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.
+*/
+//*****************************************************************
+/**
+ * \file vsPermissions.h
+ * \author Matt Horton
+ * \date Nov 2022
+ */
+
+#ifndef __VSPERMISSIONS_H__
+#define __VSPERMISSIONS_H__
+
+#include <QDebug>
+#include <QObject>
+#include <QString>
+
+class VsPermissions : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(QString micPermission READ micPermission NOTIFY micPermissionUpdated)
+
+   public:
+    VsPermissions() = default;  // define here and there
+
+    QString micPermission();              // define here
+    virtual bool micPermissionChecked();  // define here and there
+    Q_INVOKABLE virtual void getMicPermission();
+    void setMicPermission(QString status);  // define here
+
+   signals:
+    void micPermissionUpdated();  // leave here
+
+   protected:
+#if __APPLE__
+    QString m_micPermission     = "unknown";
+    bool m_micPermissionChecked = false;
+#else
+    QString m_micPermission     = "granted";
+    bool m_micPermissionChecked = true;
+#endif
+};
+
+#endif  // __VSPERMISSIONS_H__
diff --git a/src/gui/vuMeter.cpp b/src/gui/vuMeter.cpp
new file mode 100644 (file)
index 0000000..36736c7
--- /dev/null
@@ -0,0 +1,75 @@
+//*****************************************************************
+/*
+  QJackTrip: Bringing a graphical user interface to JackTrip, a
+  system for high quality audio network performance over the
+  internet.
+
+  Copyright (c) 2022 Aaron Wyatt.
+
+  This file is part of QJackTrip.
+
+  QJackTrip 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 3 of the License, or
+  (at your option) any later version.
+
+  QJackTrip 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 QJackTrip.  If not, see <https://www.gnu.org/licenses/>.
+*/
+//*****************************************************************
+
+#include "vuMeter.h"
+
+#include <cmath>
+
+VuMeter::VuMeter(QWidget* parent) : QWidget(parent), m_level(0)
+{
+    m_greenOn.setRgb(97, 197, 84);
+    m_greenOff.setRgb(29, 67, 24);
+    m_yellowOn.setRgb(245, 191, 79);
+    m_yellowOff.setRgb(85, 65, 22);
+    m_redOn.setRgb(242, 27, 27);
+    m_redOff.setRgb(84, 4, 4);
+}
+
+void VuMeter::setLevel(qreal level)
+{
+    m_level = level;
+    update();
+}
+
+void VuMeter::paintEvent([[maybe_unused]] QPaintEvent* event)
+{
+    quint32 binWidth = std::floor((width() - ((m_bins - 1) * m_margins)) / m_bins);
+    QPainter painter(this);
+
+    painter.setPen(Qt::NoPen);
+    quint32 level = std::round(m_level * (m_bins + 1));
+    for (quint32 i = 0; i < m_bins; i++) {
+        bool on = level > i;
+        if (on) {
+            if (i < 9) {
+                painter.setBrush(m_greenOn);
+            } else if (i < 12) {
+                painter.setBrush(m_yellowOn);
+            } else {
+                painter.setBrush(m_redOn);
+            }
+        } else {
+            if (i < 9) {
+                painter.setBrush(m_greenOff);
+            } else if (i < 12) {
+                painter.setBrush(m_yellowOff);
+            } else {
+                painter.setBrush(m_redOff);
+            }
+        }
+
+        painter.drawRect((binWidth + m_margins) * i, 0, binWidth, height());
+    }
+}
diff --git a/src/gui/vuMeter.h b/src/gui/vuMeter.h
new file mode 100644 (file)
index 0000000..735dc14
--- /dev/null
@@ -0,0 +1,58 @@
+//*****************************************************************
+/*
+  QJackTrip: Bringing a graphical user interface to JackTrip, a
+  system for high quality audio network performance over the
+  internet.
+
+  Copyright (c) 2022 Aaron Wyatt.
+
+  This file is part of QJackTrip.
+
+  QJackTrip 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 3 of the License, or
+  (at your option) any later version.
+
+  QJackTrip 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 QJackTrip.  If not, see <https://www.gnu.org/licenses/>.
+*/
+//*****************************************************************
+
+#ifndef VUMETER_H
+#define VUMETER_H
+
+#include <QPainter>
+#include <QWidget>
+
+class VuMeter : public QWidget
+{
+    Q_OBJECT
+
+   public:
+    VuMeter(QWidget* parent = nullptr);
+    ~VuMeter() override = default;
+
+    void setLevel(qreal level);
+
+   protected:
+    void paintEvent(QPaintEvent* event) override;
+
+   private:
+    qreal m_level;
+    QColor m_greenOn;
+    QColor m_greenOff;
+    QColor m_yellowOn;
+    QColor m_yellowOff;
+    QColor m_redOn;
+    QColor m_redOff;
+
+    quint32 m_bins    = 15;
+    quint32 m_margins = 2;
+};
+
+#endif  // VUMETER_H
index 64b553a9a4a402f8800684458ecb9b5c639dd0ed..6ed30135fcb96aa645a6e6e07faa13658a899b8e 100644 (file)
@@ -40,7 +40,7 @@
 
 #include "AudioInterface.h"
 
-constexpr const char* const gVersion = "1.6.6";  ///< JackTrip version
+constexpr const char* const gVersion = "1.6.7";  ///< JackTrip version
 
 //*******************************************************************************
 /// \name Default Values
index 7617489e555ea7aa9131f9e89223a073b543db37..565f1ac9b9d67781f45d5769564ad15d2c8901a8 100644 (file)
@@ -48,6 +48,7 @@
                 Event="DoAction" \r
                 Value="LaunchApplication">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>\r
         </UI>\r
+        <Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>\r
         <Icon Id='jacktrip.exe' SourceFile='jacktrip.exe' />\r
     </Product>\r
 </Wix>\r