New upstream version 1.8.1+ds
authorIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>
Fri, 14 Apr 2023 10:38:09 +0000 (12:38 +0200)
committerIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>
Fri, 14 Apr 2023 10:38:09 +0000 (12:38 +0200)
82 files changed:
.clang-format
.pre-commit-config.yaml [new file with mode: 0644]
CMakeLists.txt
build-aux/flatpak/org.jacktrip.JackTrip.json [new file with mode: 0644]
build-aux/flatpak/pypi-dependencies.json [new file with mode: 0644]
build-aux/flatpak/requirements.txt [new file with mode: 0644]
build-aux/flatpak/update_pip_deps.sh [new file with mode: 0755]
docs/Build/Linux.md
docs/Build/Meson_build.md
docs/DevTools/Formatting.md
docs/Documentation/MkDocs.md
docs/Install.md
docs/assets/jacktrip.svg [new file with mode: 0644]
docs/changelog.yml
jacktrip.pro
meson.build
meson_options.txt
mkdocs.yml
releases/edge/mac-manifests.json
releases/edge/win-manifests.json
releases/stable/linux-manifests.json
releases/stable/mac-manifests.json
releases/stable/win-manifests.json
src/AudioInterface.cpp
src/AudioInterface.h
src/Auth.cpp
src/Auth.h
src/JackAudioInterface.cpp
src/JackAudioInterface.h
src/JackTrip.cpp
src/JackTrip.h
src/JackTripWorker.cpp
src/Meter.cpp
src/Meter.h
src/Regulator.cpp
src/Regulator.h
src/RtAudioInterface.cpp
src/RtAudioInterface.h
src/Settings.cpp
src/Settings.h
src/StereoToMono.cpp [new file with mode: 0644]
src/StereoToMono.h [new file with mode: 0644]
src/UdpDataProtocol.cpp
src/UdpHubListener.cpp
src/gui/AudioSettings.qml [new file with mode: 0644]
src/gui/Browse.qml
src/gui/Connected.qml
src/gui/FirstLaunch.qml
src/gui/Login.qml
src/gui/SectionHeading.qml
src/gui/Settings.qml
src/gui/Setup.qml
src/gui/Studio.qml
src/gui/headphones.svg
src/gui/help.svg [new file with mode: 0644]
src/gui/loud.svg [new file with mode: 0644]
src/gui/mic.svg
src/gui/micoff.svg
src/gui/qjacktrip.cpp
src/gui/qjacktrip.h
src/gui/qjacktrip.qrc
src/gui/qjacktrip.ui
src/gui/qjacktrip_novs.qrc
src/gui/quiet.svg [new file with mode: 0644]
src/gui/refresh.svg [new file with mode: 0644]
src/gui/share.svg
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/vsDevice.h
src/gui/vsInit.cpp [new file with mode: 0644]
src/gui/vsInit.h [new file with mode: 0644]
src/gui/vsServerInfo.cpp
src/gui/vsServerInfo.h
src/gui/vuMeter.cpp
src/gui/vuMeter.h
src/jacktrip_globals.h
src/main.cpp
src/stereotomonodsp.h [new file with mode: 0644]

index 9b3480121c87b756f6108c921bf06805dc352811..273d78a8d9d2bb19917b88e3f01e5e96dd8df260 100644 (file)
@@ -178,5 +178,7 @@ WhitespaceSensitiveMacros:
   - STRINGIZE
   - PP_STRINGIZE
   - BOOST_PP_STRINGIZE
+---
+Language:        ObjC
+DisableFormat: true
 ...
-
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644 (file)
index 0000000..bcffe68
--- /dev/null
@@ -0,0 +1,9 @@
+fail_fast: false
+repos:
+  - repo: https://github.com/ssciwr/clang-format-hook
+    rev: v13.0.1
+    hooks:
+    - id: clang-format
+      files: ^src/
+      types_or: [c++]
+      exclude: '^src/.+dsp\.h'
\ No newline at end of file
index 0ea77b841f59724058c14f8e820fc0f4ce539eb2..d96731be9860357f614d377eb2afe1760737ff3c 100644 (file)
@@ -155,6 +155,7 @@ if (rtaudio)
   add_compile_definitions(RT_AUDIO)
   set (qjacktrip_SRC ${qjacktrip_SRC}
     src/RtAudioInterface.cpp
+    src/StereoToMono.cpp
   )
 endif ()
 
@@ -179,6 +180,7 @@ if (NOT nogui)
   if (NOT novs)
     set (qjacktrip_SRC ${qjacktrip_SRC}
       src/gui/virtualstudio.cpp
+      src/gui/vsInit.cpp
       src/gui/vsQuickView.cpp
       src/gui/vsServerInfo.cpp
       src/gui/vsPing.cpp
diff --git a/build-aux/flatpak/org.jacktrip.JackTrip.json b/build-aux/flatpak/org.jacktrip.JackTrip.json
new file mode 100644 (file)
index 0000000..64c2f1c
--- /dev/null
@@ -0,0 +1,36 @@
+{
+    "app-id": "org.jacktrip.JackTrip",
+    "runtime": "org.kde.Platform",
+    "runtime-version": "5.15-22.08",
+    "sdk": "org.kde.Sdk",
+    "command": "jacktrip",
+    "finish-args": [
+        "--share=ipc",
+        "--socket=x11",
+        "--device=dri",
+        "--share=network",
+        "--filesystem=xdg-run/pipewire-0",
+        "--env=PIPEWIRE_LATENCY=256/48000"
+    ],
+    "cleanup": [
+        "/lib/python3.10",
+        "/share/man"
+    ],
+    "modules": [
+        "pypi-dependencies.json",
+        {
+            "name": "jacktrip",
+            "buildsystem": "meson",
+            "config-opts": [
+                "-Dbuildtype=debugoptimized"
+            ],
+            "sources": [
+                {
+                    "type": "git",
+                    "disable-submodules": true,
+                    "url": "https://github.com/jacktrip/jacktrip/"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/build-aux/flatpak/pypi-dependencies.json b/build-aux/flatpak/pypi-dependencies.json
new file mode 100644 (file)
index 0000000..e148c9d
--- /dev/null
@@ -0,0 +1,40 @@
+{
+    "name": "pypi-dependencies",
+    "buildsystem": "simple",
+    "build-commands": [],
+    "modules": [
+        {
+            "name": "python3-pyyaml",
+            "buildsystem": "simple",
+            "build-commands": [
+                "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"pyyaml\" --no-build-isolation"
+            ],
+            "sources": [
+                {
+                    "type": "file",
+                    "url": "https://files.pythonhosted.org/packages/36/2b/61d51a2c4f25ef062ae3f74576b01638bebad5e045f747ff12643df63844/PyYAML-6.0.tar.gz",
+                    "sha256": "68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"
+                }
+            ]
+        },
+        {
+            "name": "python3-jinja2",
+            "buildsystem": "simple",
+            "build-commands": [
+                "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"jinja2\" --no-build-isolation"
+            ],
+            "sources": [
+                {
+                    "type": "file",
+                    "url": "https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl",
+                    "sha256": "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
+                },
+                {
+                    "type": "file",
+                    "url": "https://files.pythonhosted.org/packages/95/7e/68018b70268fb4a2a605e2be44ab7b4dd7ce7808adae6c5ef32e34f4b55a/MarkupSafe-2.1.2.tar.gz",
+                    "sha256": "abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/build-aux/flatpak/requirements.txt b/build-aux/flatpak/requirements.txt
new file mode 100644 (file)
index 0000000..33afd92
--- /dev/null
@@ -0,0 +1,2 @@
+pyyaml
+jinja2
diff --git a/build-aux/flatpak/update_pip_deps.sh b/build-aux/flatpak/update_pip_deps.sh
new file mode 100755 (executable)
index 0000000..b67a299
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/bash
+# https://github.com/flatpak/flatpak-builder-tools/tree/master/pip
+flatpak-pip-generator --runtime='org.freedesktop.Sdk//22.08' --requirements-file='requirements.txt' --output pypi-dependencies
\ No newline at end of file
index 3b2df229ec144623566730783210060fb4897101..c8d3af86fa32e6bec4a5c9acfaa3e5a8f3ebe8ff 100644 (file)
@@ -19,8 +19,7 @@ Optional:
 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
-dnf install qjackctl
+dnf install "pkgconfig(jack)" rtaudio-devel git help2man
 ```
 
 Clone the git repo with submodules and run `./build install` in the project
@@ -28,8 +27,8 @@ 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 libqt5websockets5-dev qtdeclarative5-dev qml-module-qtquick-controls
+apt install --no-install-recommends build-essential autoconf automake libtool make libjack-jackd2-dev git help2man
+apt install qtbase5-dev qtbase5-dev-tools qtchooser qt5-qmake qttools5-dev libqt5svg5-dev libqt5networkauth5-dev libqt5websockets5-dev qtdeclarative5-dev qtquickcontrols2-5-dev
 apt install librtaudio-dev # if building with RtAudio
 ```
 
index b416f7dff59fd4bffdf2f12d9ca2eff0b43e8176..6b95d69cbfa527a459f5397fc7c7a6c884cb6673 100644 (file)
@@ -14,7 +14,7 @@ find its documentation at [mesonbuild.com](https://mesonbuild.com/).
 === "Debian/Ubuntu"
 
     ```bash
-    apt install meson build-essential qtbase5-dev librtaudio-dev libjack-jackd2-dev help2man
+    apt install meson build-essential qtbase5-dev qtbase5-dev-tools qtchooser qt5-qmake qttools5-dev libqt5svg5-dev libqt5networkauth5-dev libqt5websockets5-dev qtdeclarative5-dev qtquickcontrols2-5-dev libjack-jackd2-dev git help2man
     ```
 
 === "MacOS"
index 17f88c18fa5d7e0c1155ee7648de184f6dc12cc9..6abc7b329348e19cadd6eec1abbf0b43a3b40d0d 100644 (file)
@@ -23,12 +23,54 @@ this+=   isnot     ;
 this = again;
 ```
 
-## Formatting the entire code base
+## Forget about formatting with a git pre-commit hook
 
-Formatting the entire code base can be done with specifying all code files of the project
-or be invoking Meson's clang-format target:
+With git the user can install hooks that are executed when specific tasks are done.
+We can add a pre-commit hook for clang-format. So that everytime we commit our
+changes, git runs clang-format for us.
+
+Handling git hooks by hand is cumbersome. With [pre-commit](https://pre-commit.com/)
+this becomes a lot easier.
+
+Pre-commit is a Python app that can be installed with pip.
 
 ```bash
-ninja -C builddir clang-format
+pip install pre-commit
 ```
 
+Within the root directory of the jacktrip repository the pre-commit hook can be
+installed as follows:
+
+```bash
+pre-commit install
+```
+
+Pre-commit only runs on changed files. Running pre-commit on all files is done
+by:
+
+```bash
+pre-commit run --all-files
+```
+
+Sometimes these hooks come into your way. But you can disable them when committing:
+
+```bash
+git commit -am "Commit all my stuff" --no-verify
+```
+
+### Pre-commit configuration
+
+Pre-commit is configured by the *.pre-commit-config.yaml* file in the repository's
+root. Currently it only includes the clang-format hook.
+
+```yaml
+fail_fast: false
+repos:
+  - repo: https://github.com/ssciwr/clang-format-hook
+    rev: v13.0.1
+    hooks:
+    - id: clang-format
+      files: ^src/
+      types_or: [c++]
+      exclude: '^src/.+dsp\.h'
+```
index 93394488a2a4c2addaebbf710bf1f06d4e3c0f6f..ab50a2602c9d5342b18bead9d4be0685443fec47 100644 (file)
@@ -1,17 +1,22 @@
 # Write Documentation
 
-This documentation of JackTrip is generated with [Material](https://squidfunk.github.io/mkdocs-material/)
+This documentation of JackTrip is generated with [Material](https://squidfunk.github.io/mkdocs-material/) theme
 for [MkDocs](https://www.mkdocs.org/). All pages are derived from Markdown files in the subdirectory `docs`
 in JackTrip's git repository. Setup and table of contents is found in a YAML file called mkdocs.yml in
 the root directory.
 
 If you only want to edit a page you can click on the pen symbol at the top of each page.
 
+## MkDocs preview in PRs
+
+If you submit changes to the docs as a Pull Request, the `Render docs preview` workflow will generate a static version of the documentation, including the proposed changes. It can be downloaded from GitHub Actions for checking offline (see "Checks" tab on top of the PRs page, then select `Render docs preview` and find the archive at the bottom of that page). 
+
+When you're working on the changes locally, it might be more convenient to test changes as they're being made by running mkdocs on your system (see below).
 ## Run MkDocs on Your System
 
 MkDocs and Material for MkDocs are installed from pip:
 ```bash
-pip install mkdocs mkdocs-material
+pip install mkdocs mkdocs-material mkdocs-macros-plugin
 ``` 
 
 When writing documentation it is very handy to run `mkdocs serve`. This will open
index f44fa5ccddd757c73b9fb636b5d87ec33b3dedc1..14ef46d30720f8cd84d09e92801677714fd7555f 100644 (file)
@@ -14,7 +14,39 @@ On Linux the easiest way to install JackTrip is to use the distribution's packag
     sudo apt install jacktrip
     ```
 
-The [GitHub releases page](https://github.com/jacktrip/jacktrip/releases) also includes a binary which should run on most Linux distributions (x64). This build is known to not look well under Wayland.
+### Latest release
+
+If your distribution doesn't include the latest release in their repository, there are different
+alternative options to install from.
+
+=== "Ubuntu"
+
+    Ubuntu users can install from a [PPA repository](https://launchpad.net/~umlaeute/+archive/ubuntu/jacktrip/)
+    maintained by the packager of Ubuntu's official JackTrip packages.
+
+    ```bash
+    sudo add-apt-repository ppa:umlaeute/jacktrip
+    sudo apt update
+    sudo apt install jacktrip
+    ```
+
+=== "Debian"
+
+    Debian stable users can obtain their latest version from the [Debian backports](https://backports.debian.org/) repository.
+    
+    As an example for Debian Bullseye follow these steps: 
+    
+    ```bash
+    mkdir -p /etc/apt/sources.list.d/
+    echo "deb http://deb.debian.org/debian bullseye-backports main" | tee /etc/apt/sources.list.d/backports.list
+    apt-get update
+    apt-get install -t bullseye-backports jacktrip
+    ``` 
+
+=== "All"
+
+    The [GitHub releases page](https://github.com/jacktrip/jacktrip/releases) also includes a binary
+    which should run on most Linux distributions (x64). This build is known to not look well under Wayland.
 
 ## macOS
 macOS installer and application bundle are available on the [GitHub releases page](https://github.com/jacktrip/jacktrip/releases). The installer will install the JackTrip app in `/Applications`, as well as create a link to the `jacktrip` executable in `/usr/local/bin` for use in the command line.
diff --git a/docs/assets/jacktrip.svg b/docs/assets/jacktrip.svg
new file mode 100644 (file)
index 0000000..2d1d67f
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="1366px" height="1366px" viewBox="0 0 1366 1366" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>jacktrip</title>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="jacktrip">
+            <path d="M754.755517,107 C789.35747,107 817.401215,135.042996 817.401215,169.633588 C817.401215,204.22418 789.35747,232.258717 754.755517,232.258717 C744.033904,232.258717 733.946957,229.56016 725.112416,224.822882 L599.778709,297.159431 C599.228666,297.523187 598.67016,297.878483 598.09473,298.2084 C565.109058,317.250568 542.88731,348.102093 533.688894,382.447362 C524.473554,416.818009 528.256159,454.631642 547.270732,487.555727 L548.463903,489.611367 C548.819315,490.220446 549.149341,490.837984 549.462443,491.463982 L808.998246,940.845596 C810.369123,942.765886 811.503059,944.804608 812.416977,946.927923 C823.857877,968.727864 825.90573,993.370171 819.91449,1015.71151 C813.830165,1038.44199 799.444418,1059.03223 778.195823,1072.16126 C765.240188,1081.24669 747.232618,1080.02007 735.656322,1068.44757 C722.692225,1055.48773 722.692225,1034.4576 735.656322,1021.49776 L735.664784,1021.49776 L735.715558,1021.54006 C737.712638,1019.55209 739.980509,1017.78407 742.536094,1016.30367 C749.280471,1012.41234 753.875448,1005.89858 755.830217,998.598096 C757.801911,991.246855 757.10801,983.294995 753.274631,976.59513 L753.23232,976.518995 L492.181782,524.498039 C491.817908,523.948176 491.462495,523.389854 491.132469,522.814613 L489.930836,520.758973 C461.709385,471.880411 456.031246,415.988985 469.604622,365.325484 C483.186459,314.645065 516.045198,269.074138 564.880579,240.887331 C565.489858,240.532035 566.107599,240.210577 566.733802,239.897579 L692.143668,167.518732 C693.260679,133.909433 720.864389,107 754.755517,107 M517.763026,1028.58676 C518.118438,1029.2043 518.448464,1029.81338 518.753104,1030.43937 L554.032033,1091.52489 L554.082806,1091.49105 C581.678054,1139.26143 626.375417,1171.44108 676.124716,1184.75622 C725.907863,1198.09673 780.700635,1192.58965 828.427467,1165.0373 C829.002897,1164.70738 829.569865,1164.40284 830.145295,1164.10676 C876.966671,1136.51211 908.547617,1092.27778 921.740193,1043.04392 C935.093551,993.217901 929.584656,938.400822 902.006333,890.647364 L677.284038,501.530698 C643.672162,500.414054 616.753891,472.827867 616.753891,438.939407 C616.753891,404.357275 644.806098,376.314278 679.408051,376.314278 C714.010004,376.314278 742.053749,404.357275 742.053749,438.939407 C742.053749,449.665959 739.354306,459.749594 734.615471,468.572775 L959.346229,857.70636 C996.114506,921.363539 1003.51047,994.173816 985.824466,1060.1658 C968.400787,1125.16802 926.589036,1183.73263 864.459533,1220.6411 C862.843252,1221.80004 861.1085,1222.82363 859.280664,1223.69496 C796.15262,1259.30068 724.257733,1266.29662 658.997214,1248.81945 C593.652073,1231.32536 534.831292,1189.27355 498.003779,1126.84299 C496.929079,1125.278 495.989774,1123.61149 495.185865,1121.86039 L461.472443,1063.47343 C461.117031,1062.92357 460.753156,1062.36525 460.42313,1061.79001 C456.530516,1055.0563 450.006157,1050.45438 442.694812,1048.50025 C435.366543,1046.54612 427.428996,1047.24825 420.743854,1051.10575 C404.91107,1060.26731 384.635629,1054.8702 375.4626,1039.04261 C366.298033,1023.21502 371.696919,1002.94624 387.529703,993.776223 C410.157637,980.706411 436.255075,978.143205 459.822314,984.437017 C483.423401,990.756208 504.722769,1006.00855 517.763026,1028.58676" id="Fill-1" fill="#F21B1B"></path>
+            <rect id="Rectangle" x="0" y="0" width="1365.33" height="1365.33"></rect>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
index dd9bc72db07a7d7af0a4f7e5f72d5a181a5ca07e..8db1bd5ca4f44afca6029fe53454ad2e92b950ab 100644 (file)
@@ -1,3 +1,56 @@
+- Version: "1.8.1"
+  Date: 2023-03-29
+  Description:
+  - (added) VS mode - tooltips to explain input and output device
+  - (added) buffer size and sample rate now settable with Pipewire
+  - (added) VS mode - link to create a studio on the login complete page
+  - (fixed) Device names with special characters work again
+  - (fixed) undefine boolean error
+  - (fixed) VS Mode - Audio settings correctly show the selected device
+  - (fixed) VS Mode - Refresh button doesn't crash Windows any more
+  - (fixed) VS Mode - Output channel selections save correctly
+  - (updated) documentation styling
+  - (updated) removed ipify from VS mode
+  - (updated) ip check can now function with only IPv6
+  - (updated) cleaned up vsinit class
+- Version: "1.8.0"
+  Date: 2023-03-01
+  Description:
+  - (added) Qt version option for Meson builds
+  - (added) GHA builds now include static preview docs
+  - (added) when using the classic GUI, command line options are now parsed
+  - (added) VS mode - Selecting and configuring device channels
+  - (added) Classic mode - Warning for machines without JACK installed
+  - (added) VS mode - high latency warning for non-ASIO devices
+  - (added) Meson build without rtaudio in GHA
+  - (updated) icons in VS mode 
+  - (updated) Linux builds now use Qt 5.15.8
+  - (updated) Replaced QVector in meter code
+  - (updated) Removed set-output from GHA scripts for deprecation
+  - (updated) Automated the auto-updater release process
+  - (updated) RtAudio is included in Linux binary releases
+  - (updated) text on audio setup confirm button when using deeplink
+  - (updated) VS Mode - improved first-time signin
+  - (updated) Flathub - improve latency by defaulting to 256 samples buffer size
+  - (fixed) ambiguous call to overloaded function in Qt6
+  - (fixed) issue where selected devices were not the devices used for output
+  - (fixed) crash when using Classic mode and CLI w/ JACK
+  - (fixed) ipify issue with Norton
+  - (fixed) compiler warnings when building without RtAudio
+  - (fixed) VS Mode - refresh button behavior on settings page
+- Version: "1.7.1"
+  Date: 2023-02-03
+  Description:
+  - (added) missing QuickControl2 dependency
+  - (added) documentation preview in build steps
+  - (added) README text about PPA and Debian backports
+  - (updated) upgraded to Qt 5.15.3
+  - (updated) linux package dependencies
+  - (updated) JackTrip now uses a random available port when connecting
+  - (updated) VS mode - Audio settings screen layouts
+  - (fixed) VS mode - video button is now available to all users of a Studio
+  - (fixed) A few memory leaks
+  - (fixed) linux static builds
 - Version: "1.7.0"
   Date: 2023-01-20
   Description:
index d7c56f0f06e306da089dfc16d1ea9e11ae72928a..9df06dd44b18e0fdf515c201f702a3b2241c2783 100644 (file)
@@ -5,7 +5,7 @@
 CONFIG += c++17 console
 CONFIG -= app_bundle
 
-CONFIG += qt thread debug_and_release build_all
+CONFIG += qt thread debug_and_release build_all qtquickcompiler
 CONFIG(debug, debug|release) {
     TARGET = jacktrip_debug
     application_id = 'org.jacktrip.JackTrip.Devel'
@@ -32,6 +32,7 @@ nogui {
     QT += networkauth
     QT += qml
     QT += quick
+    QT += quickcontrols2
     QT += svg
     QT += websockets
   }
@@ -208,6 +209,7 @@ HEADERS += src/DataProtocol.h \
            src/Meter.h \
            src/Volume.h \
            src/Tone.h \
+           src/StereoToMono.h \
            src/AudioTester.h \
            src/jacktrip_globals.h \
            src/jacktrip_types.h \
@@ -246,6 +248,7 @@ HEADERS += src/DataProtocol.h \
              src/gui/vuMeter.h
   !novs {
     HEADERS += src/gui/virtualstudio.h \
+               src/gui/vsInit.h \
                src/gui/vsDevice.h \
                src/gui/vsAudioInterface.h \
                src/gui/vsServerInfo.h \
@@ -277,6 +280,7 @@ SOURCES += src/DataProtocol.cpp \
            src/Regulator.cpp \
            src/Reverb.cpp \
            src/Meter.cpp \
+           src/StereoToMono.cpp \
            src/Volume.cpp \
            src/Tone.cpp \
            src/AudioTester.cpp \
@@ -309,6 +313,7 @@ SOURCES += src/DataProtocol.cpp \
              src/gui/vuMeter.cpp
   !novs {
     SOURCES += src/gui/virtualstudio.cpp \
+               src/gui/vsInit.cpp \
                src/gui/vsDevice.cpp \
                src/gui/vsAudioInterface.cpp \
                src/gui/vsServerInfo.cpp \
index dc19dc26c01a38275b0086b915aee78257fa5176..4ebb6e6d757533ae3107dcbbfcba41687e66a0d9 100644 (file)
@@ -9,7 +9,12 @@ else
   name_suffix = ''
 endif
 
-qt5 = import('qt5')
+if get_option('qtversion') == '5'
+  qt = import('qt5')
+else
+  qt = import('qt6')
+endif
+
 cmake = import('cmake')
 
 compiler = meson.get_compiler('cpp')
@@ -38,6 +43,7 @@ src = [       'src/DataProtocol.cpp',
        'src/Meter.cpp',
        'src/Volume.cpp',
        'src/Tone.cpp',
+       'src/StereoToMono.cpp',
        'src/Reverb.cpp',
        'src/main.cpp',
        'src/SslServer.cpp',
@@ -47,6 +53,7 @@ moc_h = ['src/DataProtocol.h',
        'src/JackTrip.h',
        'src/ProcessPlugin.h',
        'src/Meter.h',
+       'src/StereoToMono.h',
        'src/Volume.h',
        'src/Tone.h',
        'src/JackTripWorker.h',
@@ -87,7 +94,11 @@ endif
 
 if get_option('nogui') == true
        defines += '-DNO_GUI'
-       qt5_dep = dependency('qt5', modules: ['Core', 'Network'], include_type: 'system')
+       if get_option('qtversion') == '5'
+               qt_dep = dependency('qt5', modules: ['Core', 'Network'], include_type: 'system')
+       else
+               qt_dep = dependency('qt6', modules: ['Core', 'Network'], include_type: 'system')
+       endif
 else
        src += [
                'src/gui/qjacktrip.cpp',
@@ -111,11 +122,16 @@ else
 
        if get_option('novs') == true
                defines += '-DNO_VS'
-               qt5_dep = dependency('qt5', modules: ['Core', 'Gui', 'Network', 'Widgets'], include_type: 'system')
+               if get_option('qtversion') == '5'
+                       qt_dep = dependency('qt5', modules: ['Core', 'Gui', 'Network', 'Widgets'], include_type: 'system')
+               else
+                       qt_dep = dependency('qt6', modules: ['Core', 'Gui', 'Network', 'Widgets'], include_type: 'system')
+               endif
                qres = ['src/gui/qjacktrip_novs.qrc']
        else
                src += [
                        'src/gui/virtualstudio.cpp',
+                       'src/gui/vsInit.cpp',
                        'src/gui/vsDevice.cpp',
                        'src/gui/vsAudioInterface.cpp',
                        'src/gui/vsServerInfo.cpp',
@@ -128,6 +144,7 @@ else
                ]
                moc_h += [
                        'src/gui/virtualstudio.h',
+                       'src/gui/vsInit.h',
                        'src/gui/vsDevice.h',
                        'src/gui/vsAudioInterface.h',
                        'src/gui/vsServerInfo.h',
@@ -145,7 +162,11 @@ else
                        moc_h += ['src/gui/vsMacPermissions.h']
                endif
 
-               qt5_dep = dependency('qt5', modules: ['Core', 'Gui', 'Network', 'Widgets', 'Quick', 'Qml', 'Svg', 'NetworkAuth', 'WebSockets'], include_type: 'system')
+               if get_option('qtversion') == '5'
+                       qt_dep = dependency('qt5', modules: ['Core', 'Gui', 'Network', 'Widgets', 'Quick', 'QuickControls2', 'Qml', 'Svg', 'NetworkAuth', 'WebSockets'], include_type: 'system')
+               else
+                       qt_dep = dependency('qt6', modules: ['Core', 'Gui', 'Network', 'Widgets', 'Quick', 'QuickControls2', 'Qml', 'Svg', 'NetworkAuth', 'WebSockets'], include_type: 'system')
+               endif
                qres = ['src/gui/qjacktrip.qrc']
        endif
 
@@ -167,9 +188,7 @@ else
                ui_h += ['src/dblsqd/update_dialog.ui']
        endif
 endif
-deps += qt5_dep
-
-prepro_files = qt5.preprocess(moc_headers : moc_h, ui_files : ui_h, qresources : qres)
+deps += qt_dep
 
 # TODO: QT_OPENSOURCE should only be defined for open source Qt distribution
 # in QMake this can be checked with QT_EDITION == 'OpenSource'
@@ -206,7 +225,11 @@ if host_machine.system() == 'darwin' and get_option('novs') == false
        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 )
+qres_files = qt.compile_resources(sources: qres)
+moc_files = qt.compile_moc(headers: moc_h, extra_args: defines)
+ui_files = qt.compile_ui(sources: ui_h)
+
+jacktrip = executable('jacktrip', src, qres_files, ui_files, moc_files, include_directories: incdirs, dependencies: deps, c_args: c_defines, cpp_args: defines, install: true )
 
 help2man = find_program('help2man', required: false)
 if not (host_machine.system() == 'windows')
index 242ea80f5eddd1daae719042f410d1f531d1c58b..87cd61285ee1d15a28ba270a0e4b49da109c567c 100644 (file)
@@ -6,3 +6,4 @@ option('nogui', type : 'boolean', value : 'false', description: 'Build without g
 option('novs', type : 'boolean', value : 'false', description: 'Build without Virtual Studio support')
 option('noupdater', type : 'boolean', value : 'false', description: 'Build without auto-update support')
 option('profile', type: 'combo', choices: ['default', 'development'], value: 'default', description: 'Choose build profile / Sets desktop id accordingly')
+option('qtversion', type : 'combo', choices: ['5', '6'], description: 'Choose to build with either Qt5 or Qt6')
\ No newline at end of file
index aa3ca4347477034af0fa0d1801eda876991f4003..5e8d7c7b54e9dbea947d2198a561ea516393e321 100644 (file)
@@ -22,11 +22,16 @@ nav:
     - Changelog: About/CHANGELOG.md
     - License: About/License.md
 plugins:
+  - search
   - macros:
       include_yaml:
         - releases: docs/changelog.yml
 theme:
   name: material
+  logo: assets/jacktrip.svg
+  palette:
+    primary: black
+    accent: red
   features:
     - navigation.tabs
 markdown_extensions:
index 47fdfc4df4cf9906435aaacf4a2c37b294c7e17f..fb7033dab0218adda6fcf2355628d547d36e15de 100644 (file)
@@ -1,11 +1,61 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.8.0",
+            "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v1.8.0",
+            "download": {
+              "date": "2023-03-18T00:00:00Z",
+              "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.8.0-macOS-x64-signed-installer.pkg",
+              "downloadSize": "11771203",
+              "sha256": "dfee21d5e91a35baaf3b58891188f72f2cb894b2bf796ac70350a2ef9d3fb68c"
+            }
+        },
+        {
+            "version": "1.8.0-beta1",
+            "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v1.8.0-beta1",
+            "download": {
+                "date": "2023-03-01T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.8.0-beta1-macOS-x64-installer.pkg",
+                "downloadSize": 11761503,
+                "sha256": "3159d5625d42db7925867949880eceef581f4991959f85bf4203c2ae15fc623f"
+            }
+        },
+        {
+            "version": "1.7.1",
+            "changelog": "Video button, bug fixes, Linux build fixes, and Qt upgrades: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.1",
+            "download": {
+                "date": "2023-02-10T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.7.1-macOS-x64-installer.pkg",
+                "downloadSize": 11679783,
+                "sha256": "027d1d3aeb4aaca79b21824371a0bfb915b8c43afe924a8ec2be92df719a431f"
+            }
+        },
+        {
+            "version": "1.7.1-beta1",
+            "changelog": "Video button, bug fixes, and Qt upgrades: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.1-beta1",
+            "download": {
+                "date": "2023-02-03T00:00:00Z",
+                "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.7.1-beta1/JackTrip-v1.7.1-beta1-macOS-x64-installer.pkg",
+                "downloadSize": 11678822,
+                "sha256": "1dcc8b54dd67741137582638ce04359404848d5f258c9fae7ab1946730e9381d"
+            }
+        },
+        {
+            "version": "1.7.0",
+            "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.0",
+            "download": {
+                "date": "2023-01-24T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.7.0-macOS-x64-installer.pkg",
+                "downloadSize": 11530594,
+                "sha256": "0e5731c2ad71aa4bd28ccf9311e312f2386c42b02abbca142777dfb06ea1b427"
+            }
+        },
         {
             "version": "1.7.0-rc1",
             "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.0-rc1",
             "download": {
-                "date": "2022-01-20T00:00:00Z",
+                "date": "2023-01-20T00:00:00Z",
                 "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.7.0-rc1/JackTrip-v1.7.0-rc1-macOS-x64-installer.pkg",
                 "downloadSize": 11530680,
                 "sha256": "406134ee2017bcb762f968f893bc28463149d7567dd33b92f963ca9e09608636"
@@ -15,7 +65,7 @@
             "version": "1.6.9-beta3",
             "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.9-beta3",
             "download": {
-                "date": "2022-01-18T00:00:00Z",
+                "date": "2023-01-18T00:00:00Z",
                 "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.9-beta3/JackTrip-v1.6.9-beta3-macOS-x64-installer.pkg",
                 "downloadSize": 11528836,
                 "sha256": "30689d83641377c1594e1db44d8e6cf75a45780969381d02c11ede81a175561f"
@@ -25,7 +75,7 @@
             "version": "1.6.9-beta2",
             "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.9-beta2",
             "download": {
-                "date": "2022-01-10T00:00:00Z",
+                "date": "2023-01-10T00:00:00Z",
                 "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.9-beta2/JackTrip-v1.6.9-beta2-macOS-x64-installer.pkg",
                 "downloadSize": 11528427,
                 "sha256": "884e5c0cf3ea5bc82b348a739df3ba11238074a9552dcb1d1f250f484be89b77"
index 46ff9f2900f5e6f7f1544cafe490c32492b025b0..0fa745b9e939b9801ff0a197366f0e7635480004 100644 (file)
@@ -1,11 +1,61 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.8.0",
+            "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v1.8.0",
+            "download": {
+              "date": "2023-03-18T00:00:00Z",
+              "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.8.0-Windows-x64-signed-installer.msi",
+              "downloadSize": "45699072",
+              "sha256": "4b6705a2e8af7f9a516fefaf119d5e6cadf93e8f40964cd52b635ced5745b267"
+            }
+        },
+        {
+            "version": "1.8.0-beta1",
+            "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v1.8.0-beta1",
+            "download": {
+                "date": "2023-03-01T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.8.0-beta1-Windows-x64-installer.msi",
+                "downloadSize": 45568000,
+                "sha256": "61641b72fe27389ab755580d5b94fa2de993ad42967af0c5c765743ca8b30602"
+            }
+        },
+        {
+            "version": "1.7.1",
+            "changelog": "Video button, bug fixes, Linux build fixes, and Qt upgrades: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.1",
+            "download": {
+                "date": "2023-02-10T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.7.1-Windows-x64-installer.msi",
+                "downloadSize": 45330432,
+                "sha256": "fb5d756afcd471ca8ae45b05b411235026f23f2178893d85ac12f39c3f66a01a"
+            }
+        },
+        {
+            "version": "1.7.1-beta1",
+            "changelog": "Video button, bug fixes, and Qt upgrades: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.1-beta1",
+            "download": {
+                "date": "2023-02-03T00:00:00Z",
+                "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.7.1-beta1/JackTrip-v1.7.1-beta1-Windows-x64-installer.msi",
+                "downloadSize": 45326336,
+                "sha256": "eeb16bc11957413fb74da852b9e0fa3cb8c84cb2945d5c992a40f3e9b526c294"
+            }
+        },
+        {
+            "version": "1.7.0",
+            "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.0",
+            "download": {
+                "date": "2023-01-24T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.7.0-Windows-x64-installer.msi",
+                "downloadSize": 44572672,
+                "sha256": "a1890fe10de484f423a17118031d898abacc9b9eb2ccd35bdb4351e9411ff866"
+            }
+        },
         {
             "version": "1.7.0-rc1",
             "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.0-rc1",
             "download": {
-                "date": "2022-01-20T00:00:00Z",
+                "date": "2023-01-20T00:00:00Z",
                 "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.7.0-rc1/JackTrip-v1.7.0-rc1-Windows-x64-installer.msi",
                 "downloadSize": 44556288,
                 "sha256": "edba383791a598954d129d39024e87f3c062985d10f47dbea43f3d226ee37c6c"
@@ -15,7 +65,7 @@
             "version": "1.6.9-beta3",
             "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.9-beta3",
             "download": {
-                "date": "2022-01-10T00:00:00Z",
+                "date": "2023-01-10T00:00:00Z",
                 "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.9-beta3/JackTrip-v1.6.9-beta3-Windows-x64-installer.msi",
                 "downloadSize": 44552192,
                 "sha256": "f0d8157d99da5ecfa3fb21e6bb039ea48052fc0792b114ca4f40ae0a554a4852"
@@ -25,7 +75,7 @@
             "version": "1.6.9-beta2",
             "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.9-beta2",
             "download": {
-                "date": "2022-01-10T00:00:00Z",
+                "date": "2023-01-10T00:00:00Z",
                 "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.9-beta2/JackTrip-v1.6.9-beta2-Windows-x64-installer.msi",
                 "downloadSize": 44548096,
                 "sha256": "a8e7c9b353d953df894a827e2856baa71f89dc8fa835a5f3eb8422a94ffff5b5"
index 96b9b59e469810bac5e02e35a444c3db2735794d..4b4156cd041426b11a0802bbee6b145f17fe8569 100644 (file)
@@ -1,6 +1,36 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.8.0",
+            "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v1.8.0",
+            "download": {
+              "date": "2023-03-18T00:00:00Z",
+              "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.8.0-Linux-x64-binary.zip",
+              "downloadSize": "36167636",
+              "sha256": "6f14273ffd5526d576a184f4559adb4124def8760d0b9ba60c43b0fa75b2d1a5"
+            }
+        },
+        {
+            "version": "1.7.1",
+            "changelog": "Video button, bug fixes, Linux build fixes, and Qt upgrades: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.1",
+            "download": {
+                "date": "2023-02-10T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.7.1-Linux-x64-binary.zip",
+                "downloadSize": 34338503,
+                "sha256": "4298e1edd561815630e3c6573660eeea15b199a12742ab1b90d43a6e3522b632"
+            }
+        },
+        {
+            "version": "1.7.0",
+            "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.0",
+            "download": {
+                "date": "2023-01-24T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.7.0-Linux-x64-binary.zip",
+                "downloadSize": 32554865,
+                "sha256": "13b3781f6dca0713eb135c9352c23cf0f40094603357ee5d5a940868597e8bb8"
+            }
+        },
         {
             "version": "1.6.8",
             "changelog": "Critical bug fix: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.8",
index 65af705d70a55f3b0657f8d1c2232a1fac6e11cd..c94527ac5b44a2a76800ee5b5cf557dab8cdce5a 100644 (file)
@@ -1,6 +1,36 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.8.0",
+            "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v1.8.0",
+            "download": {
+              "date": "2023-03-18T00:00:00Z",
+              "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.8.0-macOS-x64-signed-installer.pkg",
+              "downloadSize": "11771203",
+              "sha256": "dfee21d5e91a35baaf3b58891188f72f2cb894b2bf796ac70350a2ef9d3fb68c"
+            }
+        },
+        {
+            "version": "1.7.1",
+            "changelog": "Video button, bug fixes, Linux build fixes, and Qt upgrades: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.1",
+            "download": {
+                "date": "2023-02-10T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.7.1-macOS-x64-installer.pkg",
+                "downloadSize": 11679783,
+                "sha256": "027d1d3aeb4aaca79b21824371a0bfb915b8c43afe924a8ec2be92df719a431f"
+            }
+        },
+        {
+            "version": "1.7.0",
+            "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.0",
+            "download": {
+                "date": "2023-01-24T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.7.0-macOS-x64-installer.pkg",
+                "downloadSize": 11530594,
+                "sha256": "0e5731c2ad71aa4bd28ccf9311e312f2386c42b02abbca142777dfb06ea1b427"
+            }
+        },
         {
             "version": "1.6.8",
             "changelog": "Critical bug fix: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.8",
index be118aa3cdfbb8890f5b5c0a0a8f2be8ab0715e2..38b892f742e77b29f625923f864fabeba3c19cf8 100644 (file)
@@ -1,6 +1,36 @@
 {
     "app_name": "JackTrip",
     "releases": [
+        {
+            "version": "1.8.0",
+            "changelog": "Full changelog at https://github.com/jacktrip/jacktrip/releases/tag/v1.8.0",
+            "download": {
+                "date": "2023-03-18T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.8.0-Windows-x64-signed-installer.msi",
+                "downloadSize": "45699072",
+                "sha256": "4b6705a2e8af7f9a516fefaf119d5e6cadf93e8f40964cd52b635ced5745b267"
+            }
+        },
+        {
+            "version": "1.7.1",
+            "changelog": "Video button, bug fixes, Linux build fixes, and Qt upgrades: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.1",
+            "download": {
+                "date": "2023-02-10T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.7.1-Windows-x64-installer.msi",
+                "downloadSize": 45330432,
+                "sha256": "fb5d756afcd471ca8ae45b05b411235026f23f2178893d85ac12f39c3f66a01a"
+            }
+        },
+        {
+            "version": "1.7.0",
+            "changelog": "Start/join inactive studios, fix crashes and hanging UI on Windows: https://github.com/jacktrip/jacktrip/releases/tag/v1.7.0",
+            "download": {
+                "date": "2023-01-24T00:00:00Z",
+                "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.7.0-Windows-x64-installer.msi",
+                "downloadSize": 44572672,
+                "sha256": "a1890fe10de484f423a17118031d898abacc9b9eb2ccd35bdb4351e9411ff866"
+            }
+        },
         {
             "version": "1.6.8",
             "changelog": "Critical bug fix: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.8",
index 29bb3081bed0316f0f526a37235818b004f8bf05..c91ecfde9a91a5fe348cf6499b6f7bac9cfbd7e6 100644 (file)
@@ -47,15 +47,16 @@ using std::cout;
 using std::endl;
 
 //*******************************************************************************
-AudioInterface::AudioInterface(JackTrip* jacktrip, int NumInChans, int NumOutChans,
+AudioInterface::AudioInterface(QVarLengthArray<int> InputChans,
+                               QVarLengthArray<int> OutputChans,
+                               inputMixModeT InputMixMode,
 #ifdef WAIR  // wair
                                int NumNetRevChans,
 #endif  // endwhere
                                audioBitResolutionT AudioBitResolution,
-                               bool processWithNetwork)
-    : mJackTrip(jacktrip)
-    , mNumInChans(NumInChans)
-    , mNumOutChans(NumOutChans)
+                               bool processWithNetwork, JackTrip* jacktrip)
+    : mInputChans(InputChans)
+    , mOutputChans(OutputChans)
     ,
 #ifdef WAIR  // WAIR
     mNumNetRevChans(NumNetRevChans)
@@ -69,24 +70,28 @@ AudioInterface::AudioInterface(JackTrip* jacktrip, int NumInChans, int NumOutCha
     , mAudioOutputPacket(NULL)
     , mLoopBack(false)
     , mProcessWithNetwork(processWithNetwork)
+    , mJackTrip(jacktrip)
+    , mInputMixMode(InputMixMode)
     , mProcessingAudio(false)
 {
 #ifndef WAIR
     // cc
     // Initialize and assign memory for ProcessPlugins Buffers
-    mInProcessBuffer.resize(mNumInChans);
-    mOutProcessBuffer.resize(mNumOutChans);
+    mInProcessBuffer.resize(mInputChans.size());
+    mOutProcessBuffer.resize(mOutputChans.size());
     // Set pointer to NULL
-    for (int i = 0; i < mNumInChans; i++) {
+    for (int i = 0; i < mInProcessBuffer.size(); i++) {
         mInProcessBuffer[i] = NULL;
     }
-    for (int i = 0; i < mNumOutChans; i++) {
+    for (int i = 0; i < mOutProcessBuffer.size(); i++) {
         mOutProcessBuffer[i] = NULL;
     }
 #else   // WAIR
-    int iCnt = (mNumInChans > mNumNetRevChans) ? mNumInChans : mNumNetRevChans;
-    int oCnt = (mNumOutChans > mNumNetRevChans) ? mNumOutChans : mNumNetRevChans;
-    int aCnt = (mNumNetRevChans) ? mNumInChans : 0;
+    int iCnt =
+        (mInputChans.size() > mNumNetRevChans) ? mInputChans.size() : mNumNetRevChans;
+    int oCnt =
+        (mOutputChans.size() > mNumNetRevChans) ? mOutputChans.size() : mNumNetRevChans;
+    int aCnt = (mNumNetRevChans) ? mInputChans.size() : 0;
     for (int i = 0; i < iCnt; i++) {
         mInProcessBuffer[i] = NULL;
     }
@@ -98,11 +103,14 @@ AudioInterface::AudioInterface(JackTrip* jacktrip, int NumInChans, int NumOutCha
     }
 #endif  // endwhere
 
-    mInBufCopy.resize(mNumInChans);
-    for (int i = 0; i < mNumInChans; i++) {
+    mInBufCopy.resize(mInputChans.size());
+    for (int i = 0; i < mInputChans.size(); i++) {
         mInBufCopy[i] =
             new sample_t[MAX_AUDIO_BUFFER_SIZE];  // required for processing audio input
     }
+
+    mNumInChans  = mInputChans.size();
+    mNumOutChans = mOutputChans.size();
 }
 
 //*******************************************************************************
@@ -111,35 +119,31 @@ AudioInterface::~AudioInterface()
     delete[] mAudioInputPacket;
     delete[] mAudioOutputPacket;
 #ifndef WAIR  // NOT WAIR:
-    for (int i = 0; i < mNumInChans; i++) {
+    for (int i = 0; i < mInProcessBuffer.size(); i++) {
         delete[] mInProcessBuffer[i];
     }
 
-    for (int i = 0; i < mNumOutChans; i++) {
+    for (int i = 0; i < mOutProcessBuffer.size(); i++) {
         delete[] mOutProcessBuffer[i];
     }
 #else   // WAIR
-    int iCnt = (mNumInChans > mNumNetRevChans) ? mNumInChans : mNumNetRevChans;
-    int oCnt = (mNumOutChans > mNumNetRevChans) ? mNumOutChans : mNumNetRevChans;
-    int aCnt = (mNumNetRevChans) ? mNumInChans : 0;
-    for (int i = 0; i < iCnt; i++) {
+    for (int i = 0; i < mInProcessBuffer.size(); i++) {
         delete[] mInProcessBuffer[i];
     }
-    for (int i = 0; i < oCnt; i++) {
+    for (int i = 0; i < mOutProcessBuffer.size(); i++) {
         delete[] mOutProcessBuffer[i];
     }
-    for (int i = 0; i < aCnt; i++) {
+    for (int i = 0; i < mAPInBuffer.size(); i++) {
         delete[] mAPInBuffer[i];
     }
 #endif  // endwhere
-
     for (auto* i : qAsConst(mProcessPluginsFromNetwork)) {
         delete i;
     }
     for (auto* i : qAsConst(mProcessPluginsToNetwork)) {
         delete i;
     }
-    for (int i = 0; i < mNumInChans; i++) {
+    for (int i = 0; i < mInBufCopy.size(); i++) {
         delete[] mInBufCopy[i];
     }
 }
@@ -147,11 +151,18 @@ AudioInterface::~AudioInterface()
 //*******************************************************************************
 void AudioInterface::setup(bool /*verbose*/)
 {
+    int nChansIn               = mInputChans.size();
+    int nChansOut              = mOutputChans.size();
+    inputMixModeT inputMixMode = mInputMixMode;
+    if (inputMixMode == MIXTOMONO) {
+        nChansIn = 1;
+    }
+
     // Allocate buffer memory to read and write
     mSizeInBytesPerChannel = getSizeInBytesPerChannel();
 
-    int size_audio_input  = mSizeInBytesPerChannel * getNumInputChannels();
-    int size_audio_output = mSizeInBytesPerChannel * getNumOutputChannels();
+    int size_audio_input  = mSizeInBytesPerChannel * nChansIn;
+    int size_audio_output = mSizeInBytesPerChannel * nChansOut;
 #ifdef WAIR               // WAIR
     if (mNumNetRevChans)  // else don't change sizes
     {
@@ -167,40 +178,40 @@ void AudioInterface::setup(bool /*verbose*/)
     if (mNumNetRevChans) {
         mInProcessBuffer.resize(mNumNetRevChans);
         mOutProcessBuffer.resize(mNumNetRevChans);
-        mAPInBuffer.resize(mNumInChans);
+        mAPInBuffer.resize(nChansIn);
         mNetInBuffer.resize(mNumNetRevChans);
     } else  // don't change sizes
 #endif      // endwhere
     {
-        mInProcessBuffer.resize(mNumInChans);
-        mOutProcessBuffer.resize(mNumOutChans);
+        mInProcessBuffer.resize(nChansIn);
+        mOutProcessBuffer.resize(nChansOut);
     }
 
     int nframes = getBufferSizeInSamples();
 
 #ifndef WAIR  // NOT WAIR:
-    for (int i = 0; i < mNumInChans; i++) {
+    for (int i = 0; i < nChansIn; i++) {
         mInProcessBuffer[i] = new sample_t[nframes];
         // set memory to 0
         std::memset(mInProcessBuffer[i], 0, sizeof(sample_t) * nframes);
     }
-    for (int i = 0; i < mNumOutChans; i++) {
+    for (int i = 0; i < nChansOut; i++) {
         mOutProcessBuffer[i] = new sample_t[nframes];
         // set memory to 0
         std::memset(mOutProcessBuffer[i], 0, sizeof(sample_t) * nframes);
     }
 #else   // WAIR
-    for (int i = 0; i < ((mNumNetRevChans) ? mNumNetRevChans : mNumInChans); i++) {
+    for (int i = 0; i < ((mNumNetRevChans) ? mNumNetRevChans : nChansIn); i++) {
         mInProcessBuffer[i] = new sample_t[nframes];
         // set memory to 0
         std::memset(mInProcessBuffer[i], 0, sizeof(sample_t) * nframes);
     }
-    for (int i = 0; i < ((mNumNetRevChans) ? mNumNetRevChans : mNumOutChans); i++) {
+    for (int i = 0; i < ((mNumNetRevChans) ? mNumNetRevChans : nChansOut); i++) {
         mOutProcessBuffer[i] = new sample_t[nframes];
         // set memory to 0
         std::memset(mOutProcessBuffer[i], 0, sizeof(sample_t) * nframes);
     }
-    for (int i = 0; i < ((mNumNetRevChans) ? mNumInChans : 0); i++) {
+    for (int i = 0; i < ((mNumNetRevChans) ? nChansIn : 0); i++) {
         mAPInBuffer[i] = new sample_t[nframes];
         // set memory to 0
         std::memset(mAPInBuffer[i], 0, sizeof(sample_t) * nframes);
@@ -224,6 +235,11 @@ void AudioInterface::callback(QVarLengthArray<sample_t*>& in_buffer,
                               QVarLengthArray<sample_t*>& out_buffer,
                               unsigned int n_frames)
 {
+    int nChansIn               = mInputChans.size();
+    inputMixModeT inputMixMode = mInputMixMode;
+    if (inputMixMode == MIXTOMONO) {
+        nChansIn = 1;
+    }
     // Allocate the Process Callback
     //-------------------------------------------------------------------
     // 1) First, process incoming packets
@@ -271,10 +287,10 @@ void AudioInterface::callback(QVarLengthArray<sample_t*>& in_buffer,
         }
     }
 #else   // WAIR:
-    for (int i = 0; i < ((mNumNetRevChans) ? mNumNetRevChans : mNumOutChans); i++) {
+    for (int i = 0; i < ((mNumNetRevChans) ? mNumNetRevChans : nChansOut); i++) {
         std::memset(mOutProcessBuffer[i], 0, sizeof(sample_t) * n_frames);
     }
-    for (int i = 0; i < ((mNumNetRevChans) ? mNumNetRevChans : mNumInChans); i++) {
+    for (int i = 0; i < ((mNumNetRevChans) ? mNumNetRevChans : nChansIn); i++) {
         std::memset(mInProcessBuffer[i], 0, sizeof(sample_t) * n_frames);
         if (mNumNetRevChans) {
             if (client)
@@ -300,7 +316,7 @@ void AudioInterface::callback(QVarLengthArray<sample_t*>& in_buffer,
     int nop = mProcessPluginsToNetwork.size();  // number of OUTGOING processing modules
     if (nop > 0 || audioTesting) {              // cannot modify in_buffer, so make a copy
         // in_buffer is "in" from local audio hardware via JACK
-        if (mInBufCopy.size() < mNumInChans) {  // created in constructor above
+        if (mInBufCopy.size() < nChansIn) {  // created in constructor above
             std::cerr << "*** AudioInterface.cpp: Number of Input Channels changed - "
                          "insufficient room reserved\n";
             exit(1);
@@ -310,7 +326,7 @@ void AudioInterface::callback(QVarLengthArray<sample_t*>& in_buffer,
                       << " larger than expected max = " << MAX_AUDIO_BUFFER_SIZE << "\n";
             exit(1);
         }
-        for (int i = 0; i < mNumInChans; i++) {
+        for (int i = 0; i < nChansIn; i++) {
             std::memcpy(mInBufCopy[i], in_buffer[i], sizeof(sample_t) * n_frames);
         }
         for (int i = 0; i < nop; i++) {
@@ -345,11 +361,11 @@ void AudioInterface::callback(QVarLengthArray<sample_t*>& in_buffer,
 #define AP
 #ifndef AP
         // straight to audio out
-        for (int i = 0; i < mNumOutChans; i++) {
+        for (int i = 0; i < nChansOut; i++) {
             std::memset(out_buffer[i], 0, sizeof(sample_t) * n_frames);
         }
         for (int i = 0; i < mNumNetRevChans; i++) {
-            sample_t* mix_sample = out_buffer[i % mNumOutChans];
+            sample_t* mix_sample = out_buffer[i % nChansOut];
             sample_t* tmp_sample = mNetInBuffer[i];  // mNetInBuffer
             for (int j = 0; j < (int)n_frames; j++) {
                 mix_sample[j] += tmp_sample[j];
@@ -360,17 +376,17 @@ void AudioInterface::callback(QVarLengthArray<sample_t*>& in_buffer,
         // output through all-pass cascade
         // AP2 is 2 channel, mixes inputs to mono, then splits to two parallel AP chains
         // AP8 is 2 channel, two parallel AP chains
-        for (int i = 0; i < mNumInChans; i++) {
+        for (int i = 0; i < nChansIn; i++) {
             std::memset(mAPInBuffer[i], 0, sizeof(sample_t) * n_frames);
         }
         for (int i = 0; i < mNumNetRevChans; i++) {
-            sample_t* mix_sample = mAPInBuffer[i % mNumOutChans];
+            sample_t* mix_sample = mAPInBuffer[i % nChansOut];
             sample_t* tmp_sample = mNetInBuffer[i];
             for (int j = 0; j < n_frames; j++) {
                 mix_sample[j] += tmp_sample[j];
             }
         }  // nib16 to apib2
-        for (int i = 0; i < mNumOutChans; i++) {
+        for (int i = 0; i < nChansOut; i++) {
             std::memset(out_buffer[i], 0, sizeof(sample_t) * n_frames);
         }
         mProcessPluginsFromNetwork[APDSP]->compute(n_frames, mAPInBuffer.data(),
@@ -379,7 +395,7 @@ void AudioInterface::callback(QVarLengthArray<sample_t*>& in_buffer,
 
         //#define ADD_DIRECT
 #ifdef ADD_DIRECT
-        for (int i = 0; i < mNumInChans; i++) {
+        for (int i = 0; i < nChansIn; i++) {
             sample_t* mix_sample = out_buffer[i];
             sample_t* tmp_sample = in_buffer[i];
             for (int j = 0; j < n_frames; j++) {
@@ -412,20 +428,22 @@ void AudioInterface::callback(QVarLengthArray<sample_t*>& in_buffer,
 void AudioInterface::broadcastCallback(QVarLengthArray<sample_t*>& mon_buffer,
                                        unsigned int n_frames)
 {
+    int nChansOut = mOutputChans.size();
+
     /// \todo cast *mInBuffer[i] to the bit resolution
     // Output Process (from NETWORK to JACK)
     // ----------------------------------------------------------------
     // Read Audio buffer from RingBuffer (read from incoming packets)
     mJackTrip->receiveBroadcastPacket(mAudioOutputPacket);
     // Extract separate channels to send to Jack
-    for (int i = 0; i < mNumOutChans; i++) {
+    for (int i = 0; i < nChansOut; i++) {
         sample_t* tmp_sample = mon_buffer[i];  // sample buffer for channel i
         for (unsigned int j = 0; j < n_frames; j++) {
             // Change the bit resolution on each sample
             fromBitToSampleConversion(
                 // use interleaved channel layout
                 //&mOutputPacket[(i*mSizeInBytesPerChannel) + (j*mBitResolutionMode)],
-                &mAudioOutputPacket[(j * mBitResolutionMode * mNumOutChans)
+                &mAudioOutputPacket[(j * mBitResolutionMode * nChansOut)
                                     + (i * mBitResolutionMode)],
                 &tmp_sample[j], mBitResolutionMode);
         }
@@ -439,6 +457,8 @@ void AudioInterface::broadcastCallback(QVarLengthArray<sample_t*>& mon_buffer,
 void AudioInterface::computeProcessFromNetwork(QVarLengthArray<sample_t*>& out_buffer,
                                                unsigned int n_frames)
 {
+    int nChansOut = mOutputChans.size();
+
     /// \todo cast *mInBuffer[i] to the bit resolution
     // Output Process (from NETWORK to JACK)
     // ----------------------------------------------------------------
@@ -455,7 +475,7 @@ void AudioInterface::computeProcessFromNetwork(QVarLengthArray<sample_t*>& out_b
                 fromBitToSampleConversion(
                     // use interleaved channel layout
                     //&mOutputPacket[(i*mSizeInBytesPerChannel) + (j*mBitResolutionMode)],
-                    &mOutputPacket[(j * mBitResolutionMode * mNumOutChans)
+                    &mOutputPacket[(j * mBitResolutionMode * nChansOut)
                                    + (i * mBitResolutionMode)],
                     &tmp_sample[j], mBitResolutionMode);
             }
@@ -464,7 +484,7 @@ void AudioInterface::computeProcessFromNetwork(QVarLengthArray<sample_t*>& out_b
 #endif    // endwhere
 
         // Extract separate channels to send to Jack
-        for (int i = 0; i < mNumOutChans; i++) {
+        for (int i = 0; i < nChansOut; i++) {
             //--------
             // This should be faster for 32 bits
             // std::memcpy(mOutBuffer[i], &mOutputPacket[i*mSizeInBytesPerChannel],
@@ -476,7 +496,7 @@ void AudioInterface::computeProcessFromNetwork(QVarLengthArray<sample_t*>& out_b
                 fromBitToSampleConversion(
                     // use interleaved channel layout
                     //&mOutputPacket[(i*mSizeInBytesPerChannel) + (j*mBitResolutionMode)],
-                    &mAudioOutputPacket[(j * mBitResolutionMode * mNumOutChans)
+                    &mAudioOutputPacket[(j * mBitResolutionMode * nChansOut)
                                         + (i * mBitResolutionMode)],
                     &tmp_sample[j], mBitResolutionMode);
             }
@@ -487,6 +507,11 @@ void AudioInterface::computeProcessFromNetwork(QVarLengthArray<sample_t*>& out_b
 void AudioInterface::computeProcessToNetwork(QVarLengthArray<sample_t*>& in_buffer,
                                              unsigned int n_frames)
 {
+    int nChansIn               = mInputChans.size();
+    inputMixModeT inputMixMode = mInputMixMode;
+    if (inputMixMode == MIXTOMONO) {
+        nChansIn = 1;
+    }
     // Input Process (from JACK to NETWORK)
     // ----------------------------------------------------------------
     // Concatenate  all the channels from jack to form packet
@@ -495,7 +520,7 @@ void AudioInterface::computeProcessToNetwork(QVarLengthArray<sample_t*>& in_buff
     if (mNumNetRevChans)
         for (int i = 0; i < mNumNetRevChans; i++) {
             sample_t* tmp_sample =
-                in_buffer[i % mNumInChans];  // sample buffer for channel i
+                in_buffer[i % nChansIn];  // sample buffer for channel i
             sample_t* tmp_process_sample =
                 mInProcessBuffer[i];  // sample buffer from the output process
             sample_t tmp_result;
@@ -511,7 +536,7 @@ void AudioInterface::computeProcessToNetwork(QVarLengthArray<sample_t*>& in_buff
                     &tmp_result,
                     // use interleaved channel layout
                     //&mInputPacket[(i*mSizeInBytesPerChannel) + (j*mBitResolutionMode)],
-                    &mInputPacket[(j * mBitResolutionMode * mNumOutChans)
+                    &mInputPacket[(j * mBitResolutionMode * nChansOut)
                                   + (i * mBitResolutionMode)],
                     mBitResolutionMode);
             }
@@ -519,7 +544,7 @@ void AudioInterface::computeProcessToNetwork(QVarLengthArray<sample_t*>& in_buff
     else  // not wair
 #endif    // endwhere
 
-        for (int i = 0; i < mNumInChans; i++) {
+        for (int i = 0; i < nChansIn; i++) {
             //--------
             // This should be faster for 32 bits
             // std::memcpy(&mInputPacket[i*mSizeInBytesPerChannel], mInBuffer[i],
@@ -538,7 +563,7 @@ void AudioInterface::computeProcessToNetwork(QVarLengthArray<sample_t*>& in_buff
                     &tmp_result,
                     // use interleaved channel layout
                     //&mInputPacket[(i*mSizeInBytesPerChannel) + (j*mBitResolutionMode)],
-                    &mAudioInputPacket[(j * mBitResolutionMode * mNumInChans)
+                    &mAudioInputPacket[(j * mBitResolutionMode * nChansIn)
                                        + (i * mBitResolutionMode)],
                     mBitResolutionMode);
             }
@@ -650,9 +675,16 @@ void AudioInterface::appendProcessPluginToNetwork(ProcessPlugin* plugin)
     if (not plugin) {
         return;
     }
+
+    int nChansIn               = mInputChans.size();
+    inputMixModeT inputMixMode = mInputMixMode;
+    if (inputMixMode == MIXTOMONO) {
+        nChansIn = 1;
+    }
+
     int nTestChans   = (mAudioTesterP && mAudioTesterP->getEnabled()) ? 1 : 0;
-    int nPluginChans = mNumInChans - nTestChans;
-    assert(nTestChans == 0 || (mAudioTesterP->getSendChannel() == mNumInChans - 1));
+    int nPluginChans = nChansIn - nTestChans;
+    assert(nTestChans == 0 || (mAudioTesterP->getSendChannel() == nChansIn - 1));
     if (plugin->getNumInputs() < nPluginChans) {
         std::cerr
             << "*** AudioInterface.cpp: appendProcessPluginToNetwork: ProcessPlugin "
@@ -669,9 +701,12 @@ void AudioInterface::appendProcessPluginFromNetwork(ProcessPlugin* plugin)
     if (not plugin) {
         return;
     }
+
+    int nChansOut = mOutputChans.size();
+
     int nTestChans   = (mAudioTesterP && mAudioTesterP->getEnabled()) ? 1 : 0;
-    int nPluginChans = mNumOutChans - nTestChans;
-    assert(nTestChans == 0 || (mAudioTesterP->getSendChannel() == mNumOutChans - 1));
+    int nPluginChans = nChansOut - nTestChans;
+    assert(nTestChans == 0 || (mAudioTesterP->getSendChannel() == nChansOut - 1));
     if (plugin->getNumOutputs() > nPluginChans) {
         std::cerr
             << "*** AudioInterface.cpp: appendProcessPluginFromNetwork: ProcessPlugin "
@@ -685,6 +720,13 @@ void AudioInterface::appendProcessPluginFromNetwork(ProcessPlugin* plugin)
 
 void AudioInterface::initPlugins(bool verbose)
 {
+    int nChansIn               = mInputChans.size();
+    int nChansOut              = mOutputChans.size();
+    inputMixModeT inputMixMode = mInputMixMode;
+    if (inputMixMode == MIXTOMONO) {
+        nChansIn = 1;
+    }
+
     int nPlugins = mProcessPluginsFromNetwork.size() + mProcessPluginsToNetwork.size();
     if (nPlugins > 0) {
         if (verbose) {
@@ -694,12 +736,12 @@ void AudioInterface::initPlugins(bool verbose)
 
         for (ProcessPlugin* plugin : qAsConst(mProcessPluginsFromNetwork)) {
             plugin->setOutgoingToNetwork(false);
-            plugin->updateNumChannels(mNumInChans, mNumOutChans);
+            plugin->updateNumChannels(nChansIn, nChansOut);
             plugin->init(mSampleRate);
         }
         for (ProcessPlugin* plugin : qAsConst(mProcessPluginsToNetwork)) {
             plugin->setOutgoingToNetwork(true);
-            plugin->updateNumChannels(mNumInChans, mNumOutChans);
+            plugin->updateNumChannels(nChansIn, nChansOut);
             plugin->init(mSampleRate);
         }
     }
@@ -776,8 +818,9 @@ 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 "
+            "The currently selected devices don't support low latency. You can use them, "
+            "but you "
+            "may 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";
@@ -851,4 +894,4 @@ std::string AudioInterface::getDevicesWarningHelpUrl()
 std::string AudioInterface::getDevicesErrorHelpUrl()
 {
     return mErrorHelpUrl;
-}
\ No newline at end of file
+}
index 20967d6f774042fda493e4b4985df041d44a0114..b5ad1bec0aafa342d70209676060a21a7ffe5196 100644 (file)
@@ -86,19 +86,28 @@ class AudioInterface
         DEVICE_ERR_NO_DEVICES
     };
 
+    enum inputMixModeT : int {
+        MIX_UNSET = 0,
+        MONO      = 1,
+        STEREO    = 2,
+        MIXTOMONO = 3,
+    };
+
     /** \brief The class constructor
-     * \param jacktrip Pointer to the JackTrip class that connects all classes (mediator)
      * \param NumInChans Number of Input Channels
      * \param NumOutChans Number of Output Channels
      * \param AudioBitResolution Audio Sample Resolutions in bits
+     * \param processWithNetwork Send audio to and from the network
+     * \param jacktrip Pointer to the JackTrip class that connects all classes (mediator)
      */
     AudioInterface(
-        JackTrip* jacktrip, int NumInChans, int NumOutChans,
+        QVarLengthArray<int> InputChans, QVarLengthArray<int> OutputChans,
+        inputMixModeT InputMixMode,
 #ifdef WAIR  // wair
         int NumNetRevChans,
 #endif  // endwhere
         AudioInterface::audioBitResolutionT AudioBitResolution = AudioInterface::BIT16,
-        bool processWithNetwork                                = true);
+        bool processWithNetwork = false, JackTrip* jacktrip = nullptr);
     /// \brief The class destructor
     virtual ~AudioInterface();
 
@@ -173,8 +182,17 @@ class AudioInterface
         const AudioInterface::audioBitResolutionT sourceBitResolution);
 
     //--------------SETTERS---------------------------------------------
-    virtual void setNumInputChannels(int nchannels) { mNumInChans = nchannels; }
-    virtual void setNumOutputChannels(int nchannels) { mNumOutChans = nchannels; }
+    virtual void setInputChannels(QVarLengthArray<int> inputChans)
+    {
+        mInputChans = inputChans;
+        mNumInChans = inputChans.size();
+    }
+    virtual void setOutputChannels(QVarLengthArray<int> outputChans)
+    {
+        mOutputChans = outputChans;
+        mNumOutChans = outputChans.size();
+    }
+    virtual void setInputMixMode(inputMixModeT mode) { mInputMixMode = mode; }
     virtual void setSampleRate(uint32_t sample_rate) { mSampleRate = sample_rate; }
     virtual void setBufferSize(uint32_t buffersize) { mBufferSizeInSamples = buffersize; }
     virtual void setDeviceID(uint32_t device_id) { mDeviceID = device_id; }
@@ -199,9 +217,12 @@ class AudioInterface
 
     //--------------GETTERS---------------------------------------------
     /// \brief Get Number of Input Channels
-    virtual int getNumInputChannels() const { return mNumInChans; }
+    virtual int getNumInputChannels() const { return mInputChans.size(); }
     /// \brief Get Number of Output Channels
-    virtual int getNumOutputChannels() const { return mNumOutChans; }
+    virtual int getNumOutputChannels() const { return mOutputChans.size(); }
+    virtual QVarLengthArray<int> getInputChannels() const { return mInputChans; }
+    virtual QVarLengthArray<int> getOutputChannels() const { return mOutputChans; }
+    virtual inputMixModeT getInputMixMode() const { return mInputMixMode; }
     virtual uint32_t getBufferSizeInSamples() const { return mBufferSizeInSamples; }
     virtual uint32_t getDeviceID() const { return mDeviceID; }
     virtual std::string getInputDevice() const { return mInputDeviceName; }
@@ -238,9 +259,8 @@ class AudioInterface
     void computeProcessToNetwork(QVarLengthArray<sample_t*>& in_buffer,
                                  unsigned int n_frames);
 
-    JackTrip* mJackTrip;  ///< JackTrip Mediator Class pointer
-    int mNumInChans;      ///< Number of Input Channels
-    int mNumOutChans;     ///<  Number of Output Channels
+    QVarLengthArray<int> mInputChans;
+    QVarLengthArray<int> mOutputChans;
 #ifdef WAIR               // wair
     int mNumNetRevChans;  ///<  Number of Network Audio Channels (net comb filters)
     QVarLengthArray<sample_t*>
@@ -275,6 +295,11 @@ class AudioInterface
     AudioTester* mAudioTesterP{nullptr};
 
    protected:
+    JackTrip* mJackTrip;          ///< JackTrip Mediator Class pointer
+    int mNumInChans;              ///< Number of Input Channels
+    int mNumOutChans;             ///<  Number of Output Channels
+    inputMixModeT mInputMixMode;  ///< Input mixing mode
+
     void setDevicesWarningMsg(warningMessageT msg);
     void setDevicesErrorMsg(errorMessageT msg);
 
index c23771c3d6b91888bf285300b1e17f1493478bd2..3b5980956e1a375a91a2313525774d70568ea1cb 100644 (file)
 #include <QCryptographicHash>
 #include <QDate>
 #include <QFile>
+#include <QRandomGenerator>
 #include <QTextStream>
 #include <QThread>
 #include <iostream>
 
-Auth::Auth(const QString& fileName, QObject* parent)
+Auth::Auth(const QString& fileName, bool monitorChanges, QObject* parent)
     : QObject(parent)
     , m_days({"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"})
     , m_authFileName(fileName)
+    , m_monitorChanges(monitorChanges)
 {
-    // Load our credentials file.
-    loadAuthFile(m_authFileName);
+    if (!m_authFileName.isEmpty()) {
+        // Load our credentials file.
+        loadAuthFile(m_authFileName);
 
-    // Monitor the file for any changes. (Reload when it does.)
-    m_authFileWatcher.addPath(m_authFileName);
-    QObject::connect(&m_authFileWatcher, &QFileSystemWatcher::fileChanged, this,
-                     &Auth::reloadAuthFile, Qt::QueuedConnection);
+        // Monitor the file for any changes. (Reload when it does.)
+        if (m_monitorChanges) {
+            m_authFileWatcher.addPath(m_authFileName);
+        }
+    }
+
+    if (m_monitorChanges) {
+        QObject::connect(&m_authFileWatcher, &QFileSystemWatcher::fileChanged, this,
+                         &Auth::reloadAuthFile, Qt::QueuedConnection);
+    }
 }
 
 Auth::AuthResponseT Auth::checkCredentials(const QString& username,
@@ -81,6 +90,91 @@ Auth::AuthResponseT Auth::checkCredentials(const QString& username,
     return WRONGCREDS;
 }
 
+bool Auth::setFileName(const QString& fileName, bool readFile)
+{
+    if (m_monitorChanges) {
+        if (!m_authFileName.isEmpty()) {
+            m_authFileWatcher.removePath(m_authFileName);
+        }
+        m_authFileWatcher.addPath(fileName);
+    }
+    m_authFileName = fileName;
+    // If we're monitoring for changes, then we read the file regardless of the readFile
+    // setting.
+    if (readFile || m_monitorChanges) {
+        return loadAuthFile(m_authFileName);
+    } else {
+        m_passwordTable.clear();
+        m_timesTable.clear();
+        return true;
+    }
+}
+
+QStringList Auth::getUsers()
+{
+    return m_passwordTable.keys();
+}
+
+QString Auth::getTimes(const QString& username)
+{
+    return m_timesTable[username];
+}
+
+void Auth::deleteUser(const QString& username)
+{
+    m_passwordTable.remove(username);
+    m_timesTable.remove(username);
+}
+
+void Auth::editUser(const QString& username, const QString& times,
+                    const QString& password)
+{
+    // Can't add a new user if the password isn't set.
+    if (!m_passwordTable.contains(username) && password.isEmpty()) {
+        return;
+    }
+
+    if (!password.isEmpty()) {
+        QString hashed            = generateSha512Hash(password, generateSalt());
+        m_passwordTable[username] = hashed;
+    }
+
+    if (verifyTimeFormat(times)) {
+        m_timesTable[username] = times;
+    } else {
+        std::cout << "WARNING: Not writing invalid time string (" << times.toStdString()
+                  << ") for user " << username.toStdString();
+    }
+}
+
+bool Auth::writeFile()
+{
+    QFile file(m_authFileName);
+    if (file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
+        QTextStream output(&file);
+        QHashIterator<QString, QString> iterator(m_passwordTable);
+        while (iterator.hasNext()) {
+            iterator.next();
+            output << QStringLiteral("%1:%2:%3\n")
+                          .arg(iterator.key(), iterator.value(),
+                               m_timesTable[iterator.key()]);
+        }
+        file.close();
+    } else {
+        return false;
+    }
+    return true;
+}
+
+QString Auth::generateSalt()
+{
+    QString result;
+    for (int i = 0; i < 16; i++) {
+        result.append(char64(QRandomGenerator::global()->bounded(0, 64)));
+    }
+    return result;
+}
+
 void Auth::reloadAuthFile()
 {
     // Some text editors will replace the original file instead of modifying the existing
@@ -91,13 +185,14 @@ void Auth::reloadAuthFile()
     loadAuthFile(m_authFileName);
 }
 
-void Auth::loadAuthFile(const QString& filename)
+bool Auth::loadAuthFile(const QString& filename)
 {
-    QFile file(filename);
-    if (file.open(QIODevice::ReadOnly)) {
-        m_passwordTable.clear();
-        m_timesTable.clear();
+    m_passwordTable.clear();
+    m_timesTable.clear();
 
+    bool errorFree = true;
+    QFile file(filename);
+    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
         // Read our file into our password table
         QTextStream input(&file);
         int lineNumber = 0;
@@ -106,9 +201,14 @@ void Auth::loadAuthFile(const QString& filename)
             QStringList lineParts = input.readLine().split(QStringLiteral(":"));
             if (lineParts.count() < 3) {
                 // We don't have a correctly formatted line. Ignore it.
-                std::cout
-                    << "WARNING: Incorrectly formatted line in auth file ignored. (Line "
-                    << lineNumber << ")" << std::endl;
+                errorFree = false;
+                if (m_monitorChanges) {
+                    // Don't output warnings if we're not monitoring changes.
+                    // (This is the case when we're loading it into the editor.)
+                    std::cout << "WARNING: Incorrectly formatted line in auth file "
+                                 "ignored. (Line "
+                              << lineNumber << ")" << std::endl;
+                }
                 continue;
             }
 
@@ -125,8 +225,11 @@ void Auth::loadAuthFile(const QString& filename)
                 invalid = true;
             }
             if (invalid) {
-                std::cout << "WARNING: Invalid password hash in auth file. (Line "
-                          << lineNumber << ")" << std::endl;
+                errorFree = false;
+                if (m_monitorChanges) {
+                    std::cout << "WARNING: Invalid password hash in auth file. (Line "
+                              << lineNumber << ")" << std::endl;
+                }
                 continue;
             }
 
@@ -135,6 +238,7 @@ void Auth::loadAuthFile(const QString& filename)
         }
         file.close();
     }
+    return errorFree;
 }
 
 bool Auth::checkTime(const QString& username)
@@ -183,6 +287,38 @@ bool Auth::checkTime(const QString& username)
     return false;
 }
 
+bool Auth::verifyTimeFormat(const QString& timeString)
+{
+    QStringList times = timeString.split(QStringLiteral(","));
+    if (times.count() == 1 && times.at(0).isEmpty()) {
+        return true;
+    }
+
+    for (int i = 0; i < times.count(); i++) {
+        if (times.at(i) == QLatin1String("*")) {
+            continue;
+        }
+        if (m_days.contains(times.at(i).left(2))) {
+            QString accessTime = QString(times.at(i)).remove(0, 2);
+            if (accessTime == QLatin1String("*")) {
+                continue;
+            }
+            QStringList range = accessTime.split(QStringLiteral("-"));
+            if (range.count() == 2) {
+                QTime start = QTime::fromString(range.at(0), QStringLiteral("hhmm"));
+                QTime end   = QTime::fromString(range.at(1), QStringLiteral("hhmm"));
+
+                if (!start.isValid() || !end.isValid()) {
+                    return false;
+                }
+            }
+        } else {
+            return false;
+        }
+    }
+    return true;
+}
+
 char Auth::char64(int value)
 {
     // Returns a base 64 encoding using the following characters:
index 712f5249edce3dddc62a796680d4a4b7f0d28ba1..10cff27e4fca3ccff08b6760102a26341e352c6b 100644 (file)
@@ -54,17 +54,28 @@ class Auth : public QObject
         WRONGTIME   = 5 << 16
     };
 
-    Auth(const QString& fileName, QObject* parent = nullptr);
+    Auth(const QString& fileName = "", bool monitorChanges = false,
+         QObject* parent = nullptr);
     ~Auth();
 
     AuthResponseT checkCredentials(const QString& username, const QString& password);
+    bool setFileName(const QString& fileName, bool readFile = true);
+    QStringList getUsers();
+    QString getTimes(const QString& username);
+    void deleteUser(const QString& username);
+    void editUser(const QString& username, const QString& times,
+                  const QString& password = "");
+    bool writeFile();
+
+    QString generateSalt();
 
    private slots:
     void reloadAuthFile();
 
    private:
-    void loadAuthFile(const QString& filename);
+    bool loadAuthFile(const QString& filename);
     bool checkTime(const QString& username);
+    bool verifyTimeFormat(const QString& times);
 
     char char64(int value);
     QByteArray charGroup(unsigned char byte3, unsigned char byte2, unsigned char byte1,
@@ -77,6 +88,7 @@ class Auth : public QObject
     QHash<QString, QString> m_timesTable;
 
     QString m_authFileName;
+    bool m_monitorChanges;
     QFileSystemWatcher m_authFileWatcher;
 };
 
index fd02495008798d54238511851bfb09340f3daec7..62d9459d1431d23b4a174318c4902d647ba367c0 100644 (file)
@@ -62,55 +62,21 @@ QMutex JackAudioInterface::sJackMutex;
 
 //*******************************************************************************
 JackAudioInterface::JackAudioInterface(
-    JackTrip* jacktrip, int NumInChans, int NumOutChans,
+    QVarLengthArray<int> InputChans, QVarLengthArray<int> OutputChans,
 #ifdef WAIR  // wair
     int NumNetRevChans,
 #endif  // endwhere
-    AudioInterface::audioBitResolutionT AudioBitResolution, const QString& ClientName)
-    : AudioInterface(jacktrip, NumInChans, NumOutChans,
+    AudioInterface::audioBitResolutionT AudioBitResolution, bool processWithNetwork,
+    JackTrip* jacktrip, const QString& ClientName)
+    : AudioInterface(InputChans, OutputChans, MIX_UNSET,
 #ifdef WAIR  // wair
                      NumNetRevChans,
 #endif  // endwhere
-                     AudioBitResolution)
-    , mNumInChans(NumInChans)
-    , mNumOutChans(NumOutChans)
-#ifdef WAIR  // WAIR
-    , mNumNetRevChans(NumNetRevChans)
-#endif  // endwhere
-    , mClient(NULL)
-    , mClientName(ClientName)
-    , mBroadcast(false)
-    , mJackTrip(jacktrip)
-{
-}
-
-//*******************************************************************************
-JackAudioInterface::JackAudioInterface(
-    int NumInChans, int NumOutChans,
-#ifdef WAIR  // wair
-    int NumNetRevChans,
-#endif  // endwhere
-    AudioInterface::audioBitResolutionT AudioBitResolution, const QString& ClientName)
-    : AudioInterface(nullptr, NumInChans, NumOutChans,
-#ifdef WAIR  // wair
-                     NumNetRevChans,
-#endif  // endwhere
-                     AudioBitResolution, false)
-    , mNumInChans(NumInChans)
-    , mNumOutChans(NumOutChans)
-#ifdef WAIR  // WAIR
-    , mNumNetRevChans(NumNetRevChans)
-#endif  // endwhere
+                     AudioBitResolution, processWithNetwork, jacktrip)
     , mClient(NULL)
     , mClientName(ClientName)
     , mBroadcast(false)
-    , mJackTrip(nullptr)
 {
-    JackAudioInterface(nullptr, NumInChans, NumOutChans,
-#ifdef WAIR  // wair
-                       NumNetRevChans,
-#endif  // endwhere
-                       AudioBitResolution, ClientName);
 }
 
 //*******************************************************************************
index 7a5501ac0a9e20632f6236eb0ad59dadf713dcf2..366cebcbcec0e3c1d76613427b1b48364e73f1a8 100644 (file)
@@ -39,7 +39,7 @@
 #define __JACKAUDIOINTERFACE_H__
 
 #include <iostream>
-//#include <tr1/memory> //for shared_ptr
+// #include <tr1/memory> //for shared_ptr
 #ifdef USE_WEAK_JACK
 #include "weak_libjack.h"
 #else
@@ -67,74 +67,76 @@ class JackAudioInterface : public AudioInterface
 {
    public:
     /** \brief The class constructor
-     * \param jacktrip Pointer to the JackTrip class that connects all classes (mediator)
      * \param NumInChans Number of Input Channels
      * \param NumOutChans Number of Output Channels
      * \param AudioBitResolution Audio Sample Resolutions in bits
+     * \param processWithNetwork Send audio to and from the network
+     * \param jacktrip Pointer to the JackTrip class that connects all classes (mediator)
      * \param ClientName Client name in Jack
      */
     JackAudioInterface(
-        JackTrip* jacktrip, int NumInChans, int NumOutChans,
-#ifdef WAIR  // wair
-        int NumNetRevChans,
-#endif  // endwhere
-        AudioInterface::audioBitResolutionT AudioBitResolution = AudioInterface::BIT16,
-        const QString& ClientName = QStringLiteral("JackTrip"));
-    /// \brief Overloaded class constructor with null JackTrip pointer
-    JackAudioInterface(
-        int NumInChans, int NumOutChans,
+        QVarLengthArray<int> InputChans, QVarLengthArray<int> OutputChans,
 #ifdef WAIR  // wair
         int NumNetRevChans,
 #endif  // endwhere
         AudioInterface::audioBitResolutionT AudioBitResolution = AudioInterface::BIT16,
+        bool processWithNetwork = false, JackTrip* jacktrip = nullptr,
         const QString& ClientName = QStringLiteral("JackTrip"));
     /// \brief The class destructor
     virtual ~JackAudioInterface();
 
     /// \brief Setup the client
-    virtual void setup(bool verbose = true);
+    virtual void setup(bool verbose = true) override;
     /** \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();
+    virtual int startProcess() override;
     /** \brief Stops the process-callback thread
      * \return 0 on success, otherwise a non-zero error code
      */
-    virtual int stopProcess();
+    virtual int stopProcess() override;
     /// \brief Connect the default ports, capture to sends, and receives to playback
-    void connectDefaultPorts();
+    void connectDefaultPorts() override;
+
+    /// \brief Get Number of Input Channels
+    virtual int getNumInputChannels() const override { return mNumInChans; }
+    /// \brief Get Number of Output Channels
+    virtual int getNumOutputChannels() const override { return mNumOutChans; }
 
     //--------------SETTERS---------------------------------------------
     /// \brief Set Client Name to something different that the default (JackTrip)
-    virtual void setClientName(const QString& ClientName) { mClientName = ClientName; }
-    virtual void setSampleRate(uint32_t /*sample_rate*/)
+    virtual void setClientName(const QString& ClientName) override
+    {
+        mClientName = ClientName;
+    }
+    virtual void setSampleRate(uint32_t /*sample_rate*/) override
     {
         std::cout << "WARNING: Setting the Sample Rate in Jack mode has no effect."
                   << std::endl;
     }
-    virtual void setBufferSizeInSamples(uint32_t /*buf_size*/)
+    virtual void setBufferSizeInSamples(uint32_t /*buf_size*/) override
     {
         std::cout << "WARNING: Setting the Sample Rate in Jack mode has no effect."
                   << std::endl;
     }
-    virtual void enableBroadcastOutput() { mBroadcast = true; }
+    virtual void enableBroadcastOutput() override { mBroadcast = true; }
     //------------------------------------------------------------------
 
     //--------------GETTERS---------------------------------------------
     /// \brief Get the actual client name assigned by the Jack server
     virtual QString getAssignedClientName() final { return mAssignedClientName; }
     /// \brief Get the Jack Server Sampling Rate, in samples/second
-    virtual uint32_t getSampleRate() const;
+    virtual uint32_t getSampleRate() const override;
     /// \brief Get the Jack Server Buffer Size, in samples
-    virtual uint32_t getBufferSizeInSamples() const;
+    virtual uint32_t getBufferSizeInSamples() const override;
     /// \brief Get the Jack Server Buffer Size, in bytes
     virtual uint32_t getBufferSizeInBytes() const
     {
         return (getBufferSizeInSamples() * getAudioBitResolution() / 8);
     }
     /// \brief Get size of each audio per channel, in bytes
-    virtual size_t getSizeInBytesPerChannel() const;
+    virtual size_t getSizeInBytesPerChannel() const override;
     //------------------------------------------------------------------
 
    private:
@@ -181,12 +183,7 @@ class JackAudioInterface : public AudioInterface
     // reference : http://article.gmane.org/gmane.comp.audio.jackit/12873
     static int wrapperProcessCallback(jack_nframes_t nframes, void* arg);
 
-    int mNumInChans;      ///< Number of Input Channels
-    int mNumOutChans;     ///<  Number of Output Channels
-#ifdef WAIR               // WAIR
-    int mNumNetRevChans;  ///<  Number of Network Audio Channels (network comb filters
-#endif                    // endwhere
-    int mNumFrames;       ///< Buffer block size, in samples
+    int mNumFrames;  ///< Buffer block size, in samples
 
     jack_client_t* mClient;  ///< Jack Client
     QString mClientName;     ///< Jack Client Name
@@ -201,9 +198,7 @@ class JackAudioInterface : public AudioInterface
     QVarLengthArray<sample_t*>
         mBroadcastBuffer;  ///< Vector of Output buffer/channel to write to JACK
     bool mBroadcast;
-    QVector<ProcessPlugin*> mProcessPlugins;  ///< Vector of ProcesPlugin<EM>s</EM>
     static QMutex sJackMutex;  ///< Mutex to make thread safe jack functions that are not
-    JackTrip* mJackTrip;       ///< JackTrip Mediator Class pointer
 };
 
 #endif
index c829b7e8a9ddacc996f19b5391cd2af8a06b32af..8732c6c9642c5fcdbe1899da25e5cdf95142f532 100644 (file)
@@ -57,6 +57,7 @@
 #include <QThread>
 #include <QTimer>
 #include <QtEndian>
+#include <QtGlobal>
 #include <cstdlib>
 #include <iomanip>
 #include <iostream>
@@ -81,7 +82,8 @@ bool JackTrip::sJackStopped = false;
 
 //*******************************************************************************
 JackTrip::JackTrip(jacktripModeT JacktripMode, dataProtocolT DataProtocolType,
-                   int NumChansIn, int NumChansOut,
+                   int BaseChanIn, int NumChansIn, int BaseChanOut, int NumChansOut,
+                   AudioInterface::inputMixModeT InputMixMode,
 #ifdef WAIR  // WAIR
                    int NumNetRevChans,
 #endif  // endwhere
@@ -96,8 +98,11 @@ JackTrip::JackTrip(jacktripModeT JacktripMode, dataProtocolT DataProtocolType,
     , mDataProtocol(DataProtocolType)
     , mPacketHeaderType(PacketHeaderType)
     , mAudiointerfaceMode(JackTrip::JACK)
+    , mBaseAudioChanIn(BaseChanIn)
     , mNumAudioChansIn(NumChansIn)
+    , mBaseAudioChanOut(BaseChanOut)
     , mNumAudioChansOut(NumChansOut)
+    , mInputMixMode(InputMixMode)
 #ifdef WAIR  // WAIR
     , mNumNetRevChans(NumNetRevChans)
 #endif  // endwhere
@@ -188,12 +193,21 @@ void JackTrip::setupAudio(
         if (gVerboseFlag)
             std::cout << "  JackTrip:setupAudio before new JackAudioInterface"
                       << std::endl;
-        mAudioInterface =
-            new JackAudioInterface(this, mNumAudioChansIn, mNumAudioChansOut,
+        QVarLengthArray<int> inputChannels;
+        QVarLengthArray<int> outputChannels;
+        inputChannels.resize(mNumAudioChansIn);
+        outputChannels.resize(mNumAudioChansOut);
+        for (int i = 0; i < mNumAudioChansIn; i++) {
+            inputChannels[i] = 1 + i;
+        }
+        for (int i = 0; i < mNumAudioChansOut; i++) {
+            outputChannels[i] = 1 + i;
+        }
+        mAudioInterface = new JackAudioInterface(inputChannels, outputChannels,
 #ifdef WAIR  // wair
-                                   mNumNetRevChans,
+                                                 mNumNetRevChans,
 #endif  // endwhere
-                                   mAudioBitResolution);
+                                                 mAudioBitResolution, true, this);
 
 #ifdef WAIRTOHUB  // WAIR
 
@@ -236,8 +250,19 @@ void JackTrip::setupAudio(
 #ifdef NO_JACK  /// \todo FIX THIS REPETITION OF CODE
 #ifdef RT_AUDIO
         cout << "Warning: using non jack version, RtAudio will be used instead" << endl;
-        mAudioInterface = new RtAudioInterface(this, mNumAudioChansIn, mNumAudioChansOut,
-                                               mAudioBitResolution);
+        QVarLengthArray<int> inputChannels;
+        QVarLengthArray<int> outputChannels;
+        inputChannels.resize(mNumAudioChansIn);
+        outputChannels.resize(mNumAudioChansOut);
+        for (int i = 0; i < mNumAudioChansIn; i++) {
+            inputChannels[i] = mBaseAudioChanIn + i;
+        }
+        for (int i = 0; i < mNumAudioChansOut; i++) {
+            outputChannels[i] = mBaseAudioChanOut + i;
+        }
+        mAudioInterface =
+            new RtAudioInterface(inputChannels, outputChannels, mInputMixMode,
+                                 mAudioBitResolution, true, this);
         mAudioInterface->setSampleRate(mSampleRate);
         mAudioInterface->setDeviceID(mDeviceID);
         mAudioInterface->setInputDevice(mInputDeviceName);
@@ -245,16 +270,32 @@ void JackTrip::setupAudio(
         mAudioInterface->setBufferSizeInSamples(mAudioBufferSize);
         mAudioInterface->setup(true);
         // Setup might have reduced number of channels
+
+        // TODO: Add check for if base input channel needs to change
         mNumAudioChansIn  = mAudioInterface->getNumInputChannels();
         mNumAudioChansOut = mAudioInterface->getNumOutputChannels();
+        if (mNumAudioChansIn == 2 && mInputMixMode == AudioInterface::MIXTOMONO) {
+            mNumAudioChansIn = 1;
+        }
         // Setup might have changed buffer size
         mAudioBufferSize = mAudioInterface->getBufferSizeInSamples();
 #endif
 #endif
     } else if (mAudiointerfaceMode == JackTrip::RTAUDIO) {
 #ifdef RT_AUDIO
-        mAudioInterface = new RtAudioInterface(this, mNumAudioChansIn, mNumAudioChansOut,
-                                               mAudioBitResolution);
+        QVarLengthArray<int> inputChannels;
+        QVarLengthArray<int> outputChannels;
+        inputChannels.resize(mNumAudioChansIn);
+        outputChannels.resize(mNumAudioChansOut);
+        for (int i = 0; i < mNumAudioChansIn; i++) {
+            inputChannels[i] = mBaseAudioChanIn + i;
+        }
+        for (int i = 0; i < mNumAudioChansOut; i++) {
+            outputChannels[i] = mBaseAudioChanOut + i;
+        }
+        mAudioInterface =
+            new RtAudioInterface(inputChannels, outputChannels, mInputMixMode,
+                                 mAudioBitResolution, true, this);
         mAudioInterface->setSampleRate(mSampleRate);
         mAudioInterface->setDeviceID(mDeviceID);
         mAudioInterface->setInputDevice(mInputDeviceName);
@@ -262,8 +303,13 @@ void JackTrip::setupAudio(
         mAudioInterface->setBufferSizeInSamples(mAudioBufferSize);
         mAudioInterface->setup(true);
         // Setup might have reduced number of channels
+
+        // TODO: Add check for if base input channel needs to change
         mNumAudioChansIn  = mAudioInterface->getNumInputChannels();
         mNumAudioChansOut = mAudioInterface->getNumOutputChannels();
+        if (mNumAudioChansIn == 2 && mInputMixMode == AudioInterface::MIXTOMONO) {
+            mNumAudioChansIn = 1;
+        }
         // Setup might have changed buffer size
         mAudioBufferSize = mAudioInterface->getBufferSizeInSamples();
 #endif
@@ -466,11 +512,23 @@ void JackTrip::startProcess(
         // qDebug() << "before mJackTrip->startProcess" << mReceiverBindPort<<
         // mSenderBindPort;
 #endif
-    checkIfPortIsBinded(mReceiverBindPort);
+    if (checkIfPortIsBinded(mReceiverBindPort)) {
+        stop(QStringLiteral("Could not bind %1 UDP socket. It may already be binded by "
+                            "another process on "
+                            "your machine. Try using a different port number")
+                 .arg(mReceiverBindPort));
+        return;
+    }
     if (gVerboseFlag)
         std::cout << "  JackTrip:startProcess before checkIfPortIsBinded(mSenderBindPort)"
                   << std::endl;
-    checkIfPortIsBinded(mSenderBindPort);
+    if (checkIfPortIsBinded(mSenderBindPort)) {
+        stop(QStringLiteral("Could not bind %1 UDP socket. It may already be binded by "
+                            "another process on "
+                            "your machine. Try using a different port number")
+                 .arg(mSenderBindPort));
+        return;
+    }
     // Set all classes and parameters
     // ------------------------------
     if (gVerboseFlag)
@@ -1290,7 +1348,7 @@ int JackTrip::clientPingToServerStart()
     connect(&mTcpClient, &QTcpSocket::readyRead, this, &JackTrip::receivedDataTCP);
     connect(&mTcpClient, &QTcpSocket::connected, this, &JackTrip::receivedConnectionTCP);
     // Enable CI builds on Ubuntu 20.04 with Qt 5.12.8
-#ifdef __linux__
+#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
     connect(&mTcpClient,
             QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), this,
             &JackTrip::receivedErrorTCP);
@@ -1303,7 +1361,8 @@ int JackTrip::clientPingToServerStart()
         mAwaitingTcp = true;
         mElapsedTime = 0;
         mEndTime     = 30000;  // Timeout after 30 seconds.
-        mRetryTimer.setInterval(randomizer.bounded(0, 2000 * pow(2, mRetries)));
+        mRetryTimer.setInterval(randomizer.bounded(
+            static_cast<int>(0), static_cast<int>(2000 * pow(2, mRetries))));
         mRetryTimer.setSingleShot(true);
         mRetryTimer.disconnect();
         connect(&mRetryTimer, &QTimer::timeout, this, &JackTrip::tcpTimerTick);
@@ -1495,17 +1554,27 @@ bool JackTrip::checkPeerSettings(int8_t* full_packet)
 }
 
 //*******************************************************************************
-void JackTrip::checkIfPortIsBinded(int port)
+bool JackTrip::checkIfPortIsBinded(int port)
 {
     QUdpSocket UdpSockTemp;  // Create socket to wait for client
     // Bind the socket
     // cc        if ( !UdpSockTemp.bind(QHostAddress::AnyIPv4, port,
     // QUdpSocket::DontShareAddress) )
-    if (!UdpSockTemp.bind(QHostAddress::Any, port, QUdpSocket::DontShareAddress)) {
-        UdpSockTemp.close();  // close the socket
-        throw std::runtime_error(
-            "Could not bind UDP socket. It may already be binded by another process on "
-            "your machine. Try using a different port number");
+
+    // check all combinations to ensure the port is free
+    std::map<std::string, QHostAddress::SpecialAddress> interfaces = {
+        {"IPv4", QHostAddress::AnyIPv4},
+        {"IPv6", QHostAddress::AnyIPv6},
+        {"IPv4+IPv6", QHostAddress::Any}};
+
+    std::map<std::string, QHostAddress::SpecialAddress>::iterator it;
+    for (it = interfaces.begin(); it != interfaces.end(); it++) {
+        bool binded = UdpSockTemp.bind(it->second, port, QUdpSocket::DontShareAddress);
+        if (!binded) {
+            UdpSockTemp.close();  // close the socket
+            return true;
+        }
+        UdpSockTemp.close();
     }
-    UdpSockTemp.close();  // close the socket
+    return false;
 }
index c86151aa7c224c7713465bff0877d1e31527922f..932f32d7c8ff89c18b010d1aabcf5d4e9c20481e 100644 (file)
@@ -134,7 +134,9 @@ class JackTrip : public QObject
      */
     JackTrip(
         jacktripModeT JacktripMode = CLIENT, dataProtocolT DataProtocolType = UDP,
-        int NumChansIn = gDefaultNumInChannels, int NumChansOut = gDefaultNumInChannels,
+        int BaseChanIn = 0, int NumChansIn = gDefaultNumInChannels, int BaseChanOut = 0,
+        int NumChansOut                            = gDefaultNumInChannels,
+        AudioInterface::inputMixModeT InputMixMode = AudioInterface::MIX_UNSET,
 #ifdef WAIR  // wair
         int NumNetRevChans = 0,
 #endif  // endwhere
@@ -191,7 +193,7 @@ class JackTrip : public QObject
 
     /// \brief Check if UDP port is already binded
     /// \param port Port number
-    virtual void checkIfPortIsBinded(int port);
+    virtual bool checkIfPortIsBinded(int port);
 
     //------------------------------------------------------------------------------------
     /// \name Getters and Setters Methods to change parameters after construction
@@ -624,8 +626,11 @@ class JackTrip : public QObject
     DataProtocol::packetHeaderTypeT mPacketHeaderType;  ///< Packet Header Type
     JackTrip::audiointerfaceModeT mAudiointerfaceMode;
 
-    int mNumAudioChansIn;   ///< Number of Audio Input Channels
-    int mNumAudioChansOut;  ///< Number of Audio Output Channels
+    int mBaseAudioChanIn;                         ///< Base Audio Input Channel
+    int mNumAudioChansIn;                         ///< Number of Audio Input Channels
+    int mBaseAudioChanOut;                        ///< Base Audio Output Channel
+    int mNumAudioChansOut;                        ///< Number of Audio Output Channels
+    AudioInterface::inputMixModeT mInputMixMode;  ///< Input mix mode
 
 #ifdef WAIR                  // WAIR
     int mNumNetRevChans;     ///< Number of Network Audio Channels (net comb filters)
index 21654909d03d265d6caa74b062bc6582726b0c95..0f6a3d5c5f9858fdd0e3b69a091e499e82df5c84 100644 (file)
@@ -125,8 +125,9 @@ void JackTripWorker::setJackTrip(int id, const QString& client_address,
     //        qDebug() << "is WAIR?" <<  tmp ;
     qDebug() << "mNumNetRevChans" << mNumNetRevChans;
 
-    mJackTrip.reset(new JackTrip(JackTrip::SERVERPINGSERVER, JackTrip::UDP, 1, 1,
-                                 mNumNetRevChans, FORCEBUFFERQ));
+    mJackTrip.reset(new JackTrip(JackTrip::SERVERPINGSERVER, JackTrip::UDP, 0, 1, 0, 1,
+                                 AudioInterface::MIX_UNSET, mNumNetRevChans,
+                                 FORCEBUFFERQ));
     // Add Plugins
     if (mWAIR) {
         cout << "Running in WAIR Mode..." << endl;
@@ -146,8 +147,8 @@ void JackTripWorker::setJackTrip(int id, const QString& client_address,
         }
     }
 #else   // endwhere
-    mJackTrip.reset(new JackTrip(JackTrip::SERVERPINGSERVER, JackTrip::UDP, 1, 1,
-                                 mBufferQueueLength));
+    mJackTrip.reset(new JackTrip(JackTrip::SERVERPINGSERVER, JackTrip::UDP, 0, 1, 0, 1,
+                                 AudioInterface::MIX_UNSET, mBufferQueueLength));
 #endif  // not wair
 #endif  // ifndef __JAMTEST__
 
index 14b26edf83d4eb7beff93616484cb51de5875e77..031e284523634a0c50d7c164e6b7fef91879327a 100644 (file)
@@ -38,8 +38,6 @@
 
 #include "Meter.h"
 
-#include <QVector>
-
 #include "jacktrip_types.h"
 
 //*******************************************************************************
@@ -57,11 +55,7 @@ void Meter::init(int samplingRate)
     }
 
     /* Set meter values to the default floor */
-    mValues.resize(mNumChannels);
-    QVector<float>::iterator it;
-    for (it = mValues.begin(); it != mValues.end(); ++it) {
-        *it = threshold;
-    }
+    setupValues();
 
     /* Start timer */
     int timeout_ms = 100;
@@ -93,21 +87,25 @@ void Meter::compute(int nframes, float** inputs, float** /*_*/)
         init(fSamplingFreq);
     }
 
-    QVector<float> meterBuf(nframes);
-    float* meterBufPtr = meterBuf.data();
-    float** output     = &meterBufPtr;
+    if (mBufSize < nframes) {
+        if (mBuffer) {
+            delete mBuffer;
+        }
+        mBufSize = nframes;
+        mBuffer  = new float[mBufSize];
+    }
+
     for (int i = 0; i < mNumChannels; i++) {
         /* Run the signal through Faust  */
-        meterP[i]->compute(nframes, &inputs[i], output);
+        meterP[i]->compute(nframes, &inputs[i], &mBuffer);
 
         /* Use the existing value of mValues[i] as
            the threshold - this will be reset to the default floor of -80dB
            on each timeout */
         float max = mValues[i];
-        QVector<float>::iterator it;
-        for (it = meterBuf.begin(); it != meterBuf.end(); ++it) {
-            if (*it > max) {
-                max = *it;
+        for (int j = 0; j < nframes; j++) {
+            if (mBuffer[j] > max) {
+                max = mBuffer[j];
             }
         }
 
@@ -133,26 +131,43 @@ void Meter::updateNumChannels(int nChansIn, int nChansOut)
         mNumChannels = nChansOut;
     }
 
-    mValues.resize(mNumChannels);
-    QVector<float>::iterator it;
-    for (it = mValues.begin(); it != mValues.end(); ++it) {
-        *it = threshold;
+    setupValues();
+}
+
+void Meter::setupValues()
+{
+    if (mValues) {
+        float* oldValues = mValues;
+        // Delete our old array after 5 seconds.
+        QTimer::singleShot(5000, this, [=]() {
+            delete oldValues;
+        });
+    }
+    if (mOutValues) {
+        float* oldOut = mOutValues;
+        QTimer::singleShot(5000, this, [=]() {
+            delete oldOut;
+        });
+    }
+    mValues    = new float[mNumChannels];
+    mOutValues = new float[mNumChannels];
+    for (int i = 0; i < mNumChannels; i++) {
+        mValues[i]    = threshold;
+        mOutValues[i] = threshold;
     }
 }
 
 //*******************************************************************************
 void Meter::onTick()
 {
+    /* Set meter values to the default floor */
+    for (int i = 0; i < mNumChannels; i++) {
+        mOutValues[i] = mValues[i];
+        mValues[i]    = threshold;
+    }
+
     if (hasProcessedAudio) {
         /* Send the measurements to whatever other component requests it */
-        QVector<float> valuesCopy(mValues);
-        valuesCopy.detach();
-        emit onComputedVolumeMeasurements(valuesCopy);
-
-        /* Set meter values to the default floor */
-        QVector<float>::iterator it;
-        for (it = mValues.begin(); it != mValues.end(); ++it) {
-            *it = threshold;
-        }
+        emit onComputedVolumeMeasurements(mOutValues, mNumChannels);
     }
-}
\ No newline at end of file
+}
index a475dc039384733ea5ead6512df39185ac960b80..fa4269b7ac6e63770343055298ce8501a89536d7 100644 (file)
@@ -41,7 +41,6 @@
 
 #include <QObject>
 #include <QTimer>
-#include <QVector>
 #include <iostream>
 #include <vector>
 
@@ -73,6 +72,15 @@ class Meter : public ProcessPlugin
             delete meterP[i];
         }
         meterP.clear();
+        if (mValues) {
+            delete mValues;
+        }
+        if (mOutValues) {
+            delete mOutValues;
+        }
+        if (mBuffer) {
+            delete mBuffer;
+        }
     }
 
     void init(int samplingRate) override;
@@ -84,6 +92,8 @@ class Meter : public ProcessPlugin
     void updateNumChannels(int nChansIn, int nChansOut) override;
 
    private:
+    void setupValues();
+
     float fs;
     int mNumChannels;
     float threshold = -80.0;
@@ -91,13 +101,16 @@ class Meter : public ProcessPlugin
     bool hasProcessedAudio = false;
 
     QTimer mTimer;
-    QVector<float> mValues;
+    float* mValues    = nullptr;
+    float* mOutValues = nullptr;
+    float* mBuffer    = nullptr;
+    int mBufSize      = 0;
 
    private slots:
     void onTick();
 
    signals:
-    void onComputedVolumeMeasurements(QVector<float> values);
+    void onComputedVolumeMeasurements(float* values, int n);
 };
 
-#endif
\ No newline at end of file
+#endif
index ea6dc2cfe687277008d3b19e752e4346fca02002..5b88bcd3417f21188fcfc75003d0c031526dbc3c 100644 (file)
@@ -281,7 +281,8 @@ void Regulator::shimFPP(const int8_t* buf, int len, int seq_num)
 {
     if (seq_num != -1) {
         if (!mFPPratioIsSet) {  // first peer packet
-            mPeerFPP = len / (mNumChannels * mBitResolutionMode);
+            mPeerFPP        = len / (mNumChannels * mBitResolutionMode);
+            mPeerFPPdurMsec = 1000.0 * mPeerFPP / 48000.0;
             // bufstrategy 1 autoq mode overloads qLen with negative val
             // creates this ugly code
             if (mMsecTolerance < 0) {  // handle -q auto or, for example, -q auto10
@@ -338,7 +339,8 @@ void Regulator::shimFPP(const int8_t* buf, int len, int seq_num)
             }
         }
         pushStat->tick();
-        double adjustAuto = pushStat->calcAuto(mAutoHeadroom, mFPPdurMsec);
+        double adjustAuto =
+            pushStat->calcAuto(mAutoHeadroom, mFPPdurMsec, mPeerFPPdurMsec);
         //        qDebug() << adjustAuto;
         if (mAuto && (pushStat->lastTime > AutoInitDur))
             mMsecTolerance = adjustAuto;
@@ -664,6 +666,9 @@ void BurgAlgorithm::predict(std::vector<long double>& coeffs, std::vector<float>
 //*******************************************************************************
 ChanData::ChanData(int i, int FPP, int hist) : ch(i)
 {
+    int shrinkCoeffsFactor = 1;
+    if (FPP == 1024)
+        shrinkCoeffsFactor = 8;
     trainSamps = (hist * FPP);
     mTruth.resize(FPP, 0.0);
     mXfadedPred.resize(FPP, 0.0);
@@ -674,7 +679,7 @@ ChanData::ChanData(int i, int FPP, int hist) : ch(i)
     }
     mTrain.resize(trainSamps, 0.0);
     mPrediction.resize(trainSamps - 1, 0.0);  // ORDER
-    mCoeffs.resize(trainSamps - 2, 0.0);
+    mCoeffs.resize(trainSamps / shrinkCoeffsFactor - 2, 0.0);
     mCrossFadeDown.resize(FPP, 0.0);
     mCrossFadeUp.resize(FPP, 0.0);
     mCrossfade.resize(FPP, 0.0);
@@ -709,7 +714,7 @@ void StdDev::reset()
     max          = -999999.0;
 };
 
-double StdDev::calcAuto(double autoHeadroom, double localFPPdur)
+double StdDev::calcAuto(double autoHeadroom, double localFPPdur, double peerFPPdur)
 {
     //    qDebug() << longTermStdDev << longTermMax << AutoMax << window <<
     //    longTermCnt;
@@ -717,7 +722,9 @@ double StdDev::calcAuto(double autoHeadroom, double localFPPdur)
         return AutoMax;
     double tmp = longTermStdDev + ((longTermMax > AutoMax) ? AutoMax : longTermMax);
     if (tmp < localFPPdur)
-        tmp = localFPPdur;  // might also check peerFPP...
+        tmp = localFPPdur;
+    if (tmp < peerFPPdur)
+        tmp = peerFPPdur;
     tmp += autoHeadroom;
     return tmp;
 };
index cfe631e03b20bb553ca89adecc83a238c51a0ca8..1cad71ba73f6ac5e42676233884df7b33f3b8e9e 100644 (file)
@@ -90,7 +90,7 @@ class StdDev
    public:
     StdDev(int id, QElapsedTimer* timer, int w);
     void tick();
-    double calcAuto(double autoHeadroom, double localFPPdur);
+    double calcAuto(double autoHeadroom, double localFPPdur, double peerFPPdur);
     int mId;
     int plcOverruns;
     int plcUnderruns;
@@ -207,6 +207,7 @@ class Regulator : public RingBuffer
     int mModSeqNumPeer;
     double mAutoHeadroom;
     double mFPPdurMsec;
+    double mPeerFPPdurMsec;
     void changeGlobal(double);
     void changeGlobal_2(int);
     void changeGlobal_3(int);
index b061fddfb7ea20948c9d22ef1dbe9d2156816cea..9be6c60557fbd94394e0d8b7e52e4c5fe096b1d7 100644 (file)
 #include <cstdlib>
 
 #include "JackTrip.h"
+#include "StereoToMono.h"
 #include "jacktrip_globals.h"
 
 using std::cout;
 using std::endl;
 
 //*******************************************************************************
-RtAudioInterface::RtAudioInterface(JackTrip* jacktrip, int NumInChans, int NumOutChans,
-                                   audioBitResolutionT AudioBitResolution)
-    : AudioInterface(jacktrip, NumInChans, NumOutChans, AudioBitResolution)
+RtAudioInterface::RtAudioInterface(QVarLengthArray<int> InputChans,
+                                   QVarLengthArray<int> OutputChans,
+                                   inputMixModeT InputMixMode,
+                                   audioBitResolutionT AudioBitResolution,
+                                   bool processWithNetwork, JackTrip* jacktrip)
+    : AudioInterface(InputChans, OutputChans, InputMixMode, AudioBitResolution,
+                     processWithNetwork, jacktrip)
     , mRtAudio(NULL)
 {
 }
 
-//*******************************************************************************
-RtAudioInterface::RtAudioInterface(int NumInChans, int NumOutChans,
-                                   audioBitResolutionT AudioBitResolution)
-    : AudioInterface(nullptr, NumInChans, NumOutChans, AudioBitResolution, false)
-    , mRtAudio(NULL)
-{
-    RtAudioInterface(nullptr, NumInChans, NumOutChans, AudioBitResolution);
-}
-
 //*******************************************************************************
 RtAudioInterface::~RtAudioInterface()
 {
     if (mRtAudio != NULL) {
         delete mRtAudio;
     }
+
+    if (mStereoToMonoMixer != NULL) {
+        delete mStereoToMonoMixer;
+    }
 }
 
 //*******************************************************************************
 void RtAudioInterface::setup(bool verbose)
 {
     // Initialize Buffer array to read and write audio and members
-    mNumInChans  = getNumInputChannels();
-    mNumOutChans = getNumOutputChannels();
-    mInBuffer.resize(getNumInputChannels());
-    mOutBuffer.resize(getNumOutputChannels());
+    QVarLengthArray<int> iChans = getInputChannels();
+    QVarLengthArray<int> oChans = getOutputChannels();
+
+    uint32_t chansIn     = iChans.size();
+    uint32_t chansOut    = oChans.size();
+    uint32_t baseChanIn  = 0;
+    uint32_t baseChanOut = 0;
+
+    if (iChans.size() >= 1) {
+        int min = iChans.at(0);
+        for (int i = 0; i < iChans.size(); i++) {
+            if (iChans.at(i) < min) {
+                min = iChans.at(i);
+            }
+        }
+        if (min >= 0) {
+            baseChanIn = min;
+        }
+    }
+
+    if (oChans.size() >= 1) {
+        int min = oChans.at(0);
+        for (int i = 0; i < oChans.size(); i++) {
+            if (oChans.at(i) < min) {
+                min = iChans.at(i);
+            }
+        }
+        if (min >= 0) {
+            baseChanOut = min;
+        }
+    }
 
     cout << "Setting Up RtAudio Interface" << endl;
     cout << gPrintSeparator << endl;
@@ -93,8 +120,8 @@ void RtAudioInterface::setup(bool verbose)
 
     QStringList all_input_devices;
     QStringList all_output_devices;
-    getDeviceList(&all_input_devices, NULL, true);
-    getDeviceList(&all_output_devices, NULL, false);
+    getDeviceList(&all_input_devices, NULL, NULL, true);
+    getDeviceList(&all_output_devices, NULL, NULL, false);
 
     unsigned int n_devices_input  = all_input_devices.size();
     unsigned int n_devices_output = all_output_devices.size();
@@ -170,12 +197,20 @@ void RtAudioInterface::setup(bool verbose)
     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);
+    if (baseChanIn + chansIn > dev_info_input.inputChannels) {
+        baseChanIn = 0;
+        chansIn    = 2;
+        if (dev_info_input.inputChannels < 2) {
+            chansIn = 1;
+        }
     }
-    if (static_cast<unsigned int>(getNumOutputChannels())
-        > dev_info_output.outputChannels) {
-        setNumOutputChannels(dev_info_output.outputChannels);
+
+    if (baseChanOut + chansOut > dev_info_output.outputChannels) {
+        baseChanOut = 0;
+        chansOut    = 2;
+        if (dev_info_output.outputChannels < 2) {
+            chansOut = 1;
+        }
     }
 
     if (verbose) {
@@ -212,10 +247,12 @@ void RtAudioInterface::setup(bool verbose)
     }
 
     RtAudio::StreamParameters in_params, out_params;
-    in_params.deviceId   = index_in;
-    out_params.deviceId  = index_out;
-    in_params.nChannels  = getNumInputChannels();
-    out_params.nChannels = getNumOutputChannels();
+    in_params.deviceId      = index_in;
+    out_params.deviceId     = index_out;
+    in_params.nChannels     = chansIn;
+    out_params.nChannels    = chansOut;
+    in_params.firstChannel  = baseChanIn;
+    out_params.firstChannel = baseChanOut;
 
     RtAudio::StreamOptions options;
     // The second flag affects linux and mac only
@@ -227,8 +264,31 @@ void RtAudioInterface::setup(bool verbose)
     options.priority   = 30;
     options.streamName = gJackDefaultClientName;
 
+    // Update parent class
+    QVarLengthArray<int> updatedInputChannels;
+    QVarLengthArray<int> updatedOutputChannels;
+    updatedInputChannels.resize(chansIn);
+    updatedOutputChannels.resize(chansOut);
+    for (uint32_t i = 0; i < chansIn; i++) {
+        updatedInputChannels[i] = baseChanIn + i;
+    }
+    for (uint32_t i = 0; i < chansOut; i++) {
+        updatedOutputChannels[i] = baseChanOut + i;
+    }
+    setInputChannels(updatedInputChannels);
+    setOutputChannels(updatedOutputChannels);
+
+    // Setup buffers
+    mInBuffer.resize(chansIn);
+    mOutBuffer.resize(chansOut);
+
     unsigned int sampleRate   = getSampleRate();           // mSamplingRate;
     unsigned int bufferFrames = getBufferSizeInSamples();  // mBufferSize;
+    mStereoToMonoMixer        = new StereoToMono();
+    mStereoToMonoMixer->init(sampleRate);
+
+    // Setup parent class
+    AudioInterface::setup(verbose);
 
     try {
         // IMPORTANT NOTE: It's VERY important to remember to pass "this"
@@ -245,9 +305,6 @@ void RtAudioInterface::setup(bool verbose)
         std::cout << e.getMessage() << '\n' << std::endl;
         throw std::runtime_error(e.getMessage());
     }
-
-    // Setup parent class
-    AudioInterface::setup(verbose);
 }
 
 //*******************************************************************************
@@ -343,18 +400,23 @@ int RtAudioInterface::RtAudioCallback(void* outputBuffer, void* inputBuffer,
     inputBuffer_sample  = (sample_t*)inputBuffer;
     outputBuffer_sample = (sample_t*)outputBuffer;
 
+    int chansIn = getNumInputChannels();
     if (inputBuffer_sample != NULL && outputBuffer_sample != NULL) {
         // Get input and output buffers
         //-------------------------------------------------------------------
-        for (int i = 0; i < mNumInChans; i++) {
+        for (int i = 0; i < mInBuffer.size(); i++) {
             // Input Ports are READ ONLY
             mInBuffer[i] = inputBuffer_sample + (nFrames * i);
         }
-        for (int i = 0; i < mNumOutChans; i++) {
+
+        for (int i = 0; i < mOutBuffer.size(); i++) {
             // Output Ports are WRITABLE
             mOutBuffer[i] = outputBuffer_sample + (nFrames * i);
         }
-
+        if (chansIn == 2 && mInBuffer.size() == chansIn
+            && mInputMixMode == AudioInterface::MIXTOMONO) {
+            mStereoToMonoMixer->compute(nFrames, mInBuffer.data(), mInBuffer.data());
+        }
         AudioInterface::callback(mInBuffer, mOutBuffer, nFrames);
     }
 
@@ -417,38 +479,22 @@ int RtAudioInterface::stopProcess()
 
 //*******************************************************************************
 void RtAudioInterface::getDeviceList(QStringList* list, QStringList* categories,
-                                     bool isInput)
+                                     QList<int>* channels, 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
+        categories->clear();
+    }
+    if (channels != NULL) {
+        channels->clear();
     }
+    list->clear();
 
     // Explicitly add default device
     QString defaultDeviceName = "";
     uint32_t defaultDeviceIdx;
+    RtAudio::DeviceInfo defaultDeviceInfo;
     if (isInput) {
         defaultDeviceIdx = baseRtAudio.getDefaultInputDevice();
     } else {
@@ -456,8 +502,8 @@ void RtAudioInterface::getDeviceList(QStringList* list, QStringList* categories,
     }
 
     if (defaultDeviceIdx != 0) {
-        RtAudio::DeviceInfo info = baseRtAudio.getDeviceInfo(defaultDeviceIdx);
-        defaultDeviceName        = QString::fromStdString(info.name);
+        defaultDeviceInfo = baseRtAudio.getDeviceInfo(defaultDeviceIdx);
+        defaultDeviceName = QString::fromStdString(defaultDeviceInfo.name);
     }
 
     if (defaultDeviceName != "") {
@@ -482,6 +528,13 @@ void RtAudioInterface::getDeviceList(QStringList* list, QStringList* categories,
             categories->append(QStringLiteral(""));
 #endif
         }
+        if (channels != NULL) {
+            if (isInput) {
+                channels->append(defaultDeviceInfo.inputChannels);
+            } else {
+                channels->append(defaultDeviceInfo.outputChannels);
+            }
+        }
     }
 
     std::vector<RtAudio::Api> apis;
@@ -520,8 +573,16 @@ void RtAudioInterface::getDeviceList(QStringList* list, QStringList* categories,
 
                 if (isInput && info.inputChannels > 0) {
                     list->append(QString::fromStdString(info.name));
+                    if (channels != NULL) {
+                        channels->append(info.inputChannels);
+                    }
                 } else if (!isInput && info.outputChannels > 0) {
                     list->append(QString::fromStdString(info.name));
+                    if (channels != NULL) {
+                        channels->append(info.outputChannels);
+                    }
+                } else {
+                    continue;
                 }
 
                 if (categories == NULL) {
@@ -568,7 +629,8 @@ void RtAudioInterface::getDeviceInfoFromName(std::string deviceName, int* index,
         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 (info.probed == true
+                && deviceName == QString::fromStdString(info.name).toStdString()) {
                 if ((isInput && info.inputChannels > 0)
                     || (!isInput && info.outputChannels > 0)) {
                     *index = j;
@@ -582,4 +644,4 @@ void RtAudioInterface::getDeviceInfoFromName(std::string deviceName, int* index,
     *index = -1;
     *api   = "";
     return;
-}
\ No newline at end of file
+}
index bb1b7462f555ab9e80f22d701cc1fe06226d798a..91f2bf5f3afbab714f84d53bdaa5bdd6d73ffb01 100644 (file)
@@ -43,6 +43,7 @@
 #include <QQueue>
 
 #include "AudioInterface.h"
+#include "StereoToMono.h"
 #include "jacktrip_globals.h"
 class JackTrip;  // Forward declaration
 
@@ -51,18 +52,16 @@ class RtAudioInterface : public AudioInterface
 {
    public:
     /** \brief The class constructor
-     * \param jacktrip Pointer to the JackTrip class that connects all classes (mediator)
      * \param NumInChans Number of Input Channels
      * \param NumOutChans Number of Output Channels
      * \param AudioBitResolution Audio Sample Resolutions in bits
+     * \param processWithNetwork Send audio to and from the network
+     * \param jacktrip Pointer to the JackTrip class that connects all classes (mediator)
      */
-    RtAudioInterface(JackTrip* jacktrip, int NumInChans = gDefaultNumInChannels,
-                     int NumOutChans                        = gDefaultNumOutChannels,
-                     audioBitResolutionT AudioBitResolution = BIT16);
-    /// \brief Overloaded class constructor with null JackTrip pointer
-    RtAudioInterface(int NumInChans                         = gDefaultNumInChannels,
-                     int NumOutChans                        = gDefaultNumOutChannels,
-                     audioBitResolutionT AudioBitResolution = BIT16);
+    RtAudioInterface(QVarLengthArray<int> InputChans, QVarLengthArray<int> OutputChans,
+                     inputMixModeT InputMixMode             = AudioInterface::MIX_UNSET,
+                     audioBitResolutionT AudioBitResolution = BIT16,
+                     bool processWithNetwork = false, JackTrip* jacktrip = nullptr);
     /// \brief The class destructor
     virtual ~RtAudioInterface();
 
@@ -74,7 +73,8 @@ class RtAudioInterface : public AudioInterface
     /// \brief This has no effect in RtAudio
     virtual void connectDefaultPorts() {}
 
-    static void getDeviceList(QStringList* list, QStringList* categories, bool isInput);
+    static void getDeviceList(QStringList* list, QStringList* categories,
+                              QList<int>* channels, bool isInput);
     static void getDeviceInfoFromName(std::string deviceName, int* index,
                                       std::string* api, bool isInput);
 
@@ -96,14 +96,15 @@ class RtAudioInterface : public AudioInterface
                                      const std::string& errorText);
     void printDeviceInfo(std::string api, unsigned int deviceId);
 
-    int mNumInChans;   ///< Number of Input Channels
-    int mNumOutChans;  ///<  Number of Output Channels
     QVarLengthArray<float*>
         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 if the input and output device are the same
+
     unsigned int getDefaultDeviceForLinuxPulseAudio(bool isInput);
+
+    StereoToMono* mStereoToMonoMixer = NULL;
 };
 
 #endif  // __RTAUDIOINTERFACE_H__
index 60ac5206ebfc68d8c59d1fbd3c08cf8d1cff63da..21d119b373b07f21d2b5f36dac422b2e426b53a9 100644 (file)
@@ -38,7 +38,7 @@
 #include "Settings.h"
 
 #include "LoopBack.h"
-//#include "NetKS.h"
+// #include "NetKS.h"
 #include "Effects.h"
 
 #ifdef WAIR  // wair
@@ -46,7 +46,7 @@
 #include "ap8x2.dsp.h"
 #endif  // endwhere
 
-//#include "JackTripWorker.h"
+// #include "JackTripWorker.h"
 #include <getopt.h>  // for command line parsing
 
 #include <cassert>
@@ -73,7 +73,7 @@
 #define PRINT_BUILD_INFO cout << "Build Info: " << TO_STRING(JACKTRIP_BUILD_INFO) << endl;
 #endif
 
-//#include "ThreadPoolTest.h"
+// #include "ThreadPoolTest.h"
 
 using std::cout;
 using std::endl;
@@ -97,7 +97,9 @@ enum JTLongOptIDS {
     OPT_LISTDEVICES,
     OPT_AUDIODEVICE,
     OPT_AUDIOINPUTDEVICE,
-    OPT_AUDIOOUTPUTDEVICE
+    OPT_AUDIOOUTPUTDEVICE,
+    OPT_GUI,
+    OPT_DEEPLINK
 };
 
 //*******************************************************************************
@@ -106,7 +108,7 @@ void Settings::parseInput(int argc, char** argv)
     // Always use decimal point for floating point numbers
     setlocale(LC_NUMERIC, "C");
     // If no command arguments are given, print instructions
-    if (argc == 1) {
+    if (argc == 1 && !mGuiEnabled) {
         printUsage();
         std::exit(0);
     }
@@ -153,10 +155,11 @@ void Settings::parseInput(int argc, char** argv)
         {"clientname", required_argument, NULL, 'J'},  // Run in JamLink mode
         {"remotename", required_argument, NULL, 'K'},  // Client name on hub server
         {"appendthreadid", no_argument, NULL,
-         OPT_APPENDTHREADID},  // Append thread id to client names
+         OPT_APPENDTHREADID},                       // Append thread id to client names
+        {"srate", required_argument, NULL, 'T'},    // Set Sample Rate
+        {"bufsize", required_argument, NULL, 'F'},  // Set buffer Size
 #ifdef RT_AUDIO
-        {"rtaudio", no_argument, NULL, 'R'},      // Run in JamLink mode
-        {"srate", required_argument, NULL, 'T'},  // Set Sample Rate
+        {"rtaudio", no_argument, NULL, 'R'},  // Run in JamLink mode
         {"deviceid", required_argument, NULL,
          'd'},  // Set RTAudio device id to use (DEPRECATED)
         {"audiodevice", required_argument, NULL,
@@ -166,8 +169,7 @@ void Settings::parseInput(int argc, char** argv)
         {"audiooutputdevice", required_argument, NULL,
          OPT_AUDIOOUTPUTDEVICE},  // Set RTAudio output device by name
         {"listdevices", no_argument, NULL,
-         OPT_LISTDEVICES},                          // Set RTAudio device id to use
-        {"bufsize", required_argument, NULL, 'F'},  // Set buffer Size
+         OPT_LISTDEVICES},  // Set RTAudio device id to use
 #endif
         {"nojackportsconnect", no_argument, NULL,
          'D'},                                // Don't connect default Audio Ports
@@ -198,13 +200,15 @@ void Settings::parseInput(int argc, char** argv)
          OPT_AUTHKEY},  // Private key for server authentication
         {"credsfile", required_argument, NULL,
          OPT_AUTHCREDS},  // Username and password store for server authentication
-        {"username", required_argument, NULL,
+        {"username", optional_argument, NULL,
          OPT_AUTHUSER},  // Username when using authentication as a hub client
-        {"password", required_argument, NULL,
+        {"password", optional_argument, NULL,
          OPT_AUTHPASS},  // Password when using authentication as a hub client
         {"help", no_argument, NULL, 'h'},  // Print Help
         {"examine-audio-delay", required_argument, NULL,
          'x'},  // test mode - measure audio round-trip latency statistics
+        {"gui", no_argument, NULL, OPT_GUI},                  // Force GUI mode
+        {"deeplink", optional_argument, NULL, OPT_DEEPLINK},  // VirtualStudio Deeplink
         {NULL, 0, NULL, 0}};
 
     // Parse Command Line Arguments
@@ -213,9 +217,9 @@ void Settings::parseInput(int argc, char** argv)
     int ch;
     while ((ch = getopt_long(
                 argc, argv,
-                "n:N:H:sc:SC:o:B:P:U:q:r:b:ztlwjeJ:K:RTd:F:p:uiDvVhI:G:f:O:a:x:A",
+                "n:N:H:sc:SC:L:o:B:P:U:q:r:b:ztlwjeJ:K:RT:d:F:p:uiDvVhI:G:f:O:a:x:A",
                 longopts, NULL))
-           != -1)
+           != -1) {
         switch (ch) {
         case OPT_NUMRECEIVE:
             if (0 < atoi(optarg)) {
@@ -269,24 +273,29 @@ void Settings::parseInput(int argc, char** argv)
         case 's':  // Run in P2P server mode
             //-------------------------------------------------------
             mJackTripMode = JackTrip::SERVER;
+            checkMode();
             break;
         case 'S':  // Run in Hub server mode
             //-------------------------------------------------------
-            mJackTripServer = true;
+            mJackTripMode = JackTrip::SERVERPINGSERVER;
+            checkMode();
             break;
         case 'c':  // P2P client mode
             //-------------------------------------------------------
             mJackTripMode = JackTrip::CLIENT;
             mPeerAddress  = optarg;
+            checkMode();
             break;
         case 'L':  // set optional local host address
             //-------------------------------------------------------
-            mLocalAddress = optarg;
+            mGuiIgnoresArguments = true;
+            mLocalAddress        = optarg;
             break;
         case 'C':  // Ping to server
             //-------------------------------------------------------
             mJackTripMode = JackTrip::CLIENTTOPINGSERVER;
             mPeerAddress  = optarg;
+            checkMode();
             break;
         case 'o':  // Port Offset
             //-------------------------------------------------------
@@ -367,15 +376,18 @@ void Settings::parseInput(int argc, char** argv)
             break;
         case 'l':  // loopback
             //-------------------------------------------------------
-            mLoopBack = true;
+            mGuiIgnoresArguments = true;
+            mLoopBack            = true;
             break;
         case 'e':  // jamlink
             //-------------------------------------------------------
-            mEmptyHeader = true;
+            mGuiIgnoresArguments = true;
+            mEmptyHeader         = true;
             break;
         case 'j':  // jamlink
             //-------------------------------------------------------
-            mJamLink = true;
+            mGuiIgnoresArguments = true;
+            mJamLink             = true;
             break;
         case 'J':  // Set client Name
             //-------------------------------------------------------
@@ -388,43 +400,56 @@ void Settings::parseInput(int argc, char** argv)
         case OPT_APPENDTHREADID:
             mAppendThreadID = true;
             break;
-#ifdef RT_AUDIO
-        case 'R':  // RtAudio
+        case 'T':  // Sampling Rate
             //-------------------------------------------------------
-            mUseJack = false;
+            mGuiIgnoresArguments = true;
+            mChangeDefaultSR     = true;
+            mSampleRate          = atoi(optarg);
             break;
-        case 'T':  // Sampling Rate
+        case 'F':  // Buffer Size
             //-------------------------------------------------------
-            mChangeDefaultSR = true;
-            mSampleRate      = atoi(optarg);
+            mGuiIgnoresArguments = true;
+            mChangeDefaultBS     = true;
+            mAudioBufferSize     = atoi(optarg);
+            break;
+#ifdef RT_AUDIO
+        case 'R':  // RtAudio
+            //-------------------------------------------------------
+            mGuiIgnoresArguments = true;
+            mUseJack             = false;
             break;
         case 'd':  // RTAudio device id
             //-------------------------------------------------------
+            mGuiIgnoresArguments = true;
             cout << "WARNING: Setting device ID is deprecated and will be removed in the "
                     "future."
                  << endl;
             mChangeDefaultID = true;
             mDeviceID        = atoi(optarg);
             break;
-        case 'F':  // Buffer Size
-            //-------------------------------------------------------
-            mChangeDefaultBS = true;
-            mAudioBufferSize = atoi(optarg);
-            break;
         case OPT_AUDIODEVICE:  // Set audio device
             //-------------------------------------------------------
-            setDevicesByString(optarg);
+            mGuiIgnoresArguments = true;
+            if (!mGuiEnabled) {
+                // Don't try to parse this if we're in the GUI and ignoring it.
+                setDevicesByString(optarg);
+            }
             break;
         case OPT_AUDIOINPUTDEVICE:
-            mInputDeviceName = optarg;
+            mGuiIgnoresArguments = true;
+            mInputDeviceName     = optarg;
             break;
         case OPT_AUDIOOUTPUTDEVICE:
-            mOutputDeviceName = optarg;
+            mGuiIgnoresArguments = true;
+            mOutputDeviceName    = optarg;
             break;
         case OPT_LISTDEVICES:  // List audio devices
             //-------------------------------------------------------
-            RtAudioInterface::printDevices();
-            std::exit(0);
+            mGuiIgnoresArguments = true;
+            if (!mGuiEnabled) {
+                RtAudioInterface::printDevices();
+                std::exit(0);
+            }
             break;
 #endif
         case 'D':
@@ -484,8 +509,9 @@ void Settings::parseInput(int argc, char** argv)
             break;
         case 'I':  // IO Stat timeout
             //-------------------------------------------------------
-            mIOStatTimeout = atoi(optarg);
-            if (0 > mIOStatTimeout) {
+            mGuiIgnoresArguments = true;
+            mIOStatTimeout       = atoi(optarg);
+            if (0 > mIOStatTimeout && !mGuiEnabled) {
                 printUsage();
                 std::cerr << "--iostat ERROR: negative timeout." << endl;
                 std::exit(1);
@@ -494,6 +520,10 @@ void Settings::parseInput(int argc, char** argv)
         case 'G':  // IO Stat log file
             //-------------------------------------------------------
             {
+                mGuiIgnoresArguments = true;
+                if (mGuiEnabled) {
+                    break;
+                }
                 std::ofstream* outStream = new std::ofstream(optarg);
                 if (!outStream->is_open()) {
                     printUsage();
@@ -513,9 +543,11 @@ void Settings::parseInput(int argc, char** argv)
             }
             break;
         case OPT_SIMLOSS:  // Simulate packet loss
-            mSimulatedLossRate = atof(optarg);
+            mGuiIgnoresArguments = true;
+            mSimulatedLossRate   = atof(optarg);
             break;
         case OPT_SIMJITTER:  // Simulate jitter
+            mGuiIgnoresArguments = true;
             char* endp;
             mSimulatedJitterRate = strtod(optarg, &endp);
             if (0 == *endp) {
@@ -537,6 +569,10 @@ void Settings::parseInput(int argc, char** argv)
             break;
         case 'O': {  // Overflow limiter (i, o, or io)
             //-------------------------------------------------------
+            mGuiIgnoresArguments = true;
+            if (mGuiEnabled) {
+                break;
+            }
             char cmd[]{"--overflowlimiting (-O)"};
             if (gVerboseFlag) {
                 printf("%s argument = %s\n", cmd, optarg);
@@ -554,6 +590,10 @@ void Settings::parseInput(int argc, char** argv)
         }
         case 'a': {  // assumed number of clients (applies to outgoing limiter)
             //-------------------------------------------------------
+            mGuiIgnoresArguments = true;
+            if (mGuiEnabled) {
+                break;
+            }
             char cmd[]{"--assumednumclients (-a)"};
             if (gVerboseFlag) {
                 printf("%s argument = %s\n", cmd, optarg);
@@ -571,6 +611,10 @@ void Settings::parseInput(int argc, char** argv)
         }
         case 'f': {  // --effects (-f) effectsSpecArg
             //-------------------------------------------------------
+            mGuiIgnoresArguments = true;
+            if (mGuiEnabled) {
+                break;
+            }
             char cmd[]{"--effects (-f)"};
             int returnCode = mEffects.parseEffectsOptArg(cmd, optarg);
             if (returnCode > 1) {
@@ -596,13 +640,27 @@ void Settings::parseInput(int argc, char** argv)
             mCredsFile = optarg;
             break;
         case OPT_AUTHUSER:
+            // Need to manually check if we have our optional argument.
+            // (getopt_long will only find an optional parameter for long arguments if
+            // there's a '=' rather than a space between them. If we don't manually check,
+            // the paramater will be interpreted as an unknown argument.)
+            if (optarg == NULL && optind < argc && argv[optind][0] != '-') {
+                optarg = argv[optind++];
+            }
             mUsername = optarg;
             break;
         case OPT_AUTHPASS:
+            if (optarg == NULL && optind < argc && argv[optind][0] != '-') {
+                optarg = argv[optind++];
+            }
             mPassword = optarg;
             break;
         case 'x': {  // examine connection (test mode)
             //-------------------------------------------------------
+            mGuiIgnoresArguments = true;
+            if (mGuiEnabled) {
+                break;
+            }
             char cmd[]{"--examine-audio-delay (-x)"};
             if (tolower(optarg[0]) == 'h') {
                 mAudioTester->printHelp(cmd, ch);
@@ -620,6 +678,15 @@ void Settings::parseInput(int argc, char** argv)
             mAudioTester->setPrintIntervalSec(atof(optarg));
             break;
         }
+        // The following two options need to be handled earlier, so are all parsed in
+        // main. Included here so that we don't get an unrecognized option error.
+        case OPT_GUI:
+            break;
+        case OPT_DEEPLINK:
+            if (optarg == NULL && optind < argc && argv[optind][0] != '-') {
+                optarg = argv[optind++];
+            }
+            break;
         case ':': {
             printUsage();
             printf("*** Missing option argument *** see above for usage\n\n");
@@ -641,6 +708,7 @@ void Settings::parseInput(int argc, char** argv)
             break;
         }
         }
+    }
 
     // Warn user if undefined options where entered
     //----------------------------------------------------------------------------
@@ -669,6 +737,11 @@ void Settings::parseInput(int argc, char** argv)
     }
     // Exit if options are incompatible
     //----------------------------------------------------------------------------
+    if (mGuiEnabled) {
+        // The following tests aren't yet needed for the supported GUI options.
+        return;
+    }
+
     bool haveSomeServerMode = not((mJackTripMode == JackTrip::CLIENT)
                                   || (mJackTripMode == JackTrip::CLIENTTOPINGSERVER));
     if (mEffects.getHaveEffect() && haveSomeServerMode) {
@@ -824,17 +897,17 @@ void Settings::printUsage()
             "(sources) mixing at Hub Server (otherwise 2 assumed by -O)"
          << endl;
     cout << endl;
+    cout << " -T, --srate #                            Set the sampling rate, works only "
+            "on some audio backends (default: 48000)"
+         << endl;
+    cout << " -F, --bufsize #                          Set the buffer size, works only "
+            "on some audio backends (default: 128)"
+         << endl;
 #ifdef RT_AUDIO
     cout << "ARGUMENTS TO USE JACKTRIP WITHOUT JACK:" << endl;
     cout << " -R, --rtaudio                            Use system's default sound system "
             "instead of Jack"
          << endl;
-    cout << " -T, --srate #                            Set the sampling rate, works on "
-            "--rtaudio mode only (default: 48000)"
-         << endl;
-    cout << " -F, --bufsize #                          Set the buffer size, works on "
-            "--rtaudio mode only (default: 128)"
-         << endl;
     cout << " --audiodevice \"input-output device name\"" << endl;
     cout << " --audiodevice \"input device name\",\"output device name\"" << endl;
     cout << " --audioinputdevice \"input device name\"" << endl;
@@ -875,6 +948,9 @@ void Settings::printUsage()
     cout << " --username                               The username to use when connecting as a hub client (if not supplied here, this is read from standard input)" << endl;
     cout << " --password                               The password to use when connecting as a hub client (if not supplied here, this is read from standard input)" << endl;
     cout << endl;
+    cout << "ARGUMENTS FOR THE GUI:" << endl;
+    cout << " --gui                                   Force JackTrip to run with the GUI. If not using VirtualStudio mode, command line switches in the required arguments, optional arguments (except -l, -j, -L, --appendthreadid), audio patching, and authentication sections will be honoured, and default settings will be used where arguments aren't supplied. Options from other sections will be ignored (and the last used settings will be loaded), except for -V, and the --version and --help switches which will override this." << endl;
+    cout << endl;
     cout << "HELP ARGUMENTS: " << endl;
     cout << " -v, --version                            Prints Version Number" << endl;
     cout << " -V, --verbose                            Verbose mode, prints debug messages"
@@ -977,7 +1053,8 @@ JackTrip* Settings::getConfiguredJackTrip()
     if (gVerboseFlag)
         std::cout << "Settings:startJackTrip before new JackTrip" << std::endl;
     JackTrip* jackTrip = new JackTrip(
-        mJackTripMode, mDataProtocol, mNumAudioInputChans, mNumAudioOutputChans,
+        mJackTripMode, mDataProtocol, mBaseAudioInputChanNum, mNumAudioInputChans,
+        mBaseAudioOutputChanNum, mNumAudioOutputChans, AudioInterface::MIX_UNSET,
 #ifdef WAIR  // wair
         mNumNetRevChans,
 #endif  // endwhere
@@ -1028,15 +1105,36 @@ JackTrip* Settings::getConfiguredJackTrip()
         jackTrip->setPacketHeaderType(DataProtocol::EMPTY);
     }
 
-    // Set RtAudio
-#ifdef RT_AUDIO
-    if (!mUseJack) {
-        jackTrip->setAudiointerfaceMode(JackTrip::RTAUDIO);
+    // Change default Buffer Size
+    if (mChangeDefaultBS) {
+        jackTrip->setAudioBufferSizeInSamples(mAudioBufferSize);
+        if (!mChangeDefaultSR) {
+            jackTrip->setSampleRate(48000);
+            mSampleRate = 48000;
+        }
     }
 
     // Change default Sampling Rate
     if (mChangeDefaultSR) {
         jackTrip->setSampleRate(mSampleRate);
+        if (!mChangeDefaultBS) {
+            jackTrip->setAudioBufferSizeInSamples(128);
+            mAudioBufferSize = 128;
+        }
+    }
+
+#if defined(__unix__)
+    if (mChangeDefaultBS or mChangeDefaultSR) {
+        char latency_env[40];
+        sprintf(latency_env, "%d/%d", mAudioBufferSize, mSampleRate);
+        setenv("PIPEWIRE_LATENCY", latency_env, 1);
+    }
+#endif
+
+    // Set RtAudio
+#ifdef RT_AUDIO
+    if (!mUseJack) {
+        jackTrip->setAudiointerfaceMode(JackTrip::RTAUDIO);
     }
 
     // Change default device ID
@@ -1044,11 +1142,6 @@ JackTrip* Settings::getConfiguredJackTrip()
         jackTrip->setDeviceID(mDeviceID);
     }
 
-    // Change default Buffer Size
-    if (mChangeDefaultBS) {
-        jackTrip->setAudioBufferSizeInSamples(mAudioBufferSize);
-    }
-
     // Set device names
     jackTrip->setInputDevice(mInputDeviceName);
     jackTrip->setOutputDevice(mOutputDeviceName);
@@ -1176,3 +1269,15 @@ void Settings::disableEcho(bool disabled)
     tcsetattr(STDIN_FILENO, TCSANOW, &tty);
 #endif
 }
+
+void Settings::checkMode()
+{
+    if (mModeSet) {
+        std::cerr
+            << "Conflicting arguments given. Please choose only one of -c, -s, -C or -S."
+            << std::endl;
+        std::exit(1);
+    } else {
+        mModeSet = true;
+    }
+}
index ba10b8a695bbae51d2dcf1e4a608deab77c906ae..6b556f86836ae078015b542fd9cb7a45c805706c 100644 (file)
@@ -60,7 +60,12 @@ class Settings : public QObject
     Q_OBJECT;
 
    public:
-    Settings(QObject* parent = nullptr) : QObject(parent), mAudioTester(new AudioTester)
+    Settings(bool guiEnabled = false, QObject* parent = nullptr)
+        : QObject(parent)
+#ifndef NO_GUI
+        , mGuiEnabled(guiEnabled)
+#endif
+        , mAudioTester(new AudioTester)
     {
     }
 
@@ -77,16 +82,56 @@ class Settings : public QObject
 #endif
 
     bool getLoopBack() { return mLoopBack; }
-    bool isHubServer() { return mJackTripServer; }
+    bool isHubServer() { return mJackTripMode == JackTrip::SERVERPINGSERVER; }
+    bool guiIgnoresArguments() { return mGuiIgnoresArguments; }
+    bool isModeSet() { return mModeSet; }
+
+    JackTrip::jacktripModeT getJackTripMode() { return mJackTripMode; }
+    int getNumAudioInputChans() { return mNumAudioInputChans; }
+    int getNumAudioOutputChans() { return mNumAudioOutputChans; }
+    int getQueueLength() { return mBufferQueueLength; }
+    unsigned int getRedundancy() { return mRedundancy; }
+    QString getPeerAddress() { return mPeerAddress; }
+    int getBindPort() { return mBindPortNum; }
+    int getPeerPort() { return mPeerPortNum; }
+    int getServerUdpPort() { return mServerUdpPortNum; }
+    AudioInterface::audioBitResolutionT getAudioBitResolution()
+    {
+        return mAudioBitResolution;
+    }
+    JackTrip::underrunModeT getUnderrunMode() { return mUnderrunMode; }
+    bool getStopOnTimeout() { return mStopOnTimeout; }
+    QString getClientName() { return mClientName; }
+    QString getRemoteClientName() { return mRemoteClientName; }
+    bool getConnectDefaultAudioPorts() { return mConnectDefaultAudioPorts; }
+    int getBufferStrategy() { return mBufferStrategy; }
+    int getBroadCastQueue() { return mBroadcastQueue; }
+    bool getUseRtUdpPriority() { return mUseRtUdpPriority; }
+    unsigned int getHubConnectionMode() { return mHubConnectionMode; }
+    bool getPatchServerAudio() { return mPatchServerAudio; }
+    bool getStereoUpmix() { return mStereoUpmix; }
+    bool getUseAuthentication() { return mAuth; }
+    QString getCertFile() { return mCertFile; }
+    QString getKeyFile() { return mKeyFile; }
+    QString getCredsFile() { return mCredsFile; }
+    QString getUsername() { return mUsername; }
+    QString getPassword() { return mPassword; }
 
    private:
     void disableEcho(bool disabled);
+    void checkMode();
+
+    bool mGuiEnabled          = false;
+    bool mGuiIgnoresArguments = false;
 
     JackTrip::jacktripModeT mJackTripMode =
-        JackTrip::SERVER;                                   ///< JackTrip::jacktripModeT
+        JackTrip::SERVER;  ///< JackTrip::jacktripModeT
+    bool mModeSet                         = false;
     JackTrip::dataProtocolT mDataProtocol = JackTrip::UDP;  ///< Data Protocol
     int mNumAudioInputChans               = 2;              ///< Number of Input Channels
     int mNumAudioOutputChans              = 2;              ///< Number of Output Channels
+    int mBaseAudioInputChanNum            = 0;              ///< Base Input Channel Number
+    int mBaseAudioOutputChanNum           = 0;  ///< Base Output Channel Number
     int mBufferQueueLength =
         gDefaultQueueLength;  ///< Audio Buffer from network queue length
     AudioInterface::audioBitResolutionT mAudioBitResolution = AudioInterface::BIT16;
@@ -111,17 +156,18 @@ class Settings : public QObject
     bool mLoopBack           = false;                 ///< Loop-back mode
     bool mJamLink            = false;                 ///< JamLink mode
     bool mEmptyHeader        = false;                 ///< EmptyHeader mode
-    bool mJackTripServer     = false;                 ///< JackTrip Server mode
     QString mLocalAddress    = gDefaultLocalAddress;  ///< Local Address
     unsigned int mRedundancy = 1;      ///< Redundancy factor for data in the network
     bool mUseJack            = true;   ///< Use or not JackAduio
     bool mChangeDefaultSR    = false;  ///< Change Default Sampling Rate
     bool mChangeDefaultID    = 0;      ///< Change Default device ID
     bool mChangeDefaultBS    = false;  ///< Change Default Buffer Size
-#ifdef RT_AUDIO
+
     unsigned int mSampleRate;
-    unsigned int mDeviceID;
     unsigned int mAudioBufferSize;
+
+#ifdef RT_AUDIO
+    unsigned int mDeviceID;
     std::string mInputDeviceName, mOutputDeviceName;
 #endif
     unsigned int mHubConnectionMode = JackTrip::SERVERTOCLIENT;
diff --git a/src/StereoToMono.cpp b/src/StereoToMono.cpp
new file mode 100644 (file)
index 0000000..bd78e21
--- /dev/null
@@ -0,0 +1,73 @@
+//*****************************************************************
+/*
+  JackTrip: A System for High-Quality Audio Network Performance
+  over the Internet
+
+  Copyright (c) 2020 Julius Smith, 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 StereoToMono.cpp
+ * \author Dominick Hing
+ * \date February 2023
+ */
+
+#include "StereoToMono.h"
+
+#include <QVector>
+
+#include "jacktrip_types.h"
+
+//*******************************************************************************
+void StereoToMono::init(int samplingRate)
+{
+    ProcessPlugin::init(samplingRate);
+    if (samplingRate != fSamplingFreq) {
+        std::cerr << "Sampling rate not set by superclass!\n";
+        std::exit(1);
+    }
+
+    fs = float(fSamplingFreq);
+    stereoToMonoP->init(fs);
+
+    inited = true;
+}
+
+//*******************************************************************************
+void StereoToMono::compute(int nframes, float** inputs, float** outputs)
+{
+    if (not inited) {
+        std::cerr << "*** Stereo-to-Mono " << this
+                  << ": init never called! Doing it now.\n";
+        if (fSamplingFreq <= 0) {
+            fSamplingFreq = 48000;
+            std::cout << "Stereo-to-Mono " << this
+                      << ": *** HAD TO GUESS the sampling rate (chose 48000 Hz) ***\n";
+        }
+        init(fSamplingFreq);
+    }
+    stereoToMonoP->compute(nframes, inputs, outputs);
+}
\ No newline at end of file
diff --git a/src/StereoToMono.h b/src/StereoToMono.h
new file mode 100644 (file)
index 0000000..3fdbcaf
--- /dev/null
@@ -0,0 +1,80 @@
+//*****************************************************************
+/*
+  JackTrip: A System for High-Quality Audio Network Performance
+  over the Internet
+
+  Copyright (c) 2020 Julius Smith, 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 StereoToMono.h
+ * \author Dominick Hing
+ * \date February 2023
+ * \license MIT
+ */
+
+#ifndef __STEREOTOMONO_H__
+#define __STEREOTOMONO_H__
+
+#include <QObject>
+#include <QVector>
+#include <iostream>
+#include <vector>
+
+#include "ProcessPlugin.h"
+#include "stereotomonodsp.h"
+
+/** \brief The Meter class measures the live audio loudness level
+ */
+class StereoToMono : public ProcessPlugin
+{
+    Q_OBJECT;
+
+   public:
+    /// \brief The class constructor sets the number of channels to measure
+    StereoToMono(bool verboseFlag = false)
+    {
+        setVerbose(verboseFlag);
+        stereoToMonoP = new stereotomonodsp;
+    }
+
+    /// \brief The class destructor
+    virtual ~StereoToMono() { delete stereoToMonoP; }
+
+    void init(int samplingRate) override;
+    int getNumInputs() override { return 2; }
+    int getNumOutputs() override { return 2; }
+    void compute(int nframes, float** inputs, float** outputs) override;
+    const char* getName() const override { return "Stereo-to-Mono"; };
+
+   private:
+    float fs;
+    // int mNumChannels;
+    stereotomonodsp* stereoToMonoP;
+    // bool hasProcessedAudio = false;
+};
+
+#endif
\ No newline at end of file
index bfd862cb858139f6c28da79342061c48f5e7a7df..df281d71bc69d7b94b3666d886fb5e00e5b5c634 100644 (file)
@@ -635,7 +635,7 @@ void UdpDataProtocol::run()
         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");
@@ -644,7 +644,7 @@ void UdpDataProtocol::run()
         }
         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) {
@@ -682,7 +682,7 @@ void UdpDataProtocol::run()
             }
         }
 #else
-            
+
             // OLD CODE WITHOUT REDUNDANCY----------------------------------------------------
             /*
         // This is blocking until we get a packet...
index b9bd65bf9cf9832baa2d1f3b6ed8be2f8bc5a946..6508e6fa6d9ddb9de30c642ae7cd61827b25afa8 100644 (file)
@@ -218,7 +218,7 @@ void UdpHubListener::start()
             emit signalError(error_message);
             return;
         }
-        mAuth.reset(new Auth(mCredsFile));
+        mAuth.reset(new Auth(mCredsFile, true));
     }
 
     cout << "JackTrip HUB SERVER: Waiting for client connections..." << endl;
diff --git a/src/gui/AudioSettings.qml b/src/gui/AudioSettings.qml
new file mode 100644 (file)
index 0000000..aa902f1
--- /dev/null
@@ -0,0 +1,1110 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtGraphicalEffects 1.12
+
+Rectangle {
+    width: parent.width
+    height: parent.height
+    color: backgroundColour
+
+    property int fontBig: 20
+    property int fontMedium: 13
+    property int fontSmall: 11
+    property int fontExtraSmall: 8
+
+    property int leftMargin: 48
+    property int rightMargin: 16
+    property int bottomToolTipMargin: 8
+    property int rightToolTipMargin: 4
+    property int buttonWidth: 103
+    property int buttonHeight: 25
+
+    property string backgroundColour: virtualstudio.darkMode ? "#272525" : "#FAFBFB"
+    property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D"
+    property string buttonColour: virtualstudio.darkMode ? "#494646" : "#EAECEC"
+    property string buttonHoverColour: virtualstudio.darkMode ? "#5B5858" : "#D3D4D4"
+    property string buttonPressedColour: virtualstudio.darkMode ? "#524F4F" : "#DEE0E0"
+    property string buttonStroke: virtualstudio.darkMode ? "#80827D7D" : "#40979797"
+    property string buttonHoverStroke: virtualstudio.darkMode ? "#7B7777" : "#BABCBC"
+    property string buttonPressedStroke: virtualstudio.darkMode ? "#827D7D" : "#BABCBC"
+    property string sliderColour: virtualstudio.darkMode ? "#BABCBC" :  "#EAECEC"
+    property string sliderPressedColour: virtualstudio.darkMode ? "#ACAFAF" : "#DEE0E0"
+    property string sliderTrackColour: virtualstudio.darkMode ? "#5B5858" : "light gray"
+    property string sliderActiveTrackColour: virtualstudio.darkMode ? "light gray" : "black"
+    property string warningTextColour: "#DB0A0A"
+    property string linkText: virtualstudio.darkMode ? "#8B8D8D" : "#272525"
+    property string toolTipBackgroundColour: virtualstudio.darkMode ? "#323232" : "#F3F3F3"
+    property string toolTipTextColour: textColour
+
+    property string errorFlagColour: "#DB0A0A"
+    property string disabledButtonTextColour: virtualstudio.darkMode ? "#827D7D" : "#BABCBC"
+
+    property bool isUsingJack: virtualstudio.audioBackend == "JACK"
+    property bool isUsingRtAudio: virtualstudio.audioBackend == "RtAudio"
+    property bool hasNoBackend: !isUsingJack && !isUsingRtAudio && !virtualstudio.backendAvailable;
+
+    property int inputCurrIndex: getCurrentInputDeviceIndex()
+    property int outputCurrIndex: getCurrentOutputDeviceIndex()
+
+    function getCurrentInputDeviceIndex () {
+        if (virtualstudio.inputDevice === "") {
+            return inputComboModel.findIndex(elem => elem.type === "element");
+        }
+
+        let idx = inputComboModel.findIndex(elem => elem.type === "element" && elem.text === virtualstudio.inputDevice);
+        if (idx < 0) {
+            idx = inputComboModel.findIndex(elem => elem.type === "element");
+        }
+        return idx;
+    }
+
+    function getCurrentOutputDeviceIndex() {
+        if (virtualstudio.outputDevice === "") {
+            return outputComboModel.findIndex(elem => elem.type === "element");
+        }
+
+        let idx = outputComboModel.findIndex(elem => elem.type === "element" && elem.text === virtualstudio.outputDevice);
+        if (idx < 0) {
+            idx = outputComboModel.findIndex(elem => elem.type === "element");
+        }
+        return idx;
+    }
+
+    Item {
+        id: usingRtAudio
+        anchors.top: parent.top
+        anchors.topMargin: 24 * virtualstudio.uiScale
+        anchors.bottom: parent.bottom
+        anchors.left: parent.left
+        anchors.leftMargin: 24 * virtualstudio.uiScale
+        anchors.right: parent.right
+
+        visible: parent.isUsingRtAudio
+
+        Rectangle {
+            id: leftSpacer
+            x: 0; y: 0
+            width: 144 * virtualstudio.uiScale
+            height: 0
+            color: "transparent"
+        }
+
+        Text {
+            id: outputLabel
+            x: 0; y: 0
+            text: "Output Device"
+            font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+        }
+
+        Image {
+            id: outputHelpIcon
+            anchors.left: outputLabel.right
+            anchors.bottom: outputLabel.top
+            anchors.bottomMargin: -8 * virtualstudio.uiScale
+            source: "help.svg"
+            sourceSize: Qt.size(12 * virtualstudio.uiScale, 12 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            property bool showToolTip: false
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 0.8 : 0.2
+            }
+
+            MouseArea {
+                id: outputMouseArea
+                anchors.fill: parent
+                hoverEnabled: true
+                onEntered: outputHelpIcon.showToolTip = true
+                onExited: outputHelpIcon.showToolTip = false
+            }
+
+            ToolTip {
+                visible: outputHelpIcon.showToolTip
+                contentItem: Rectangle {
+                    color: toolTipBackgroundColour
+                    radius: 3
+                    anchors.fill: parent
+                    anchors.bottomMargin: bottomToolTipMargin * virtualstudio.uiScale
+                    anchors.rightMargin: rightToolTipMargin * virtualstudio.uiScale
+                    layer.enabled: true
+                    border.width: 1
+                    border.color: buttonStroke
+
+                    Text {
+                        anchors.centerIn: parent
+                        font { family: "Poppins"; pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale}
+                        text: qsTr("How you'll hear the studio audio")
+                        color: toolTipTextColour
+                    }
+                }
+                background: Rectangle {
+                    color: "transparent"
+                }
+            }
+        }
+
+        Image {
+            id: headphonesIcon
+            anchors.left: outputLabel.left
+            anchors.verticalCenter: outputDeviceMeters.verticalCenter
+            source: "headphones.svg"
+            sourceSize: Qt.size(28 * virtualstudio.uiScale, 28 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 1 : 0
+            }
+        }
+
+        ComboBox {
+            id: outputCombo
+            anchors.left: leftSpacer.right
+            anchors.verticalCenter: outputLabel.verticalCenter
+            anchors.rightMargin: rightMargin * virtualstudio.uiScale
+            width: parent.width - leftSpacer.width - rightMargin * virtualstudio.uiScale
+            model: outputComboModel
+            currentIndex: outputCurrIndex
+            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 = modelData.text
+                            virtualstudio.validateDevicesState()
+                        }
+                    }
+                }
+            }
+            contentItem: Text {
+                leftPadding: 12
+                font: outputCombo.font
+                horizontalAlignment: Text.AlignHLeft
+                verticalAlignment: Text.AlignVCenter
+                elide: Text.ElideRight
+                text: outputCombo.model[outputCombo.currentIndex].text ? outputCombo.model[outputCombo.currentIndex].text : ""
+            }
+        }
+
+        Meter {
+            id: outputDeviceMeters
+            anchors.left: outputCombo.left
+            anchors.right: outputCombo.right
+            anchors.top: outputCombo.bottom
+            anchors.topMargin: 16 * virtualstudio.uiScale
+            height: 24 * virtualstudio.uiScale
+            model: outputMeterModel
+            clipped: outputClipped
+            enabled: virtualstudio.audioReady && !Boolean(virtualstudio.devicesError)
+        }
+
+        Slider {
+            id: outputSlider
+            from: 0.0
+            value: audioInterface ? audioInterface.outputVolume : 0.5
+            onMoved: { audioInterface.outputVolume = value }
+            to: 1.0
+            padding: 0
+            anchors.left: outputQuieterIcon.right
+            anchors.leftMargin: 8 * virtualstudio.uiScale
+            anchors.right: outputLouderIcon.left
+            anchors.rightMargin: 8 * virtualstudio.uiScale
+            anchors.top: outputDeviceMeters.bottom
+            anchors.topMargin: 16 * virtualstudio.uiScale
+
+            background: Rectangle {
+                x: outputSlider.leftPadding
+                y: outputSlider.topPadding + outputSlider.availableHeight / 2 - height / 2
+                implicitWidth: parent.width
+                implicitHeight: 6
+                width: outputSlider.availableWidth
+                height: implicitHeight
+                radius: 4
+                color: sliderTrackColour
+
+                Rectangle {
+                    width: outputSlider.visualPosition * parent.width
+                    height: parent.height
+                    color: sliderActiveTrackColour
+                    radius: 4
+                }
+            }
+
+            handle: Rectangle {
+                x: outputSlider.leftPadding + outputSlider.visualPosition * (outputSlider.availableWidth - width)
+                y: outputSlider.topPadding + outputSlider.availableHeight / 2 - height / 2
+                implicitWidth: 26 * virtualstudio.uiScale
+                implicitHeight: 26 * virtualstudio.uiScale
+                radius: 13 * virtualstudio.uiScale
+                color: outputSlider.pressed ? sliderPressedColour : sliderColour
+                border.color: buttonStroke
+            }
+        }
+
+        Image {
+            id: outputQuieterIcon
+            anchors.left: outputCombo.left
+            anchors.verticalCenter: outputSlider.verticalCenter
+            source: "quiet.svg"
+            sourceSize: Qt.size(16 * virtualstudio.uiScale, 16 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 1 : 0
+            }
+        }
+
+        Image {
+            id: outputLouderIcon
+            anchors.right: parent.right
+            anchors.rightMargin: rightMargin * virtualstudio.uiScale
+            anchors.verticalCenter: outputSlider.verticalCenter
+            source: "loud.svg"
+            sourceSize: Qt.size(16 * virtualstudio.uiScale, 16 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 1 : 0
+            }
+        }
+
+        Text {
+            id: outputChannelsLabel
+            anchors.left: outputCombo.left
+            anchors.right: outputCombo.horizontalCenter
+            anchors.top: outputSlider.bottom
+            anchors.topMargin: 12 * virtualstudio.uiScale
+            textFormat: Text.RichText
+            text: "Output Channel(s)"
+            font { family: "Poppins"; pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+        }
+
+        ComboBox {
+            id: outputChannelsCombo
+            anchors.left: outputCombo.left
+            anchors.right: outputCombo.horizontalCenter
+            anchors.rightMargin: 8 * virtualstudio.uiScale
+            anchors.top: outputChannelsLabel.bottom
+            anchors.topMargin: 12 * virtualstudio.uiScale
+            model: outputChannelsComboModel
+            currentIndex: (() => {
+                let idx = outputChannelsComboModel.findIndex(elem => elem.baseChannel === virtualstudio.baseOutputChannel
+                    && elem.numChannels === virtualstudio.numOutputChannels);
+                if (idx < 0) {
+                    idx = 0;
+                }
+                return idx;
+            })()
+            delegate: ItemDelegate {
+                required property var modelData
+                required property int index
+                width: parent.width
+                contentItem: Text {
+                    text: modelData.label
+                }
+                highlighted: outputChannelsCombo.highlightedIndex === index
+                MouseArea {
+                    anchors.fill: parent
+                    onClicked: {
+                        outputChannelsCombo.currentIndex = index
+                        outputChannelsCombo.popup.close()
+                        virtualstudio.baseOutputChannel = modelData.baseChannel
+                        virtualstudio.numOutputChannels = modelData.numChannels
+                        virtualstudio.validateDevicesState()
+                    }
+                }
+            }
+            contentItem: Text {
+                leftPadding: 12
+                font: inputCombo.font
+                horizontalAlignment: Text.AlignHLeft
+                verticalAlignment: Text.AlignVCenter
+                elide: Text.ElideRight
+                text: outputChannelsCombo.model[outputChannelsCombo.currentIndex].label || ""
+            }
+        }
+
+        Button {
+            id: testOutputAudioButton
+            background: Rectangle {
+                radius: 6 * virtualstudio.uiScale
+                color: testOutputAudioButton.down ? buttonPressedColour : (testOutputAudioButton.hovered ? buttonHoverColour : buttonColour)
+                border.width: 1
+                border.color: testOutputAudioButton.down || testOutputAudioButton.hovered ? buttonPressedStroke : (testOutputAudioButton.hovered ? buttonHoverStroke : buttonStroke)
+            }
+            onClicked: { virtualstudio.playOutputAudio() }
+            anchors.right: parent.right
+            anchors.rightMargin: rightMargin * virtualstudio.uiScale
+            anchors.verticalCenter: outputChannelsCombo.verticalCenter
+            width: 144 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
+            Text {
+                text: "Play Test Tone"
+                font { family: "Poppins"; pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+                anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
+                color: textColour
+            }
+        }
+
+        Rectangle {
+            id: divider1
+            anchors.top: testOutputAudioButton.bottom
+            anchors.topMargin: 24 * virtualstudio.uiScale
+            width: parent.width - x - (16 * virtualstudio.uiScale); height: 2 * virtualstudio.uiScale
+            color: "#E0E0E0"
+        }
+
+        Text {
+            id: inputLabel
+            anchors.left: outputLabel.left
+            anchors.top: divider1.bottom
+            anchors.topMargin: 32 * virtualstudio.uiScale
+            text: "Input Device"
+            font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+        }
+
+        Image {
+            id: inputHelpIcon
+            anchors.left: inputLabel.right
+            anchors.bottom: inputLabel.top
+            anchors.bottomMargin: -8 * virtualstudio.uiScale
+            source: "help.svg"
+            sourceSize: Qt.size(12 * virtualstudio.uiScale, 12 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            property bool showToolTip: false
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 0.8 : 0.2
+            }
+
+            MouseArea {
+                id: inputMouseArea
+                anchors.fill: parent
+                hoverEnabled: true
+                onEntered: inputHelpIcon.showToolTip = true
+                onExited: inputHelpIcon.showToolTip = false
+            }
+
+            ToolTip {
+                visible: inputHelpIcon.showToolTip
+                contentItem: Rectangle {
+                    color: toolTipBackgroundColour
+                    radius: 3
+                    anchors.fill: parent
+                    anchors.bottomMargin: bottomToolTipMargin * virtualstudio.uiScale
+                    anchors.rightMargin: rightToolTipMargin * virtualstudio.uiScale
+                    layer.enabled: true
+                    border.width: 1
+                    border.color: buttonStroke
+
+                    Text {
+                        anchors.centerIn: parent
+                        font { family: "Poppins"; pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale}
+                        text: qsTr("Send audio to the studio (microphone, instrument, mixer, etc.)")
+                        color: toolTipTextColour
+                    }
+                }
+                background: Rectangle {
+                    color: "transparent"
+                }
+            }
+        }
+
+        Image {
+            id: microphoneIcon
+            anchors.left: outputLabel.left
+            anchors.verticalCenter: inputDeviceMeters.verticalCenter
+            source: "mic.svg"
+            sourceSize: Qt.size(32 * virtualstudio.uiScale, 32 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 1 : 0
+            }
+        }
+
+        ComboBox {
+            id: inputCombo
+            model: inputComboModel
+            currentIndex: inputCurrIndex
+            anchors.left: outputCombo.left
+            anchors.right: outputCombo.right
+            anchors.verticalCenter: inputLabel.verticalCenter
+            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 = modelData.text
+                            virtualstudio.validateDevicesState()
+                        }
+                    }
+                }
+            }
+            contentItem: Text {
+                leftPadding: 12
+                font: inputCombo.font
+                horizontalAlignment: Text.AlignHLeft
+                verticalAlignment: Text.AlignVCenter
+                elide: Text.ElideRight
+                text: inputCombo.model[inputCombo.currentIndex].text ? inputCombo.model[inputCombo.currentIndex].text : ""
+            }
+        }
+
+        Meter {
+            id: inputDeviceMeters
+            anchors.left: inputCombo.left
+            anchors.right: inputCombo.right
+            anchors.top: inputCombo.bottom
+            anchors.topMargin: 16 * virtualstudio.uiScale
+            height: 24 * virtualstudio.uiScale
+            model: inputMeterModel
+            clipped: inputClipped
+            enabled: virtualstudio.audioReady && !Boolean(virtualstudio.devicesError)
+        }
+
+        Slider {
+            id: inputSlider
+            from: 0.0
+            value: audioInterface ? audioInterface.inputVolume : 0.5
+            onMoved: { audioInterface.inputVolume = value }
+            to: 1.0
+            padding: 0
+            anchors.left: inputQuieterIcon.right
+            anchors.leftMargin: 8 * virtualstudio.uiScale
+            anchors.right: inputLouderIcon.left
+            anchors.rightMargin: 8 * virtualstudio.uiScale
+            anchors.top: inputDeviceMeters.bottom
+            anchors.topMargin: 16 * virtualstudio.uiScale
+
+            background: Rectangle {
+                x: inputSlider.leftPadding
+                y: inputSlider.topPadding + inputSlider.availableHeight / 2 - height / 2
+                implicitWidth: parent.width
+                implicitHeight: 6
+                width: inputSlider.availableWidth
+                height: implicitHeight
+                radius: 4
+                color: sliderTrackColour
+
+                Rectangle {
+                    width: inputSlider.visualPosition * parent.width
+                    height: parent.height
+                    color: sliderActiveTrackColour
+                    radius: 4
+                }
+            }
+
+            handle: Rectangle {
+                x: inputSlider.leftPadding + inputSlider.visualPosition * (inputSlider.availableWidth - width)
+                y: inputSlider.topPadding + inputSlider.availableHeight / 2 - height / 2
+                implicitWidth: 26 * virtualstudio.uiScale
+                implicitHeight: 26 * virtualstudio.uiScale
+                radius: 13 * virtualstudio.uiScale
+                color: inputSlider.pressed ? sliderPressedColour : sliderColour
+                border.color: buttonStroke
+            }
+        }
+
+        Image {
+            id: inputQuieterIcon
+            anchors.left: inputDeviceMeters.left
+            anchors.verticalCenter: inputSlider.verticalCenter
+            source: "quiet.svg"
+            sourceSize: Qt.size(16 * virtualstudio.uiScale, 16 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 1 : 0
+            }
+        }
+
+        Image {
+            id: inputLouderIcon
+            anchors.right: parent.right
+            anchors.rightMargin: rightMargin * virtualstudio.uiScale
+            anchors.verticalCenter: inputSlider.verticalCenter
+            source: "loud.svg"
+            sourceSize: Qt.size(16 * virtualstudio.uiScale, 16 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 1 : 0
+            }
+        }
+
+        Button {
+            id: hiddenInputButton
+            anchors.right: parent.right
+            anchors.rightMargin: rightMargin * virtualstudio.uiScale
+            anchors.verticalCenter: inputSlider.verticalCenter
+            width: 144 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
+            visible: false
+        }
+
+        Text {
+            id: inputChannelsLabel
+            anchors.left: inputCombo.left
+            anchors.right: inputCombo.horizontalCenter
+            anchors.top: inputSlider.bottom
+            anchors.topMargin: 12 * virtualstudio.uiScale
+            textFormat: Text.RichText
+            text: "Input Channel(s)"
+            font { family: "Poppins"; pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+        }
+
+        ComboBox {
+            id: inputChannelsCombo
+            anchors.left: inputCombo.left
+            anchors.right: inputCombo.horizontalCenter
+            anchors.rightMargin: 8 * virtualstudio.uiScale
+            anchors.top: inputChannelsLabel.bottom
+            anchors.topMargin: 12 * virtualstudio.uiScale
+            model: inputChannelsComboModel
+            currentIndex: (() => {
+                let idx = inputChannelsComboModel.findIndex(elem => elem.baseChannel === virtualstudio.baseInputChannel
+                    && elem.numChannels === virtualstudio.numInputChannels);
+                if (idx < 0) {
+                    idx = 0;
+                }
+                return idx;
+            })()
+            delegate: ItemDelegate {
+                required property var modelData
+                required property int index
+                width: parent.width
+                contentItem: Text {
+                    text: modelData.label
+                }
+                highlighted: inputChannelsCombo.highlightedIndex === index
+                MouseArea {
+                    anchors.fill: parent
+                    onClicked: {
+                        inputChannelsCombo.currentIndex = index
+                        inputChannelsCombo.popup.close()
+                        virtualstudio.baseInputChannel = modelData.baseChannel
+                        virtualstudio.numInputChannels = modelData.numChannels
+                        virtualstudio.validateDevicesState()
+                    }
+                }
+            }
+            contentItem: Text {
+                leftPadding: 12
+                font: inputCombo.font
+                horizontalAlignment: Text.AlignHLeft
+                verticalAlignment: Text.AlignVCenter
+                elide: Text.ElideRight
+                text: inputChannelsCombo.model[inputChannelsCombo.currentIndex].label || ""
+            }
+        }
+
+        Text {
+            id: inputMixModeLabel
+            anchors.left: inputCombo.horizontalCenter
+            anchors.right: inputCombo.right
+            anchors.rightMargin: 8 * virtualstudio.uiScale
+            anchors.top: inputSlider.bottom
+            anchors.topMargin: 12 * virtualstudio.uiScale
+            textFormat: Text.RichText
+            text: "Mono / Stereo"
+            font { family: "Poppins"; pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+        }
+
+        ComboBox {
+            id: inputMixModeCombo
+            anchors.left: inputCombo.horizontalCenter
+            anchors.right: inputCombo.right
+            anchors.rightMargin: 8 * virtualstudio.uiScale
+            anchors.top: inputMixModeLabel.bottom
+            anchors.topMargin: 12 * virtualstudio.uiScale
+            model: inputMixModeComboModel
+            currentIndex: (() => {
+                let idx = inputMixModeComboModel.findIndex(elem => elem.value === virtualstudio.inputMixMode);
+                if (idx < 0) {
+                    idx = 0;
+                }
+                return idx;
+            })()
+            delegate: ItemDelegate {
+                required property var modelData
+                required property int index
+                width: parent.width
+                contentItem: Text {
+                    text: modelData.label
+                }
+                highlighted: inputMixModeCombo.highlightedIndex === index
+                MouseArea {
+                    anchors.fill: parent
+                    onClicked: {
+                        inputMixModeCombo.currentIndex = index
+                        inputMixModeCombo.popup.close()
+                        virtualstudio.inputMixMode = inputMixModeComboModel[index].value
+                        virtualstudio.validateDevicesState()
+                    }
+                }
+            }
+            contentItem: Text {
+                leftPadding: 12
+                font: inputCombo.font
+                horizontalAlignment: Text.AlignHLeft
+                verticalAlignment: Text.AlignVCenter
+                elide: Text.ElideRight
+                text: inputMixModeCombo.model[inputMixModeCombo.currentIndex].label || ""
+            }
+        }
+
+        Text {
+            id: inputChannelHelpMessage
+            anchors.left: inputChannelsCombo.left
+            anchors.leftMargin: 2 * virtualstudio.uiScale
+            anchors.right: inputChannelsCombo.right
+            anchors.top: inputChannelsCombo.bottom
+            anchors.topMargin: 8 * virtualstudio.uiScale
+            textFormat: Text.RichText
+            wrapMode: Text.WordWrap
+            text: "Choose up to 2 channels"
+            font { family: "Poppins"; pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+        }
+
+        Text {
+            id: inputMixModeHelpMessage
+            anchors.left: inputMixModeCombo.left
+            anchors.leftMargin: 2 * virtualstudio.uiScale
+            anchors.right: inputMixModeCombo.right
+            anchors.top: inputMixModeCombo.bottom
+            anchors.topMargin: 8 * virtualstudio.uiScale
+            textFormat: Text.RichText
+            wrapMode: Text.WordWrap
+            text: (() => {
+                if (virtualstudio.inputMixMode === 2) {
+                    return "Treat the channels as Left and Right signals, coming through each speaker separately.";
+                } else if (virtualstudio.inputMixMode === 3) {
+                    return "Combine the channels into one central channel coming through both speakers.";
+                } else if (virtualstudio.inputMixMode === 1) {
+                    return "Send a single channel of audio";
+                } else {
+                    return "";
+                }
+            })()
+            font { family: "Poppins"; pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+        }
+
+        Text {
+            id: warningOrErrorMessage
+            anchors.left: inputLabel.left
+            anchors.right: parent.right
+            anchors.rightMargin: 16 * virtualstudio.uiScale
+            anchors.top: inputMixModeHelpMessage.bottom
+            anchors.topMargin: 8 * virtualstudio.uiScale
+            anchors.bottomMargin: 8 * 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) || Boolean(virtualstudio.devicesWarning);
+        }
+    }
+
+    Item {
+        id: usingJACK
+        anchors.top: parent.top
+        anchors.topMargin: 24 * virtualstudio.uiScale
+        anchors.bottom: parent.bottom
+        anchors.left: parent.left
+        anchors.leftMargin: leftMargin * virtualstudio.uiScale
+        anchors.right: parent.right
+
+        visible: parent.isUsingJack
+
+        Text {
+            id: jackLabel
+            x: 0; y: 0
+            width: parent.width - rightMargin * virtualstudio.uiScale
+            text: "Using JACK for audio input and output. Use QjackCtl to adjust your sample rate, buffer, and device settings."
+            font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            wrapMode: Text.WordWrap
+            color: textColour
+        }
+
+        Text {
+            id: jackOutputLabel
+            anchors.left: jackLabel.left
+            anchors.top: jackLabel.bottom
+            anchors.topMargin: 48 * virtualstudio.uiScale
+            width: 144 * virtualstudio.uiScale
+            text: "Output Volume"
+            font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            wrapMode: Text.WordWrap
+            color: textColour
+        }
+
+        Image {
+            id: jackHeadphonesIcon
+            anchors.left: jackOutputLabel.left
+            anchors.verticalCenter: jackOutputVolumeSlider.verticalCenter
+            source: "headphones.svg"
+            sourceSize: Qt.size(28 * virtualstudio.uiScale, 28 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 1 : 0
+            }
+        }
+
+        Meter {
+            id: jackOutputMeters
+            anchors.left: jackOutputLabel.right
+            anchors.right: parent.right
+            anchors.rightMargin: rightMargin * virtualstudio.uiScale
+            anchors.verticalCenter: jackOutputLabel.verticalCenter
+            height: 24 * virtualstudio.uiScale
+            model: outputMeterModel
+            clipped: outputClipped
+            enabled: virtualstudio.audioReady && !Boolean(virtualstudio.devicesError)
+        }
+
+        Button {
+            id: jackTestOutputAudioButton
+            background: Rectangle {
+                radius: 6 * virtualstudio.uiScale
+                color: jackTestOutputAudioButton.down ? buttonPressedColour : (jackTestOutputAudioButton.hovered ? buttonHoverColour : buttonColour)
+                border.width: 1
+                border.color: jackTestOutputAudioButton.down ? buttonPressedStroke : (jackTestOutputAudioButton.hovered ? buttonHoverStroke : buttonStroke)
+            }
+            onClicked: { virtualstudio.playOutputAudio() }
+            anchors.right: parent.right
+            anchors.rightMargin: rightMargin * virtualstudio.uiScale
+            anchors.verticalCenter: jackOutputVolumeSlider.verticalCenter
+            width: 144 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
+            Text {
+                text: "Play Test Tone"
+                font { family: "Poppins"; pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+                anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
+                color: textColour
+            }
+        }
+
+        Slider {
+            id: jackOutputVolumeSlider
+            from: 0.0
+            value: audioInterface ? audioInterface.outputVolume : 0.5
+            onMoved: { audioInterface.outputVolume = value }
+            to: 1.0
+            padding: 0
+            anchors.left: jackOutputQuieterButton.right
+            anchors.leftMargin: 8 * virtualstudio.uiScale
+            anchors.right: jackOutputLouderIcon.left
+            anchors.rightMargin: 8 * virtualstudio.uiScale
+            anchors.top: jackOutputMeters.bottom
+            anchors.topMargin: 16 * virtualstudio.uiScale
+
+            background: Rectangle {
+                x: jackOutputVolumeSlider.leftPadding
+                y: jackOutputVolumeSlider.topPadding + jackOutputVolumeSlider.availableHeight / 2 - height / 2
+                implicitWidth: parent.width
+                implicitHeight: 6
+                width: jackOutputVolumeSlider.availableWidth
+                height: implicitHeight
+                radius: 4
+                color: sliderTrackColour
+
+                Rectangle {
+                    width: jackOutputVolumeSlider.visualPosition * parent.width
+                    height: parent.height
+                    color: sliderActiveTrackColour
+                    radius: 4
+                }
+            }
+
+            handle: Rectangle {
+                x: jackOutputVolumeSlider.leftPadding + jackOutputVolumeSlider.visualPosition * (jackOutputVolumeSlider.availableWidth - width)
+                y: jackOutputVolumeSlider.topPadding + jackOutputVolumeSlider.availableHeight / 2 - height / 2
+                implicitWidth: 26 * virtualstudio.uiScale
+                implicitHeight: 26 * virtualstudio.uiScale
+                radius: 13 * virtualstudio.uiScale
+                color: jackOutputVolumeSlider.pressed ? sliderPressedColour : sliderColour
+                border.color: buttonStroke
+            }
+        }
+
+        Image {
+            id: jackOutputQuieterButton
+            anchors.left: jackOutputMeters.left
+            anchors.verticalCenter: jackOutputVolumeSlider.verticalCenter
+            source: "quiet.svg"
+            sourceSize: Qt.size(16 * virtualstudio.uiScale, 16 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 1 : 0
+            }
+        }
+
+        Image {
+            id: jackOutputLouderIcon
+            anchors.right: jackTestOutputAudioButton.left
+            anchors.rightMargin: rightMargin * virtualstudio.uiScale
+            anchors.verticalCenter: jackOutputVolumeSlider.verticalCenter
+            source: "loud.svg"
+            sourceSize: Qt.size(16 * virtualstudio.uiScale, 16 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 1 : 0
+            }
+        }
+
+        Text {
+            id: jackInputLabel
+            anchors.left: jackLabel.left
+            anchors.top: jackOutputVolumeSlider.bottom
+            anchors.topMargin: 48 * virtualstudio.uiScale
+            width: 144 * virtualstudio.uiScale
+            text: "Input Volume"
+            font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            wrapMode: Text.WordWrap
+            color: textColour
+        }
+
+        Image {
+            id: jackMicrophoneIcon
+            anchors.left: jackInputLabel.left
+            anchors.verticalCenter: jackInputVolumeSlider.verticalCenter
+            source: "mic.svg"
+            sourceSize: Qt.size(32 * virtualstudio.uiScale, 32 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 1 : 0
+            }
+        }
+
+        Meter {
+            id: jackInputMeters
+            anchors.left: jackInputLabel.right
+            anchors.right: parent.right
+            anchors.rightMargin: rightMargin * virtualstudio.uiScale
+            anchors.verticalCenter: jackInputLabel.verticalCenter
+            height: 24 * virtualstudio.uiScale
+            model: inputMeterModel
+            clipped: inputClipped
+            enabled: virtualstudio.audioReady && !Boolean(virtualstudio.devicesError)
+        }
+
+        Slider {
+            id: jackInputVolumeSlider
+            from: 0.0
+            value: audioInterface ? audioInterface.inputVolume : 0.5
+            onMoved: { audioInterface.inputVolume = value }
+            to: 1.0
+            padding: 0
+            anchors.left: jackInputQuieterButton.right
+            anchors.leftMargin: 8 * virtualstudio.uiScale
+            anchors.right: jackInputLouderIcon.left
+            anchors.rightMargin: 8 * virtualstudio.uiScale
+            anchors.top: jackInputMeters.bottom
+            anchors.topMargin: 16 * virtualstudio.uiScale
+
+            background: Rectangle {
+                x: jackInputVolumeSlider.leftPadding
+                y: jackInputVolumeSlider.topPadding + jackInputVolumeSlider.availableHeight / 2 - height / 2
+                implicitWidth: parent.width
+                implicitHeight: 6
+                width: jackInputVolumeSlider.availableWidth
+                height: implicitHeight
+                radius: 4
+                color: sliderTrackColour
+
+                Rectangle {
+                    width: jackInputVolumeSlider.visualPosition * parent.width
+                    height: parent.height
+                    color: sliderActiveTrackColour
+                    radius: 4
+                }
+            }
+
+            handle: Rectangle {
+                x: jackInputVolumeSlider.leftPadding + jackInputVolumeSlider.visualPosition * (jackInputVolumeSlider.availableWidth - width)
+                y: jackInputVolumeSlider.topPadding + jackInputVolumeSlider.availableHeight / 2 - height / 2
+                implicitWidth: 26 * virtualstudio.uiScale
+                implicitHeight: 26 * virtualstudio.uiScale
+                radius: 13 * virtualstudio.uiScale
+                color: jackInputVolumeSlider.pressed ? sliderPressedColour : sliderColour
+                border.color: buttonStroke
+            }
+        }
+
+        Image {
+            id: jackInputQuieterButton
+            anchors.left: jackInputMeters.left
+            anchors.verticalCenter: jackInputVolumeSlider.verticalCenter
+            source: "quiet.svg"
+            sourceSize: Qt.size(16 * virtualstudio.uiScale, 16 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 1 : 0
+            }
+        }
+
+        Image {
+            id: jackInputLouderIcon
+            anchors.right: parent.right
+            anchors.rightMargin: rightMargin * virtualstudio.uiScale
+            anchors.verticalCenter: jackInputVolumeSlider.verticalCenter
+            source: "loud.svg"
+            sourceSize: Qt.size(16 * virtualstudio.uiScale, 16 * virtualstudio.uiScale)
+            fillMode: Image.PreserveAspectFit
+            smooth: true
+
+            Colorize {
+                anchors.fill: parent
+                source: parent
+                hue: 0
+                saturation: 0
+                lightness: virtualstudio.darkMode ? 1 : 0
+            }
+        }
+
+        Button {
+            id: jackHiddenInputButton
+            anchors.right: parent.right
+            anchors.rightMargin: rightMargin * virtualstudio.uiScale
+            anchors.verticalCenter: jackInputVolumeSlider.verticalCenter
+            width: 144 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
+            visible: false
+        }
+
+    }
+
+    Item {
+        id: noBackend
+        anchors.top: parent.top
+        anchors.topMargin: 24 * virtualstudio.uiScale
+        anchors.bottom: parent.bottom
+        anchors.left: parent.left
+        anchors.leftMargin: leftMargin * virtualstudio.uiScale
+        anchors.right: parent.right
+
+        visible: parent.hasNoBackend
+
+        Text {
+            id: noBackendLabel
+            x: 0; y: 0
+            width: parent.width - (16 * virtualstudio.uiScale)
+            text: "JackTrip has been compiled without an audio backend. Please rebuild with the rtaudio flag or without the nojack flag."
+            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+            wrapMode: Text.WordWrap
+            color: textColour
+        }
+    }
+}
\ No newline at end of file
index 01026db274cf6bcf5ff45a1eaacaf6c0b708707d..4b6e719e0d6913d844c590a2895fbec3641e3d07 100644 (file)
@@ -1,6 +1,5 @@
 import QtQuick 2.12
 import QtQuick.Controls 2.12
-import QtGraphicalEffects 1.12
 
 Item {
     width: parent.width; height: parent.height
@@ -32,6 +31,7 @@ Item {
     property string buttonStroke: virtualstudio.darkMode ? "#80827D7D" : "#40979797"
     property string buttonHoverStroke: virtualstudio.darkMode ? "#7B7777" : "#BABCBC"
     property string buttonPressedStroke: virtualstudio.darkMode ? "#827D7D" : "#BABCBC"
+    property string createButtonStroke: virtualstudio.darkMode ? "#AB0F0F" : "#0F0D0D"
     
     function refresh() {
         scrollY = studioListView.contentY;
@@ -81,7 +81,7 @@ Item {
             flagImage: bannerURL ? bannerURL : flag
             studioName: name
             publicStudio: isPublic
-            manageable: isManageable
+            admin: isAdmin
             available: canConnect
             connected: false
             studioId: id ? id : ""
@@ -181,15 +181,8 @@ Item {
                         radius: 6 * virtualstudio.uiScale
                         color: createButton.down ? "#E7E8E8" : "#F2F3F3"
                         border.width: 1
-                        border.color: createButton.down ? "#B0B5B5" : "#EAEBEB"
+                        border.color: createButton.down ? "#B0B5B5" : createButtonStroke
                         layer.enabled: createButton.hovered && !createButton.down
-                        layer.effect: DropShadow {
-                            horizontalOffset: 1 * virtualstudio.uiScale
-                            verticalOffset: 1 * virtualstudio.uiScale
-                            radius: 8.0 * virtualstudio.uiScale
-                            samples: 17
-                            color: "#80A1A1A1"
-                        }
                     }
                     onClicked: { virtualstudio.createStudio(); }
                     anchors.top: createStudioMessage.bottom
index bd728c96af471f975a0171c66bd1873a11ae4f72..c1528185fc8e3170dc67af78021a39ab2cfbc404 100644 (file)
@@ -21,7 +21,7 @@ Item {
     property string studioStatus: (virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].status : "")
     property bool showReadyScreen: studioStatus === "Ready"
     property bool showStartingScreen: studioStatus === "Starting"
-    property bool showStoppingScreen: (virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].isManageable && !serverModel[virtualstudio.currentStudio].enabled && serverModel[virtualstudio.currentStudio].cloudId !== "" : false)
+    property bool showStoppingScreen: (virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].isAdmin && !serverModel[virtualstudio.currentStudio].enabled && serverModel[virtualstudio.currentStudio].cloudId !== "" : false)
     property bool showWaitingScreen: !showStoppingScreen && !showStartingScreen && !showReadyScreen
 
     property string buttonColour: virtualstudio.darkMode ? "#494646" : "#EAECEC"
@@ -43,9 +43,13 @@ Item {
     property string buttonStroke: virtualstudio.darkMode ? "#80827D7D" : "#34979797"
     property string sliderColour: virtualstudio.darkMode ? "#BABCBC" :  "#EAECEC"
     property string sliderPressedColour: virtualstudio.darkMode ? "#ACAFAF" : "#DEE0E0"
+    property string sliderTrackColour: virtualstudio.darkMode ? "#5B5858" : "light gray"
+    property string sliderActiveTrackColour: virtualstudio.darkMode ? "light gray" : "black"
     property string shadowColour: virtualstudio.darkMode ? "#40000000" : "#80A1A1A1"
     property string toolTipBackgroundColour: virtualstudio.darkMode ? "#323232" : "#F3F3F3"
     property string toolTipTextColour: textColour
+    property string warningTextColour: "#DB0A0A"
+    property string linkText: virtualstudio.darkMode ? "#8B8D8D" : "#272525"
 
     property string meterGreen: "#61C554"
     property string meterYellow: "#F5BF4F"
@@ -109,7 +113,7 @@ Item {
         flagImage: virtualstudio.currentStudio >= 0 ? ( serverModel[virtualstudio.currentStudio].bannerURL ? serverModel[virtualstudio.currentStudio].bannerURL : serverModel[virtualstudio.currentStudio].flag ) : "flags/DE.svg"
         studioName: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].name : "Test Studio"
         publicStudio: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].isPublic : false
-        manageable: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].isManageable : false
+        admin: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].isAdmin : false
         available: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].canConnect : false
         studioId: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].id : ""
         inviteKeyString: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].inviteKey : ""
@@ -127,7 +131,7 @@ Item {
             id: mic
             source: "mic.svg"
             x: 0; y: 0
-            width: 18 * virtualstudio.uiScale; height: 28 * virtualstudio.uiScale
+            width: 28 * virtualstudio.uiScale; height: 28 * virtualstudio.uiScale
             sourceSize: Qt.size(mic.width,mic.height)
             fillMode: Image.PreserveAspectFit
             smooth: true
@@ -158,7 +162,7 @@ Item {
             anchors.top: inputDeviceHeader.bottom
             anchors.left: inputDeviceHeader.left
             text: virtualstudio.audioBackend == "JACK" ?
-                virtualstudio.audioBackend : inputComboModel.filter(item => item.type === "element")[virtualstudio.inputDevice].text
+                virtualstudio.audioBackend : virtualstudio.inputDevice
             font {family: "Poppins"; pixelSize: fontTiny * virtualstudio.fontScale * virtualstudio.uiScale }
             color: textColour
             elide: Text.ElideRight
@@ -208,7 +212,7 @@ Item {
             anchors.top: outputDeviceHeader.bottom
             anchors.left: outputDeviceHeader.left
             text: virtualstudio.audioBackend == "JACK" ?
-                virtualstudio.audioBackend : outputComboModel.filter(item => item.type === "element")[virtualstudio.outputDevice].text
+                virtualstudio.audioBackend : virtualstudio.outputDevice
             font {family: "Poppins"; pixelSize: fontTiny * virtualstudio.fontScale * virtualstudio.uiScale }
             color: textColour
             elide: Text.ElideRight
@@ -243,6 +247,25 @@ Item {
             anchors.leftMargin: 8 * virtualstudio.uiScale
             anchors.right: inputDeviceMeters.right
             opacity: virtualstudio.inputMuted ? 0.3 : 1
+
+            background: Rectangle {
+                x: inputSlider.leftPadding
+                y: inputSlider.topPadding + inputSlider.availableHeight / 2 - height / 2
+                implicitWidth: parent.width
+                implicitHeight: 6
+                width: inputSlider.availableWidth
+                height: implicitHeight
+                radius: 4
+                color: sliderTrackColour
+
+                Rectangle {
+                    width: inputSlider.visualPosition * parent.width
+                    height: parent.height
+                    color: sliderActiveTrackColour
+                    radius: 4
+                }
+            }
+
             handle: Rectangle {
                 x: inputSlider.leftPadding + inputSlider.visualPosition * (inputSlider.availableWidth - width)
                 y: inputSlider.topPadding + inputSlider.availableHeight / 2 - height / 2
@@ -269,7 +292,7 @@ Item {
             onClicked: { virtualstudio.inputMuted = !virtualstudio.inputMuted }
             Image {
                 id: micMute
-                width: 11.57 * virtualstudio.uiScale; height: 18 * virtualstudio.uiScale
+                width: 18 * 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)
@@ -296,13 +319,8 @@ Item {
                     anchors.bottomMargin: bottomToolTipMargin * virtualstudio.uiScale
                     anchors.rightMargin: rightToolTipMargin * virtualstudio.uiScale
                     layer.enabled: true
-                    layer.effect: DropShadow {
-                        horizontalOffset: 1 * virtualstudio.uiScale
-                        verticalOffset: 1 * virtualstudio.uiScale
-                        radius: 10.0 * virtualstudio.uiScale
-                        samples: 21
-                        color: shadowColour
-                    }
+                    border.width: 1
+                    border.color: buttonStroke
 
                     Text {
                         anchors.centerIn: parent
@@ -343,6 +361,25 @@ Item {
             y: outputDeviceMeters.y + 36 * virtualstudio.uiScale
             anchors.left: outputDeviceMeters.left
             anchors.right: outputDeviceMeters.right
+
+            background: Rectangle {
+                x: outputSlider.leftPadding
+                y: outputSlider.topPadding + outputSlider.availableHeight / 2 - height / 2
+                implicitWidth: parent.width
+                implicitHeight: 6
+                width: outputSlider.availableWidth
+                height: implicitHeight
+                radius: 4
+                color: sliderTrackColour
+
+                Rectangle {
+                    width: outputSlider.visualPosition * parent.width
+                    height: parent.height
+                    color: sliderActiveTrackColour
+                    radius: 4
+                }
+            }
+
             handle: Rectangle {
                 x: outputSlider.leftPadding + outputSlider.visualPosition * (outputSlider.availableWidth - width)
                 y: outputSlider.topPadding + outputSlider.availableHeight / 2 - height / 2
@@ -395,7 +432,7 @@ Item {
         visible: showReadyScreen
         x: networkStatsHeader.x + networkStatsHeader.width; y: 410 * virtualstudio.uiScale
         width: parent.width - networkStatsHeader.width - 2 * bodyMargin * virtualstudio.uiScale
-        height: 128 * virtualstudio.uiScale
+        height: 72 * virtualstudio.uiScale
 
         Text {
             id: netstat0
@@ -416,13 +453,40 @@ Item {
         }
     }
 
+    Item {
+        id: devicesWarning
+        visible: showReadyScreen && Boolean(virtualstudio.devicesWarning)
+        x: bodyMargin * virtualstudio.uiScale
+        width: parent.width - (2 * x)
+        anchors.top: networkStatsText.bottom
+        anchors.topMargin: 12 * virtualstudio.uiScale
+
+        Text {
+            x: 0; y: 0
+            width: devicesWarning.width
+            textFormat: Text.RichText
+            text: (virtualstudio.devicesWarning)
+                + ((virtualstudio.devicesWarningHelpUrl)
+                    ? `&nbsp;<a style="color: ${linkText};" href=${virtualstudio.devicesWarningHelpUrl}>Learn More.</a>`
+                    : ""
+                )
+            onLinkActivated: link => {
+                virtualstudio.openLink(link)
+            }
+            horizontalAlignment: Text.AlignHLeft
+            wrapMode: Text.WordWrap
+            font {family: "Poppins"; pixelSize: fontTiny * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: warningTextColour
+        }
+    }
+
     Item {
         id: waitingScreen
         visible: showWaitingScreen
         x: bodyMargin * virtualstudio.uiScale; y: 230 * virtualstudio.uiScale
         width: parent.width - (2 * x)
 
-        property bool isManageable: (virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].isManageable : false)
+        property bool isAdmin: (virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].isAdmin : false)
 
         Text {
             id: waitingText0
@@ -430,7 +494,7 @@ Item {
             width: parent.width
             color: textColour
             font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-            text: parent.isManageable
+            text: parent.isAdmin
                     ? "Waiting for this studio to start. Please start the studio using one of the options below."
                     : "This studio is currently inactive. Please contact an owner or admin for this studio to start it."
             wrapMode: Text.WordWrap
@@ -441,7 +505,7 @@ Item {
             anchors.top: waitingText0.bottom
             anchors.topMargin: 16 * virtualstudio.uiScale
             anchors.bottomMargin: 16 * virtualstudio.uiScale
-            visible: parent.isManageable
+            visible: parent.isAdmin
 
             height: 64 * virtualstudio.uiScale
 
@@ -494,10 +558,10 @@ Item {
             x: 0
             width: parent.width
             color: textColour
-            anchors.top: parent.isManageable ? startButtonsBox.bottom : waitingText0.bottom
+            anchors.top: parent.isAdmin ? startButtonsBox.bottom : waitingText0.bottom
             anchors.topMargin: 16 * virtualstudio.uiScale
             anchors.bottomMargin: 16 * virtualstudio.uiScale
-            visible: parent.isManageable
+            visible: parent.isAdmin
             font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
             text: "You will be automatically connected to the studio when it is ready."
             wrapMode: Text.WordWrap
index a27d55a80af8927513b17229ec3e2257d1cb0543..f80ad8f34b71dde8fb5663db36f23f65a721b8fc 100644 (file)
@@ -1,6 +1,5 @@
 import QtQuick 2.12
 import QtQuick.Controls 2.12
-import QtGraphicalEffects 1.12
 
 Item {
     width: parent.width; height: parent.height
@@ -8,11 +7,11 @@ Item {
     
     property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D"
     property string shadowColour: virtualstudio.darkMode ? "40000000" : "#80A1A1A1"
-    property string buttonColour: virtualstudio.darkMode ? "#565252" : "#F0F1F1"
-    property string buttonHoverColour: virtualstudio.darkMode ? "#6F6C6C" : "#F0F1F1"
-    property string buttonPressedColour: virtualstudio.darkMode ? "#494646" : "#D8D9D9"
+    property string buttonColour: virtualstudio.darkMode ? "#FAFBFB" : "#F0F1F1"
+    property string buttonHoverColour: virtualstudio.darkMode ? "#E9E9E9" : "#E4E5E5"
+    property string buttonPressedColour: virtualstudio.darkMode ? "#FAFBFB" : "#E4E5E5"
     property string buttonStroke: virtualstudio.darkMode ? "#636060" : "#DEDFDF"
-    property string buttonHoverStroke: virtualstudio.darkMode ? "#777575" : "#DEDFDF"
+    property string buttonHoverStroke: virtualstudio.darkMode ? "#6F6C6C" : "#B0B5B5"
     property string buttonPressedStroke: virtualstudio.darkMode ? "#6F6C6C" : "#B0B5B5"
     
     Image {
@@ -52,13 +51,6 @@ Item {
             border.width: 1
             border.color: vsButton.down ? buttonPressedStroke : (vsButton.hovered ? buttonHoverStroke : buttonStroke)
             layer.enabled: vsButton.hovered && !vsButton.down
-            layer.effect: DropShadow {
-                horizontalOffset: 1 * virtualstudio.uiScale
-                verticalOffset: 1 * virtualstudio.uiScale
-                radius: 8.0 * virtualstudio.uiScale
-                samples: 17
-                color: shadowColour
-            }
         }
         onClicked: { virtualstudio.showFirstRun = false; virtualstudio.windowState = "login"; virtualstudio.toVirtualStudio(); }
         x: parent.width / 2 - (265 * virtualstudio.uiScale); y: 290 * virtualstudio.uiScale
@@ -100,13 +92,6 @@ Item {
             border.width: 1
             border.color: standardButton.down ? buttonPressedStroke : (standardButton.hovered ? buttonHoverStroke : buttonStroke)
             layer.enabled: standardButton.hovered && !standardButton.down
-            layer.effect: DropShadow {
-                horizontalOffset: 1 * virtualstudio.uiScale
-                verticalOffset: 1 * virtualstudio.uiScale
-                radius: 8.0 * virtualstudio.uiScale
-                samples: 17
-                color: shadowColour
-            }
         }
         onClicked: { virtualstudio.windowState = "login"; virtualstudio.toStandard(); }
         x: parent.width / 2 + (32 * virtualstudio.uiScale); y: 290 * virtualstudio.uiScale
index 955cfb1de58641aec6114d922642300d516f257e..c4b2b25745c6fd5df958c37dbd7eed345d93a3e3 100644 (file)
@@ -1,6 +1,5 @@
 import QtQuick 2.12
 import QtQuick.Controls 2.12
-import QtGraphicalEffects 1.12
 
 Item {
     width: parent.width; height: parent.height
@@ -18,7 +17,9 @@ Item {
     property string buttonColour: virtualstudio.darkMode ? "#FAFBFB" : "#F0F1F1"
     property string buttonHoverColour: virtualstudio.darkMode ? "#E9E9E9" : "#E4E5E5"
     property string buttonPressedColour: virtualstudio.darkMode ? "#FAFBFB" : "#E4E5E5"
-    property string buttonStroke: virtualstudio.darkMode ? "#9C9C9C" : "#A4A7A7"
+    property string buttonStroke: virtualstudio.darkMode ? "#80827D7D" : "#34979797"
+    property string buttonHoverStroke: virtualstudio.darkMode ? "#6F6C6C" : "#B0B5B5"
+    property string buttonPressedStroke: virtualstudio.darkMode ? "#6F6C6C" : "#B0B5B5"
     property string buttonTextColour: virtualstudio.darkMode ? "#272525" : "#DB0A0A"
     property string buttonTextHover: virtualstudio.darkMode ? "#242222" : "#D00A0A"
     property string buttonTextPressed: virtualstudio.darkMode ? "#323030" : "#D00A0A"
@@ -84,16 +85,9 @@ Item {
         background: Rectangle {
             radius: 6 * virtualstudio.uiScale
             color: loginButton.down ? buttonPressedColour : (loginButton.hovered ? buttonHoverColour : buttonColour)
-            border.width: loginButton.down ? 1 : 0
-            border.color: buttonStroke
+            border.width: 1
+            border.color: loginButton.down ? buttonPressedStroke : (loginButton.hovered ? buttonHoverStroke : buttonStroke)
             layer.enabled: !loginButton.down
-            layer.effect: DropShadow {
-                horizontalOffset: 1 * virtualstudio.uiScale
-                verticalOffset: 1 * virtualstudio.uiScale
-                radius: 8.0 * virtualstudio.uiScale
-                samples: 17
-                color: shadowColour
-            }
         }
         onClicked: { failTextVisible = false; virtualstudio.login() }
         anchors.horizontalCenter: parent.horizontalCenter
@@ -116,16 +110,9 @@ Item {
         background: Rectangle {
             radius: 6 * virtualstudio.uiScale
             color: backButton.down ? buttonPressedColour : (backButton.hovered ? buttonHoverColour : buttonColour)
-            border.width: backButton.down ? 1 : 0
-            border.color: buttonStroke
+            border.width: 1
+            border.color: backButton.down ? buttonPressedStroke : (backButton.hovered ? buttonHoverStroke : buttonStroke)
             layer.enabled: !backButton.down
-            layer.effect: DropShadow {
-                horizontalOffset: 1 * virtualstudio.uiScale
-                verticalOffset: 1 * virtualstudio.uiScale
-                radius: 8.0 * virtualstudio.uiScale
-                samples: 17
-                color: shadowColour
-            }
         }
         onClicked: { virtualstudio.windowState = "start" }
         anchors.horizontalCenter: parent.horizontalCenter
index a996d0ef4294853e6af210de3efd79fcbe2c5b81..e4ab9eff033588bb3640f44e60c1dca7b8f82dc3 100644 (file)
@@ -1,8 +1,9 @@
 import QtQuick 2.12
 import QtQuick.Controls 2.12
-import QtGraphicalEffects 1.12
 
 Rectangle {
+  property string filterStroke: virtualstudio.darkMode ? "#827D7D" : "#BABCBC"
+
   property bool listIsEmpty: false
   // required property string section: section (for 5.15)
   color: "transparent"
@@ -30,13 +31,6 @@ Rectangle {
           border.width: 1
           border.color: createButton.down ? "#B0B5B5" : "#EAEBEB"
           layer.enabled: createButton.hovered && !createButton.down
-          layer.effect: DropShadow {
-              horizontalOffset: 1 * virtualstudio.uiScale
-              verticalOffset: 1 * virtualstudio.uiScale
-              radius: 8.0 * virtualstudio.uiScale
-              samples: 17
-              color: "#80A1A1A1"
-          }
       }
       onClicked: { virtualstudio.createStudio(); }
       anchors.right: filterButton.left
@@ -62,13 +56,6 @@ Rectangle {
           border.width: 1
           border.color: filterButton.down ? "#B0B5B5" : "#EAEBEB"
           layer.enabled: filterButton.hovered && !filterButton.down
-          layer.effect: DropShadow {
-              horizontalOffset: 1 * virtualstudio.uiScale
-              verticalOffset: 1 * virtualstudio.uiScale
-              radius: 8.0 * virtualstudio.uiScale
-              samples: 17
-              color: "#80A1A1A1"
-          }
       }
       onClicked: { filterMenu.open(); }
       anchors.right: parent.right
@@ -95,15 +82,8 @@ Rectangle {
               radius: 6 * virtualstudio.uiScale
               color: "#F6F8F8"
               border.width: 1
-              border.color: "#34979797"
+              border.color: filterStroke
               layer.enabled: true
-              layer.effect: DropShadow {
-                  horizontalOffset: 1 * virtualstudio.uiScale
-                  verticalOffset: 1 * virtualstudio.uiScale
-                  radius: 8.0 * virtualstudio.uiScale
-                  samples: 17
-                  color: "#80A1A1A1"
-              }
           }
           contentItem: Column {
               anchors.fill: parent
index e033a19aaa5cdd799b80d4a2e0ae23d5c1d02cf9..915fe2b376a884e5a1a801a55a62021942e63f7d 100644 (file)
@@ -1,5 +1,6 @@
 import QtQuick 2.12
 import QtQuick.Controls 2.12
+import QtGraphicalEffects 1.12
 
 Item {
     width: parent.width; height: parent.height
@@ -10,7 +11,7 @@ Item {
         color: backgroundColour
     }
 
-    property int fontBig: 28
+    property int fontBig: 20
     property int fontMedium: 13
     property int fontSmall: 11
     property int fontExtraSmall: 8
@@ -28,18 +29,64 @@ Item {
     property string buttonStroke: virtualstudio.darkMode ? "#80827D7D" : "#40979797"
     property string buttonHoverStroke: virtualstudio.darkMode ? "#7B7777" : "#BABCBC"
     property string buttonPressedStroke: virtualstudio.darkMode ? "#827D7D" : "#BABCBC"
+    property string sliderColour: virtualstudio.darkMode ? "#BABCBC" :  "#EAECEC"
+    property string sliderPressedColour: virtualstudio.darkMode ? "#ACAFAF" : "#DEE0E0"
+    property string sliderTrackColour: virtualstudio.darkMode ? "#5B5858" : "light gray"
+    property string sliderActiveTrackColour: virtualstudio.darkMode ? "light gray" : "black"
     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"
 
+    property int inputCurrIndex: getCurrentInputDeviceIndex()
+    property int outputCurrIndex: getCurrentOutputDeviceIndex()
+
+    function getCurrentInputDeviceIndex () {
+        if (virtualstudio.inputDevice === "") {
+            return inputComboModel.findIndex(elem => elem.type === "element");
+        }
+
+        let idx = inputComboModel.findIndex(elem => elem.type === "element" && elem.text === virtualstudio.inputDevice);
+        if (idx < 0) {
+            idx = inputComboModel.findIndex(elem => elem.type === "element");
+        }
+
+        return idx;
+    }
+
+    function getCurrentOutputDeviceIndex() {
+        if (virtualstudio.outputDevice === "") {
+            return outputComboModel.findIndex(elem => elem.type === "element");
+        }
+
+        let idx = outputComboModel.findIndex(elem => elem.type === "element" && elem.text === virtualstudio.outputDevice);
+        if (idx < 0) {
+            idx = outputComboModel.findIndex(elem => elem.type === "element");
+        }
+
+        return idx;
+    }
+
+    Rectangle {
+        id: audioSettingsView
+        width: 0.8 * parent.width
+        height: parent.height - header.height
+        x: 0.2 * window.width
+        y: header.height
+        visible: settingsGroupView == "Audio"
+
+        AudioSettings{
+            id: audioSettings
+        }
+    }
+
     ToolBar {
         id: header
         width: parent.width
+        height: 64 * virtualstudio.uiScale
 
         background: Rectangle {
             border.color: "#33979797"
@@ -47,14 +94,62 @@ Item {
             width: parent.width
         }
 
-        contentItem: Label {
-            text: "Settings"
-            elide: Label.ElideRight
-            horizontalAlignment: Text.AlignHCenter
-            verticalAlignment: Text.AlignVCenter
-            font { family: "Poppins"; weight: Font.Bold; pixelSize: fontBig * virtualstudio.fontScale * virtualstudio.uiScale }
-            color: textColour
+        contentItem: Item {
+            id: headerContent
+            width: header.width
+            height: header.height
+            x: 16 * virtualstudio.uiScale; y: 32 * virtualstudio.uiScale
+
+            property bool isUsingRtAudio: virtualstudio.audioBackend == "RtAudio"
+
+            Label {
+                id: pageTitle
+                text: "Settings"
+                height: headerContent.height;
+                anchors.left: headerContent.left;
+                anchors.leftMargin: 32 * virtualstudio.uiScale
+                elide: Label.ElideRight
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                font { family: "Poppins"; weight: Font.Bold; pixelSize: fontBig * virtualstudio.fontScale * virtualstudio.uiScale }
+                color: textColour
+            }
+
+            Button {
+                id: refreshButton
+                text: "Refresh Devices"
+                anchors.verticalCenter: pageTitle.verticalCenter;
+                anchors.right: headerContent.right;
+                anchors.rightMargin: 16 * virtualstudio.uiScale;
+
+                palette.buttonText: textColour
+                background: Rectangle {
+                    radius: 6 * virtualstudio.uiScale
+                    color: refreshButton.down ? buttonPressedColour : (refreshButton.hovered ? buttonHoverColour : buttonColour)
+                    border.width: 1
+                    border.color: refreshButton.down ? buttonPressedStroke : (refreshButton.hovered ? buttonHoverStroke : buttonStroke)
+                }
+                icon {
+                    source: "refresh.svg";
+                    color: textColour;
+                }
+                display: AbstractButton.TextBesideIcon
+                onClicked: {
+                    virtualstudio.refreshDevices();
+                    inputCurrIndex = getCurrentInputDeviceIndex();
+                    outputCurrIndex = getCurrentOutputDeviceIndex();
+                }
+                width: 144 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
+                font {
+                    family: "Poppins"
+                    pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale
+                }
+                visible: parent.isUsingRtAudio && settingsGroupView == "Audio"
+            }
+
         }
+
+
     }
 
     Drawer {
@@ -104,8 +199,8 @@ Item {
                         anchors.left: audioButtonText.right
                         anchors.verticalCenter: audioButtonText.verticalCenter
                         anchors.rightMargin: 16 * virtualstudio.uiScale
-                        width: 8 * virtualstudio.uiScale 
-                        height: 8 * virtualstudio.uiScale 
+                        width: 8 * virtualstudio.uiScale
+                        height: 8 * virtualstudio.uiScale
                         color: errorFlagColour
                         radius: 4 * virtualstudio.uiScale
                         visible: Boolean(virtualstudio.devicesError)
@@ -134,7 +229,7 @@ Item {
                         color: textColour
                     }
                 }
-                
+
 
                 background: Rectangle {
                     width: parent.width
@@ -209,303 +304,6 @@ Item {
         }
     }
 
-    Rectangle {
-        id: audioSettingsView
-        width: 0.8 * parent.width
-        height: parent.height - header.height
-        x: 0.2 * window.width
-        y: header.height
-        color: backgroundColour
-        visible: settingsGroupView == "Audio"
-
-        ComboBox {
-            id: backendCombo
-            model: backendComboModel
-            currentIndex: virtualstudio.audioBackend == "JACK" ? 0 : 1
-            onActivated: { virtualstudio.audioBackend = currentText }
-            x: 234 * virtualstudio.uiScale; y: 48 * virtualstudio.uiScale
-            width: parent.width - x - (16 * virtualstudio.uiScale); height: 36 * virtualstudio.uiScale
-            visible: virtualstudio.selectableBackend
-        }
-
-        Text {
-            id: backendLabel
-            anchors.verticalCenter: backendCombo.verticalCenter
-            x: leftMargin * virtualstudio.uiScale
-            text: "Audio Backend"
-            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-            visible: virtualstudio.selectableBackend
-            color: textColour
-        }
-
-        Text {
-            id: jackLabel
-            x: leftMargin * virtualstudio.uiScale; y: 100 * virtualstudio.uiScale
-            width: parent.width - x - (16 * virtualstudio.uiScale)
-            text: "Using JACK for audio input and output. Use QjackCtl to adjust your sample rate, buffer, and device settings."
-            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-            wrapMode: Text.WordWrap
-            visible: virtualstudio.audioBackend == "JACK" && !virtualstudio.selectableBackend && virtualstudio.backendAvailable
-            color: textColour
-        }
-
-        Text {
-            id: noBackendLabel
-            x: leftMargin * virtualstudio.uiScale; y: 150 * virtualstudio.uiScale
-            width: parent.width - x - (16 * virtualstudio.uiScale)
-            text: "JackTrip has been compiled without an audio backend. Please rebuild with the rtaudio flag or without the nojack flag."
-            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-            wrapMode: Text.WordWrap
-            visible: !virtualstudio.backendAvailable
-            color: textColour
-        }
-
-        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
-        }
-
-        ComboBox {
-            id: outputCombo
-            model: outputComboModel
-            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
-
-                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 ? outputCombo.model[outputCombo.currentIndex].text : ""
-            }
-        }
-
-        Button {
-            id: testOutputAudioButton
-            background: Rectangle {
-                radius: 6 * virtualstudio.uiScale
-                color: testOutputAudioButton.down ? buttonPressedColour : (testOutputAudioButton.hovered ? buttonHoverColour : buttonColour)
-                border.width: 1
-                border.color: testOutputAudioButton.down ? buttonPressedStroke : (testOutputAudioButton.hovered ? buttonHoverStroke : buttonStroke)
-            }
-            onClicked: { virtualstudio.playOutputAudio() }
-            width: 216 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
-            x: parent.width - (232 * virtualstudio.uiScale)
-            y: virtualstudio.audioBackend != "JACK" ? outputCombo.y + (48 * virtualstudio.uiScale) : jackLabel.y + (72 * virtualstudio.uiScale)
-            Text {
-                text: "Test Output Audio"
-                font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
-                anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
-                color: textColour
-            }
-            visible: virtualstudio.audioReady
-        }
-
-        Text {
-            id: inputLabel
-            anchors.verticalCenter: inputCombo.verticalCenter
-            x: leftMargin * virtualstudio.uiScale
-            anchors.top: virtualstudio.audioBackend != "JACK" ? inputCombo.top : inputDeviceMeters.top
-            anchors.topMargin: virtualstudio.audioBackend != "JACK" ? (inputCombo.height - inputLabel.height)/2 : 0
-            text: "Input Device"
-            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-            visible: virtualstudio.backendAvailable
-            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 ? 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 : testOutputAudioButton.y + 72 * virtualstudio.uiScale
-            height: 100 * virtualstudio.uiScale
-            model: inputMeterModel
-            clipped: inputClipped
-            enabled: !Boolean(virtualstudio.devicesError)
-            visible: virtualstudio.audioReady
-        }
-
-        Text {
-            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
-            text: "Preparing audio..."
-            font { family: "Poppins"; pixelSize: 13 * virtualstudio.fontScale * virtualstudio.uiScale }
-            visible: virtualstudio.audioBackend != "JACK" && !virtualstudio.audioReady
-            color: textColour
-        }
-
-        Button {
-            id: refreshButton
-            background: Rectangle {
-                radius: 6 * virtualstudio.uiScale
-                color: refreshButton.down ? buttonPressedColour : (refreshButton.hovered ? buttonHoverColour : buttonColour)
-                border.width: 1
-                border.color: refreshButton.down ? buttonPressedStroke : (refreshButton.hovered ? buttonHoverStroke : buttonStroke)
-            }
-            onClicked: { virtualstudio.refreshDevices() }
-            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 {
-                text: "Refresh Devices"
-                font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
-                anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
-                color: textColour
-            }
-        }
-
-        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: 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"
-        }
-
-        ComboBox {
-            id: bufferCombo
-            x: backendCombo.x; y: divider.y + (24 * virtualstudio.uiScale)
-            width: backendCombo.width; height: backendCombo.height
-            model: bufferComboModel
-            currentIndex: virtualstudio.bufferSize
-            onActivated: { virtualstudio.bufferSize = currentIndex }
-            font.family: "Poppins"
-            visible: virtualstudio.audioBackend != "JACK"
-        }
-
-        Text {
-            anchors.verticalCenter: bufferCombo.verticalCenter
-            x: 48 * virtualstudio.uiScale
-            text: "Buffer Size"
-            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-            visible: virtualstudio.audioBackend != "JACK"
-            color: textColour
-        }
-    }
-
     Rectangle {
         id: appearanceSettingsView
         width: 0.8 * parent.width
@@ -521,6 +319,34 @@ Item {
             width: backendCombo.width
             from: 1; to: 2; value: virtualstudio.uiScale
             onMoved: { virtualstudio.uiScale = value }
+
+            background: Rectangle {
+                x: scaleSlider.leftPadding
+                y: scaleSlider.topPadding + scaleSlider.availableHeight / 2 - height / 2
+                implicitWidth: parent.width
+                implicitHeight: 6
+                width: scaleSlider.availableWidth
+                height: implicitHeight
+                radius: 4
+                color: sliderTrackColour
+
+                Rectangle {
+                    width: scaleSlider.visualPosition * parent.width
+                    height: parent.height
+                    color: sliderActiveTrackColour
+                    radius: 4
+                }
+            }
+
+            handle: Rectangle {
+                x: scaleSlider.leftPadding + scaleSlider.visualPosition * (scaleSlider.availableWidth - width)
+                y: scaleSlider.topPadding + scaleSlider.availableHeight / 2 - height / 2
+                implicitWidth: 26 * virtualstudio.uiScale
+                implicitHeight: 26 * virtualstudio.uiScale
+                radius: 13 * virtualstudio.uiScale
+                color: scaleSlider.pressed ? sliderPressedColour : sliderColour
+                border.color: buttonStroke
+            }
         }
 
         Text {
@@ -615,9 +441,49 @@ Item {
             visible: !virtualstudio.noUpdater
         }
 
+        ComboBox {
+            id: backendCombo
+            model: backendComboModel
+            currentIndex: virtualstudio.audioBackend == "JACK" ? 0 : 1
+            onActivated: { virtualstudio.audioBackend = currentText }
+            x: 234 * virtualstudio.uiScale; y: updateChannelCombo.y + (48 * virtualstudio.uiScale)
+            width: updateChannelCombo.width; height: updateChannelCombo.height
+            visible: virtualstudio.selectableBackend
+        }
+
+        Text {
+            id: backendLabel
+            anchors.verticalCenter: backendCombo.verticalCenter
+            x: leftMargin * virtualstudio.uiScale
+            text: "Audio Backend"
+            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+            visible: virtualstudio.selectableBackend
+            color: textColour
+        }
+
+        ComboBox {
+            id: bufferCombo
+            x: 234 * virtualstudio.uiScale; y: backendCombo.y + (48 * virtualstudio.uiScale)
+            width: backendCombo.width; height: updateChannelCombo.height
+            model: bufferComboModel
+            currentIndex: virtualstudio.bufferSize
+            onActivated: { virtualstudio.bufferSize = currentIndex }
+            font.family: "Poppins"
+            visible: virtualstudio.audioBackend != "JACK"
+        }
+
+        Text {
+            anchors.verticalCenter: bufferCombo.verticalCenter
+            x: 48 * virtualstudio.uiScale
+            text: "Buffer Size"
+            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+            visible: virtualstudio.audioBackend != "JACK"
+            color: textColour
+        }
+
         ComboBox {
             id: bufferStrategyCombo
-            x: updateChannelCombo.x; y: updateChannelCombo.y + (48 * virtualstudio.uiScale)
+            x: updateChannelCombo.x; y: bufferCombo.y + (48 * virtualstudio.uiScale)
             width: updateChannelCombo.width; height: updateChannelCombo.height
             model: bufferStrategyComboModel
             currentIndex: virtualstudio.bufferStrategy
@@ -750,8 +616,8 @@ Item {
             }
             onClicked: {
                 virtualstudio.windowState = "browse";
-                inputCombo.currentIndex = virtualstudio.previousInput;
-                outputCombo.currentIndex = virtualstudio.previousOutput;
+                inputCurrIndex = virtualstudio.previousInput;
+                outputCurrIndex = virtualstudio.previousOutput;
                 virtualstudio.revertSettings()
             }
             anchors.verticalCenter: parent.verticalCenter
index 3fa839d38613e89400efb4d4d9a56f0756c99a8a..99c1ca5ee4809ce9ea507acb4d007aa36c2a73da 100644 (file)
@@ -6,7 +6,7 @@ Item {
     width: parent.width; height: parent.height
     clip: true
 
-    property int fontBig: 28
+    property int fontBig: 20
     property int fontMedium: 13
     property int fontSmall: 11
     property int fontExtraSmall: 8
@@ -28,6 +28,8 @@ Item {
     property string buttonPressedStroke: virtualstudio.darkMode ? "#827D7D" : "#BABCBC"
     property string sliderColour: virtualstudio.darkMode ? "#BABCBC" :  "#EAECEC"
     property string sliderPressedColour: virtualstudio.darkMode ? "#ACAFAF" : "#DEE0E0"
+    property string sliderTrackColour: virtualstudio.darkMode ? "#5B5858" : "light gray"
+    property string sliderActiveTrackColour: virtualstudio.darkMode ? "light gray" : "black"
     property string saveButtonShadow: "#80A1A1A1"
     property string saveButtonBackgroundColour: "#F2F3F3"
     property string saveButtonPressedColour: "#E7E8E8"
@@ -42,7 +44,7 @@ Item {
 
     property bool currShowWarnings: virtualstudio.showWarnings
     property string warningScreen: virtualstudio.showWarnings ? "ethernet" : ( permissions.micPermission == "unknown" ? "microphone" : "acknowledged")
+
     Item {
         id: ethernetWarningItem
         width: parent.width; height: parent.height
@@ -107,15 +109,8 @@ Item {
                 radius: 6 * virtualstudio.uiScale
                 color: okButtonEthernet.down ? saveButtonPressedColour : saveButtonBackgroundColour
                 border.width: 1
-                border.color: okButtonEthernet.down ? saveButtonPressedStroke : saveButtonStroke
+                border.color: okButtonEthernet.down || okButtonEthernet.hovered ? saveButtonPressedStroke : saveButtonStroke
                 layer.enabled: okButtonEthernet.hovered && !okButtonEthernet.down
-                layer.effect: DropShadow {
-                    horizontalOffset: 1 * virtualstudio.uiScale
-                    verticalOffset: 1 * virtualstudio.uiScale
-                    radius: 8.0 * virtualstudio.uiScale
-                    samples: 17
-                    color: saveButtonShadow
-                }
             }
             onClicked: { warningScreen = "headphones" }
             anchors.right: parent.right
@@ -148,7 +143,7 @@ Item {
                 x: showEthernetWarningCheckbox.leftPadding
                 y: parent.height / 2 - height / 2
                 radius: 3 * virtualstudio.uiScale
-                border.color: showEthernetWarningCheckbox.down ? checkboxPressedStroke : checkboxStroke
+                border.color: showEthernetWarningCheckbox.down || showEthernetWarningCheckbox.hovered  ? checkboxPressedStroke : checkboxStroke
 
                 Rectangle {
                     width: 10 * virtualstudio.uiScale
@@ -156,7 +151,7 @@ Item {
                     x: 3 * virtualstudio.uiScale
                     y: 3 * virtualstudio.uiScale
                     radius: 2 * virtualstudio.uiScale
-                    color: showEthernetWarningCheckbox.down ? checkboxPressedStroke : checkboxStroke
+                    color: showEthernetWarningCheckbox.down ||  showEthernetWarningCheckbox.hovered ? checkboxPressedStroke : checkboxStroke
                     visible: showEthernetWarningCheckbox.checked
                 }
             }
@@ -256,15 +251,8 @@ Item {
                 radius: 6 * virtualstudio.uiScale
                 color: okButtonHeadphones.down ? saveButtonPressedColour : saveButtonBackgroundColour
                 border.width: 1
-                border.color: okButtonHeadphones.down ? saveButtonPressedStroke : saveButtonStroke
+                border.color: okButtonHeadphones.down || okButtonHeadphones.hovered ? saveButtonPressedStroke : saveButtonStroke
                 layer.enabled: okButtonHeadphones.hovered && !okButtonHeadphones.down
-                layer.effect: DropShadow {
-                    horizontalOffset: 1 * virtualstudio.uiScale
-                    verticalOffset: 1 * virtualstudio.uiScale
-                    radius: 8.0 * virtualstudio.uiScale
-                    samples: 17
-                    color: saveButtonShadow
-                }
             }
             onClicked: {
                 if (permissions.micPermission == "unknown") {
@@ -303,7 +291,7 @@ Item {
                 x: showHeadphonesWarningCheckbox.leftPadding
                 y: parent.height / 2 - height / 2
                 radius: 3 * virtualstudio.uiScale
-                border.color: showHeadphonesWarningCheckbox.down ? checkboxPressedStroke : checkboxStroke
+                border.color: showHeadphonesWarningCheckbox.down || showHeadphonesWarningCheckbox.hovered ? checkboxPressedStroke : checkboxStroke
 
                 Rectangle {
                     width: 10 * virtualstudio.uiScale
@@ -311,7 +299,7 @@ Item {
                     x: 3 * virtualstudio.uiScale
                     y: 3 * virtualstudio.uiScale
                     radius: 2 * virtualstudio.uiScale
-                    color: showHeadphonesWarningCheckbox.down ? checkboxPressedStroke : checkboxStroke
+                    color: showHeadphonesWarningCheckbox.down || showHeadphonesWarningCheckbox.hovered ? checkboxPressedStroke : checkboxStroke
                     visible: showHeadphonesWarningCheckbox.checked
                 }
             }
@@ -372,17 +360,10 @@ Item {
                 radius: 6 * virtualstudio.uiScale
                 color: showPromptButton.down ? saveButtonPressedColour : saveButtonBackgroundColour
                 border.width: 2
-                border.color: showPromptButton.down ? saveButtonPressedStroke : saveButtonStroke
+                border.color: showPromptButton.down || showPromptButton.hovered ? 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: { 
+            onClicked: {
                 permissions.getMicPermission();
             }
             anchors.right: microphonePrompt.right
@@ -467,17 +448,10 @@ Item {
                 radius: 6 * virtualstudio.uiScale
                 color: openSettingsButton.down ? saveButtonPressedColour : saveButtonBackgroundColour
                 border.width: 1
-                border.color: openSettingsButton.down ? saveButtonPressedStroke : saveButtonStroke
+                border.color: openSettingsButton.down || openSettingsButton.hovered ? 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: { 
+            onClicked: {
                 permissions.openSystemPrivacy();
             }
             anchors.right: parent.right
@@ -538,6 +512,8 @@ Item {
         width: parent.width; height: parent.height
         visible: (warningScreen == "acknowledged" || warningScreen == "microphone") && permissions.micPermission == "granted"
 
+        property bool isUsingRtAudio: virtualstudio.audioBackend == "RtAudio"
+
         Text {
             id: pageTitle
             x: 16 * virtualstudio.uiScale; y: 32 * virtualstudio.uiScale
@@ -546,305 +522,40 @@ Item {
             color: textColour
         }
 
-        ComboBox {
-            id: backendCombo
-            model: backendComboModel
-            currentIndex: virtualstudio.audioBackend == "JACK" ? 0 : 1
-            onActivated: { virtualstudio.audioBackend = currentText }
-            anchors.right: parent.right
-            anchors.rightMargin: rightMargin * virtualstudio.uiScale
-            y: pageTitle.y + 96 * virtualstudio.uiScale
-            width: parent.width - (234 * virtualstudio.uiScale); height: 36 * virtualstudio.uiScale
-            visible: virtualstudio.selectableBackend
-        }
-
-        Text {
-            id: backendLabel
-            anchors.verticalCenter: backendCombo.verticalCenter
-            x: leftMargin * virtualstudio.uiScale
-            text: "Audio Backend"
-            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-            visible: virtualstudio.selectableBackend
-            color: textColour
-        }
-
-        Text {
-            id: jackLabel
-            x: leftMargin * virtualstudio.uiScale; y: 150 * virtualstudio.uiScale
-            width: parent.width - x - (16 * virtualstudio.uiScale)
-            text: "Using JACK for audio input and output. Use QjackCtl to adjust your sample rate, buffer, and device settings."
-            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-            wrapMode: Text.WordWrap
-            visible: virtualstudio.audioBackend == "JACK" && !virtualstudio.selectableBackend && virtualstudio.backendAvailable
-            color: textColour
-        }
-
-        Text {
-            id: noBackendLabel
-            x: leftMargin * virtualstudio.uiScale; y: 150 * virtualstudio.uiScale
-            width: parent.width - x - (16 * virtualstudio.uiScale)
-            text: "JackTrip has been compiled without an audio backend. Please rebuild with the rtaudio flag or without the nojack flag."
-            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-            wrapMode: Text.WordWrap
-            visible: !virtualstudio.backendAvailable
-            color: textColour
-        }
-
-        ComboBox {
-            id: outputCombo
-            model: outputComboModel
-            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 ? outputCombo.model[outputCombo.currentIndex].text : ""
-            }
-        }
-
-        Text {
-            id: outputLabel
-            anchors.verticalCenter: virtualstudio.audioBackend != "JACK" ? outputCombo.verticalCenter : outputSlider.verticalCenter
-            x: leftMargin * virtualstudio.uiScale
-            text: virtualstudio.audioBackend != "JACK" ? "Output Device" : "Output Volume"
-            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-            color: textColour
-            visible: virtualstudio.backendAvailable
-        }
-
-        Slider {
-            id: outputSlider
-            from: 0.0
-            value: audioInterface ? audioInterface.outputVolume : 0.5
-            onMoved: { audioInterface.outputVolume = value }
-            to: 1.0
-            padding: 0
-            y: virtualstudio.audioBackend != "JACK" ? outputCombo.y + 48 * virtualstudio.uiScale : jackLabel.y + 72 * virtualstudio.uiScale
-            anchors.left: outputCombo.left
-            anchors.right: parent.right
-            anchors.rightMargin: rightMargin * virtualstudio.uiScale
-            handle: Rectangle {
-                x: outputSlider.leftPadding + outputSlider.visualPosition * (outputSlider.availableWidth - width)
-                y: outputSlider.topPadding + outputSlider.availableHeight / 2 - height / 2
-                implicitWidth: 26 * virtualstudio.uiScale
-                implicitHeight: 26 * virtualstudio.uiScale
-                radius: 13 * virtualstudio.uiScale
-                color: outputSlider.pressed ? sliderPressedColour : sliderColour
-                border.color: buttonStroke
-            }
-            visible: virtualstudio.backendAvailable
-        }
-
-        Button {
-            id: testOutputAudioButton
-            background: Rectangle {
-                radius: 6 * virtualstudio.uiScale
-                color: testOutputAudioButton.down ? buttonPressedColour : (testOutputAudioButton.hovered ? buttonHoverColour : buttonColour)
-                border.width: 1
-                border.color: testOutputAudioButton.down ? buttonPressedStroke : (testOutputAudioButton.hovered ? buttonHoverStroke : buttonStroke)
-            }
-            onClicked: { virtualstudio.playOutputAudio() }
-            anchors.right: parent.right
-            anchors.rightMargin: rightMargin * virtualstudio.uiScale
-            y: outputSlider.y + (36 * virtualstudio.uiScale)
-            width: 216 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
-            visible: virtualstudio.audioReady
-            Text {
-                text: "Test Output Audio"
-                font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
-                anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
-                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;
-            })()
-            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 ? inputCombo.model[inputCombo.currentIndex].text : ""
-            }
-        }
-
-        Text {
-            id: inputLabel
-            anchors.top: virtualstudio.audioBackend != "JACK" ? inputCombo.top : inputDeviceMeters.top
-            anchors.topMargin: virtualstudio.audioBackend != "JACK" ? (inputCombo.height - inputLabel.height)/2 : 0
-            x: leftMargin * virtualstudio.uiScale
-            text: virtualstudio.audioBackend != "JACK" ? "Input Device" : "Input Volume"
-            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-            color: textColour
-            visible: virtualstudio.backendAvailable
-        }
-
-        Meter {
-            id: inputDeviceMeters
-            anchors.left: backendCombo.left
-            anchors.right: parent.right
-            anchors.rightMargin: rightMargin * virtualstudio.uiScale
-            y: virtualstudio.audioBackend != "JACK" ? inputCombo.y + 72 * virtualstudio.uiScale : testOutputAudioButton.y + (72 * virtualstudio.uiScale)
-            height: 100 * virtualstudio.uiScale
-            model: inputMeterModel
-            clipped: inputClipped
-            enabled: !Boolean(virtualstudio.devicesError)
-            visible: virtualstudio.backendAvailable
-        }
-
-        Slider {
-            id: inputSlider
-            from: 0.0
-            value: audioInterface ? audioInterface.inputVolume : 0.5
-            onMoved: { audioInterface.inputVolume = value }
-            to: 1.0
-            padding: 0
-            y: inputDeviceMeters.y + 48 * virtualstudio.uiScale
-            anchors.left: inputDeviceMeters.left
-            anchors.right: parent.right
-            anchors.rightMargin: rightMargin * virtualstudio.uiScale
-            handle: Rectangle {
-                x: inputSlider.leftPadding + inputSlider.visualPosition * (inputSlider.availableWidth - width)
-                y: inputSlider.topPadding + inputSlider.availableHeight / 2 - height / 2
-                implicitWidth: 26 * virtualstudio.uiScale
-                implicitHeight: 26 * virtualstudio.uiScale
-                radius: 13 * virtualstudio.uiScale
-                color: inputSlider.pressed ? sliderPressedColour : sliderColour
-                border.color: buttonStroke
-            }
-            visible: virtualstudio.backendAvailable
-        }
-
         Button {
             id: refreshButton
+            text: "Refresh Devices"
+            palette.buttonText: textColour
             background: Rectangle {
                 radius: 6 * virtualstudio.uiScale
                 color: refreshButton.down ? buttonPressedColour : (refreshButton.hovered ? buttonHoverColour : buttonColour)
                 border.width: 1
                 border.color: refreshButton.down ? buttonPressedStroke : (refreshButton.hovered ? buttonHoverStroke : buttonStroke)
             }
-            onClicked: { virtualstudio.refreshDevices() }
+            icon {
+                source: "refresh.svg";
+                color: textColour;
+            }
+            display: AbstractButton.TextBesideIcon
+            onClicked: {
+                virtualstudio.refreshDevices();
+            }
             anchors.right: parent.right
             anchors.rightMargin: rightMargin * virtualstudio.uiScale
-            anchors.topMargin: 18 * virtualstudio.uiScale
-            anchors.top: inputSlider.bottom
-            width: 216 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
-            visible: virtualstudio.audioBackend != "JACK" && virtualstudio.backendAvailable
-            Text {
-                text: "Refresh Devices"
-                font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
-                anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
-                color: textColour
+            anchors.verticalCenter: pageTitle.verticalCenter
+            width: 144 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
+            font {
+                family: "Poppins"
+                pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale
             }
+            visible: parent.isUsingRtAudio
         }
 
-        Text {
-            anchors.left: inputLabel.left
-            anchors.right: refreshButton.left
-            anchors.rightMargin: 16 * virtualstudio.uiScale
-            anchors.top: refreshButton.top
-            anchors.bottomMargin: 60 * 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: warningText
-            font { family: "Poppins"; pixelSize: fontExtraSmall * virtualstudio.fontScale * virtualstudio.uiScale }
-            visible: Boolean(virtualstudio.devicesError) || Boolean(virtualstudio.devicesWarning);
+        AudioSettings {
+            id: audioSettings
+            width: parent.width
+            anchors.top: pageTitle.bottom
+            anchors.topMargin: 24 * virtualstudio.uiScale
         }
 
         Button {
@@ -853,15 +564,7 @@ Item {
                 radius: 6 * virtualstudio.uiScale
                 color: saveButton.down ? saveButtonPressedColour : saveButtonBackgroundColour
                 border.width: 1
-                border.color: saveButton.down ? saveButtonPressedStroke : saveButtonStroke
-                layer.enabled: saveButton.hovered && !saveButton.down
-                layer.effect: DropShadow {
-                    horizontalOffset: 1 * virtualstudio.uiScale
-                    verticalOffset: 1 * virtualstudio.uiScale
-                    radius: 8.0 * virtualstudio.uiScale
-                    samples: 17
-                    color: saveButtonShadow
-                }
+                border.color: saveButton.down || saveButton.hovered ? saveButtonPressedStroke : saveButtonStroke
             }
             enabled: !Boolean(virtualstudio.devicesError) && virtualstudio.backendAvailable
             onClicked: { virtualstudio.windowState = "browse"; virtualstudio.applySettings() }
@@ -871,7 +574,7 @@ Item {
             anchors.bottom: parent.bottom
             width: 150 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale
             Text {
-                text: "Save Settings"
+                text: virtualstudio.studioToJoin.toString() ? "Connect to Studio" : "Save Settings"
                 font.family: "Poppins"
                 font.pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale
                 font.weight: Font.Bold
@@ -896,7 +599,7 @@ Item {
                 x: showAgainCheckbox.leftPadding
                 y: parent.height / 2 - height / 2
                 radius: 3 * virtualstudio.uiScale
-                border.color: showAgainCheckbox.down ? checkboxPressedStroke : checkboxStroke
+                border.color: showAgainCheckbox.down || showAgainCheckbox.hovered ? checkboxPressedStroke : checkboxStroke
 
                 Rectangle {
                     width: 10 * virtualstudio.uiScale
@@ -904,7 +607,7 @@ Item {
                     x: 3 * virtualstudio.uiScale
                     y: 3 * virtualstudio.uiScale
                     radius: 2 * virtualstudio.uiScale
-                    color: showAgainCheckbox.down ? checkboxPressedStroke : checkboxStroke
+                    color: showAgainCheckbox.down || showAgainCheckbox.hovered ? checkboxPressedStroke : checkboxStroke
                     visible: showAgainCheckbox.checked
                 }
             }
index f6808a3547e2743436d188feb8cfa68c2344bf4c..274851907cb5df04062b544ed20fdb8de73b2435 100644 (file)
@@ -7,17 +7,6 @@ Rectangle {
     width: 664; height: 83 * virtualstudio.uiScale
     radius: 6 * virtualstudio.uiScale
     color: backgroundColour
-    border.width: 0.3
-    border.color: "#40979797"
-
-    layer.enabled: true
-    layer.effect: DropShadow {
-        horizontalOffset: 1 * virtualstudio.uiScale
-        verticalOffset: 1 * virtualstudio.uiScale
-        radius: 8.0 * virtualstudio.uiScale
-        samples: 17
-        color: shadowColour
-    }
     
     property string serverLocation: "Germany - Berlin"
     property string flagImage: "flags/DE.svg"
@@ -26,7 +15,7 @@ Rectangle {
     property string studioId: ""
     property string inviteKeyString: ""
     property bool publicStudio: false
-    property bool manageable: false
+    property bool admin: false
     property bool available: true
     property bool connected: false
     property bool inviteCopied: false
@@ -45,6 +34,7 @@ Rectangle {
     property string shadowColour: virtualstudio.darkMode ? "#40000000" : "#80A1A1A1"
     property string toolTipBackgroundColour: inviteCopied ? "#57B147" : (virtualstudio.darkMode ? "#323232" : "#F3F3F3")
     property string toolTipTextColour: inviteCopied ? "#FAFBFB" : textColour
+    property string tooltipStroke: virtualstudio.darkMode ? "#80827D7D" : "#34979797"
 
     property string baseButtonColour: virtualstudio.darkMode ? "#F0F1F1" : "#EAEBEB"
     property string baseButtonHoverColour: virtualstudio.darkMode ? "#CCCDCD" : "#D3D3D3"
@@ -76,6 +66,11 @@ Rectangle {
     property string leavePressedColour: virtualstudio.darkMode ? "#F2AEAE" : "#EFADAD"
     property string leaveStroke: virtualstudio.darkMode ? "#A65959" : "#C95E5E"
 
+    property string studioStroke: virtualstudio.darkMode ? "#80827D7D" : "#34979797"
+
+    border.width: 1
+    border.color: studioStroke
+
     Clipboard {
         id: clipboard
     }
@@ -87,15 +82,6 @@ Rectangle {
         radius: 6
     }
 
-    DropShadow {
-        horizontalOffset: -1 * virtualstudio.uiScale
-        verticalOffset: -1 * virtualstudio.uiScale
-        radius: 8.0 * virtualstudio.uiScale
-        samples: 17
-        color: shadowColour
-        source: shadow
-    }
-
     Rectangle {
         width: 12 * virtualstudio.uiScale; height: parent.height
         radius: width / 2
@@ -153,7 +139,7 @@ Rectangle {
     
     Text {
         x: leftMargin * virtualstudio.uiScale; y: 11 * virtualstudio.uiScale;
-        width: manageable ? parent.width - (310 * virtualstudio.uiScale) : parent.width - (233 * virtualstudio.uiScale)
+        width: (admin || connected) ? parent.width - (310 * virtualstudio.uiScale) : parent.width - (233 * virtualstudio.uiScale)
         text: studioName
         fontSizeMode: Text.HorizontalFit
         font { family: "Poppins"; weight: Font.Bold; pixelSize: fontBig * virtualstudio.fontScale * virtualstudio.uiScale }
@@ -181,7 +167,7 @@ Rectangle {
     Text {
         anchors.verticalCenter: publicRect.verticalCenter
         x: (leftMargin + 22) * virtualstudio.uiScale
-        width: manageable ? parent.width - (255 * virtualstudio.uiScale) : parent.width - (178 * virtualstudio.uiScale)
+        width: (admin || connected) ? parent.width - (255 * virtualstudio.uiScale) : parent.width - (178 * virtualstudio.uiScale)
         text: publicStudio ? "Public hub studio " + serverLocation : "Private hub studio " + serverLocation
         font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
         elide: Text.ElideRight
@@ -190,7 +176,7 @@ Rectangle {
     
     Button {
         id: joinButton
-        x: manageable ? parent.width - (219 * virtualstudio.uiScale) : parent.width - (142 * virtualstudio.uiScale)
+        x: (admin || connected) ? parent.width - (219 * virtualstudio.uiScale) : parent.width - (142 * virtualstudio.uiScale)
         y: topMargin * virtualstudio.uiScale; width: 40 * virtualstudio.uiScale; height: width
         background: Rectangle {
             radius: width / 2
@@ -217,7 +203,7 @@ Rectangle {
 
     Button {
         id: leaveButton
-        x: manageable ? parent.width - (219 * virtualstudio.uiScale) : parent.width - (142 * virtualstudio.uiScale)
+        x: (admin || connected) ? parent.width - (219 * virtualstudio.uiScale) : parent.width - (142 * virtualstudio.uiScale)
         y: topMargin * virtualstudio.uiScale; width: 40 * virtualstudio.uiScale; height: width
         background: Rectangle {
             radius: width / 2
@@ -251,7 +237,7 @@ Rectangle {
 
     Button {
         id: inviteButton
-        x: manageable ? parent.width - (142 * virtualstudio.uiScale) : parent.width - (65 * virtualstudio.uiScale)
+        x: (admin || connected) ? parent.width - (142 * virtualstudio.uiScale) : parent.width - (65 * virtualstudio.uiScale)
         y: topMargin * virtualstudio.uiScale; width: 40 * virtualstudio.uiScale; height: width
         background: Rectangle {
             radius: width / 2
@@ -279,7 +265,7 @@ Rectangle {
         visible: true
         Image {
             id: shareImg
-            width: 20 * virtualstudio.uiScale; height: width
+            width: 24 * virtualstudio.uiScale; height: width
             anchors { verticalCenter: parent.verticalCenter; horizontalCenter: parent.horizontalCenter }
             source: "share.svg"
             sourceSize: Qt.size(shareImg.width,shareImg.height)
@@ -299,13 +285,8 @@ Rectangle {
                 anchors.bottomMargin: bottomToolTipMargin * virtualstudio.uiScale
                 anchors.rightMargin: rightToolTipMargin * virtualstudio.uiScale
                 layer.enabled: true
-                layer.effect: DropShadow {
-                    horizontalOffset: 1 * virtualstudio.uiScale
-                    verticalOffset: 1 * virtualstudio.uiScale
-                    radius: 10.0 * virtualstudio.uiScale
-                    samples: 21
-                    color: shadowColour
-                }
+                border.width: 1
+                border.color: tooltipStroke
 
                 Text {
                     anchors.centerIn: parent
@@ -330,30 +311,28 @@ Rectangle {
     }
     
     Button {
-        id: manageButton
+        id: manageOrVideoButton
         x: parent.width - (65 * virtualstudio.uiScale); y: topMargin * virtualstudio.uiScale
         width: 40 * virtualstudio.uiScale; height: width
         background: Rectangle {
             radius: width / 2
-            color: manageButton.down ? managePressedColour : (manageButton.hovered ? manageHoverColour : manageColour)
-            border.width:  manageButton.down ? 1 : 0
+            color: manageOrVideoButton.down ? managePressedColour : (manageOrVideoButton.hovered ? manageHoverColour : manageColour)
+            border.width:  manageOrVideoButton.down ? 1 : 0
             border.color: manageStroke
         }
         onClicked: { 
-            if (manageable && connected) {
+            if (connected) {
                 virtualstudio.launchVideo(-1)
-            } else if (connected) {
-                virtualstudio.manageStudio(-1);
             } else {
                 virtualstudio.manageStudio(index);
             }
         }
-        visible: manageable
+        visible: admin || connected
         Image {
             id: manageImg
             width: 20 * virtualstudio.uiScale; height: width
             anchors { verticalCenter: parent.verticalCenter; horizontalCenter: parent.horizontalCenter }
-            source: manageable && connected ? "video.svg" : "manage.svg"
+            source: connected ? "video.svg" : "manage.svg"
             sourceSize: Qt.size(manageImg.width,manageImg.height)
             fillMode: Image.PreserveAspectFit
             smooth: true
@@ -361,11 +340,11 @@ Rectangle {
     }
     
     Text {
-        anchors.horizontalCenter: manageButton.horizontalCenter
+        anchors.horizontalCenter: manageOrVideoButton.horizontalCenter
         y: 56 * virtualstudio.uiScale
-        text: manageable && connected ? "Video" : "Manage"
+        text: connected ? "Video" : "Manage"
         font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
-        visible: manageable
+        visible: admin || connected
         color: textColour
     }
 }
index fa3a213b4e7e55604a0c91e72fe9f8f0116ba846..8041f67ef8e21771229a9520abacbaef70e095a3 100644 (file)
@@ -1,3 +1 @@
-<svg width="24" height="26" viewBox="0 0 24 26" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12 0C8.81846 0.00344108 5.76821 1.26883 3.51852 3.51852C1.26883 5.76821 0.00344108 8.81846 0 12V22.444C0.00105872 23.3868 0.376048 24.2907 1.0427 24.9573C1.70935 25.624 2.61322 25.9989 3.556 26H6C6.26522 26 6.51957 25.8946 6.70711 25.7071C6.89464 25.5196 7 25.2652 7 25V15C7 14.7348 6.89464 14.4804 6.70711 14.2929C6.51957 14.1054 6.26522 14 6 14H2V12C2 9.34784 3.05357 6.8043 4.92893 4.92893C6.8043 3.05357 9.34784 2 12 2C14.6522 2 17.1957 3.05357 19.0711 4.92893C20.9464 6.8043 22 9.34784 22 12V14H18C17.7348 14 17.4804 14.1054 17.2929 14.2929C17.1054 14.4804 17 14.7348 17 15V25C17 25.2652 17.1054 25.5196 17.2929 25.7071C17.4804 25.8946 17.7348 26 18 26H20.444C21.3868 25.9989 22.2907 25.624 22.9573 24.9573C23.624 24.2907 23.9989 23.3868 24 22.444V12C23.9966 8.81846 22.7312 5.76821 20.4815 3.51852C18.2318 1.26883 15.1815 0.00344108 12 0Z" fill="#0F0D0D"/>
-</svg>
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none" opacity=".1"/><path d="M12 1c-4.97 0-9 4.03-9 9v7c0 1.66 1.34 3 3 3h3v-8H5v-2c0-3.87 3.13-7 7-7s7 3.13 7 7v2h-4v8h3c1.66 0 3-1.34 3-3v-7c0-4.97-4.03-9-9-9z"/></svg>
\ No newline at end of file
diff --git a/src/gui/help.svg b/src/gui/help.svg
new file mode 100644 (file)
index 0000000..d2a8d02
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"/></svg>
\ No newline at end of file
diff --git a/src/gui/loud.svg b/src/gui/loud.svg
new file mode 100644 (file)
index 0000000..b3aeb16
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg viewBox="0 0 13 12" width="13" height="12" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path d="M 7.543 12 L 7.543 10.937 C 8.651 10.617 9.557 10.003 10.26 9.094 C 10.963 8.186 11.314 7.154 11.314 6 C 11.314 4.846 10.603 3.712 9.906 2.798 C 9.209 1.884 8.663 1.371 7.543 1.063 L 7.543 0 C 8.96 0.32 10.114 1.037 11.006 2.151 C 11.897 3.266 12.343 4.549 12.343 6 C 12.343 7.451 11.897 8.734 11.006 9.849 C 10.114 10.963 8.96 11.68 7.543 12 Z M 0 8.074 L 0 3.96 L 2.743 3.96 L 6.171 0.531 L 6.171 11.503 L 2.743 8.074 L 0 8.074 Z M 7.2 8.897 L 7.2 3.12 C 7.829 3.314 8.329 3.68 8.7 4.217 C 9.071 4.754 9.257 5.354 9.257 6.017 C 9.257 6.669 9.069 7.263 8.691 7.8 C 8.314 8.337 7.817 8.703 7.2 8.897 Z M 5.143 3.137 L 3.206 4.989 L 1.029 4.989 L 1.029 7.046 L 3.206 7.046 L 5.143 8.914 L 5.143 3.137 Z" fill="#353637"/>
+</svg>
\ No newline at end of file
index 2568def564260ab3b7a22420637acc33d5992092..520b7f9174b6d7ecf70bc09cb87afbc040f54cc2 100644 (file)
@@ -1,4 +1 @@
-<svg width="19" height="28" viewBox="0 0 19 28" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M14.7516 5.43539C14.7516 2.43351 12.3181 0 9.31624 0C6.31436 0 3.88086 2.43351 3.88086 5.43539V13.4077C3.88086 16.4096 6.31436 18.8431 9.31624 18.8431C12.3181 18.8431 14.7516 16.4096 14.7516 13.4077V5.43539Z" fill="#0F0D0D"/>
-<path d="M0.906172 12.2445C0.66584 12.2445 0.435352 12.34 0.265412 12.5099C0.0954714 12.6799 0 12.9104 0 13.1507C0 17.9818 3.69688 21.9658 8.41039 22.4224V26.1882H4.7868C4.66596 26.1852 4.54575 26.2065 4.43324 26.2507C4.32074 26.2949 4.21821 26.3611 4.1317 26.4455C4.04518 26.5299 3.97643 26.6308 3.92949 26.7422C3.88255 26.8536 3.85837 26.9732 3.85837 27.0941C3.85837 27.215 3.88255 27.3346 3.92949 27.446C3.97643 27.5574 4.04518 27.6583 4.1317 27.7427C4.21821 27.8271 4.32074 27.8934 4.43324 27.9375C4.54575 27.9817 4.66596 28.003 4.7868 28H13.8463C14.0827 27.9942 14.3075 27.8962 14.4727 27.7269C14.6378 27.5577 14.7302 27.3306 14.7302 27.0941C14.7302 26.8576 14.6378 26.6305 14.4727 26.4613C14.3075 26.292 14.0827 26.194 13.8463 26.1882H10.2227V22.4219C14.9363 21.9647 18.6331 17.9813 18.6331 13.1502C18.6273 12.9138 18.5293 12.689 18.3601 12.5238C18.1908 12.3587 17.9637 12.2663 17.7272 12.2663C17.4908 12.2663 17.2636 12.3587 17.0944 12.5238C16.9251 12.689 16.8271 12.9138 16.8213 13.1502C16.8213 17.2878 13.4548 20.6544 9.31711 20.6544C5.17945 20.6544 1.81234 17.2884 1.81234 13.1507C1.81234 12.9104 1.71687 12.6799 1.54693 12.5099C1.37699 12.34 1.1465 12.2445 0.906172 12.2445Z" fill="#0F0D0D"/>
-</svg>
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z"/></svg>
\ No newline at end of file
index e827d6f1d50c3a4de10c7555182ec3f1a3574725..88165381e1cb352df04518118a3fdeb01ce14bc5 100644 (file)
@@ -1,17 +1 @@
-<svg width="19" height="28" viewBox="0 0 19 28" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g clip-path="url(#clip0_1_2)">
-<g clip-path="url(#clip1_1_2)">
-<path d="M14.7516 5.43539C14.7516 2.43351 12.3181 0 9.31624 0C6.31436 0 3.88086 2.43351 3.88086 5.43539V13.4077C3.88086 16.4096 6.31436 18.8431 9.31624 18.8431C12.3181 18.8431 14.7516 16.4096 14.7516 13.4077V5.43539Z" fill="#0F0D0D"/>
-<path d="M0.906172 12.2445C0.66584 12.2445 0.435352 12.34 0.265412 12.5099C0.0954714 12.6799 0 12.9104 0 13.1507C0 17.9818 3.69688 21.9658 8.41039 22.4224V26.1882H4.7868C4.66596 26.1852 4.54575 26.2065 4.43324 26.2507C4.32074 26.2949 4.21821 26.3611 4.1317 26.4455C4.04518 26.5299 3.97643 26.6308 3.92949 26.7422C3.88255 26.8536 3.85837 26.9732 3.85837 27.0941C3.85837 27.215 3.88255 27.3346 3.92949 27.446C3.97643 27.5574 4.04518 27.6583 4.1317 27.7427C4.21821 27.8271 4.32074 27.8934 4.43324 27.9375C4.54575 27.9817 4.66596 28.003 4.7868 28H13.8463C14.0827 27.9942 14.3075 27.8962 14.4727 27.7269C14.6378 27.5577 14.7302 27.3306 14.7302 27.0941C14.7302 26.8576 14.6378 26.6305 14.4727 26.4613C14.3075 26.292 14.0827 26.194 13.8463 26.1882H10.2227V22.4219C14.9363 21.9647 18.6331 17.9813 18.6331 13.1502C18.6273 12.9138 18.5293 12.689 18.3601 12.5238C18.1908 12.3587 17.9637 12.2663 17.7272 12.2663C17.4908 12.2663 17.2636 12.3587 17.0944 12.5238C16.9251 12.689 16.8271 12.9138 16.8213 13.1502C16.8213 17.2878 13.4548 20.6544 9.31711 20.6544C5.17945 20.6544 1.81234 17.2884 1.81234 13.1507C1.81234 12.9104 1.71687 12.6799 1.54693 12.5099C1.37699 12.34 1.1465 12.2445 0.906172 12.2445Z" fill="#0F0D0D"/>
-<line x1="1.38898" y1="2.26597" x2="17.111" y2="25.4353" stroke="#0F0D0D" stroke-width="2" stroke-linecap="round"/>
-</g>
-</g>
-<defs>
-<clipPath id="clip0_1_2">
-<rect width="19" height="28" fill="white"/>
-</clipPath>
-<clipPath id="clip1_1_2">
-<rect width="19" height="28" fill="white"/>
-</clipPath>
-</defs>
-</svg>
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0zm0 0h24v24H0z" fill="none"/><path d="M19 11h-1.7c0 .74-.16 1.43-.43 2.05l1.23 1.23c.56-.98.9-2.09.9-3.28zm-4.02.17c0-.06.02-.11.02-.17V5c0-1.66-1.34-3-3-3S9 3.34 9 5v.18l5.98 5.99zM4.27 3L3 4.27l6.01 6.01V11c0 1.66 1.33 3 2.99 3 .22 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c.91-.13 1.77-.45 2.54-.9L19.73 21 21 19.73 4.27 3z"/></svg>
\ No newline at end of file
index ad5ebb284c93794fdf13a6f09191589baffee3d2..cf0e61af72b18b4eab1dd3cbcff01163671b9972 100644 (file)
@@ -28,6 +28,7 @@
 #include <QFileDialog>
 #include <QHostAddress>
 #include <QMessageBox>
+#include <QProcess>
 #include <QSettings>
 #include <QVector>
 #include <cstdlib>
@@ -52,7 +53,7 @@
 #include "../Meter.h"
 #include "../Reverb.h"
 
-QJackTrip::QJackTrip(int argc, bool suppressCommandlineWarning, QWidget* parent)
+QJackTrip::QJackTrip(Settings* settings, bool suppressCommandlineWarning, QWidget* parent)
     : QMainWindow(parent)
     , m_ui(new Ui::QJackTrip)
     , m_netManager(new QNetworkAccessManager(this))
@@ -63,8 +64,6 @@ QJackTrip::QJackTrip(int argc, bool suppressCommandlineWarning, QWidget* parent)
     , m_jackTripRunning(false)
     , m_isExiting(false)
     , m_exitSent(false)
-    , m_hasIPv4Reply(false)
-    , m_argc(argc)
     , m_hideWarning(false)
 {
     m_ui->setupUi(this);
@@ -222,11 +221,21 @@ QJackTrip::QJackTrip(int argc, bool suppressCommandlineWarning, QWidget* parent)
         m_ui->outClientsSpinBox->setEnabled(m_ui->outLimiterCheckBox->isChecked());
     });
 
-    connect(m_netManager.data(), &QNetworkAccessManager::finished, this,
-            &QJackTrip::receivedIP);
-    // Use the ipify API to find our external IP address.
-    m_netManager->get(QNetworkRequest(QUrl(QStringLiteral("https://api.ipify.org"))));
-    m_netManager->get(QNetworkRequest(QUrl(QStringLiteral("https://api6.ipify.org"))));
+    connect(m_ui->connectScriptCheckBox, &QCheckBox::stateChanged, this, [=]() {
+        m_ui->connectScriptEdit->setEnabled(m_ui->connectScriptCheckBox->isChecked());
+        m_ui->connectScriptBrowse->setEnabled(m_ui->connectScriptCheckBox->isChecked());
+    });
+    connect(m_ui->disconnectScriptCheckBox, &QCheckBox::stateChanged, this, [=]() {
+        m_ui->disconnectScriptEdit->setEnabled(
+            m_ui->disconnectScriptCheckBox->isChecked());
+        m_ui->disconnectScriptBrowse->setEnabled(
+            m_ui->disconnectScriptCheckBox->isChecked());
+    });
+    connect(m_ui->connectScriptBrowse, &QPushButton::clicked, this,
+            &QJackTrip::browseForFile);
+    connect(m_ui->disconnectScriptBrowse, &QPushButton::clicked, this,
+            &QJackTrip::browseForFile);
+
     m_ui->statusBar->showMessage(QStringLiteral("JackTrip version ").append(gVersion));
 
     // Set up our interface for the default Client run mode.
@@ -283,20 +292,20 @@ QJackTrip::QJackTrip(int argc, bool suppressCommandlineWarning, QWidget* parent)
     }
 #endif
 
-    // One of our arguments will always be --gui, so if that's the only one
-    // then we don't need to show the warning message.
-    if (((!gVerboseFlag && m_argc > 2) || m_argc > 3) && !suppressCommandlineWarning) {
+    migrateSettings();
+    loadSettings(settings);
+
+    // Display a warning about any ignored command line options.
+    if (settings->guiIgnoresArguments() && !suppressCommandlineWarning) {
         QMessageBox msgBox;
         msgBox.setText(
-            "The GUI version of JackTrip currently ignores any command line "
-            "options other than the verbose option (-V).\n\nThis may change in future.");
+            "You have supplied command line options that the GUI version of JackTrip "
+            "currently ignores. (Everything else will run as expected.)\n\nRun "
+            "\"jacktrip -h\" for more details.");
         msgBox.setWindowTitle(QStringLiteral("Command line options"));
         msgBox.exec();
     }
 
-    migrateSettings();
-    loadSettings();
-
     QVector<QLabel*> labels;
     labels << m_ui->inFreeverbLabel << m_ui->inZitarevLabel << m_ui->outFreeverbLabel;
     std::srand(std::time(nullptr));
@@ -318,9 +327,8 @@ QJackTrip::QJackTrip(int argc, bool suppressCommandlineWarning, QWidget* parent)
     // Check if Jack is actually available
     if (have_libjack() != 0) {
 #ifdef RT_AUDIO
-#ifdef PSI
-        bool usingRtAudioAlready = m_ui->backendComboBox->currentIndex() == 1;
-#endif  // PSI
+        m_audioFallback       = true;
+        m_usingRtAudioAlready = m_ui->backendComboBox->currentIndex() == 1;
         m_ui->backendComboBox->setCurrentIndex(1);
         m_ui->backendComboBox->setEnabled(false);
         m_ui->backendLabel->setEnabled(false);
@@ -333,34 +341,6 @@ QJackTrip::QJackTrip(int argc, bool suppressCommandlineWarning, QWidget* parent)
         m_ui->backendWarningLabel->setText(
             "JACK was not found. This means that only the RtAudio backend is available "
             "and that JackTrip cannot be run in hub server mode.");
-
-#ifdef PSI
-        QSettings settings;
-        settings.beginGroup(QStringLiteral("Audio"));
-        if (!settings.value(QStringLiteral("HideJackWarning"), false).toBool()) {
-            QCheckBox* dontBugMe =
-                new QCheckBox(QStringLiteral("Don't show this warning again"));
-            QMessageBox msgBox;
-            msgBox.setText(
-                "An installation of JACK was not found. JackTrip will still run using "
-                "a different audio backend (RtAudio) but some more advanced features, "
-                "like the ability to run your own hub server, will not be available."
-                "\n\n(If you install JACK at a later stage, these features will "
-                "automatically be re-enabled.)");
-            msgBox.setWindowTitle(QStringLiteral("JACK Not Available"));
-            msgBox.setCheckBox(dontBugMe);
-            QObject::connect(dontBugMe, &QCheckBox::stateChanged, this, [=]() {
-                m_hideWarning = dontBugMe->isChecked();
-            });
-            msgBox.exec();
-            if (m_hideWarning) {
-                settings.setValue(QStringLiteral("HideJackWarning"), true);
-            }
-            if (!usingRtAudioAlready) {
-                settings.setValue(QStringLiteral("UsingFallback"), true);
-            }
-        }
-        settings.endGroup();
     } else {
         // If we've fallen back to RtAudio before and JACK is now installed, use JACK.
         QSettings settings;
@@ -370,7 +350,6 @@ QJackTrip::QJackTrip(int argc, bool suppressCommandlineWarning, QWidget* parent)
             settings.setValue(QStringLiteral("UsingFallback"), false);
         }
         settings.endGroup();
-#endif  // PSI
 #else   // RT_AUDIO
         QMessageBox msgBox;
         msgBox.setText(
@@ -455,6 +434,45 @@ void QJackTrip::showEvent(QShowEvent* event)
             this->resize(QSize(this->size().height(), 600));
         }
         settings.endGroup();
+
+        // Use the ipify API to find our external IP address.
+        connect(m_netManager.data(), &QNetworkAccessManager::finished, this,
+                &QJackTrip::receivedIP);
+        m_netManager->get(QNetworkRequest(QUrl(QStringLiteral("https://api.ipify.org"))));
+        m_netManager->get(
+            QNetworkRequest(QUrl(QStringLiteral("https://api6.ipify.org"))));
+
+        // Also show our JACK not found warning if needed.
+#ifdef RT_AUDIO
+        if (m_audioFallback) {
+            QSettings settings;
+            settings.beginGroup(QStringLiteral("Audio"));
+            if (!settings.value(QStringLiteral("HideJackWarning"), false).toBool()) {
+                QCheckBox* dontBugMe =
+                    new QCheckBox(QStringLiteral("Don't show this warning again"));
+                QMessageBox msgBox;
+                msgBox.setText(
+                    "An installation of JACK was not found. JackTrip will still run "
+                    "using a different audio backend (RtAudio) but some more advanced "
+                    "features, like the ability to run your own hub server, will not be "
+                    "available.\n\n(If you install JACK at a later stage, these features "
+                    "will automatically be re-enabled.)");
+                msgBox.setWindowTitle(QStringLiteral("JACK Not Available"));
+                msgBox.setCheckBox(dontBugMe);
+                QObject::connect(dontBugMe, &QCheckBox::stateChanged, this, [=]() {
+                    m_hideWarning = dontBugMe->isChecked();
+                });
+                msgBox.exec();
+                if (m_hideWarning) {
+                    settings.setValue(QStringLiteral("HideJackWarning"), true);
+                }
+                if (!m_usingRtAudioAlready) {
+                    settings.setValue(QStringLiteral("UsingFallback"), true);
+                }
+            }
+            settings.endGroup();
+        }
+#endif  // RT_AUDIO
         m_firstShow = false;
     }
 }
@@ -483,6 +501,21 @@ void QJackTrip::processFinished()
     } else {
         m_jackTrip.reset();
     }
+
+    if (m_ui->disconnectScriptCheckBox->isChecked()) {
+        QStringList arguments = m_ui->disconnectScriptEdit->text().split(
+            QStringLiteral(" "), Qt::SkipEmptyParts);
+        if (!arguments.isEmpty()) {
+            QProcess disconnectScript;
+            disconnectScript.setProgram(arguments.takeFirst());
+            disconnectScript.setWorkingDirectory(QDir::homePath());
+            disconnectScript.setArguments(arguments);
+            disconnectScript.setStandardOutputFile(QProcess::nullDevice());
+            disconnectScript.setStandardErrorFile(QProcess::nullDevice());
+            disconnectScript.startDetached();
+        }
+    }
+
     if (m_isExiting) {
         m_exitSent = true;
         emit signalExit();
@@ -511,6 +544,19 @@ void QJackTrip::processError(const QString& errorMessage)
 void QJackTrip::receivedConnectionFromPeer()
 {
     m_ui->statusBar->showMessage(QStringLiteral("Received Connection from Peer!"));
+    if (m_ui->connectScriptCheckBox->isChecked()) {
+        QStringList arguments = m_ui->connectScriptEdit->text().split(QStringLiteral(" "),
+                                                                      Qt::SkipEmptyParts);
+        if (!arguments.isEmpty()) {
+            QProcess connectScript;
+            connectScript.setProgram(arguments.takeFirst());
+            connectScript.setWorkingDirectory(QDir::homePath());
+            connectScript.setArguments(arguments);
+            connectScript.setStandardOutputFile(QProcess::nullDevice());
+            connectScript.setStandardErrorFile(QProcess::nullDevice());
+            connectScript.startDetached();
+        }
+    }
 }
 
 void QJackTrip::queueLengthChanged(int queueLength)
@@ -560,6 +606,10 @@ void QJackTrip::chooseRunType(int index)
         if (index != -1) {
             m_ui->optionsTabWidget->removeTab(index);
         }
+        index = findTab(QStringLiteral("Scripting"));
+        if (index != -1) {
+            m_ui->optionsTabWidget->removeTab(index);
+        }
         authFilesChanged();
 #ifdef RT_AUDIO
         index = findTab(QStringLiteral("Audio Backend"));
@@ -576,6 +626,10 @@ void QJackTrip::chooseRunType(int index)
         if (findTab(QStringLiteral("Plugins")) == -1) {
             m_ui->optionsTabWidget->addTab(m_ui->pluginsTab, QStringLiteral("Plugins"));
         }
+        if (findTab(QStringLiteral("Scripting")) == -1) {
+            m_ui->optionsTabWidget->addTab(m_ui->scriptingTab,
+                                           QStringLiteral("Scripting"));
+        }
 #ifdef RT_AUDIO
         if (findTab(QStringLiteral("Audio Backend")) == -1) {
             m_ui->optionsTabWidget->insertTab(2, m_ui->backendTab,
@@ -653,7 +707,13 @@ void QJackTrip::browseForFile()
         fileEdit = m_ui->keyEdit;
     } else {
         fileType = QLatin1String("");
-        fileEdit = m_ui->credsEdit;
+        if (sender == m_ui->connectScriptBrowse) {
+            fileEdit = m_ui->connectScriptEdit;
+        } else if (sender == m_ui->disconnectScriptBrowse) {
+            fileEdit = m_ui->disconnectScriptEdit;
+        } else {
+            fileEdit = m_ui->credsEdit;
+        }
     }
     QString fileName = QFileDialog::getOpenFileName(this, QStringLiteral("Open File"),
                                                     m_lastPath, fileType);
@@ -667,6 +727,8 @@ void QJackTrip::browseForFile()
 void QJackTrip::receivedIP(QNetworkReply* reply)
 {
     QMutexLocker locker(&m_requestMutex);
+    m_replyCount++;
+
     // Check whether we're dealing with our IPv4 or IPv6 request.
     if (reply->url().host().startsWith(QLatin1String("api6"))) {
         if (reply->error() == QNetworkReply::NoError) {
@@ -677,28 +739,33 @@ void QJackTrip::receivedIP(QNetworkReply* reply)
                 reply->deleteLater();
                 return;
             }
-            if (m_hasIPv4Reply) {
-                m_ui->ipLabel->setText(m_ui->ipLabel->text().append(
-                    QStringLiteral("\n(IPv6: %1)").arg(m_IPv6Address)));
-            }
-            m_ui->ipLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
         }
     } else {
-        if (reply->error() != QNetworkReply::NoError) {
+        if (reply->error() == QNetworkReply::NoError) {
+            m_IPv4Address = QString(reply->readAll());
+        }
+    }
+
+    if (m_replyCount == 2) {
+        // Set our label if both replies have arrived.
+        if (m_IPv4Address.isEmpty() && m_IPv6Address.isEmpty()) {
             m_ui->ipLabel->setText(
                 QStringLiteral("Unable to determine external IP address."));
+        } else if (m_IPv4Address.isEmpty()) {
+            m_ui->ipLabel->setText(
+                QStringLiteral("External IPv6 address: ").append(m_IPv6Address));
+            m_ui->ipLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
         } else {
-            QByteArray address = reply->readAll();
             m_ui->ipLabel->setText(
-                QStringLiteral("External IP address: ").append(address));
+                QStringLiteral("External IP address: ").append(m_IPv4Address));
             m_ui->ipLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
+            if (!m_IPv6Address.isEmpty()) {
+                m_ui->ipLabel->setText(m_ui->ipLabel->text().append(
+                    QStringLiteral("\n(IPv6: %1)").arg(m_IPv6Address)));
+            }
         }
-        if (!m_IPv6Address.isEmpty()) {
-            m_ui->ipLabel->setText(m_ui->ipLabel->text().append(
-                QStringLiteral("\n(IPv6: %1)").arg(m_IPv6Address)));
-        }
-        m_hasIPv4Reply = true;
     }
+
     reply->deleteLater();
 }
 
@@ -754,11 +821,8 @@ void QJackTrip::start()
         if (m_ui->typeComboBox->currentIndex() == HUB_SERVER) {
             m_udpHub.reset(new UdpHubListener(m_ui->localPortSpinBox->value(),
                                               m_ui->basePortSpinBox->value()));
-            int hubConnectionMode = m_ui->autoPatchComboBox->currentIndex();
-            if (hubConnectionMode > CLIENTFOFI) {
-                // Adjust for the RESERVEDMATRIX gap.
-                hubConnectionMode++;
-            }
+            int hubConnectionMode = hubModeFromPatchType(
+                static_cast<patchTypeT>(m_ui->autoPatchComboBox->currentIndex()));
             if (m_ui->patchServerCheckBox->isChecked()) {
                 if (m_ui->autoPatchComboBox->currentIndex() == CLIENTFOFI) {
                     hubConnectionMode = JackTrip::SERVFOFI;
@@ -827,14 +891,14 @@ void QJackTrip::start()
                 jackTripMode = JackTrip::CLIENTTOPINGSERVER;
             }
 
-            m_jackTrip.reset(new JackTrip(jackTripMode, JackTrip::UDP,
-                                          m_ui->channelSendSpinBox->value(),
-                                          m_ui->channelRecvSpinBox->value(),
+            m_jackTrip.reset(new JackTrip(
+                jackTripMode, JackTrip::UDP, 0, m_ui->channelSendSpinBox->value(), 0,
+                m_ui->channelRecvSpinBox->value(), AudioInterface::MIX_UNSET,
 #ifdef WAIR  // wair
-                                          0,
+                0,
 #endif  // endwhere
-                                          m_ui->queueLengthSpinBox->value(),
-                                          m_ui->redundancySpinBox->value(), resolution));
+                m_ui->queueLengthSpinBox->value(), m_ui->redundancySpinBox->value(),
+                resolution));
             m_jackTrip->setConnectDefaultAudioPorts(
                 m_ui->connectAudioCheckBox->isChecked());
             if (m_ui->zeroCheckBox->isChecked()) {
@@ -1003,13 +1067,13 @@ void QJackTrip::exit()
     }
 }
 
-void QJackTrip::updatedInputMeasurements(const QVector<float> valuesInDb)
+void QJackTrip::updatedInputMeasurements(const float* valuesInDb, int numChannels)
 {
     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));
+        if (i < numChannels) {
+            dB = std::max(m_meterMin, valuesInDb[i]);
         }
 
         // Produce a normalized value from 0 to 1
@@ -1018,13 +1082,13 @@ void QJackTrip::updatedInputMeasurements(const QVector<float> valuesInDb)
     }
 }
 
-void QJackTrip::updatedOutputMeasurements(const QVector<float> valuesInDb)
+void QJackTrip::updatedOutputMeasurements(const float* valuesInDb, int numChannels)
 {
     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));
+        if (i < numChannels) {
+            dB = std::max(m_meterMin, valuesInDb[i]);
         }
 
         // Produce a normalized value from 0 to 1
@@ -1085,8 +1149,6 @@ void QJackTrip::advancedOptionsForHubServer(bool isHubServer)
     m_ui->clientNameEdit->setVisible(!isHubServer);
     m_ui->redundancyLabel->setVisible(!isHubServer);
     m_ui->redundancySpinBox->setVisible(!isHubServer);
-    m_ui->resolutionLabel->setVisible(!isHubServer);
-    m_ui->resolutionComboBox->setVisible(!isHubServer);
     m_ui->connectAudioCheckBox->setVisible(!isHubServer);
     m_ui->basePortLabel->setVisible(isHubServer);
     m_ui->basePortSpinBox->setVisible(isHubServer);
@@ -1120,58 +1182,22 @@ void QJackTrip::migrateSettings()
     settings.setValue(QStringLiteral("Migrated"), true);
 }
 
-void QJackTrip::loadSettings()
+void QJackTrip::loadSettings(Settings* cliSettings)
 {
     QSettings settings;
-    m_ui->typeComboBox->setCurrentIndex(
-        settings.value(QStringLiteral("RunMode"), 2).toInt());
+    bool useCommandLine = false;
+    if (cliSettings) {
+        useCommandLine = cliSettings->isModeSet();
+    }
 
-    // Migrate to separate send and receive channel numbers.
+    // Migrate to separate send and receive channel numbers first if needed
     int oldChannelSetting = settings.value(QStringLiteral("Channels"), -1).toInt();
     if (oldChannelSetting != -1) {
-        m_ui->channelSendSpinBox->setValue(oldChannelSetting);
-        m_ui->channelRecvSpinBox->setValue(oldChannelSetting);
+        settings.setValue(QStringLiteral("ChannelsSend"), oldChannelSetting);
+        settings.setValue(QStringLiteral("ChannelsRecv"), oldChannelSetting);
         settings.remove(QStringLiteral("Channels"));
-    } else {
-        m_ui->channelSendSpinBox->setValue(
-            settings.value(QStringLiteral("ChannelsSend"), gDefaultNumInChannels)
-                .toInt());
-        m_ui->channelRecvSpinBox->setValue(
-            settings.value(QStringLiteral("ChannelsRecv"), gDefaultNumOutChannels)
-                .toInt());
     }
 
-    m_ui->autoPatchComboBox->setCurrentIndex(
-        settings.value(QStringLiteral("AutoPatchMode"), 0).toInt());
-    m_ui->patchServerCheckBox->setChecked(
-        settings.value(QStringLiteral("PatchIncludesServer"), false).toBool());
-    m_ui->upmixCheckBox->setChecked(
-        settings.value(QStringLiteral("StereoUpmix"), false).toBool());
-    m_ui->zeroCheckBox->setChecked(
-        settings.value(QStringLiteral("ZeroUnderrun"), false).toBool());
-    m_ui->timeoutCheckBox->setChecked(
-        settings.value(QStringLiteral("Timeout"), false).toBool());
-    m_ui->clientNameEdit->setText(
-        settings.value(QStringLiteral("ClientName"), "").toString());
-    m_ui->remoteNameEdit->setText(
-        settings.value(QStringLiteral("RemoteName"), "").toString());
-    m_ui->localPortSpinBox->setValue(
-        settings.value(QStringLiteral("LocalPort"), gDefaultPort).toInt());
-    m_ui->remotePortSpinBox->setValue(
-        settings.value(QStringLiteral("RemotePort"), gDefaultPort).toInt());
-    m_ui->basePortSpinBox->setValue(
-        settings.value(QStringLiteral("BasePort"), 61002).toInt());
-    m_ui->queueLengthSpinBox->setValue(
-        settings.value(QStringLiteral("QueueLength"), gDefaultQueueLength).toInt());
-    m_ui->redundancySpinBox->setValue(
-        settings.value(QStringLiteral("Redundancy"), gDefaultRedundancy).toInt());
-    m_ui->resolutionComboBox->setCurrentIndex(
-        settings.value(QStringLiteral("Resolution"), 1).toInt());
-    m_ui->connectAudioCheckBox->setChecked(
-        settings.value(QStringLiteral("ConnectAudio"), true).toBool());
-    m_ui->realTimeCheckBox->setChecked(
-        settings.value(QStringLiteral("RTNetworking"), true).toBool());
-    // This may have been set by the command line, so don't overwrite if that's the case.
     m_ui->verboseCheckBox->setChecked(
         gVerboseFlag || settings.value(QStringLiteral("Debug"), 0).toBool());
     m_lastPath = settings.value(QStringLiteral("LastPath"), QDir::homePath()).toString();
@@ -1186,9 +1212,217 @@ void QJackTrip::loadSettings()
     }
     settings.endGroup();
     // Need to get this here so it isn't overwritten by the previous section.
-    m_ui->addressComboBox->setCurrentText(
-        settings.value(QStringLiteral("LastAddress"), "").toString());
+    if (useCommandLine && !cliSettings->getPeerAddress().isEmpty()) {
+        m_ui->addressComboBox->setCurrentText(cliSettings->getPeerAddress());
+    } else {
+        m_ui->addressComboBox->setCurrentText(
+            settings.value(QStringLiteral("LastAddress"), "").toString());
+    }
+
+    if (useCommandLine) {
+        JackTrip::jacktripModeT mode = cliSettings->getJackTripMode();
+        if (mode == JackTrip::CLIENT) {
+            m_ui->typeComboBox->setCurrentIndex(P2P_CLIENT);
+        } else if (mode == JackTrip::SERVER) {
+            m_ui->typeComboBox->setCurrentIndex(P2P_SERVER);
+        } else if (mode == JackTrip::CLIENTTOPINGSERVER) {
+            m_ui->typeComboBox->setCurrentIndex(HUB_CLIENT);
+        } else {
+            m_ui->typeComboBox->setCurrentIndex(HUB_SERVER);
+        }
+        m_ui->channelSendSpinBox->setValue(cliSettings->getNumAudioInputChans());
+        m_ui->channelRecvSpinBox->setValue(cliSettings->getNumAudioOutputChans());
+
+        unsigned int patchMode = cliSettings->getHubConnectionMode();
+        if (patchMode == JackTrip::SERVERTOCLIENT) {
+            m_ui->autoPatchComboBox->setCurrentIndex(SERVERTOCLIENT);
+        } else if (patchMode == JackTrip::CLIENTECHO) {
+            m_ui->autoPatchComboBox->setCurrentIndex(CLIENTECHO);
+        } else if (patchMode == JackTrip::CLIENTFOFI) {
+            m_ui->autoPatchComboBox->setCurrentIndex(CLIENTFOFI);
+        } else if (patchMode == JackTrip::FULLMIX) {
+            m_ui->autoPatchComboBox->setCurrentIndex(FULLMIX);
+        } else {
+            // Accomodate for the fact that the GUI doesn't support the reserved patching
+            // mode by disabling patching if selected.
+            m_ui->autoPatchComboBox->setCurrentIndex(NOAUTO);
+        }
+
+        m_ui->patchServerCheckBox->setChecked(cliSettings->getPatchServerAudio());
+        m_ui->upmixCheckBox->setChecked(cliSettings->getPatchServerAudio());
+        m_ui->zeroCheckBox->setChecked(cliSettings->getUnderrunMode() == JackTrip::ZEROS);
+        m_ui->timeoutCheckBox->setChecked(cliSettings->getStopOnTimeout());
+        m_ui->clientNameEdit->setText(cliSettings->getClientName());
+        m_ui->remoteNameEdit->setText(cliSettings->getRemoteClientName());
+        m_ui->localPortSpinBox->setValue(cliSettings->getBindPort());
+        m_ui->remotePortSpinBox->setValue(cliSettings->getPeerPort());
+        int basePort = cliSettings->getServerUdpPort();
+        if (basePort == 0) {
+            // TODO: This currently mirrors the behaviour seen in UdpHubListener.cpp, but
+            // I'm not sure it's particularly intuitive. It makes sense if the bind port
+            // was changed using the offset flag -o but not if it was changed using -B.
+            // These two cases are not currently distinguished between.
+            basePort = 61002 + cliSettings->getBindPort() - gDefaultPort;
+        }
+        m_ui->basePortSpinBox->setValue(basePort);
+        int queueLength = cliSettings->getQueueLength();
+        m_ui->queueLengthSpinBox->setValue(
+            queueLength > 0
+                ? queueLength
+                : settings.value(QStringLiteral("QueueLength"), gDefaultQueueLength)
+                      .toInt());
+        m_ui->redundancySpinBox->setValue(cliSettings->getRedundancy());
+        AudioInterface::audioBitResolutionT resolution =
+            cliSettings->getAudioBitResolution();
+        if (resolution == AudioInterface::BIT8) {
+            m_ui->resolutionComboBox->setCurrentIndex(0);
+        } else if (resolution == AudioInterface::BIT16) {
+            m_ui->resolutionComboBox->setCurrentIndex(1);
+        } else if (resolution == AudioInterface::BIT24) {
+            m_ui->resolutionComboBox->setCurrentIndex(2);
+        } else {
+            m_ui->resolutionComboBox->setCurrentIndex(3);
+        }
+        m_ui->connectAudioCheckBox->setChecked(
+            cliSettings->getConnectDefaultAudioPorts());
+        m_ui->realTimeCheckBox->setChecked(cliSettings->getUseRtUdpPriority());
+
+        m_ui->requireAuthCheckBox->setChecked(cliSettings->getUseAuthentication());
+        m_ui->authCheckBox->setChecked(cliSettings->getUseAuthentication());
+        m_ui->certEdit->setText(cliSettings->getCertFile());
+        m_ui->keyEdit->setText(cliSettings->getKeyFile());
+        m_ui->credsEdit->setText(cliSettings->getCredsFile());
+        m_ui->usernameEdit->setText(cliSettings->getUsername());
+        m_ui->passwordEdit->setText(cliSettings->getPassword());
+
+        settings.beginGroup(QStringLiteral("JitterBuffer"));
+        settings.setValue(QStringLiteral("JitterAnnounce"), true);
+        int bufferStrategy = cliSettings->getBufferStrategy();
+        m_ui->jitterCheckBox->setChecked(bufferStrategy > 0);
+        m_ui->broadcastCheckBox->setChecked(cliSettings->getBroadCastQueue() > 0);
+        m_ui->broadcastQueueSpinBox->setValue(
+            cliSettings->getBroadCastQueue() > 0
+                ? cliSettings->getBroadCastQueue()
+                : settings
+                      .value(QStringLiteral("BroadcastLength"), gDefaultQueueLength * 2)
+                      .toInt());
+        if (bufferStrategy > 0) {
+            m_ui->bufferStrategyComboBox->setCurrentIndex(bufferStrategy - 1);
+        } else {
+            m_ui->bufferStrategyComboBox->setCurrentIndex(
+                settings.value(QStringLiteral("Strategy"), 1).toInt() - 1);
+        }
+        m_ui->autoQueueCheckBox->setChecked(queueLength < 0);
+        m_ui->autoQueueSpinBox->setValue(
+            queueLength < 0
+                ? std::abs(queueLength)
+                : settings.value(QStringLiteral("TuningParameter"), 500).toInt());
+        settings.endGroup();
+    } else {
+        m_ui->typeComboBox->setCurrentIndex(
+            settings.value(QStringLiteral("RunMode"), 2).toInt());
+        m_ui->zeroCheckBox->setChecked(
+            settings.value(QStringLiteral("ZeroUnderrun"), false).toBool());
+        m_ui->localPortSpinBox->setValue(
+            settings.value(QStringLiteral("LocalPort"), gDefaultPort).toInt());
+        m_ui->queueLengthSpinBox->setValue(
+            settings.value(QStringLiteral("QueueLength"), gDefaultQueueLength).toInt());
+        m_ui->resolutionComboBox->setCurrentIndex(
+            settings.value(QStringLiteral("Resolution"), 1).toInt());
+        m_ui->realTimeCheckBox->setChecked(
+            settings.value(QStringLiteral("RTNetworking"), true).toBool());
+
+        settings.beginGroup(QStringLiteral("JitterBuffer"));
+        bool jitterAnnounce =
+            settings.value(QStringLiteral("JitterAnnounce"), false).toBool();
+        if (!jitterAnnounce
+            && !settings.value(QStringLiteral("Enabled"), true).toBool()) {
+            QMessageBox msgBox;
+            msgBox.setText(
+                "From this build onwards, the new jitter buffer is being enabled by "
+                "default. "
+                "You can turn it off in the Jitter Buffer settings tab.");
+            msgBox.setWindowTitle(QStringLiteral("Jitter Buffer"));
+            msgBox.exec();
+            settings.setValue(QStringLiteral("Enabled"), true);
+        }
+        settings.setValue(QStringLiteral("JitterAnnounce"), true);
+        m_ui->jitterCheckBox->setChecked(
+            settings.value(QStringLiteral("Enabled"), true).toBool());
+        m_ui->broadcastCheckBox->setChecked(
+            settings.value(QStringLiteral("Broadcast"), false).toBool());
+        m_ui->broadcastQueueSpinBox->setValue(
+            settings.value(QStringLiteral("BroadcastLength"), gDefaultQueueLength * 2)
+                .toInt());
+        m_ui->bufferStrategyComboBox->setCurrentIndex(
+            settings.value(QStringLiteral("Strategy"), 1).toInt() - 1);
+        m_ui->autoQueueCheckBox->setChecked(
+            settings.value(QStringLiteral("AutoQueue"), true).toBool());
+        m_ui->autoQueueSpinBox->setValue(
+            settings.value(QStringLiteral("TuningParameter"), 500).toInt());
+        settings.endGroup();
+    }
+
+    // These settings may need to be loaded even if we were using our command line.
+    // (This depends on the mode that was selected.)
+    if (!useCommandLine || m_ui->typeComboBox->currentIndex() == HUB_SERVER) {
+        m_ui->channelSendSpinBox->setValue(
+            settings.value(QStringLiteral("ChannelsSend"), gDefaultNumInChannels)
+                .toInt());
+        m_ui->channelRecvSpinBox->setValue(
+            settings.value(QStringLiteral("ChannelsRecv"), gDefaultNumOutChannels)
+                .toInt());
+        m_ui->timeoutCheckBox->setChecked(
+            settings.value(QStringLiteral("Timeout"), false).toBool());
+        m_ui->clientNameEdit->setText(
+            settings.value(QStringLiteral("ClientName"), "").toString());
+        m_ui->redundancySpinBox->setValue(
+            settings.value(QStringLiteral("Redundancy"), gDefaultRedundancy).toInt());
+        m_ui->connectAudioCheckBox->setChecked(
+            settings.value(QStringLiteral("ConnectAudio"), true).toBool());
+    }
+    if (!useCommandLine || !(m_ui->typeComboBox->currentIndex() == HUB_SERVER)) {
+        m_ui->autoPatchComboBox->setCurrentIndex(
+            settings.value(QStringLiteral("AutoPatchMode"), 0).toInt());
+        m_ui->patchServerCheckBox->setChecked(
+            settings.value(QStringLiteral("PatchIncludesServer"), false).toBool());
+        m_ui->upmixCheckBox->setChecked(
+            settings.value(QStringLiteral("StereoUpmix"), false).toBool());
+        m_ui->basePortSpinBox->setValue(
+            settings.value(QStringLiteral("BasePort"), 61002).toInt());
+    }
+    if (!useCommandLine || !(m_ui->typeComboBox->currentIndex() == HUB_CLIENT)) {
+        m_ui->remoteNameEdit->setText(
+            settings.value(QStringLiteral("RemoteName"), "").toString());
+    }
+    if (!useCommandLine
+        || !(m_ui->typeComboBox->currentIndex() == HUB_CLIENT
+             || m_ui->typeComboBox->currentIndex() == P2P_CLIENT)) {
+        m_ui->remotePortSpinBox->setValue(
+            settings.value(QStringLiteral("RemotePort"), gDefaultPort).toInt());
+    }
 
+    settings.beginGroup(QStringLiteral("Auth"));
+    if (!useCommandLine || !(m_ui->typeComboBox->currentIndex() == HUB_SERVER)) {
+        m_ui->requireAuthCheckBox->setChecked(
+            settings.value(QStringLiteral("Require"), false).toBool());
+        m_ui->certEdit->setText(
+            settings.value(QStringLiteral("CertFile"), "").toString());
+        m_ui->keyEdit->setText(settings.value(QStringLiteral("KeyFile"), "").toString());
+        m_ui->credsEdit->setText(
+            settings.value(QStringLiteral("CredsFile"), "").toString());
+    }
+    if (!useCommandLine || !(m_ui->typeComboBox->currentIndex() == HUB_CLIENT)) {
+        m_ui->authCheckBox->setChecked(
+            settings.value(QStringLiteral("Use"), false).toBool());
+        m_ui->usernameEdit->setText(
+            settings.value(QStringLiteral("Username"), "").toString());
+        m_ui->passwordEdit->setText("");
+    }
+    settings.endGroup();
+
+    // Settings from this point onwards are currently read only from the previously stored
+    // values and not from the commmand line.
 #ifdef RT_AUDIO
     settings.beginGroup(QStringLiteral("Audio"));
     m_ui->backendComboBox->setCurrentIndex(
@@ -1211,17 +1445,6 @@ void QJackTrip::loadSettings()
     settings.endGroup();
 #endif
 
-    settings.beginGroup(QStringLiteral("Auth"));
-    m_ui->requireAuthCheckBox->setChecked(
-        settings.value(QStringLiteral("Require"), false).toBool());
-    m_ui->certEdit->setText(settings.value(QStringLiteral("CertFile"), "").toString());
-    m_ui->keyEdit->setText(settings.value(QStringLiteral("KeyFile"), "").toString());
-    m_ui->credsEdit->setText(settings.value(QStringLiteral("CredsFile"), "").toString());
-    m_ui->authCheckBox->setChecked(settings.value(QStringLiteral("Use"), false).toBool());
-    m_ui->usernameEdit->setText(
-        settings.value(QStringLiteral("Username"), "").toString());
-    settings.endGroup();
-
     settings.beginGroup(QStringLiteral("IOStats"));
     m_ui->ioStatsCheckBox->setChecked(
         settings.value(QStringLiteral("Display"), false).toBool());
@@ -1229,34 +1452,6 @@ void QJackTrip::loadSettings()
         settings.value(QStringLiteral("ReportingInterval"), 1).toInt());
     settings.endGroup();
 
-    settings.beginGroup(QStringLiteral("JitterBuffer"));
-    bool jitterAnnounce =
-        settings.value(QStringLiteral("JitterAnnounce"), false).toBool();
-    if (!jitterAnnounce && !settings.value(QStringLiteral("Enabled"), true).toBool()) {
-        QMessageBox msgBox;
-        msgBox.setText(
-            "From this build onwards, the new jitter buffer is being enabled by default. "
-            "You can turn it off in the Jitter Buffer settings tab.");
-        msgBox.setWindowTitle(QStringLiteral("Jitter Buffer"));
-        msgBox.exec();
-        settings.setValue(QStringLiteral("Enabled"), true);
-    }
-    settings.setValue(QStringLiteral("JitterAnnounce"), true);
-    m_ui->jitterCheckBox->setChecked(
-        settings.value(QStringLiteral("Enabled"), true).toBool());
-    m_ui->broadcastCheckBox->setChecked(
-        settings.value(QStringLiteral("Broadcast"), false).toBool());
-    m_ui->broadcastQueueSpinBox->setValue(
-        settings.value(QStringLiteral("BroadcastLength"), gDefaultQueueLength * 2)
-            .toInt());
-    m_ui->bufferStrategyComboBox->setCurrentIndex(
-        settings.value(QStringLiteral("Strategy"), 1).toInt() - 1);
-    m_ui->autoQueueCheckBox->setChecked(
-        settings.value(QStringLiteral("AutoQueue"), true).toBool());
-    m_ui->autoQueueSpinBox->setValue(
-        settings.value(QStringLiteral("TuningParameter"), 500).toInt());
-    settings.endGroup();
-
     settings.beginGroup(QStringLiteral("InPlugins"));
     m_ui->inFreeverbCheckBox->setChecked(
         settings.value(QStringLiteral("Freeverb"), false).toBool());
@@ -1288,6 +1483,17 @@ void QJackTrip::loadSettings()
     m_ui->outClientsSpinBox->setValue(
         settings.value(QStringLiteral("Clients"), 1).toInt());
     settings.endGroup();
+
+    settings.beginGroup(QStringLiteral("Scripting"));
+    m_ui->connectScriptCheckBox->setChecked(
+        settings.value(QStringLiteral("ConnectEnabled"), false).toBool());
+    m_ui->connectScriptEdit->setText(
+        settings.value(QStringLiteral("ConnectScript"), "").toString());
+    m_ui->disconnectScriptCheckBox->setChecked(
+        settings.value(QStringLiteral("DisconnectEnabled"), false).toBool());
+    m_ui->disconnectScriptEdit->setText(
+        settings.value(QStringLiteral("DisconnectScript"), "").toString());
+    settings.endGroup();
 }
 
 void QJackTrip::saveSettings()
@@ -1392,6 +1598,16 @@ void QJackTrip::saveSettings()
     settings.setValue(QStringLiteral("Clients"), m_ui->outClientsSpinBox->value());
     settings.endGroup();
 
+    settings.beginGroup(QStringLiteral("Scripting"));
+    settings.setValue(QStringLiteral("ConnectEnabled"),
+                      m_ui->connectScriptCheckBox->isChecked());
+    settings.setValue(QStringLiteral("ConnectScript"), m_ui->connectScriptEdit->text());
+    settings.setValue(QStringLiteral("DisconnectEnabled"),
+                      m_ui->disconnectScriptCheckBox->isChecked());
+    settings.setValue(QStringLiteral("DisconnectScript"),
+                      m_ui->disconnectScriptEdit->text());
+    settings.endGroup();
+
     settings.beginGroup(QStringLiteral("Window"));
     settings.setValue(QStringLiteral("Geometry"), saveGeometry());
     settings.endGroup();
@@ -1526,11 +1742,8 @@ QString QJackTrip::commandLineFromCurrentOptions()
     }
 
     if (m_ui->typeComboBox->currentIndex() == HUB_SERVER) {
-        int hubConnectionMode = m_ui->autoPatchComboBox->currentIndex();
-        if (hubConnectionMode > CLIENTFOFI) {
-            // Adjust for the RESERVEDMATRIX gap.
-            hubConnectionMode++;
-        }
+        int hubConnectionMode = hubModeFromPatchType(
+            static_cast<patchTypeT>(m_ui->autoPatchComboBox->currentIndex()));
         if (hubConnectionMode > 0) {
             commandLine.append(QStringLiteral(" -p %1").arg(hubConnectionMode));
         }
@@ -1790,6 +2003,22 @@ void QJackTrip::showCommandLineMessageBox()
     msgBox.exec();
 }
 
+JackTrip::hubConnectionModeT QJackTrip::hubModeFromPatchType(
+    QJackTrip::patchTypeT patchType)
+{
+    if (patchType == SERVERTOCLIENT) {
+        return JackTrip::SERVERTOCLIENT;
+    } else if (patchType == CLIENTECHO) {
+        return JackTrip::CLIENTECHO;
+    } else if (patchType == CLIENTFOFI) {
+        return JackTrip::CLIENTFOFI;
+    } else if (patchType == FULLMIX) {
+        return JackTrip::FULLMIX;
+    } else {
+        return JackTrip::NOAUTO;
+    }
+}
+
 QJackTrip::~QJackTrip()
 {
     // Restore cout. (Stops a crash on exit.)
index ff0a75eb50d9366c5f1451ec9ccfcd9fba58d05c..db444b64c56a5d81e9c506e40f7c05baaacf9022 100644 (file)
@@ -39,6 +39,7 @@
 #include <QTemporaryFile>
 
 #include "../JackTrip.h"
+#include "../Settings.h"
 #include "../UdpHubListener.h"
 #include "messageDialog.h"
 #include "vuMeter.h"
@@ -65,7 +66,7 @@ class QJackTrip : public QMainWindow
     Q_OBJECT
 
    public:
-    explicit QJackTrip(int argc = 0, bool suppressCommandlineWarning = false,
+    explicit QJackTrip(Settings* settings, bool suppressCommandlineWarning = false,
                        QWidget* parent = nullptr);
     ~QJackTrip() override;
 
@@ -97,8 +98,8 @@ class QJackTrip : public QMainWindow
     void start();
     void stop();
     void exit();
-    void updatedInputMeasurements(const QVector<float> valuesInDb);
-    void updatedOutputMeasurements(const QVector<float> valuesInDb);
+    void updatedInputMeasurements(const float* valuesInDb, int numChannels);
+    void updatedOutputMeasurements(const float* valuesInDb, int numChannels);
 #ifndef NO_VS
     void virtualStudioMode();
 #endif
@@ -111,7 +112,7 @@ class QJackTrip : public QMainWindow
     void enableUi(bool enabled);
     void advancedOptionsForHubServer(bool isHubServer);
     void migrateSettings();
-    void loadSettings();
+    void loadSettings(Settings* cliSettings = nullptr);
     void saveSettings();
 
 #ifdef RT_AUDIO
@@ -125,6 +126,8 @@ class QJackTrip : public QMainWindow
     QString commandLineFromCurrentOptions();
     void showCommandLineMessageBox();
 
+    JackTrip::hubConnectionModeT hubModeFromPatchType(patchTypeT patchType);
+
     QScopedPointer<Ui::QJackTrip> m_ui;
     QScopedPointer<UdpHubListener> m_udpHub;
     QScopedPointer<JackTrip> m_jackTrip;
@@ -149,13 +152,15 @@ class QJackTrip : public QMainWindow
 
     QMutex m_requestMutex;
     QString m_IPv6Address;
-    bool m_hasIPv4Reply;
+    QString m_IPv4Address;
+    int m_replyCount = 0;
     QString m_lastPath;
 
     QLabel m_autoQueueIndicator;
-    int m_argc;
     bool m_hideWarning;
-    bool m_firstShow = true;
+    bool m_audioFallback       = false;
+    bool m_usingRtAudioAlready = false;
+    bool m_firstShow           = true;
 
 #ifndef NO_VS
     QSharedPointer<VirtualStudio> m_vs;
index 98c85bbad1421cf2e233414f99d5b65c5f5024d8..34117a0cbdf9ddd49e1589a1accf60b31457b366 100644 (file)
@@ -10,6 +10,7 @@
     <file>Login.qml</file>
     <file>Studio.qml</file>
     <file>Browse.qml</file>
+    <file>AudioSettings.qml</file>
     <file>Settings.qml</file>
     <file>Meter.qml</file>
     <file>Connected.qml</file>
     <file>cog.svg</file>
     <file>mic.svg</file>
     <file>micoff.svg</file>
+    <file>help.svg</file>
+    <file>quiet.svg</file>
+    <file>loud.svg</file>
+    <file>refresh.svg</file>
     <file>ethernet.png</file>
     <file>ohno.png</file>
     <file>headphones.svg</file>
index 11a8fb28f0d740d7b4ce7faf770328f8f0dc62e8..b562774904f0986c0af8dea2da25694ac68d2ee1 100644 (file)
@@ -1763,6 +1763,74 @@ and wetness is the essence of beauty.</string>
         </item>
        </layout>
       </widget>
+      <widget class="QWidget" name="scriptingTab">
+       <attribute name="title">
+        <string>Scripting</string>
+       </attribute>
+       <layout class="QGridLayout" name="gridLayout_12">
+        <item row="1" column="0">
+         <widget class="QLineEdit" name="connectScriptEdit">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="0">
+         <widget class="QLineEdit" name="disconnectScriptEdit">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="4" column="0">
+         <spacer name="scriptingVerticalSpacer">
+          <property name="orientation">
+           <enum>Qt::Vertical</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>20</width>
+            <height>40</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+        <item row="1" column="1">
+         <widget class="QPushButton" name="connectScriptBrowse">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="text">
+           <string>Browse</string>
+          </property>
+         </widget>
+        </item>
+        <item row="0" column="0" colspan="2">
+         <widget class="QCheckBox" name="connectScriptCheckBox">
+          <property name="text">
+           <string>Execute script on &amp;connection</string>
+          </property>
+         </widget>
+        </item>
+        <item row="2" column="0" colspan="2">
+         <widget class="QCheckBox" name="disconnectScriptCheckBox">
+          <property name="text">
+           <string>Execute script on &amp;disconnection</string>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="1">
+         <widget class="QPushButton" name="disconnectScriptBrowse">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="text">
+           <string>Browse</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
      </widget>
     </item>
     <item row="0" column="1">
@@ -1905,6 +1973,12 @@ To connect to a hub server you need to run as a hub client.</string>
   <tabstop>outCompressorCheckBox</tabstop>
   <tabstop>outLimiterCheckBox</tabstop>
   <tabstop>outClientsSpinBox</tabstop>
+  <tabstop>connectScriptCheckBox</tabstop>
+  <tabstop>connectScriptEdit</tabstop>
+  <tabstop>connectScriptBrowse</tabstop>
+  <tabstop>disconnectScriptCheckBox</tabstop>
+  <tabstop>disconnectScriptEdit</tabstop>
+  <tabstop>disconnectScriptBrowse</tabstop>
  </tabstops>
  <resources>
   <include location="qjacktrip.qrc"/>
index 5fe08a894b3a5806b36a18a7ad6beec11c0245cc..179c85a0338656107e5fbea5fe12d4335cec3e66 100644 (file)
@@ -1,7 +1,7 @@
 <RCC>
   <qresource prefix="qjacktrip">
-    <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>
+    <file>about@2x.png</file>
+    <file>about.png</file>
+    <file>icon.png</file>
   </qresource>
 </RCC>
diff --git a/src/gui/quiet.svg b/src/gui/quiet.svg
new file mode 100644 (file)
index 0000000..b2ea070
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg viewBox="0 0 10.05 12" width="10.05" height="12" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path d="M 0 8.074 L 0 3.96 L 2.743 3.96 L 6.171 0.531 L 6.171 11.503 L 2.743 8.074 L 0 8.074 Z M 7.2 8.897 L 7.2 3.12 C 7.829 3.314 8.329 3.68 8.7 4.217 C 9.071 4.754 9.257 5.354 9.257 6.017 C 9.257 6.669 9.069 7.263 8.691 7.8 C 8.314 8.337 7.817 8.703 7.2 8.897 Z M 5.143 3.137 L 3.206 4.989 L 1.029 4.989 L 1.029 7.046 L 3.206 7.046 L 5.143 8.914 L 5.143 3.137 Z" fill="#353637"/>
+</svg>
\ No newline at end of file
diff --git a/src/gui/refresh.svg b/src/gui/refresh.svg
new file mode 100644 (file)
index 0000000..d2c5a92
--- /dev/null
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24">
+<path d="M12 20q-3.35 0-5.675-2.325Q4 15.35 4 12q0-3.35 2.325-5.675Q8.65 4 12 4q1.725 0 3.3.713 1.575.712 2.7 2.037V4h2v7h-7V9h4.2q-.8-1.4-2.187-2.2Q13.625 6 12 6 9.5 6 7.75 7.75T6 12q0 2.5 1.75 4.25T12 18q1.925 0 3.475-1.1T17.65 14h2.1q-.7 2.65-2.85 4.325Q14.75 20 12 20Z"/>
+</svg>
\ No newline at end of file
index 98a1ea2040d82b1548ed4b1f1de08bca4ed88d8b..77f6e6c4b02c22edd7a4ab38b5b29b4e17e3d2af 100644 (file)
@@ -1,3 +1 @@
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M18.8377 23.1914C17.9686 23.1914 17.2242 22.8816 16.6047 22.2621C15.9852 21.6426 15.6755 20.8983 15.6755 20.0291C15.6755 19.8997 15.6893 19.7471 15.7171 19.5714C15.7448 19.3957 15.7864 19.2339 15.8419 19.086L7.43689 14.2039C7.1595 14.5183 6.81738 14.7725 6.41054 14.9667C6.0037 15.1609 5.58761 15.258 5.16227 15.258C4.29311 15.258 3.54877 14.9482 2.92927 14.3287C2.30976 13.7092 2 12.9649 2 12.0957C2 11.208 2.30976 10.4591 2.92927 9.84882C3.54877 9.23856 4.29311 8.93343 5.16227 8.93343C5.58761 8.93343 5.99445 9.01664 6.3828 9.18308C6.77115 9.34951 7.12252 9.58992 7.43689 9.9043L15.8419 5.07767C15.7864 4.94822 15.7448 4.80028 15.7171 4.63384C15.6893 4.46741 15.6755 4.31022 15.6755 4.16227C15.6755 3.27462 15.9852 2.52566 16.6047 1.9154C17.2242 1.30513 17.9686 1 18.8377 1C19.7254 1 20.4743 1.30513 21.0846 1.9154C21.6949 2.52566 22 3.27462 22 4.16227C22 5.03144 21.6949 5.77577 21.0846 6.39529C20.4743 7.01479 19.7254 7.32455 18.8377 7.32455C18.4124 7.32455 18.0009 7.2552 17.6033 7.11651C17.2057 6.97781 16.8682 6.75127 16.5908 6.43689L8.18585 11.0971C8.22284 11.245 8.2552 11.4161 8.28294 11.6103C8.31068 11.8044 8.32455 11.9663 8.32455 12.0957C8.32455 12.2252 8.31068 12.3638 8.28294 12.5118C8.2552 12.6597 8.22284 12.8077 8.18585 12.9556L16.5908 17.7268C16.8682 17.4679 17.1919 17.2598 17.5617 17.1026C17.9316 16.9454 18.3569 16.8669 18.8377 16.8669C19.7254 16.8669 20.4743 17.172 21.0846 17.7822C21.6949 18.3925 22 19.1415 22 20.0291C22 20.8983 21.6949 21.6426 21.0846 22.2621C20.4743 22.8816 19.7254 23.1914 18.8377 23.1914Z" fill="#000000"/>
-</svg>
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M15 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm-9-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9 4c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
\ No newline at end of file
index 29bf7566ccaa87d00818f23dec6125ce4671ee44..fb168ef4e04e7d21fce20bf3b0c54bb77c5dd4f5 100644 (file)
@@ -31,7 +31,7 @@
 
 /**
  * \file virtualstudio.cpp
- * \author Aaron Wyatt
+ * \author Matt Horton, based on code by Aaron Wyatt
  * \date March 2022
  */
 
@@ -113,9 +113,49 @@ 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"), 1).toInt() == 1;
-    m_inputDevice    = settings.value(QStringLiteral("InputDevice"), "").toString();
-    m_outputDevice   = settings.value(QStringLiteral("OutputDevice"), "").toString();
+    m_useRtAudio   = settings.value(QStringLiteral("Backend"), 1).toInt() == 1;
+    m_inputDevice  = settings.value(QStringLiteral("InputDevice"), "").toString();
+    m_outputDevice = settings.value(QStringLiteral("OutputDevice"), "").toString();
+
+    if (m_inputDevice == QStringLiteral("(default)")) {
+        m_inputDevice = "";
+    }
+    if (m_outputDevice == QStringLiteral("(default)")) {
+        m_outputDevice = "";
+    }
+
+    // use default base channel 0, if the setting does not exist
+    m_baseInputChannel  = settings.value(QStringLiteral("BaseInputChannel"), 0).toInt();
+    m_baseOutputChannel = settings.value(QStringLiteral("BaseOutputChannel"), 0).toInt();
+
+    // Handle migration scenarios. Assume this is a new user
+    // if we have m_inputDevice == "" and m_outputDevice == ""
+    if (m_inputDevice == "" && m_outputDevice == "") {
+        // for fresh installs, use mono by default
+        m_numInputChannels =
+            settings.value(QStringLiteral("NumInputChannels"), 1).toInt();
+        m_inputMixMode = settings
+                             .value(QStringLiteral("InputMixMode"),
+                                    static_cast<int>(AudioInterface::MONO))
+                             .toInt();
+
+        // use 2 channels for output
+        m_numOutputChannels =
+            settings.value(QStringLiteral("NumOutputChannels"), 2).toInt();
+    } else {
+        // existing installs - keep using stereo
+        m_numInputChannels =
+            settings.value(QStringLiteral("NumInputChannels"), 2).toInt();
+        m_inputMixMode = settings
+                             .value(QStringLiteral("InputMixMode"),
+                                    static_cast<int>(AudioInterface::STEREO))
+                             .toInt();
+
+        // use 2 channels for output
+        m_numOutputChannels =
+            settings.value(QStringLiteral("NumOutputChannels"), 2).toInt();
+    }
+
     m_bufferSize     = settings.value(QStringLiteral("BufferSize"), 128).toInt();
     m_previousBuffer = m_bufferSize;
     refreshDevices();
@@ -136,6 +176,41 @@ VirtualStudio::VirtualStudio(bool firstRun, QObject* parent)
     m_view.engine()->rootContext()->setContextProperty(
         QStringLiteral("outputComboModel"),
         QVariant::fromValue(QStringList(QLatin1String(""))));
+
+    QJsonObject inputMixModeComboElement = QJsonObject();
+    inputMixModeComboElement.insert(QString::fromStdString("label"),
+                                    QString::fromStdString("Mono"));
+    inputMixModeComboElement.insert(QString::fromStdString("value"),
+                                    static_cast<int>(AudioInterface::MONO));
+    m_view.engine()->rootContext()->setContextProperty(
+        QStringLiteral("inputMixModeComboModel"),
+        QVariant::fromValue(
+            QVariant(QVariantList() << QVariant(QJsonValue(inputMixModeComboElement)))));
+
+    QJsonObject inputChannelsComboElement = QJsonObject();
+    inputChannelsComboElement.insert(QString::fromStdString("label"),
+                                     QString::fromStdString("1"));
+    inputChannelsComboElement.insert(QString::fromStdString("baseChannel"),
+                                     QVariant(0).toInt());
+    inputChannelsComboElement.insert(QString::fromStdString("numChannels"),
+                                     QVariant(1).toInt());
+    m_view.engine()->rootContext()->setContextProperty(
+        QStringLiteral("inputChannelsComboModel"),
+        QVariant::fromValue(
+            QVariant(QVariantList() << QVariant(QJsonValue(inputChannelsComboElement)))));
+
+    QJsonObject outputChannelsComboElement = QJsonObject();
+    outputChannelsComboElement.insert(QString::fromStdString("label"),
+                                      QString::fromStdString("1 & 2"));
+    outputChannelsComboElement.insert(QString::fromStdString("baseChannel"),
+                                      QVariant(0).toInt());
+    outputChannelsComboElement.insert(QString::fromStdString("numChannels"),
+                                      QVariant(2).toInt());
+    m_view.engine()->rootContext()->setContextProperty(
+        QStringLiteral("outputChannelsComboModel"),
+        QVariant::fromValue(QVariant(
+            QVariantList() << QVariant(QJsonValue(outputChannelsComboElement)))));
+
 #endif
     m_bufferStrategy = settings.value(QStringLiteral("BufferStrategy"), 0).toInt();
     settings.endGroup();
@@ -204,7 +279,7 @@ VirtualStudio::VirtualStudio(bool firstRun, QObject* parent)
     m_view.setMinimumSize(QSize(594, 519));
     // m_view.setMaximumSize(QSize(696, 577));
     m_view.setResizeMode(QQuickView::SizeRootObjectToView);
-    m_view.resize(696 * m_uiScale, 577 * m_uiScale);
+    m_view.resize(696 * m_uiScale, 676 * m_uiScale);
 
     // Connect our timers
     connect(&m_retryPeriodTimer, &QTimer::timeout, this, &VirtualStudio::endRetryPeriod);
@@ -249,7 +324,10 @@ void VirtualStudio::show()
     if (m_checkSsl) {
         // Check our available SSL version
         QString sslVersion = QSslSocket::sslLibraryVersionString();
-        std::cout << "SSL Library: " << sslVersion.toStdString() << std::endl;
+        // Important: this needs to be output with qDebug rather than to std::cout
+        // otherwise it may get passed to an existing JackTrip instance in place of our
+        // deeplink. (Need to find the root cause of this.)
+        qDebug() << "SSL Library: " << sslVersion;
         if (sslVersion.isEmpty()) {
             QMessageBox msgBox;
             msgBox.setText(
@@ -273,6 +351,11 @@ void VirtualStudio::raiseToTop()
     m_view.requestActivate();  // Raise to top
 }
 
+bool VirtualStudio::vsModeActive()
+{
+    return m_vsModeActive;
+}
+
 bool VirtualStudio::showFirstRun()
 {
     return m_showFirstRun;
@@ -318,93 +401,140 @@ void VirtualStudio::setAudioBackend(const QString& backend)
     emit audioBackendChanged(m_useRtAudio);
 }
 
-int VirtualStudio::inputDevice()
+QString VirtualStudio::inputDevice()
 {
 #ifdef RT_AUDIO
-    if (m_useRtAudio) {
-        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;
-    }
+    return m_inputDevice;
 #endif
-    return 0;
+    return QStringLiteral("");
 }
 
-void VirtualStudio::setInputDevice([[maybe_unused]] int device)
+void VirtualStudio::setInputDevice([[maybe_unused]] const QString& device)
 {
     if (!m_useRtAudio) {
         return;
     }
 #ifdef RT_AUDIO
-    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);
+    m_inputDevice = device;
     emit inputDeviceChanged(m_inputDevice, false);
     emit inputDeviceSelected(m_inputDevice);
 #endif
 }
 
-int VirtualStudio::outputDevice()
-{
 #ifdef RT_AUDIO
+int VirtualStudio::baseInputChannel()
+{
     if (m_useRtAudio) {
-        QStringList filteredOutputDeviceList;
-        for (int i = 0; i < m_outputDeviceList.size(); i++) {
-            if (m_outputDeviceList.at(i) != "(default)") {
-                filteredOutputDeviceList += m_outputDeviceList.at(i);
-            }
-        }
+        return m_baseInputChannel;
+    }
+    return 0;
+}
 
-        int index = filteredOutputDeviceList.indexOf(m_outputDevice);
-        return index >= 0 ? index : 0;
+void VirtualStudio::setBaseInputChannel([[maybe_unused]] int baseChannel)
+{
+    if (!m_useRtAudio) {
+        return;
+    }
+    m_baseInputChannel = baseChannel;
+    emit baseInputChannelChanged(m_baseInputChannel, true);
+}
+
+int VirtualStudio::numInputChannels()
+{
+    if (m_useRtAudio) {
+        return m_numInputChannels;
     }
-#endif
     return 0;
 }
 
-void VirtualStudio::setOutputDevice([[maybe_unused]] int device)
+void VirtualStudio::setNumInputChannels([[maybe_unused]] int numChannels)
 {
     if (!m_useRtAudio) {
         return;
     }
-#ifdef RT_AUDIO
-    QStringList filteredOutputDeviceList;
-    for (int i = 0; i < m_outputDeviceList.size(); i++) {
-        if (m_outputDeviceList.at(i) != "(default)") {
-            filteredOutputDeviceList += m_outputDeviceList.at(i);
-        }
+    m_numInputChannels = numChannels;
+    emit numInputChannelsChanged(m_numInputChannels, true);
+}
+
+void VirtualStudio::setInputMixMode(int mode)
+{
+    if (!m_useRtAudio) {
+        return;
     }
+    m_inputMixMode = mode;
+    emit inputMixModeChanged(m_inputMixMode, true);
+}
+
+int VirtualStudio::inputMixMode()
+{
+    if (!m_useRtAudio) {
+        return -1;
+    }
+    return m_inputMixMode;
+}
+#endif
 
-    m_outputDevice = filteredOutputDeviceList.at(device);
+QString VirtualStudio::outputDevice()
+{
+#ifdef RT_AUDIO
+    return m_outputDevice;
+#endif
+    return QStringLiteral("");
+}
+
+void VirtualStudio::setOutputDevice([[maybe_unused]] const QString& device)
+{
+    if (!m_useRtAudio) {
+        return;
+    }
+#ifdef RT_AUDIO
+    m_outputDevice = device;
     emit outputDeviceChanged(m_outputDevice, false);
     emit outputDeviceSelected(m_outputDevice);
 #endif
 }
 
+#ifdef RT_AUDIO
+int VirtualStudio::baseOutputChannel()
+{
+    if (m_useRtAudio) {
+        return m_baseOutputChannel;
+    }
+    return 0;
+}
+
+void VirtualStudio::setBaseOutputChannel([[maybe_unused]] int baseChannel)
+{
+    if (!m_useRtAudio) {
+        return;
+    }
+    m_baseOutputChannel = baseChannel;
+    emit baseOutputChannelChanged(m_baseOutputChannel, true);
+}
+
+int VirtualStudio::numOutputChannels()
+{
+    if (m_useRtAudio) {
+        return m_numOutputChannels;
+    }
+    return 0;
+}
+
+void VirtualStudio::setNumOutputChannels([[maybe_unused]] int numChannels)
+{
+    if (!m_useRtAudio) {
+        return;
+    }
+    m_numOutputChannels = numChannels;
+    emit numOutputChannelsChanged(m_numOutputChannels, true);
+}
+#endif
+
 int VirtualStudio::previousInput()
 {
 #ifdef RT_AUDIO
     if (m_useRtAudio) {
-        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_previousInput);
+        int index = m_inputDeviceList.indexOf(m_previousInput);
         return index >= 0 ? index : 0;
     }
 #endif
@@ -417,14 +547,7 @@ void VirtualStudio::setPreviousInput([[maybe_unused]] int device)
         return;
     }
 #ifdef RT_AUDIO
-    QStringList filteredInputDeviceList;
-    for (int i = 0; i < m_inputDeviceList.size(); i++) {
-        if (m_inputDeviceList.at(i) != "(default)") {
-            filteredInputDeviceList += m_inputDeviceList.at(i);
-        }
-    }
-
-    m_previousInput = filteredInputDeviceList.at(device);
+    m_previousInput = m_inputDeviceList.at(device);
     emit previousInputChanged();
 #endif
 }
@@ -433,14 +556,7 @@ int VirtualStudio::previousOutput()
 {
 #ifdef RT_AUDIO
     if (m_useRtAudio) {
-        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_previousOutput);
+        int index = m_outputDeviceList.indexOf(m_previousOutput);
         return index >= 0 ? index : 0;
     }
 #endif
@@ -453,14 +569,7 @@ void VirtualStudio::setPreviousOutput([[maybe_unused]] int device)
         return;
     }
 #ifdef RT_AUDIO
-    QStringList filteredOutputDeviceList;
-    for (int i = 0; i < m_outputDeviceList.size(); i++) {
-        if (m_outputDeviceList.at(i) != "(default)") {
-            filteredOutputDeviceList += m_outputDeviceList.at(i);
-        }
-    }
-
-    m_previousOutput = filteredOutputDeviceList.at(device);
+    m_previousOutput = m_outputDeviceList.at(device);
     emit previousOutputChanged();
 #endif
 }
@@ -879,6 +988,7 @@ void VirtualStudio::toStandard()
     if (!m_standardWindow.isNull()) {
         m_view.hide();
         m_standardWindow->show();
+        m_vsModeActive = false;
     }
     QSettings settings;
     settings.setValue(QStringLiteral("UiMode"), QJackTrip::STANDARD);
@@ -906,7 +1016,6 @@ void VirtualStudio::toVirtualStudio()
                 (*parameters)[QStringLiteral("code")] = QUrl::fromPercentEncoding(code);
             } else if (stage == QAbstractOAuth2::Stage::RequestingAuthorization) {
                 parameters->insert(QStringLiteral("audience"), AUTH_AUDIENCE);
-                parameters->insert(QStringLiteral("prompt"), QStringLiteral("login"));
             }
             if (!parameters->contains("client_id")) {
                 parameters->insert("client_id", AUTH_CLIENT_ID);
@@ -930,6 +1039,20 @@ void VirtualStudio::logout()
         m_device->removeApp();
     }
 
+    QUrl logoutURL = QUrl("https://auth.jacktrip.org/v2/logout");
+    QUrlQuery query;
+    query.addQueryItem(QStringLiteral("client_id"), AUTH_CLIENT_ID);
+    if (m_testMode) {
+        query.addQueryItem(QStringLiteral("returnTo"),
+                           QStringLiteral("https://test.jacktrip.org/"));
+    } else {
+        query.addQueryItem(QStringLiteral("returnTo"),
+                           QStringLiteral("https://app.jacktrip.org/"));
+    }
+
+    logoutURL.setQuery(query);
+    launchBrowser(logoutURL);
+
     m_authenticator->setToken(QLatin1String(""));
     m_authenticator->setRefreshToken(QLatin1String(""));
 
@@ -961,30 +1084,271 @@ void VirtualStudio::refreshStudios(int index, bool signalRefresh)
 void VirtualStudio::refreshDevices()
 {
 #ifdef RT_AUDIO
-    RtAudioInterface::getDeviceList(&m_inputDeviceList, &m_inputDeviceCategories, true);
+    if (!m_vsAudioInterface.isNull()) {
+        m_vsAudioInterface->closeAudio();
+        setAudioReady(false);
+    }
+
+    RtAudioInterface::getDeviceList(&m_inputDeviceList, &m_inputDeviceCategories,
+                                    &m_inputDeviceChannels, true);
     RtAudioInterface::getDeviceList(&m_outputDeviceList, &m_outputDeviceCategories,
-                                    false);
+                                    &m_outputDeviceChannels, false);
 
-    QVariant inputComboModel =
-        formatDeviceList(m_inputDeviceList, m_inputDeviceCategories);
-    QVariant outputComboModel =
-        formatDeviceList(m_outputDeviceList, m_outputDeviceCategories);
+    QVariant inputComboModel = formatDeviceList(
+        m_inputDeviceList, m_inputDeviceCategories, m_inputDeviceChannels);
+    QVariant outputComboModel = formatDeviceList(
+        m_outputDeviceList, m_outputDeviceCategories, m_outputDeviceChannels);
     m_view.engine()->rootContext()->setContextProperty(QStringLiteral("inputComboModel"),
                                                        inputComboModel);
     m_view.engine()->rootContext()->setContextProperty(QStringLiteral("outputComboModel"),
                                                        outputComboModel);
+    validateDevicesState();
+    if (!m_vsAudioInterface.isNull()) {
+        restartAudio();
+    }
+#endif
+}
 
-    // Make sure we keep our current settings if the device still exists
-    if (!m_inputDeviceList.contains(m_inputDevice)) {
-        m_inputDevice = QStringLiteral("(default)");
+void VirtualStudio::validateDevicesState()
+{
+    validateInputDevicesState();
+    validateOutputDevicesState();
+}
+
+void VirtualStudio::validateInputDevicesState()
+{
+    if (!m_useRtAudio) {
+        return;
     }
-    if (!m_outputDeviceList.contains(m_outputDevice)) {
-        m_outputDevice = QStringLiteral("(default)");
+#ifdef RT_AUDIO
+    if (m_inputDeviceList.size() == 0 || m_outputDeviceList.size() == 0) {
+        return;
     }
 
-    emit inputDeviceChanged(m_inputDevice, false);
-    emit outputDeviceChanged(m_outputDevice, false);
-#endif
+    // Given input device list, check that the currently set device
+    // actually exists
+    if (m_inputDevice == QStringLiteral("")
+        || m_inputDeviceList.indexOf(m_inputDevice) == -1) {
+        m_inputDevice = m_inputDeviceList[0];
+        emit inputDeviceChanged(m_inputDevice, false);
+    }
+
+    // Given the currently selected input device, reset the available input channel
+    // options
+    int indexOfInput = m_inputDeviceList.indexOf(m_inputDevice);
+    if (indexOfInput == -1) {
+        std::cerr << "Invalid state. Input device index should never be -1" << std::endl;
+        return;
+    }
+
+    int numDevicesChannelsAvailable = m_inputDeviceChannels.at(indexOfInput);
+    if (numDevicesChannelsAvailable < 1) {
+        std::cerr << "Invalid state. Number of channels should never be less than 1"
+                  << std::endl;
+        return;
+    } else if (numDevicesChannelsAvailable == 1) {
+        // Set the input mix mode to just have "Mono" as the option
+        QJsonObject inputMixModeComboElement = QJsonObject();
+        inputMixModeComboElement.insert(QString::fromStdString("label"),
+                                        QString::fromStdString("Mono"));
+        inputMixModeComboElement.insert(QString::fromStdString("value"),
+                                        static_cast<int>(AudioInterface::MONO));
+        m_view.engine()->rootContext()->setContextProperty(
+            QStringLiteral("inputMixModeComboModel"),
+            QVariant::fromValue(QVariant(
+                QVariantList() << QVariant(QJsonValue(inputMixModeComboElement)))));
+
+        // Set the input channels combo to only have channel 1 as an option
+        QJsonObject inputChannelsComboElement = QJsonObject();
+        inputChannelsComboElement.insert(QString::fromStdString("label"),
+                                         QString::fromStdString("1"));
+        inputChannelsComboElement.insert(QString::fromStdString("baseChannel"),
+                                         QVariant(0).toInt());
+        inputChannelsComboElement.insert(QString::fromStdString("numChannels"),
+                                         QVariant(1).toInt());
+        m_view.engine()->rootContext()->setContextProperty(
+            QStringLiteral("inputChannelsComboModel"),
+            QVariant::fromValue(QVariant(
+                QVariantList() << QVariant(QJsonValue(inputChannelsComboElement)))));
+
+        // Set the only allowed options for these variables automatically
+        m_baseInputChannel = 0;
+        m_numInputChannels = 1;
+        m_inputMixMode     = static_cast<int>(AudioInterface::MONO);
+
+        emit baseInputChannelChanged(m_baseInputChannel, false);
+        emit numInputChannelsChanged(m_numInputChannels, false);
+        emit inputMixModeChanged(m_inputMixMode, false);
+    } else {
+        // set the input channels selector to have the options based on the currently
+        // selected device
+        QVariantList items = QVariantList();
+        for (int i = 0; i < numDevicesChannelsAvailable; i++) {
+            QJsonObject element = QJsonObject();
+            element.insert(QString::fromStdString("label"), QVariant(i + 1).toString());
+            element.insert(QString::fromStdString("baseChannel"), QVariant(i).toInt());
+            element.insert(QString::fromStdString("numChannels"), QVariant(1).toInt());
+            items.push_back(QVariant(QJsonValue(element)));
+        }
+        for (int i = 0; i < numDevicesChannelsAvailable; i++) {
+            if (i % 2 == 0) {
+                QJsonObject element = QJsonObject();
+                element.insert(
+                    QString::fromStdString("label"),
+                    QVariant(i + 1).toString() + " & " + QVariant(i + 2).toString());
+                element.insert(QString::fromStdString("baseChannel"),
+                               QVariant(i).toInt());
+                element.insert(QString::fromStdString("numChannels"),
+                               QVariant(2).toInt());
+                items.push_back(QVariant(QJsonValue(element)));
+            }
+        }
+        m_view.engine()->rootContext()->setContextProperty(
+            QStringLiteral("inputChannelsComboModel"), QVariant(items));
+
+        // if the current m_baseInputChannel or m_numInputChannels is invalid based on
+        // this device's option, use the first two channels by default
+        if (m_baseInputChannel + m_numInputChannels > numDevicesChannelsAvailable) {
+            // we're in the case where numDevicesChannelsAvailable >= 2, so always have
+            // the ability to use the first 2 channels
+            m_baseInputChannel = 0;
+            m_numInputChannels = 2;
+            emit baseInputChannelChanged(m_baseInputChannel, false);
+            emit numInputChannelsChanged(m_numInputChannels, false);
+        }
+        if (m_numInputChannels != 1) {
+            // Set the input mix mode to have two options: "Stereo" and "Mix to Mono" if
+            // we're using 2 channels
+            QJsonObject inputMixModeComboElement1 = QJsonObject();
+            inputMixModeComboElement1.insert(QString::fromStdString("label"),
+                                             QString::fromStdString("Stereo"));
+            inputMixModeComboElement1.insert(QString::fromStdString("value"),
+                                             static_cast<int>(AudioInterface::STEREO));
+            QJsonObject inputMixModeComboElement2 = QJsonObject();
+            inputMixModeComboElement2.insert(QString::fromStdString("label"),
+                                             QString::fromStdString("Mix to Mono"));
+            inputMixModeComboElement2.insert(QString::fromStdString("value"),
+                                             static_cast<int>(AudioInterface::MIXTOMONO));
+
+            m_view.engine()->rootContext()->setContextProperty(
+                QStringLiteral("inputMixModeComboModel"),
+                QVariant::fromValue(QVariant(
+                    QVariantList() << QVariant(QJsonValue(inputMixModeComboElement1))
+                                   << QVariant(QJsonValue(inputMixModeComboElement2)))));
+
+            // if m_inputMixMode is an invalid value, set it to "stereo" by default
+            // given that we are using 2 channels
+            if (m_inputMixMode != static_cast<int>(AudioInterface::STEREO)
+                && m_inputMixMode != static_cast<int>(AudioInterface::MIXTOMONO)) {
+                m_inputMixMode = static_cast<int>(AudioInterface::STEREO);
+                emit inputMixModeChanged(m_inputMixMode, false);
+            }
+        } else {
+            // Set the input mix mode to just have "Mono" as the option if we're using 1
+            // channel
+            QJsonObject inputMixModeComboElement = QJsonObject();
+            inputMixModeComboElement.insert(QString::fromStdString("label"),
+                                            QString::fromStdString("Mono"));
+            inputMixModeComboElement.insert(QString::fromStdString("value"),
+                                            static_cast<int>(AudioInterface::MONO));
+            m_view.engine()->rootContext()->setContextProperty(
+                QStringLiteral("inputMixModeComboModel"),
+                QVariant::fromValue(QVariant(
+                    QVariantList() << QVariant(QJsonValue(inputMixModeComboElement)))));
+
+            // if m_inputMixMode is an invalid value, set it to AudioInterface::MONO
+            if (m_inputMixMode != static_cast<int>(AudioInterface::MONO)) {
+                m_inputMixMode = static_cast<int>(AudioInterface::MONO);
+                emit inputMixModeChanged(m_inputMixMode, false);
+            }
+        }
+    }
+#endif  // RT_AUDIO
+}
+
+void VirtualStudio::validateOutputDevicesState()
+{
+    if (!m_useRtAudio) {
+        return;
+    }
+#ifdef RT_AUDIO
+    if (m_outputDeviceList.size() == 0 || m_outputDeviceList.size() == 0) {
+        return;
+    }
+
+    // Given output device list, check that the currently set device
+    // actually exists
+    if (m_outputDevice == QStringLiteral("")
+        || m_outputDeviceList.indexOf(m_outputDevice) == -1) {
+        m_outputDevice = m_outputDeviceList[0];
+        emit outputDeviceChanged(m_outputDevice, false);
+    }
+
+    // Given the currently selected output device, reset the available output channel
+    // options
+    int indexOfOutput = m_outputDeviceList.indexOf(m_outputDevice);
+    if (indexOfOutput == -1) {
+        std::cerr << "Invalid state. Output device index should never be -1" << std::endl;
+        return;
+    }
+
+    int numDevicesChannelsAvailable = m_outputDeviceChannels.at(indexOfOutput);
+    if (numDevicesChannelsAvailable < 1) {
+        std::cerr << "Invalid state. Number of channels should never be less than 1"
+                  << std::endl;
+        return;
+    } else if (numDevicesChannelsAvailable == 1) {
+        // Set the output channels combo to only have channel 1 as an option
+        QJsonObject outputChannelsComboElement = QJsonObject();
+        outputChannelsComboElement.insert(QString::fromStdString("label"),
+                                          QString::fromStdString("1"));
+        outputChannelsComboElement.insert(QString::fromStdString("baseChannel"),
+                                          QVariant(0).toInt());
+        outputChannelsComboElement.insert(QString::fromStdString("numChannels"),
+                                          QVariant(1).toInt());
+        m_view.engine()->rootContext()->setContextProperty(
+            QStringLiteral("outputChannelsComboModel"),
+            QVariant::fromValue(QVariant(
+                QVariantList() << QVariant(QJsonValue(outputChannelsComboElement)))));
+
+        // Set the only allowed options for these variables automatically
+        m_baseOutputChannel = 0;
+        m_numOutputChannels = 1;
+
+        emit baseOutputChannelChanged(m_baseOutputChannel, false);
+        emit numOutputChannelsChanged(m_numOutputChannels, false);
+    } else {
+        // set the output channels selector to have the options based on the currently
+        // selected device
+        QVariantList items = QVariantList();
+        for (int i = 0; i < numDevicesChannelsAvailable; i++) {
+            if (i % 2 == 0) {
+                QJsonObject element = QJsonObject();
+                element.insert(
+                    QString::fromStdString("label"),
+                    QVariant(i + 1).toString() + " & " + QVariant(i + 2).toString());
+                element.insert(QString::fromStdString("baseChannel"),
+                               QVariant(i).toInt());
+                element.insert(QString::fromStdString("numChannels"),
+                               QVariant(2).toInt());
+                items.push_back(QVariant(QJsonValue(element)));
+            }
+        }
+        m_view.engine()->rootContext()->setContextProperty(
+            QStringLiteral("outputChannelsComboModel"), QVariant(items));
+
+        // if the current m_baseOutputChannel or m_numOutputChannels is invalid based on
+        // this device's option, use the first two channels by default
+        if (m_baseOutputChannel + m_numOutputChannels > numDevicesChannelsAvailable) {
+            // we're in the case where numDevicesChannelsAvailable >= 2, so always have
+            // the ability to use the first 2 channels
+            m_baseOutputChannel = 0;
+            m_numOutputChannels = 2;
+            emit baseOutputChannelChanged(m_baseOutputChannel, false);
+            emit numOutputChannelsChanged(m_numOutputChannels, false);
+        }
+    }
+#endif  // RT_AUDIO
 }
 
 void VirtualStudio::playOutputAudio()
@@ -1007,6 +1371,9 @@ void VirtualStudio::revertSettings()
 
     emit inputDeviceChanged(m_inputDevice, false);
     emit outputDeviceChanged(m_outputDevice, false);
+    emit baseInputChannelChanged(m_baseInputChannel, false);
+    emit numInputChannelsChanged(m_numInputChannels, false);
+    emit inputMixModeChanged(m_inputMixMode, false);
     emit bufferSizeChanged();
     if (m_useRtAudio != m_previousUseRtAudio) {
         emit audioBackendChanged(m_useRtAudio, false);
@@ -1032,6 +1399,11 @@ void VirtualStudio::applySettings()
     settings.setValue(QStringLiteral("BufferSize"), m_bufferSize);
     settings.setValue(QStringLiteral("InputDevice"), m_inputDevice);
     settings.setValue(QStringLiteral("OutputDevice"), m_outputDevice);
+    settings.setValue(QStringLiteral("BaseInputChannel"), m_baseInputChannel);
+    settings.setValue(QStringLiteral("NumInputChannels"), m_numInputChannels);
+    settings.setValue(QStringLiteral("InputMixMode"), m_inputMixMode);
+    settings.setValue(QStringLiteral("BaseOutputChannel"), m_baseOutputChannel);
+    settings.setValue(QStringLiteral("NumOutputChannels"), m_numOutputChannels);
     settings.endGroup();
 
     m_previousUseRtAudio = m_useRtAudio;
@@ -1043,6 +1415,9 @@ void VirtualStudio::applySettings()
     emit previousOutputChanged();
     emit inputDeviceChanged(m_inputDevice, false);
     emit outputDeviceChanged(m_outputDevice, false);
+    emit baseInputChannelChanged(m_baseInputChannel, false);
+    emit numInputChannelsChanged(m_numInputChannels, false);
+    emit inputMixModeChanged(m_inputMixMode, false);
 #endif
 
     // attempt to join studio if requested
@@ -1081,7 +1456,7 @@ void VirtualStudio::connectToStudio(int studioIndex)
     m_studioSocket->openSocket();
 
     // Check if we have an address for our server
-    if (studioInfo->status() != "Ready" && studioInfo->isManageable() == true) {
+    if (studioInfo->status() != "Ready" && studioInfo->isAdmin() == true) {
         m_connectionState = QStringLiteral("Waiting...");
         emit connectionStateChanged();
     } else {
@@ -1109,19 +1484,28 @@ void VirtualStudio::completeConnection()
         int buffer_size    = 0;
 #ifdef RT_AUDIO
         if (m_useRtAudio) {
-            input = m_inputDevice.toStdString();
-            if (m_inputDevice == QLatin1String("(default)")) {
-                input = "";
-            }
-            output = m_outputDevice.toStdString();
-            if (m_outputDevice == QLatin1String("(default)")) {
-                output = "";
-            }
+            input       = m_inputDevice.toStdString();
+            output      = m_outputDevice.toStdString();
             buffer_size = m_bufferSize;
         }
+        int inputMixMode = m_inputMixMode;
+#else
+        int inputMixMode = -1;
 #endif
         JackTrip* jackTrip = m_device->initJackTrip(
-            m_useRtAudio, input, output, buffer_size, m_bufferStrategy, studioInfo);
+            m_useRtAudio, input, output,
+#ifdef RT_AUDIO
+            m_baseInputChannel, m_numInputChannels, m_baseOutputChannel,
+            m_numOutputChannels,
+#else
+            0, 2, 0, 2,  // default to 2 channels for input and 2 channels for output
+                         // starting at channel 0
+#endif
+            inputMixMode, buffer_size, m_bufferStrategy, studioInfo);
+        if (jackTrip == 0) {
+            processError("Could not bind port");
+            return;
+        }
 
         QObject::connect(jackTrip, &JackTrip::signalProcessesStopped, this,
                          &VirtualStudio::processFinished, Qt::QueuedConnection);
@@ -1150,13 +1534,13 @@ void VirtualStudio::completeConnection()
                 &Volume::muteUpdated);
 
         // Setup output meter
-        Meter* m_outputMeter = new Meter(jackTrip->getNumOutputChannels());
+        m_outputMeter = new Meter(jackTrip->getNumOutputChannels());
         jackTrip->appendProcessPluginFromNetwork(m_outputMeter);
         connect(m_outputMeter, &Meter::onComputedVolumeMeasurements, this,
                 &VirtualStudio::updatedOutputVuMeasurements);
 
         // Setup input meter
-        Meter* m_inputMeter = new Meter(jackTrip->getNumInputChannels());
+        m_inputMeter = new Meter(jackTrip->getNumInputChannels());
         jackTrip->appendProcessPluginToNetwork(m_inputMeter);
         connect(m_inputMeter, &Meter::onComputedVolumeMeasurements, this,
                 &VirtualStudio::updatedInputVuMeasurements);
@@ -1237,6 +1621,12 @@ void VirtualStudio::disconnect()
 
     m_connectionState = QStringLiteral("Disconnected");
     emit connectionStateChanged();
+
+    // cleanup
+    m_inputMeter         = nullptr;
+    m_outputMeter        = nullptr;
+    m_inputVolumePlugin  = nullptr;
+    m_outputVolumePlugin = nullptr;
 }
 
 void VirtualStudio::manageStudio(int studioIndex, bool start)
@@ -1349,6 +1739,7 @@ void VirtualStudio::slotAuthSucceded()
     settings.beginGroup(QStringLiteral("VirtualStudio"));
     settings.setValue(QStringLiteral("RefreshToken"), m_refreshToken);
     settings.endGroup();
+    m_vsModeActive = true;
 
     m_device = new VsDevice(m_authenticator.data(), m_testMode);
     m_device->registerApp();
@@ -1564,7 +1955,8 @@ void VirtualStudio::updatedDevicesWarningHelpUrl(const QString& url)
     return;
 }
 
-void VirtualStudio::updatedInputVuMeasurements(const QVector<float>& valuesInDecibels)
+void VirtualStudio::updatedInputVuMeasurements(const float* valuesInDecibels,
+                                               int numChannels)
 {
     QJsonArray uiValues;
     bool detectedClip = false;
@@ -1573,7 +1965,7 @@ void VirtualStudio::updatedInputVuMeasurements(const QVector<float>& valuesInDec
     for (int i = 0; i < 2; i++) {
         // Determine decibel reading
         float dB = m_meterMin;
-        if (i < valuesInDecibels.size()) {
+        if (i < numChannels) {
             dB = std::max(m_meterMin, valuesInDecibels[i]);
         }
 
@@ -1592,11 +1984,23 @@ void VirtualStudio::updatedInputVuMeasurements(const QVector<float>& valuesInDec
         }
     }
 
+#ifdef RT_AUDIO
+    // For certain specific cases, copy the first channel's value into the second
+    // channel's value
+    if ((m_inputMixMode == static_cast<int>(AudioInterface::MONO)
+         && m_numInputChannels == 1)
+        || (m_inputMixMode == static_cast<int>(AudioInterface::MIXTOMONO)
+            && m_numInputChannels == 2)) {
+        uiValues[1] = uiValues[0];
+    }
+#endif
+
     m_view.engine()->rootContext()->setContextProperty(QStringLiteral("inputMeterModel"),
                                                        QVariant::fromValue(uiValues));
 }
 
-void VirtualStudio::updatedOutputVuMeasurements(const QVector<float>& valuesInDecibels)
+void VirtualStudio::updatedOutputVuMeasurements(const float* valuesInDecibels,
+                                                int numChannels)
 {
     QJsonArray uiValues;
     bool detectedClip = false;
@@ -1605,7 +2009,7 @@ void VirtualStudio::updatedOutputVuMeasurements(const QVector<float>& valuesInDe
     for (int i = 0; i < 2; i++) {
         // Determine decibel reading
         float dB = m_meterMin;
-        if (i < valuesInDecibels.size()) {
+        if (i < numChannels) {
             dB = std::max(m_meterMin, valuesInDecibels[i]);
         }
 
@@ -1623,7 +2027,11 @@ void VirtualStudio::updatedOutputVuMeasurements(const QVector<float>& valuesInDe
             detectedClip = true;
         }
     }
-
+#ifdef RT_AUDIO
+    if (m_numOutputChannels == 1) {
+        uiValues[1] = uiValues[0];
+    }
+#endif
     m_view.engine()->rootContext()->setContextProperty(QStringLiteral("outputMeterModel"),
                                                        QVariant::fromValue(uiValues));
 }
@@ -1653,7 +2061,6 @@ void VirtualStudio::setupAuthenticator()
             } else if (stage == QAbstractOAuth2::Stage::RequestingAuthorization) {
                 parameters->insert(QStringLiteral("audience"),
                                    QStringLiteral("https://api.jacktrip.org"));
-                parameters->insert(QStringLiteral("prompt"), QStringLiteral("login"));
             }
         });
 
@@ -1668,6 +2075,9 @@ void VirtualStudio::setupAuthenticator()
             "Studio Login Successful</h1>\n"
             "<p style=\"font-size: 21px; font-weight:300;\">You may close this window "
             "and return to the JackTrip application.</p>\n"
+            "<p style=\"font-size: 21px; font-weight:300;\">Alternatively, "
+            "&nbsp;<a href=\"https://app.jacktrip.org/studios/create\">click "
+            "here</a>&nbsp; to create your first studio.</p>\n"
             "</div>\n"));
         m_authenticator->setReplyHandler(replyHandler);
         connect(m_authenticator.data(), &QOAuth2AuthorizationCodeFlow::granted, this,
@@ -1744,20 +2154,20 @@ void VirtualStudio::getServerList(bool firstLoad, bool signalRefresh, int index)
                 if (servers.at(i)[QStringLiteral("type")].toString().contains(
                         QStringLiteral("JackTrip"))) {
                     VsServerInfo* serverInfo = new VsServerInfo(this);
-                    serverInfo->setIsManageable(
+                    serverInfo->setIsAdmin(
                         servers.at(i)[QStringLiteral("admin")].toBool());
                     QString status = servers.at(i)[QStringLiteral("status")].toString();
                     bool activeStudio = status == QLatin1String("Ready");
                     bool hostedStudio = servers.at(i)[QStringLiteral("managed")].toBool();
                     // Only iterate through servers that we want to show
                     if (!m_showSelfHosted && !hostedStudio) {
-                        if (activeStudio || (serverInfo->isManageable())) {
+                        if (activeStudio || (serverInfo->isAdmin())) {
                             skippedStudios++;
                         }
                         continue;
                     }
                     if (!m_showInactive && !activeStudio) {
-                        if (serverInfo->isManageable()) {
+                        if (serverInfo->isAdmin()) {
                             skippedStudios++;
                         }
                         continue;
@@ -1767,6 +2177,8 @@ void VirtualStudio::getServerList(bool firstLoad, bool signalRefresh, int index)
                             servers.at(i)[QStringLiteral("name")].toString());
                         serverInfo->setHost(
                             servers.at(i)[QStringLiteral("serverHost")].toString());
+                        serverInfo->setIsManaged(
+                            servers.at(i)[QStringLiteral("managed")].toBool());
                         serverInfo->setStatus(
                             servers.at(i)[QStringLiteral("status")].toString());
                         serverInfo->setPort(
@@ -1794,8 +2206,6 @@ void VirtualStudio::getServerList(bool firstLoad, bool signalRefresh, int index)
                             servers.at(i)[QStringLiteral("enabled")].toBool());
                         serverInfo->setIsOwner(
                             servers.at(i)[QStringLiteral("owner")].toBool());
-                        serverInfo->setIsAdmin(
-                            servers.at(i)[QStringLiteral("admin")].toBool());
                         if (servers.at(i)[QStringLiteral("owner")].toBool()) {
                             yourServers.append(serverInfo);
                             serverInfo->setSection(VsServerInfo::YOUR_STUDIOS);
@@ -2001,9 +2411,15 @@ void VirtualStudio::startAudio()
             QStringLiteral("audioInterface"), m_vsAudioInterface.data());
     }
 #ifdef RT_AUDIO
+    validateDevicesState();
     m_vsAudioInterface->setInputDevice(m_inputDevice, false);
     m_vsAudioInterface->setOutputDevice(m_outputDevice, false);
-    m_vsAudioInterface->setAudioInterfaceMode(m_useRtAudio);
+    m_vsAudioInterface->setAudioInterfaceMode(m_useRtAudio, false);
+    m_vsAudioInterface->setBaseInputChannel(m_baseInputChannel, false);
+    m_vsAudioInterface->setNumInputChannels(m_numInputChannels, false);
+    m_vsAudioInterface->setInputMixMode(m_inputMixMode, false);
+    m_vsAudioInterface->setBaseOutputChannel(m_baseOutputChannel, false);
+    m_vsAudioInterface->setNumOutputChannels(m_numOutputChannels, false);
 #endif
     connect(m_vsAudioInterface.data(), &VsAudioInterface::devicesErrorMsgChanged, this,
             &VirtualStudio::updatedDevicesErrorMsg);
@@ -2023,12 +2439,26 @@ void VirtualStudio::startAudio()
             &VsAudioInterface::setOutputDevice);
     connect(this, &VirtualStudio::outputDeviceSelected, m_vsAudioInterface.data(),
             &VsAudioInterface::setOutputDevice);
+#ifdef RT_AUDIO
+    connect(this, &VirtualStudio::baseInputChannelChanged, m_vsAudioInterface.data(),
+            &VsAudioInterface::setBaseInputChannel);
+    connect(this, &VirtualStudio::numInputChannelsChanged, m_vsAudioInterface.data(),
+            &VsAudioInterface::setNumInputChannels);
+    connect(this, &VirtualStudio::inputMixModeChanged, m_vsAudioInterface.data(),
+            &VsAudioInterface::setInputMixMode);
+    connect(this, &VirtualStudio::baseOutputChannelChanged, m_vsAudioInterface.data(),
+            &VsAudioInterface::setBaseOutputChannel);
+    connect(this, &VirtualStudio::numOutputChannelsChanged, m_vsAudioInterface.data(),
+            &VsAudioInterface::setNumOutputChannels);
+#endif
     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::newInputMeterMeasurements, this,
+            &VirtualStudio::updatedInputVuMeasurements);
+    connect(m_vsAudioInterface.data(), &VsAudioInterface::newOutputMeterMeasurements,
+            this, &VirtualStudio::updatedOutputVuMeasurements);
     connect(m_vsAudioInterface.data(), &VsAudioInterface::errorToProcess, this,
             &VirtualStudio::processError);
 
@@ -2041,6 +2471,10 @@ void VirtualStudio::startAudio()
         QStringLiteral("inputMeterModel"),
         QVariant::fromValue(QVector<float>(m_vsAudioInterface->getNumInputChannels())));
 
+    m_view.engine()->rootContext()->setContextProperty(
+        QStringLiteral("outputMeterModel"),
+        QVariant::fromValue(QVector<float>(m_vsAudioInterface->getNumOutputChannels())));
+
     m_vsAudioInterface->startProcess();
 }
 
@@ -2053,6 +2487,11 @@ void VirtualStudio::restartAudio()
 #endif
     // Start VsAudioInterface again
     if (!m_vsAudioInterface.isNull()) {
+#ifdef RT_AUDIO
+        validateDevicesState();
+        m_vsAudioInterface->setInputDevice(m_inputDevice, false);
+        m_vsAudioInterface->setOutputDevice(m_outputDevice, false);
+#endif
         m_vsAudioInterface->setupAudio();
         m_vsAudioInterface->setupPlugins();
 
@@ -2064,6 +2503,11 @@ void VirtualStudio::restartAudio()
             QVariant::fromValue(
                 QVector<float>(m_vsAudioInterface->getNumInputChannels())));
 
+        m_view.engine()->rootContext()->setContextProperty(
+            QStringLiteral("outputMeterModel"),
+            QVariant::fromValue(
+                QVector<float>(m_vsAudioInterface->getNumOutputChannels())));
+
         m_vsAudioInterface->startProcess();
     } else {
         startAudio();
@@ -2131,19 +2575,10 @@ bool VirtualStudio::readyToJoin()
 
 #ifdef RT_AUDIO
 QVariant VirtualStudio::formatDeviceList(const QStringList& devices,
-                                         const QStringList& categories)
+                                         const QStringList& categories,
+                                         const QList<int>& channels)
 {
-    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);
+    QStringList uniqueCategories = QStringList(categories);
     uniqueCategories.removeDuplicates();
 
     bool containsCategories = true;
@@ -2165,12 +2600,13 @@ QVariant VirtualStudio::formatDeviceList(const QStringList& devices,
             items.push_back(QVariant(QJsonValue(header)));
         }
 
-        for (int j = 0; j < filteredDevices.size(); j++) {
-            if (filteredCategories.at(j).toStdString() == category.toStdString()) {
+        for (int j = 0; j < devices.size(); j++) {
+            if (categories.at(j).toStdString() == category.toStdString()) {
                 QJsonObject element = QJsonObject();
-                element.insert(QString::fromStdString("text"), filteredDevices.at(j));
+                element.insert(QString::fromStdString("text"), devices.at(j));
                 element.insert(QString::fromStdString("type"),
                                QString::fromStdString("element"));
+                element.insert(QString::fromStdString("channels"), channels.at(j));
                 items.push_back(QVariant(QJsonValue(element)));
             }
         }
@@ -2188,6 +2624,8 @@ VirtualStudio::~VirtualStudio()
 
     delete m_inputMeter;
     delete m_outputMeter;
+    delete m_outputVolumePlugin;
+    delete m_inputVolumePlugin;
     delete m_inputTestMeter;
     delete m_studioSocket;
 
index f6d91f49cfe63cc717c7d1ab4e6b9f7be4ae1640..eb097d58c4a7e5817468869472a1dac6a802ae8e 100644 (file)
@@ -31,7 +31,7 @@
 
 /**
  * \file virtualstudio.h
- * \author Aaron Wyatt
+ * \author Matt Horton, based on code by Aaron Wyatt
  * \date March 2022
  */
 
@@ -78,15 +78,26 @@ class VirtualStudio : public QObject
     Q_PROPERTY(bool selectableBackend READ selectableBackend CONSTANT)
     Q_PROPERTY(QString audioBackend READ audioBackend WRITE setAudioBackend NOTIFY
                    audioBackendChanged)
-    Q_PROPERTY(
-        int inputDevice READ inputDevice WRITE setInputDevice NOTIFY inputDeviceChanged)
-    Q_PROPERTY(int outputDevice READ outputDevice WRITE setOutputDevice NOTIFY
+    Q_PROPERTY(QString inputDevice READ inputDevice WRITE setInputDevice NOTIFY
+                   inputDeviceChanged)
+    Q_PROPERTY(QString outputDevice READ outputDevice WRITE setOutputDevice NOTIFY
                    outputDeviceChanged)
     Q_PROPERTY(int previousInput READ previousInput WRITE setPreviousInput NOTIFY
                    previousInputChanged)
     Q_PROPERTY(int previousOutput READ previousOutput WRITE setPreviousOutput NOTIFY
                    previousOutputChanged)
-
+#ifdef RT_AUDIO
+    Q_PROPERTY(int baseInputChannel READ baseInputChannel WRITE setBaseInputChannel NOTIFY
+                   baseInputChannelChanged)
+    Q_PROPERTY(int numInputChannels READ numInputChannels WRITE setNumInputChannels NOTIFY
+                   numInputChannelsChanged)
+    Q_PROPERTY(int inputMixMode READ inputMixMode WRITE setInputMixMode NOTIFY
+                   inputMixModeChanged)
+    Q_PROPERTY(int baseOutputChannel READ baseOutputChannel WRITE setBaseOutputChannel
+                   NOTIFY baseOutputChannelChanged)
+    Q_PROPERTY(int numOutputChannels READ numOutputChannels WRITE setNumOutputChannels
+                   NOTIFY numOutputChannelsChanged)
+#endif
     Q_PROPERTY(QString devicesWarning READ devicesWarning NOTIFY devicesWarningChanged)
     Q_PROPERTY(QString devicesError READ devicesError NOTIFY devicesErrorChanged)
     Q_PROPERTY(QString devicesWarningHelpUrl READ devicesWarningHelpUrl NOTIFY
@@ -99,6 +110,8 @@ class VirtualStudio : public QObject
     Q_PROPERTY(int bufferStrategy READ bufferStrategy WRITE setBufferStrategy NOTIFY
                    bufferStrategyChanged)
     Q_PROPERTY(int currentStudio READ currentStudio NOTIFY currentStudioChanged)
+    Q_PROPERTY(QUrl studioToJoin READ studioToJoin WRITE setStudioToJoin NOTIFY
+                   studioToJoinChanged)
     Q_PROPERTY(QJsonObject regions READ regions NOTIFY regionsChanged)
     Q_PROPERTY(QJsonObject userMetadata READ userMetadata NOTIFY userMetadataChanged)
     Q_PROPERTY(bool showInactive READ showInactive WRITE setShowInactive NOTIFY
@@ -145,6 +158,7 @@ class VirtualStudio : public QObject
     void setStandardWindow(QSharedPointer<QJackTrip> window);
     void show();
     void raiseToTop();
+    bool vsModeActive();
 
     bool showFirstRun();
     void setShowFirstRun(bool show);
@@ -154,10 +168,24 @@ class VirtualStudio : public QObject
     bool selectableBackend();
     QString audioBackend();
     void setAudioBackend(const QString& backend);
-    int inputDevice();
-    void setInputDevice(int device);
-    int outputDevice();
-    void setOutputDevice(int device);
+    QString inputDevice();
+    void setInputDevice(const QString& device);
+#ifdef RT_AUDIO
+    int baseInputChannel();
+    void setBaseInputChannel(int baseChannel);
+    int numInputChannels();
+    void setNumInputChannels(int numChannels);
+    void setInputMixMode(int mode);
+    int inputMixMode();
+#endif
+    QString outputDevice();
+    void setOutputDevice(const QString& device);
+#ifdef RT_AUDIO
+    int baseOutputChannel();
+    void setBaseOutputChannel(int baseChannel);
+    int numOutputChannels();
+    void setNumOutputChannels(int numChannels);
+#endif
     int previousInput();
     void setPreviousInput(int device);
     int previousOutput();
@@ -220,6 +248,9 @@ class VirtualStudio : public QObject
     void logout();
     void refreshStudios(int index, bool signalRefresh = false);
     void refreshDevices();
+    void validateDevicesState();
+    void validateInputDevicesState();
+    void validateOutputDevicesState();
     void playOutputAudio();
     void revertSettings();
     void applySettings();
@@ -232,8 +263,8 @@ class VirtualStudio : public QObject
     void editProfile();
     void showAbout();
     void openLink(const QString& url);
-    void updatedInputVuMeasurements(const QVector<float>& valuesInDecibels);
-    void updatedOutputVuMeasurements(const QVector<float>& valuesInDecibels);
+    void updatedInputVuMeasurements(const float* valuesInDecibels, int numChannels);
+    void updatedOutputVuMeasurements(const float* valuesInDecibels, int numChannels);
     void setInputVolume(float multiplier);
     void setOutputVolume(float multiplier);
     void setInputMuted(bool muted);
@@ -255,9 +286,14 @@ class VirtualStudio : public QObject
     void logoSectionChanged();
     void audioBackendChanged(bool useRtAudio, bool shouldRestart = true);
     void inputDeviceChanged(QString device, bool shouldRestart = true);
+    void baseInputChannelChanged(int baseChannel, bool shouldRestart = true);
+    void numInputChannelsChanged(int numChannels, bool shouldRestart = true);
+    void inputMixModeChanged(int mode, bool shouldRestart = true);
     void outputDeviceChanged(QString device, bool shouldRestart = true);
     void inputDeviceSelected(QString device, bool shouldRestart = true);
     void outputDeviceSelected(QString device, bool shouldRestart = true);
+    void baseOutputChannelChanged(int baseChannel, bool shouldRestart = true);
+    void numOutputChannelsChanged(int numChannels, bool shouldRestart = true);
     void previousInputChanged();
     void previousOutputChanged();
     void devicesWarningChanged();
@@ -327,11 +363,13 @@ class VirtualStudio : public QObject
     void stopAudio();
     bool readyToJoin();
 #ifdef RT_AUDIO
-    QVariant formatDeviceList(const QStringList& devices, const QStringList& categories);
+    QVariant formatDeviceList(const QStringList& devices, const QStringList& categories,
+                              const QList<int>& channels);
 #endif
 
     bool m_showFirstRun = false;
     bool m_checkSsl     = true;
+    bool m_vsModeActive = false;
     QString m_updateChannel;
     QString m_refreshToken;
     QString m_userId;
@@ -415,12 +453,22 @@ class VirtualStudio : public QObject
     QStringList m_outputDeviceList;
     QStringList m_inputDeviceCategories;
     QStringList m_outputDeviceCategories;
+    QList<int> m_inputDeviceChannels;
+    QList<int> m_outputDeviceChannels;
     QString m_inputDevice;
     QString m_outputDevice;
     quint16 m_bufferSize;
     QString m_previousInput;
     QString m_previousOutput;
     quint16 m_previousBuffer;
+
+    int m_baseInputChannel;
+    int m_numInputChannels;
+    int m_inputMixMode;
+
+    int m_baseOutputChannel;
+    int m_numOutputChannels;
+
     bool m_previousUseRtAudio = false;
     inline void delay(int millisecondsWait)
     {
index b694d5669baa62040e3a7473889569b792260eae..8ca20957855b4c15b315de3d3979e581a4425492 100644 (file)
@@ -125,23 +125,23 @@ Rectangle {
 
     Connections {
         target: virtualstudio
-        onAuthSucceeded: { 
+        function onAuthSucceeded() {
             if (virtualstudio.showDeviceSetup) {
                 virtualstudio.windowState = "setup";
             } else {
                 virtualstudio.windowState = "browse";
             }
         }
-        onAuthFailed: {
+        function onAuthFailed() {
             loginScreen.failTextVisible = true;
         }
-        onConnected: {
+        function onConnected() {
             virtualstudio.windowState = "connected";
         }
-        onFailed: {
+        function onFailed() {
             virtualstudio.windowState = "failed";
         }
-        onDisconnected: {
+        function onDisconnected() {
             virtualstudio.windowState = "browse";
         }
     }
index 45a65da321938a2fdd0cfbc32bb7ac41fd7271d8..a27a6c729ffa68805762fa1725c95fd5e14ce256 100644 (file)
@@ -161,8 +161,21 @@ void VsAudioInterface::setupJackAudio()
         if (gVerboseFlag)
             std::cout << "  JackTrip:setupAudio before new JackAudioInterface"
                       << std::endl;
-        m_audioInterface.reset(new JackAudioInterface(
-            m_numAudioChansIn, m_numAudioChansOut, m_audioBitResolution));
+
+        QVarLengthArray<int> inputChans;
+        QVarLengthArray<int> outputChans;
+        inputChans.resize(m_numAudioChansIn);
+        outputChans.resize(m_numAudioChansOut);
+
+        for (int i = 0; i < m_numAudioChansIn; i++) {
+            inputChans[i] = 1 + i;
+        }
+        for (int i = 0; i < m_numAudioChansOut; i++) {
+            outputChans[i] = 1 + i;
+        }
+
+        m_audioInterface.reset(
+            new JackAudioInterface(inputChans, outputChans, m_audioBitResolution));
 
         m_audioInterface->setClientName(QStringLiteral("JackTrip"));
 
@@ -211,20 +224,30 @@ void VsAudioInterface::setupRtAudio()
 #ifdef RT_AUDIO
     if constexpr (isBackendAvailable<AudioInterfaceMode::ALL>()
                   || isBackendAvailable<AudioInterfaceMode::RTAUDIO>()) {
-        m_audioInterface.reset(new RtAudioInterface(m_numAudioChansIn, m_numAudioChansOut,
-                                                    m_audioBitResolution));
+        QVarLengthArray<int> inputChans;
+        QVarLengthArray<int> outputChans;
+        inputChans.resize(m_numAudioChansIn);
+        outputChans.resize(m_numAudioChansOut);
+
+        for (int i = 0; i < m_numAudioChansIn; i++) {
+            inputChans[i] = m_baseInputChannel + i;
+        }
+        for (int i = 0; i < m_numAudioChansOut; i++) {
+            outputChans[i] = m_baseOutputChannel + i;
+        }
+
+        m_audioInterface.reset(new RtAudioInterface(
+            inputChans, outputChans,
+            static_cast<AudioInterface::inputMixModeT>(m_inputMixMode),
+            m_audioBitResolution));
         m_audioInterface->setSampleRate(m_sampleRate);
         m_audioInterface->setDeviceID(m_deviceID);
         m_audioInterface->setInputDevice(m_inputDeviceName);
         m_audioInterface->setOutputDevice(m_outputDeviceName);
         m_audioInterface->setBufferSizeInSamples(m_audioBufferSize);
 
+        // Note: setup might change the number of channels and/or buffer size
         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();
@@ -270,9 +293,7 @@ void VsAudioInterface::closeAudio()
             emit errorToProcess(QString::fromUtf8(e.what()));
         }
         m_audioInterface.clear();
-        m_numAudioChansIn  = gDefaultNumInChannels;
-        m_numAudioChansOut = gDefaultNumOutChannels;
-        m_deviceID         = gDefaultDeviceID;
+        m_deviceID = gDefaultDeviceID;
     }
 }
 
@@ -286,9 +307,14 @@ void VsAudioInterface::replaceProcess()
     }
 }
 
-void VsAudioInterface::processMeterMeasurements(QVector<float> values)
+void VsAudioInterface::processInputMeterMeasurements(float* values, int numChannels)
+{
+    emit newInputMeterMeasurements(values, numChannels);
+}
+
+void VsAudioInterface::processOutputMeterMeasurements(float* values, int numChannels)
 {
-    emit newVolumeMeterMeasurements(values);
+    emit newOutputMeterMeasurements(values, numChannels);
 }
 
 void VsAudioInterface::addInputPlugin(ProcessPlugin* plugin)
@@ -304,32 +330,93 @@ void VsAudioInterface::addOutputPlugin(ProcessPlugin* plugin)
 void VsAudioInterface::setInputDevice(QString deviceName, bool shouldRestart)
 {
     m_inputDeviceName = deviceName.toStdString();
-    if (m_inputDeviceName == "(default)") {
-        m_inputDeviceName = "";
+    if (!m_audioInterface.isNull()) {
+        if (m_audioActive && shouldRestart) {
+            emit settingsUpdated();
+        }
     }
+}
 
+#ifdef RT_AUDIO
+void VsAudioInterface::setBaseInputChannel(int baseChannel, bool shouldRestart)
+{
+    if (m_audioInterfaceMode != VsAudioInterface::RTAUDIO) {
+        return;
+    }
+    m_baseInputChannel = baseChannel;
+    if (!m_audioInterface.isNull()) {
+        if (m_audioActive && shouldRestart) {
+            emit settingsUpdated();
+        }
+    }
+    return;
+}
+
+void VsAudioInterface::setNumInputChannels(int numChannels, bool shouldRestart)
+{
+    if (m_audioInterfaceMode != VsAudioInterface::RTAUDIO) {
+        return;
+    }
+    m_numAudioChansIn = numChannels;
     if (!m_audioInterface.isNull()) {
-        m_audioInterface->setInputDevice(m_inputDeviceName);
         if (m_audioActive && shouldRestart) {
             emit settingsUpdated();
         }
     }
 }
 
+void VsAudioInterface::setInputMixMode(const int mode, bool shouldRestart)
+{
+    if (m_audioInterfaceMode != VsAudioInterface::RTAUDIO) {
+        return;
+    }
+    m_inputMixMode = mode;
+    if (!m_audioInterface.isNull()) {
+        if (m_audioActive && shouldRestart) {
+            emit settingsUpdated();
+        }
+    }
+    return;
+}
+#endif
 void VsAudioInterface::setOutputDevice(QString deviceName, bool shouldRestart)
 {
     m_outputDeviceName = deviceName.toStdString();
-    if (m_outputDeviceName == "(default)") {
-        m_outputDeviceName = "";
+    if (!m_audioInterface.isNull()) {
+        if (m_audioActive && shouldRestart) {
+            emit settingsUpdated();
+        }
     }
+}
 
+#ifdef RT_AUDIO
+void VsAudioInterface::setBaseOutputChannel(int baseChannel, bool shouldRestart)
+{
+    if (m_audioInterfaceMode != VsAudioInterface::RTAUDIO) {
+        return;
+    }
+    m_baseOutputChannel = baseChannel;
+    if (!m_audioInterface.isNull()) {
+        if (m_audioActive && shouldRestart) {
+            emit settingsUpdated();
+        }
+    }
+    return;
+}
+
+void VsAudioInterface::setNumOutputChannels(int numChannels, bool shouldRestart)
+{
+    if (m_audioInterfaceMode != VsAudioInterface::RTAUDIO) {
+        return;
+    }
+    m_numAudioChansOut = numChannels;
     if (!m_audioInterface.isNull()) {
-        m_audioInterface->setOutputDevice(m_outputDeviceName);
         if (m_audioActive && shouldRestart) {
             emit settingsUpdated();
         }
     }
 }
+#endif
 
 void VsAudioInterface::setAudioInterfaceMode(bool useRtAudio, bool shouldRestart)
 {
@@ -361,6 +448,7 @@ void VsAudioInterface::setupPlugins()
 {
     // Create plugins
     m_inputMeter         = new Meter(getNumInputChannels());
+    m_outputMeter        = new Meter(getNumOutputChannels());
     m_inputVolumePlugin  = new Volume(getNumInputChannels());
     m_outputVolumePlugin = new Volume(getNumOutputChannels());
     m_outputTonePlugin   = new Tone(getNumOutputChannels());
@@ -370,10 +458,13 @@ void VsAudioInterface::setupPlugins()
     addInputPlugin(m_inputVolumePlugin);
     addOutputPlugin(m_outputVolumePlugin);
     addInputPlugin(m_inputMeter);
+    addOutputPlugin(m_outputMeter);
 
     // Connect plugins for communication with UI
     connect(m_inputMeter, &Meter::onComputedVolumeMeasurements, this,
-            &VsAudioInterface::processMeterMeasurements);
+            &VsAudioInterface::processInputMeterMeasurements);
+    connect(m_outputMeter, &Meter::onComputedVolumeMeasurements, this,
+            &VsAudioInterface::processOutputMeterMeasurements);
     connect(this, &VsAudioInterface::updatedInputVolume, m_inputVolumePlugin,
             &Volume::volumeUpdated);
     connect(this, &VsAudioInterface::updatedOutputVolume, m_outputVolumePlugin,
index 9c28c9c9aceb3859a659a5248b4a876b5db10ff3..6a6d94a061ecccc4d6296efbd8eba7902f83bbd5 100644 (file)
@@ -93,7 +93,16 @@ class VsAudioInterface : public QObject
 
    public slots:
     void setInputDevice(QString deviceName, bool shouldRestart = true);
+#ifdef RT_AUDIO
+    void setBaseInputChannel(int baseChannel, bool shouldRestart = true);
+    void setNumInputChannels(int numChannels, bool shouldRestart = true);
+    void setInputMixMode(const int mode, bool shouldRestart = true);
+#endif
     void setOutputDevice(QString deviceName, bool shouldRestart = true);
+#ifdef RT_AUDIO
+    void setBaseOutputChannel(int baseChannel, bool shouldRestart = true);
+    void setNumOutputChannels(int numChannels, bool shouldRestart = true);
+#endif
     void setAudioInterfaceMode(bool useRtAudio, bool shouldRestart = true);
     void setInputVolume(float multiplier);
     void setOutputVolume(float multiplier);
@@ -108,7 +117,8 @@ class VsAudioInterface : public QObject
     void triggerPlayback();
     void settingsUpdated();
     void modeUpdated();
-    void newVolumeMeterMeasurements(QVector<float> values);
+    void newInputMeterMeasurements(float* values, int numChannels);
+    void newOutputMeterMeasurements(float* values, int numChannels);
     void errorToProcess(const QString& errorMessage);
     void devicesErrorMsgChanged(const QString& msg);
     void devicesWarningMsgChanged(const QString& msg);
@@ -118,7 +128,8 @@ class VsAudioInterface : public QObject
    private slots:
     // void refreshAudioStream();
     void replaceProcess();
-    void processMeterMeasurements(QVector<float> values);
+    void processInputMeterMeasurements(float* values, int numChannels);
+    void processOutputMeterMeasurements(float* values, int numChannels);
 
    private:
     void setupJackAudio();
@@ -131,6 +142,10 @@ class VsAudioInterface : public QObject
     bool m_audioActive    = false;
     bool m_hasBeenActive  = false;
 
+    int m_baseInputChannel  = 0;
+    int m_baseOutputChannel = 0;
+    int m_inputMixMode      = 0;
+
     // Needed in constructor
     int m_numAudioChansIn;   ///< Number of Audio Input Channels
     int m_numAudioChansOut;  ///< Number of Audio Output Channels
@@ -142,6 +157,7 @@ class VsAudioInterface : public QObject
     std::string m_inputDeviceName, m_outputDeviceName;  ///< RTAudio device names
     uint32_t m_audioBufferSize;  ///< Audio buffer size to process on each callback
     VsAudioInterface::audiointerfaceModeT m_audioInterfaceMode;
+    Meter* m_outputMeter;
     Meter* m_inputMeter;
     Volume* m_inputVolumePlugin;
     Volume* m_outputVolumePlugin;
index 4f06599bdc540a3ab39d5b4ec598e8f01b5f989e..08c79f2d9c52fc8d554c07dccdd16d89cec87062 100644 (file)
@@ -341,18 +341,22 @@ void VsDevice::sendLevels()
 }
 
 // initJackTrip spawns a new jacktrip process with the desired settings
-JackTrip* VsDevice::initJackTrip([[maybe_unused]] bool useRtAudio,
-                                 [[maybe_unused]] std::string input,
-                                 [[maybe_unused]] std::string output,
-                                 [[maybe_unused]] int bufferSize,
-                                 [[maybe_unused]] int bufferStrategy,
-                                 VsServerInfo* studioInfo)
+JackTrip* VsDevice::initJackTrip(
+    [[maybe_unused]] bool useRtAudio, [[maybe_unused]] std::string input,
+    [[maybe_unused]] std::string output, [[maybe_unused]] int baseInputChannel,
+    [[maybe_unused]] int numChannelsIn, [[maybe_unused]] int baseOutputChannel,
+    [[maybe_unused]] int numChannelsOut, [[maybe_unused]] int inputMixMode,
+    [[maybe_unused]] int bufferSize, [[maybe_unused]] int bufferStrategy,
+    VsServerInfo* studioInfo)
 {
-    m_jackTrip.reset(new JackTrip(JackTrip::CLIENTTOPINGSERVER, JackTrip::UDP, 2, 2,
+    m_jackTrip.reset(
+        new JackTrip(JackTrip::CLIENTTOPINGSERVER, JackTrip::UDP, baseInputChannel,
+                     numChannelsIn, baseOutputChannel, numChannelsOut,
+                     static_cast<AudioInterface::inputMixModeT>(inputMixMode),
 #ifdef WAIR  // wair
-                                  0,
+                     0,
 #endif  // endwhere
-                                  4, 1));
+                     4, 1));
     m_jackTrip->setConnectDefaultAudioPorts(true);
 #ifdef RT_AUDIO
     if (useRtAudio) {
@@ -363,6 +367,11 @@ JackTrip* VsDevice::initJackTrip([[maybe_unused]] bool useRtAudio,
         m_jackTrip->setOutputDevice(output);
     }
 #endif
+    int bindPort = selectBindPort();
+    if (bindPort == 0) {
+        return 0;
+    }
+    m_jackTrip->setBindPorts(bindPort);
     m_jackTrip->setRemoteClientName(m_appID);
     // increment m_bufferStrategy by 1 for array-index mapping
     m_jackTrip->setBufferStrategy(bufferStrategy + 1);
@@ -662,3 +671,21 @@ QString VsDevice::randomString(int stringLength)
 
     return str;
 }
+
+// selectBindPort finds the next open bind port to use for jacktrip
+int VsDevice::selectBindPort()
+{
+    int candidate = gDefaultPort;
+    if (m_jackTrip.isNull()) {
+        return candidate;
+    }
+    int attempt = 0;
+    while (attempt <= 5000) {
+        candidate = QRandomGenerator::global()->bounded(gBindPortLow, gBindPortHigh + 1);
+        attempt++;
+        if (!m_jackTrip->checkIfPortIsBinded(candidate)) {
+            return candidate;
+        }
+    }
+    return 0;
+}
index 373c7535eae95e84ff7e7e6a4d52111f36d9c41d..b13a733c12f54bfd1e9576230fb77939a9928c58 100644 (file)
@@ -67,7 +67,9 @@ class VsDevice : public QObject
     void sendHeartbeat();
     void setServerId(QString studioID);
     JackTrip* initJackTrip(bool useRtAudio, std::string input, std::string output,
-                           int bufferSize, int bufferStrategy, VsServerInfo* studioInfo);
+                           int baseInputChannel, int numChannelsIn, int baseOutputChannel,
+                           int numChannelsOut, int inputMixMode, int bufferSize,
+                           int bufferStrategy, VsServerInfo* studioInfo);
     void startJackTrip();
     void stopJackTrip();
     void reconcileAgentConfig(QJsonDocument newState);
@@ -96,6 +98,7 @@ class VsDevice : public QObject
    private:
     void registerJTAsDevice();
     bool enabled();
+    int selectBindPort();
     QString randomString(int stringLength);
 
     VsPinger* m_pinger = NULL;
diff --git a/src/gui/vsInit.cpp b/src/gui/vsInit.cpp
new file mode 100644 (file)
index 0000000..b74f92b
--- /dev/null
@@ -0,0 +1,170 @@
+//*****************************************************************
+/*
+  JackTrip: A System for High-Quality Audio Network Performance
+  over the Internet
+
+  Copyright (c) 2008-2023 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 vsInit.cpp
+ * \author Aaron Wyatt, based on code by Matt Horton
+ * \date February 2023
+ */
+
+#include "vsInit.h"
+
+#include <QCommandLineParser>
+#include <QDir>
+#include <QSettings>
+
+QString VsInit::parseDeeplink(QCoreApplication* app)
+{
+    // Parse command line for deep link
+    QCommandLineParser parser;
+    QCommandLineOption deeplinkOption(QStringList() << QStringLiteral("deeplink"));
+    deeplinkOption.setValueName(QStringLiteral("deeplink"));
+    parser.addOption(deeplinkOption);
+    parser.parse(app->arguments());
+    if (parser.isSet(deeplinkOption)) {
+        return parser.value(deeplinkOption);
+    } else {
+        return QLatin1String("");
+    }
+}
+
+void VsInit::checkForInstance(QString& deeplink)
+{
+    m_deeplink = deeplink;
+    // Create socket
+    m_instanceCheckSocket.reset(new QLocalSocket(this));
+    QObject::connect(m_instanceCheckSocket.data(), &QLocalSocket::connected, this,
+                     &VsInit::connectionReceived, Qt::QueuedConnection);
+    // Create instanceServer to prevent new instances from being created
+    void (QLocalSocket::*errorFunc)(QLocalSocket::LocalSocketError);
+#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
+    errorFunc = &QLocalSocket::error;
+#else
+    errorFunc = &QLocalSocket::errorOccurred;
+#endif
+    QObject::connect(m_instanceCheckSocket.data(), errorFunc, this,
+                     &VsInit::connectionFailed);
+    // Check for existing instance
+    m_instanceCheckSocket->connectToServer("jacktripExists");
+}
+
+#ifdef _WIN32
+void VsInit::setUrlScheme()
+{
+    // Set url scheme in registry
+    QString path = QDir::toNativeSeparators(qApp->applicationFilePath());
+
+    QSettings set("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat);
+    set.beginGroup("jacktrip");
+    set.setValue("Default", "URL:JackTrip Protocol");
+    set.setValue("DefaultIcon/Default", path);
+    set.setValue("URL Protocol", "");
+    set.setValue("shell/open/command/Default",
+                 QString("\"%1\"").arg(path) + " --gui --deeplink \"%1\"");
+    set.endGroup();
+}
+#endif
+
+void VsInit::connectionReceived()
+{
+    // pass deeplink to existing instance before quitting
+    if (!m_deeplink.isEmpty()) {
+        QByteArray baDeeplink = m_deeplink.toLocal8Bit();
+        qint64 writeBytes     = m_instanceCheckSocket->write(baDeeplink);
+        m_instanceCheckSocket->flush();
+        m_instanceCheckSocket->disconnectFromServer();  // remove next
+
+        if (writeBytes < 0) {
+            qDebug() << "sending deeplink failed";
+        }
+    }
+    // End process if instance exists
+    emit QCoreApplication::quit();
+}
+
+void VsInit::connectionFailed(QLocalSocket::LocalSocketError socketError)
+{
+    switch (socketError) {
+    case QLocalSocket::ServerNotFoundError:
+    case QLocalSocket::SocketTimeoutError:
+    case QLocalSocket::ConnectionRefusedError:
+        m_instanceServer.reset(new QLocalServer(this));
+        m_instanceServer->setSocketOptions(QLocalServer::WorldAccessOption);
+        m_instanceServer->listen("jacktripExists");
+        QObject::connect(m_instanceServer.data(), &QLocalServer::newConnection, this,
+                         &VsInit::responseReceived, Qt::QueuedConnection);
+        break;
+    case QLocalSocket::PeerClosedError:
+        break;
+    default:
+        qDebug() << m_instanceCheckSocket->errorString();
+    }
+}
+
+void VsInit::responseReceived()
+{
+    // This is the first instance. Bring it to the top.
+    if (!m_vs.isNull() && m_vs->vsModeActive()) {
+        m_vs->raiseToTop();
+    }
+    while (m_instanceServer->hasPendingConnections()) {
+        // Receive URL from 2nd instance
+        QLocalSocket* connectedSocket = m_instanceServer->nextPendingConnection();
+
+        if (!connectedSocket->waitForConnected()) {
+            qDebug() << "Never received connection";
+            return;
+        }
+
+        if (!connectedSocket->waitForReadyRead()) {
+            qDebug() << "Never ready to read";
+            if (!(connectedSocket->bytesAvailable() > 0)) {
+                qDebug() << "Not ready and no bytes available";
+                return;
+            }
+        }
+
+        if (connectedSocket->bytesAvailable() < (int)sizeof(quint16)) {
+            qDebug() << "no bytes available";
+            break;
+        }
+
+        QByteArray in(connectedSocket->readAll());
+        QString urlString(in);
+        QUrl url(urlString);
+
+        // Join studio using received URL
+        if (!m_vs.isNull() && m_vs->vsModeActive() && url.scheme() == "jacktrip"
+            && url.host() == "join") {
+            m_vs->setStudioToJoin(url);
+        }
+    }
+}
diff --git a/src/gui/vsInit.h b/src/gui/vsInit.h
new file mode 100644 (file)
index 0000000..cfe7b2f
--- /dev/null
@@ -0,0 +1,74 @@
+//*****************************************************************
+/*
+  JackTrip: A System for High-Quality Audio Network Performance
+  over the Internet
+
+  Copyright (c) 2008-2023 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 vsInit.h
+ * \author Aaron Wyatt, based on code by Matt Horton
+ * \date February 2023
+ */
+
+#ifndef __VSINIT_H__
+#define __VSINIT_H__
+
+#include <QCoreApplication>
+#include <QLocalServer>
+#include <QLocalSocket>
+#include <QScopedPointer>
+
+#include "virtualstudio.h"
+
+class VsInit : public QObject
+{
+    Q_OBJECT
+
+   public:
+    VsInit() = default;
+
+    static QString parseDeeplink(QCoreApplication* app);
+    void checkForInstance(QString& deeplink);
+    void setVs(QSharedPointer<VirtualStudio> vs) { m_vs = vs; }
+#ifdef _WIN32
+    static void setUrlScheme();
+#endif
+
+   private slots:
+    void connectionReceived();
+    void connectionFailed(QLocalSocket::LocalSocketError socketError);
+    void responseReceived();
+
+   private:
+    QScopedPointer<QLocalServer> m_instanceServer;
+    QScopedPointer<QLocalSocket> m_instanceCheckSocket;
+    QSharedPointer<VirtualStudio> m_vs;
+    QString m_deeplink;
+};
+
+#endif  // __VSINIT_H__
index 5657a44ea32956682d4db7e316b6b6d6da6a3656..4f6cdcd5b717ea54f41da56267be273bde3a1a63 100644 (file)
@@ -181,14 +181,14 @@ void VsServerInfo::setRegion(const QString& region)
     m_region = region;
 }
 
-bool VsServerInfo::isManageable() const
+bool VsServerInfo::isManaged() const
 {
-    return m_isManageable;
+    return m_isManaged;
 }
 
-void VsServerInfo::setIsManageable(bool isManageable)
+void VsServerInfo::setIsManaged(bool isManaged)
 {
-    m_isManageable = isManageable;
+    m_isManaged = isManaged;
 }
 
 quint16 VsServerInfo::period() const
index 956d6117a3877681819948d7af1414bb5b9c4eec..e1c945d68a6dbf16b63d0c79746e8ce16a90fa0b 100644 (file)
@@ -54,7 +54,8 @@ class VsServerInfo : public QObject
     Q_PROPERTY(QString flag READ flag CONSTANT)
     Q_PROPERTY(QString bannerURL READ bannerURL CONSTANT)
     Q_PROPERTY(QString location READ location CONSTANT)
-    Q_PROPERTY(bool isManageable READ isManageable CONSTANT)
+    Q_PROPERTY(bool isAdmin READ isAdmin CONSTANT)
+    Q_PROPERTY(bool isManaged READ isManaged CONSTANT)
     Q_PROPERTY(quint16 period READ period CONSTANT)
     Q_PROPERTY(quint32 sampleRate READ sampleRate CONSTANT)
     Q_PROPERTY(quint16 queueBuffer READ queueBuffer CONSTANT)
@@ -93,8 +94,8 @@ class VsServerInfo : public QObject
     QString flag() const;
     QString location() const;
     void setRegion(const QString& region);
-    bool isManageable() const;
-    void setIsManageable(bool isManageable);
+    bool isManaged() const;
+    void setIsManaged(bool isManageable);
     quint16 period() const;
     void setPeriod(quint16 period);
     quint32 sampleRate() const;
@@ -126,9 +127,9 @@ class VsServerInfo : public QObject
     bool m_enabled;
     bool m_owner;
     bool m_admin;
+    bool m_isManaged;
     bool m_isPublic;
     QString m_region;
-    bool m_isManageable;
     quint16 m_period;
     quint32 m_sampleRate;
     quint16 m_queueBuffer;
@@ -143,7 +144,6 @@ class VsServerInfo : public QObject
     "loopback": true,
     "stereo": true,
     "type": "JackTrip",
-    "managed": true,
     "size": "c5.large",
     "mixBranch": "main",
     "mixCode": "SimpleMix(~maxClients).masterVolume_(1).connect.start;",
index 36736c717a0b65c786640d5e74ae8466c34974ed..1e19ea79cea228ce0b3545743ba20b5bdcd35f1c 100644 (file)
@@ -35,6 +35,9 @@ VuMeter::VuMeter(QWidget* parent) : QWidget(parent), m_level(0)
     m_yellowOff.setRgb(85, 65, 22);
     m_redOn.setRgb(242, 27, 27);
     m_redOff.setRgb(84, 4, 4);
+
+    m_threshhold1 = std::round(m_bins * 0.6);
+    m_threshhold2 = std::round(m_bins * 0.8);
 }
 
 void VuMeter::setLevel(qreal level)
@@ -53,17 +56,17 @@ void VuMeter::paintEvent([[maybe_unused]] QPaintEvent* event)
     for (quint32 i = 0; i < m_bins; i++) {
         bool on = level > i;
         if (on) {
-            if (i < 9) {
+            if (i < m_threshhold1) {
                 painter.setBrush(m_greenOn);
-            } else if (i < 12) {
+            } else if (i < m_threshhold2) {
                 painter.setBrush(m_yellowOn);
             } else {
                 painter.setBrush(m_redOn);
             }
         } else {
-            if (i < 9) {
+            if (i < m_threshhold1) {
                 painter.setBrush(m_greenOff);
-            } else if (i < 12) {
+            } else if (i < m_threshhold2) {
                 painter.setBrush(m_yellowOff);
             } else {
                 painter.setBrush(m_redOff);
index 735dc14791911596ba11a3bd491004942a01a16f..d6d3693803a4e6ad60127cd5d0c441b09cb9ceb3 100644 (file)
@@ -51,8 +51,10 @@ class VuMeter : public QWidget
     QColor m_redOn;
     QColor m_redOff;
 
-    quint32 m_bins    = 15;
+    quint32 m_bins    = 30;
     quint32 m_margins = 2;
+    quint32 m_threshhold1;
+    quint32 m_threshhold2;
 };
 
 #endif  // VUMETER_H
index 740b5ea1cdfb044641e80204ed280eb5a0526d12..a1e4805af14b759a6e3b780e5b5b12a67b516f61 100644 (file)
@@ -40,7 +40,7 @@
 
 #include "AudioInterface.h"
 
-constexpr const char* const gVersion = "1.7.0";  ///< JackTrip version
+constexpr const char* const gVersion = "1.8.1";  ///< JackTrip version
 
 //*******************************************************************************
 /// \name Default Values
index 9bf27f518b7a3339b663b1e539c7a8afa3317547..c561b2a891267d7850ae981e91b5f652f796030c 100644 (file)
@@ -37,7 +37,6 @@
 
 #ifndef NO_GUI
 #include <QApplication>
-#include <QCommandLineParser>
 
 #ifndef NO_UPDATER
 #include "dblsqd/feed.h"
@@ -47,8 +46,6 @@
 #ifndef NO_VS
 #include <QDebug>
 #include <QFile>
-#include <QLocalServer>
-#include <QLocalSocket>
 #include <QQmlEngine>
 #include <QQuickView>
 #include <QSettings>
@@ -56,6 +53,7 @@
 
 #include "JTApplication.h"
 #include "gui/virtualstudio.h"
+#include "gui/vsInit.h"
 #include "gui/vsQmlClipboard.h"
 #include "gui/vsUrlHandler.h"
 #endif
@@ -285,11 +283,10 @@ int main(int argc, char* argv[])
     QSharedPointer<QJackTrip> window;
 
 #ifndef NO_VS
-    QString deeplink = QLatin1String("");
+    QString deeplink;
     QSharedPointer<VirtualStudio> vs;
 #ifdef _WIN32
-    QSharedPointer<QLocalServer> instanceServer;
-    QSharedPointer<QLocalSocket> instanceCheckSocket;
+    QScopedPointer<VsInit> vsInit;
 #endif
 #endif
 
@@ -311,135 +308,28 @@ int main(int argc, char* argv[])
         app->setApplicationName(QStringLiteral("JackTrip"));
         app->setApplicationVersion(gVersion);
 
-        QCommandLineParser parser;
-        QCommandLineOption verboseOption(QStringList() << QStringLiteral("V")
-                                                       << QStringLiteral("verbose"));
-        parser.addOption(verboseOption);
-        parser.parse(app->arguments());
-        if (parser.isSet(verboseOption)) {
-            gVerboseFlag = true;
-        }
+        Settings cliSettings(true);
+        cliSettings.parseInput(argc, argv);
 
 #ifndef NO_VS
         // Register clipboard Qml type
         qmlRegisterType<VsQmlClipboard>("VS", 1, 0, "Clipboard");
 
         // Parse command line for deep link
-        QCommandLineOption deeplinkOption(QStringList() << QStringLiteral("deeplink"));
-        deeplinkOption.setValueName(QStringLiteral("deeplink"));
-        parser.addOption(deeplinkOption);
-        parser.parse(app->arguments());
-        if (parser.isSet(deeplinkOption)) {
-            deeplink = parser.value(deeplinkOption);
-        }
+        deeplink = VsInit::parseDeeplink(app.data());
 
         // Check if we need to show our first run window.
         QSettings settings;
         int uiMode = settings.value(QStringLiteral("UiMode"), QJackTrip::UNSET).toInt();
 #ifdef _WIN32
         // Set url scheme in registry
-        QString path = QDir::toNativeSeparators(qApp->applicationFilePath());
-
-        QSettings set("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat);
-        set.beginGroup("jacktrip");
-        set.setValue("Default", "URL:JackTrip Protocol");
-        set.setValue("DefaultIcon/Default", path);
-        set.setValue("URL Protocol", "");
-        set.setValue("shell/open/command/Default",
-                     QString("\"%1\"").arg(path) + " --gui --deeplink \"%1\"");
-        set.endGroup();
-
-        // Create socket
-        instanceCheckSocket =
-            QSharedPointer<QLocalSocket>::create(new QLocalSocket(app.data()));
-        // End process if instance exists
-        QObject::connect(
-            instanceCheckSocket.data(), &QLocalSocket::connected, app.data(),
-            [&]() {
-                // pass deeplink to existing instance before quitting
-                if (!deeplink.isEmpty()) {
-                    QByteArray baDeeplink = deeplink.toLocal8Bit();
-                    qint64 writeBytes     = instanceCheckSocket->write(baDeeplink);
-                    instanceCheckSocket->flush();
-                    instanceCheckSocket->disconnectFromServer();  // remove next
-
-                    if (writeBytes < 0) {
-                        qDebug() << "sending deeplink failed";
-                    }
-                }
-                emit QCoreApplication::quit();
-            },
-            Qt::QueuedConnection);
-        // Create instanceServer to prevent new instances from being created
-        void (QLocalSocket::*errorFunc)(QLocalSocket::LocalSocketError);
-#ifdef Q_OS_LINUX
-        errorFunc = &QLocalSocket::error;
-#else
-        errorFunc = &QLocalSocket::errorOccurred;
-#endif
-        QObject::connect(
-            instanceCheckSocket.data(), errorFunc, app.data(),
-            [&](QLocalSocket::LocalSocketError socketError) {
-                switch (socketError) {
-                case QLocalSocket::ServerNotFoundError:
-                case QLocalSocket::SocketTimeoutError:
-                case QLocalSocket::ConnectionRefusedError:
-                    instanceServer = QSharedPointer<QLocalServer>::create(
-                        new QLocalServer(app.data()));
-                    instanceServer->setSocketOptions(QLocalServer::WorldAccessOption);
-                    instanceServer->listen("jacktripExists");
-                    QObject::connect(
-                        instanceServer.data(), &QLocalServer::newConnection, app.data(),
-                        [&]() {
-                            // This is the first instance. Bring it to the
-                            // top.
-                            vs->raiseToTop();
-                            while (instanceServer->hasPendingConnections()) {
-                                // Receive URL from 2nd instance
-                                QLocalSocket* connectedSocket =
-                                    instanceServer->nextPendingConnection();
-
-                                if (!connectedSocket->waitForConnected()) {
-                                    qDebug() << "Never received connection";
-                                    return;
-                                }
-
-                                if (!connectedSocket->waitForReadyRead()) {
-                                    qDebug() << "Never ready to read";
-                                    return;
-                                }
-
-                                if (connectedSocket->bytesAvailable()
-                                    < (int)sizeof(quint16)) {
-                                    qDebug() << "no bytes available";
-                                    break;
-                                }
-
-                                QByteArray in(connectedSocket->readAll());
-                                QString urlString(in);
-                                QUrl url(urlString);
-
-                                // Join studio using received URL
-                                if (url.scheme() == "jacktrip" && url.host() == "join") {
-                                    vs->setStudioToJoin(url);
-                                }
-                            }
-                        },
-                        Qt::QueuedConnection);
-                    break;
-                case QLocalSocket::PeerClosedError:
-                    break;
-                default:
-                    qDebug() << instanceCheckSocket->errorString();
-                }
-            });
-        // Check for existing instance
-        instanceCheckSocket->connectToServer("jacktripExists");
-
+        VsInit::setUrlScheme();
+        vsInit.reset(new VsInit());
+        vsInit->checkForInstance(deeplink);
 #endif  // _WIN32
-        window.reset(new QJackTrip(argc, !deeplink.isEmpty()));
+        window.reset(new QJackTrip(&cliSettings, !deeplink.isEmpty()));
 #else
-        window.reset(new QJackTrip(argc));
+        window.reset(new QJackTrip(&cliSettings));
 #endif  // NO_VS
         QObject::connect(window.data(), &QJackTrip::signalExit, app.data(),
                          &QCoreApplication::quit, Qt::QueuedConnection);
@@ -447,6 +337,9 @@ int main(int argc, char* argv[])
         vs.reset(new VirtualStudio(uiMode == QJackTrip::UNSET));
         QObject::connect(vs.data(), &VirtualStudio::signalExit, app.data(),
                          &QCoreApplication::quit, Qt::QueuedConnection);
+#ifdef _WIN32
+        vsInit->setVs(vs);
+#endif
         vs->setStandardWindow(window);
         window->setVs(vs);
 
@@ -499,10 +392,8 @@ int main(int argc, char* argv[])
         QString updateChannel = settings.value(QStringLiteral("UpdateChannel"), "stable")
                                     .toString()
                                     .toLower();
-        QString baseUrl =
-            QStringLiteral(
-                "https://raw.githubusercontent.com/jacktrip/jacktrip/dev/releases/%1")
-                .arg(updateChannel);
+        QString baseUrl = QStringLiteral("https://files.jacktrip.org/app-releases/%1")
+                              .arg(updateChannel);
 #else
         QString baseUrl = QStringLiteral("https://nuages.psi-borg.org/jacktrip");
 #endif  // PSI
@@ -522,7 +413,9 @@ int main(int argc, char* argv[])
     } else {
 #endif  // NO_GUI
         // Otherwise use the non-GUI version, and parse our command line.
+#ifndef PSI
         QLoggingCategory::setFilterRules(QStringLiteral("*.debug=true"));
+#endif
         try {
             Settings settings;
             settings.parseInput(argc, argv);
diff --git a/src/stereotomonodsp.h b/src/stereotomonodsp.h
new file mode 100644 (file)
index 0000000..40a1403
--- /dev/null
@@ -0,0 +1,2078 @@
+/* ------------------------------------------------------------
+author: "Dominick Hing"
+license: "MIT Style STK-4.2"
+name: "stereo-to-mono"
+version: "1.0"
+Code generated with Faust 2.50.6 (https://faust.grame.fr)
+Compilation options: -a faust2header.cpp -lang cpp -i -inpl -cn stereotomonodsp -es 1 -mcd 16 -single -ftz 0
+------------------------------------------------------------ */
+
+#ifndef  __stereotomonodsp_H__
+#define  __stereotomonodsp_H__
+
+// NOTE: ANY INCLUDE-GUARD HERE MUST BE DERIVED FROM THE CLASS NAME
+//
+// faust2header.cpp - FAUST Architecture File
+// This is a simple variation of matlabplot.cpp in the Faust distribution
+// aimed at creating a simple C++ header file (.h) containing a Faust DSP.
+// See the Makefile for how to use it.
+
+/************************** BEGIN dsp.h ********************************
+ FAUST Architecture File
+ Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale
+ ---------------------------------------------------------------------
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ EXCEPTION : As a special exception, you may create a larger work
+ that contains this FAUST architecture section and distribute
+ that work under terms of your choice, so long as this FAUST
+ architecture section is not modified.
+ ************************************************************************/
+
+#ifndef __dsp__
+#define __dsp__
+
+#include <string>
+#include <vector>
+
+/************************************************************************
+ ************************************************************************
+    FAUST compiler
+    Copyright (C) 2003-2018 GRAME, Centre National de Creation Musicale
+    ---------------------------------------------------------------------
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ ************************************************************************
+ ************************************************************************/
+
+#ifndef __export__
+#define __export__
+
+#define FAUSTVERSION "2.50.6"
+
+// Use FAUST_API for code that is part of the external API but is also compiled in faust and libfaust
+// Use LIBFAUST_API for code that is compiled in faust and libfaust
+
+#ifdef _WIN32
+    #pragma warning (disable: 4251)
+    #ifdef FAUST_EXE
+        #define FAUST_API
+        #define LIBFAUST_API
+    #elif FAUST_LIB
+        #define FAUST_API __declspec(dllexport)
+        #define LIBFAUST_API __declspec(dllexport)
+    #else
+        #define FAUST_API
+        #define LIBFAUST_API 
+    #endif
+#else
+    #ifdef FAUST_EXE
+        #define FAUST_API
+        #define LIBFAUST_API
+    #else
+        #define FAUST_API __attribute__((visibility("default")))
+        #define LIBFAUST_API __attribute__((visibility("default")))
+    #endif
+#endif
+
+#endif
+
+#ifndef FAUSTFLOAT
+#define FAUSTFLOAT float
+#endif
+
+struct FAUST_API UI;
+struct FAUST_API Meta;
+
+/**
+ * DSP memory manager.
+ */
+
+struct FAUST_API dsp_memory_manager {
+    
+    virtual ~dsp_memory_manager() {}
+    
+    /**
+     * Inform the Memory Manager with the number of expected memory zones.
+     * @param count - the number of expected memory zones
+     */
+    virtual void begin(size_t /* count */) {}
+    
+    /**
+     * Give the Memory Manager information on a given memory zone.
+     * @param size - the size in bytes of the memory zone
+     * @param reads - the number of Read access to the zone used to compute one frame
+     * @param writes - the number of Write access to the zone used to compute one frame
+     */
+    virtual void info(size_t /* size */, size_t /* reads */, size_t /* writes */) {}
+    
+    /**
+     * Inform the Memory Manager that all memory zones have been described,
+     * to possibly start a 'compute the best allocation strategy' step.
+     */
+    virtual void end() {}
+    
+    /**
+     * Allocate a memory zone.
+     * @param size - the memory zone size in bytes
+     */
+    virtual void* allocate(size_t size) = 0;
+    
+    /**
+     * Destroy a memory zone.
+     * @param ptr - the memory zone pointer to be deallocated
+     */
+    virtual void destroy(void* ptr) = 0;
+    
+};
+
+/**
+* Signal processor definition.
+*/
+
+class FAUST_API dsp {
+
+    public:
+
+        dsp() {}
+        virtual ~dsp() {}
+
+        /* Return instance number of audio inputs */
+        virtual int getNumInputs() = 0;
+    
+        /* Return instance number of audio outputs */
+        virtual int getNumOutputs() = 0;
+    
+        /**
+         * Trigger the ui_interface parameter with instance specific calls
+         * to 'openTabBox', 'addButton', 'addVerticalSlider'... in order to build the UI.
+         *
+         * @param ui_interface - the user interface builder
+         */
+        virtual void buildUserInterface(UI* ui_interface) = 0;
+    
+        /* Return the sample rate currently used by the instance */
+        virtual int getSampleRate() = 0;
+    
+        /**
+         * Global init, calls the following methods:
+         * - static class 'classInit': static tables initialization
+         * - 'instanceInit': constants and instance state initialization
+         *
+         * @param sample_rate - the sampling rate in Hz
+         */
+        virtual void init(int sample_rate) = 0;
+
+        /**
+         * Init instance state
+         *
+         * @param sample_rate - the sampling rate in Hz
+         */
+        virtual void instanceInit(int sample_rate) = 0;
+    
+        /**
+         * Init instance constant state
+         *
+         * @param sample_rate - the sampling rate in Hz
+         */
+        virtual void instanceConstants(int sample_rate) = 0;
+    
+        /* Init default control parameters values */
+        virtual void instanceResetUserInterface() = 0;
+    
+        /* Init instance state (like delay lines...) but keep the control parameter values */
+        virtual void instanceClear() = 0;
+        /**
+         * Return a clone of the instance.
+         *
+         * @return a copy of the instance on success, otherwise a null pointer.
+         */
+        virtual dsp* clone() = 0;
+    
+        /**
+         * Trigger the Meta* parameter with instance specific calls to 'declare' (key, value) metadata.
+         *
+         * @param m - the Meta* meta user
+         */
+        virtual void metadata(Meta* m) = 0;
+    
+        /**
+         * DSP instance computation, to be called with successive in/out audio buffers.
+         *
+         * @param count - the number of frames to compute
+         * @param inputs - the input audio buffers as an array of non-interleaved FAUSTFLOAT samples (eiher float, double or quad)
+         * @param outputs - the output audio buffers as an array of non-interleaved FAUSTFLOAT samples (eiher float, double or quad)
+         *
+         */
+        virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) = 0;
+    
+        /**
+         * DSP instance computation: alternative method to be used by subclasses.
+         *
+         * @param date_usec - the timestamp in microsec given by audio driver.
+         * @param count - the number of frames to compute
+         * @param inputs - the input audio buffers as an array of non-interleaved FAUSTFLOAT samples (either float, double or quad)
+         * @param outputs - the output audio buffers as an array of non-interleaved FAUSTFLOAT samples (either float, double or quad)
+         *
+         */
+        virtual void compute(double /*date_usec*/, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { compute(count, inputs, outputs); }
+       
+};
+
+/**
+ * Generic DSP decorator.
+ */
+
+class FAUST_API decorator_dsp : public dsp {
+
+    protected:
+
+        dsp* fDSP;
+
+    public:
+
+        decorator_dsp(dsp* dsp = nullptr):fDSP(dsp) {}
+        virtual ~decorator_dsp() { delete fDSP; }
+
+        virtual int getNumInputs() { return fDSP->getNumInputs(); }
+        virtual int getNumOutputs() { return fDSP->getNumOutputs(); }
+        virtual void buildUserInterface(UI* ui_interface) { fDSP->buildUserInterface(ui_interface); }
+        virtual int getSampleRate() { return fDSP->getSampleRate(); }
+        virtual void init(int sample_rate) { fDSP->init(sample_rate); }
+        virtual void instanceInit(int sample_rate) { fDSP->instanceInit(sample_rate); }
+        virtual void instanceConstants(int sample_rate) { fDSP->instanceConstants(sample_rate); }
+        virtual void instanceResetUserInterface() { fDSP->instanceResetUserInterface(); }
+        virtual void instanceClear() { fDSP->instanceClear(); }
+        virtual decorator_dsp* clone() { return new decorator_dsp(fDSP->clone()); }
+        virtual void metadata(Meta* m) { fDSP->metadata(m); }
+        // Beware: subclasses usually have to overload the two 'compute' methods
+        virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { fDSP->compute(count, inputs, outputs); }
+        virtual void compute(double date_usec, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) { fDSP->compute(date_usec, count, inputs, outputs); }
+    
+};
+
+/**
+ * DSP factory class, used with LLVM and Interpreter backends
+ * to create DSP instances from a compiled DSP program.
+ */
+
+class FAUST_API dsp_factory {
+    
+    protected:
+    
+        // So that to force sub-classes to use deleteDSPFactory(dsp_factory* factory);
+        virtual ~dsp_factory() {}
+    
+    public:
+    
+        virtual std::string getName() = 0;
+        virtual std::string getSHAKey() = 0;
+        virtual std::string getDSPCode() = 0;
+        virtual std::string getCompileOptions() = 0;
+        virtual std::vector<std::string> getLibraryList() = 0;
+        virtual std::vector<std::string> getIncludePathnames() = 0;
+    
+        virtual dsp* createDSPInstance() = 0;
+    
+        virtual void setMemoryManager(dsp_memory_manager* manager) = 0;
+        virtual dsp_memory_manager* getMemoryManager() = 0;
+    
+};
+
+// Denormal handling
+
+#if defined (__SSE__)
+#include <xmmintrin.h>
+#endif
+
+class FAUST_API ScopedNoDenormals {
+    
+    private:
+    
+        intptr_t fpsr = 0;
+        
+        void setFpStatusRegister(intptr_t fpsr_aux) noexcept
+        {
+        #if defined (__arm64__) || defined (__aarch64__)
+            asm volatile("msr fpcr, %0" : : "ri" (fpsr_aux));
+        #elif defined (__SSE__)
+            // The volatile keyword here is needed to workaround a bug in AppleClang 13.0
+            // which aggressively optimises away the variable otherwise
+            volatile uint32_t fpsr_w = static_cast<uint32_t>(fpsr_aux);
+            _mm_setcsr(fpsr_w);
+        #endif
+        }
+        
+        void getFpStatusRegister() noexcept
+        {
+        #if defined (__arm64__) || defined (__aarch64__)
+            asm volatile("mrs %0, fpcr" : "=r" (fpsr));
+        #elif defined (__SSE__)
+            fpsr = static_cast<intptr_t>(_mm_getcsr());
+        #endif
+        }
+    
+    public:
+    
+        ScopedNoDenormals() noexcept
+        {
+        #if defined (__arm64__) || defined (__aarch64__)
+            intptr_t mask = (1 << 24 /* FZ */);
+        #elif defined (__SSE__)
+        #if defined (__SSE2__)
+            intptr_t mask = 0x8040;
+        #else
+            intptr_t mask = 0x8000;
+        #endif
+        #else
+            intptr_t mask = 0x0000;
+        #endif
+            getFpStatusRegister();
+            setFpStatusRegister(fpsr | mask);
+        }
+        
+        ~ScopedNoDenormals() noexcept
+        {
+            setFpStatusRegister(fpsr);
+        }
+
+};
+
+#define AVOIDDENORMALS ScopedNoDenormals ftz_scope;
+
+#endif
+
+/************************** END dsp.h **************************/
+/************************** BEGIN APIUI.h *****************************
+FAUST Architecture File
+Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale
+---------------------------------------------------------------------
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+EXCEPTION : As a special exception, you may create a larger work
+that contains this FAUST architecture section and distribute
+that work under terms of your choice, so long as this FAUST
+architecture section is not modified.
+************************************************************************/
+
+#ifndef API_UI_H
+#define API_UI_H
+
+#include <sstream>
+#include <string>
+#include <vector>
+#include <stdio.h>
+#include <map>
+
+/************************** BEGIN meta.h *******************************
+ FAUST Architecture File
+ Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale
+ ---------------------------------------------------------------------
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ EXCEPTION : As a special exception, you may create a larger work
+ that contains this FAUST architecture section and distribute
+ that work under terms of your choice, so long as this FAUST
+ architecture section is not modified.
+ ************************************************************************/
+
+#ifndef __meta__
+#define __meta__
+
+
+/**
+ The base class of Meta handler to be used in dsp::metadata(Meta* m) method to retrieve (key, value) metadata.
+ */
+struct FAUST_API Meta {
+    virtual ~Meta() {}
+    virtual void declare(const char* key, const char* value) = 0;
+};
+
+#endif
+/**************************  END  meta.h **************************/
+/************************** BEGIN UI.h *****************************
+ FAUST Architecture File
+ Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale
+ ---------------------------------------------------------------------
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ EXCEPTION : As a special exception, you may create a larger work
+ that contains this FAUST architecture section and distribute
+ that work under terms of your choice, so long as this FAUST
+ architecture section is not modified.
+ ********************************************************************/
+
+#ifndef __UI_H__
+#define __UI_H__
+
+
+#ifndef FAUSTFLOAT
+#define FAUSTFLOAT float
+#endif
+
+/*******************************************************************************
+ * UI : Faust DSP User Interface
+ * User Interface as expected by the buildUserInterface() method of a DSP.
+ * This abstract class contains only the method that the Faust compiler can
+ * generate to describe a DSP user interface.
+ ******************************************************************************/
+
+struct Soundfile;
+
+template <typename REAL>
+struct FAUST_API UIReal {
+    
+    UIReal() {}
+    virtual ~UIReal() {}
+    
+    // -- widget's layouts
+    
+    virtual void openTabBox(const char* label) = 0;
+    virtual void openHorizontalBox(const char* label) = 0;
+    virtual void openVerticalBox(const char* label) = 0;
+    virtual void closeBox() = 0;
+    
+    // -- active widgets
+    
+    virtual void addButton(const char* label, REAL* zone) = 0;
+    virtual void addCheckButton(const char* label, REAL* zone) = 0;
+    virtual void addVerticalSlider(const char* label, REAL* zone, REAL init, REAL min, REAL max, REAL step) = 0;
+    virtual void addHorizontalSlider(const char* label, REAL* zone, REAL init, REAL min, REAL max, REAL step) = 0;
+    virtual void addNumEntry(const char* label, REAL* zone, REAL init, REAL min, REAL max, REAL step) = 0;
+    
+    // -- passive widgets
+    
+    virtual void addHorizontalBargraph(const char* label, REAL* zone, REAL min, REAL max) = 0;
+    virtual void addVerticalBargraph(const char* label, REAL* zone, REAL min, REAL max) = 0;
+    
+    // -- soundfiles
+    
+    virtual void addSoundfile(const char* label, const char* filename, Soundfile** sf_zone) = 0;
+    
+    // -- metadata declarations
+    
+    virtual void declare(REAL* /* zone */, const char* /* key */, const char* /* val */) {}
+    
+    // To be used by LLVM client
+    virtual int sizeOfFAUSTFLOAT() { return sizeof(FAUSTFLOAT); }
+};
+
+struct FAUST_API UI : public UIReal<FAUSTFLOAT> {
+    UI() {}
+    virtual ~UI() {}
+};
+
+#endif
+/**************************  END  UI.h **************************/
+/************************** BEGIN PathBuilder.h **************************
+ FAUST Architecture File
+ Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale
+ ---------------------------------------------------------------------
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ EXCEPTION : As a special exception, you may create a larger work
+ that contains this FAUST architecture section and distribute
+ that work under terms of your choice, so long as this FAUST
+ architecture section is not modified.
+ ************************************************************************/
+
+#ifndef __PathBuilder__
+#define __PathBuilder__
+
+#include <vector>
+#include <set>
+#include <map>
+#include <string>
+#include <algorithm>
+#include <regex>
+
+
+/*******************************************************************************
+ * PathBuilder : Faust User Interface
+ * Helper class to build complete hierarchical path for UI items.
+ ******************************************************************************/
+
+class FAUST_API PathBuilder {
+
+    protected:
+    
+        std::vector<std::string> fControlsLevel;
+        std::vector<std::string> fFullPaths;
+        std::map<std::string, std::string> fFull2Short;  // filled by computeShortNames()
+    
+        /**
+         * @brief check if a character is acceptable for an ID
+         *
+         * @param c
+         * @return true is the character is acceptable for an ID
+         */
+        bool isIDChar(char c) const
+        {
+            return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) || ((c >= '0') && (c <= '9'));
+        }
+    
+        /**
+         * @brief remove all "/0x00" parts
+         *
+         * @param src
+         * @return modified string
+         */
+        std::string remove0x00(const std::string& src) const
+        {
+            return std::regex_replace(src, std::regex("/0x00"), "");
+        }
+    
+        /**
+         * @brief replace all non ID char with '_' (one '_' may replace several non ID char)
+         *
+         * @param src
+         * @return modified string
+         */
+        std::string str2ID(const std::string& src) const
+        {
+            std::string dst;
+            bool need_underscore = false;
+            for (char c : src) {
+                if (isIDChar(c) || (c == '/')) {
+                    if (need_underscore) {
+                        dst.push_back('_');
+                        need_underscore = false;
+                    }
+                    dst.push_back(c);
+                } else {
+                    need_underscore = true;
+                }
+            }
+            return dst;
+        }
+    
+        /**
+         * @brief Keep only the last n slash-parts
+         *
+         * @param src
+         * @param n : 1 indicates the last slash-part
+         * @return modified string
+         */
+        std::string cut(const std::string& src, int n) const
+        {
+            std::string rdst;
+            for (int i = int(src.length())-1; i >= 0; i--) {
+                char c = src[i];
+                if (c != '/') {
+                    rdst.push_back(c);
+                } else if (n == 1) {
+                    std::string dst;
+                    for (int j = int(rdst.length())-1; j >= 0; j--) {
+                        dst.push_back(rdst[j]);
+                    }
+                    return dst;
+                } else {
+                    n--;
+                    rdst.push_back(c);
+                }
+            }
+            return src;
+        }
+    
+        void addFullPath(const std::string& label) { fFullPaths.push_back(buildPath(label)); }
+    
+        /**
+         * @brief Compute the mapping between full path and short names
+         */
+        void computeShortNames()
+        {
+            std::vector<std::string>           uniquePaths;  // all full paths transformed but made unique with a prefix
+            std::map<std::string, std::string> unique2full;  // all full paths transformed but made unique with a prefix
+            char num_buffer[16];
+            int pnum = 0;
+        
+            for (const auto& s : fFullPaths) {
+                sprintf(num_buffer, "%d", pnum++);
+                std::string u = "/P" + std::string(num_buffer) + str2ID(remove0x00(s));
+                uniquePaths.push_back(u);
+                unique2full[u] = s;  // remember the full path associated to a unique path
+            }
+        
+            std::map<std::string, int> uniquePath2level;                // map path to level
+            for (const auto& s : uniquePaths) uniquePath2level[s] = 1;   // we init all levels to 1
+            bool have_collisions = true;
+        
+            while (have_collisions) {
+                // compute collision list
+                std::set<std::string>              collisionSet;
+                std::map<std::string, std::string> short2full;
+                have_collisions = false;
+                for (const auto& it : uniquePath2level) {
+                    std::string u = it.first;
+                    int n = it.second;
+                    std::string shortName = cut(u, n);
+                    auto p = short2full.find(shortName);
+                    if (p == short2full.end()) {
+                        // no collision
+                        short2full[shortName] = u;
+                    } else {
+                        // we have a collision, add the two paths to the collision set
+                        have_collisions = true;
+                        collisionSet.insert(u);
+                        collisionSet.insert(p->second);
+                    }
+                }
+                for (const auto& s : collisionSet) uniquePath2level[s]++;  // increase level of colliding path
+            }
+        
+            for (const auto& it : uniquePath2level) {
+                std::string u = it.first;
+                int n = it.second;
+                std::string shortName = replaceCharList(cut(u, n), {'/'}, '_');
+                fFull2Short[unique2full[u]] = shortName;
+            }
+        }
+    
+        std::string replaceCharList(const std::string& str, const std::vector<char>& ch1, char ch2)
+        {
+            auto beg = ch1.begin();
+            auto end = ch1.end();
+            std::string res = str;
+            for (size_t i = 0; i < str.length(); ++i) {
+                if (std::find(beg, end, str[i]) != end) res[i] = ch2;
+            }
+            return res;
+        }
+     
+    public:
+    
+        PathBuilder() {}
+        virtual ~PathBuilder() {}
+    
+        // Return true for the first level of groups
+        bool pushLabel(const std::string& label) { fControlsLevel.push_back(label); return fControlsLevel.size() == 1;}
+    
+        // Return true for the last level of groups
+        bool popLabel() { fControlsLevel.pop_back(); return fControlsLevel.size() == 0; }
+    
+        std::string buildPath(const std::string& label)
+        {
+            std::string res = "/";
+            for (size_t i = 0; i < fControlsLevel.size(); i++) {
+                res = res + fControlsLevel[i] + "/";
+            }
+            res += label;
+            return replaceCharList(res, {' ', '#', '*', ',', '?', '[', ']', '{', '}', '(', ')'}, '_');
+        }
+    
+};
+
+#endif  // __PathBuilder__
+/**************************  END  PathBuilder.h **************************/
+/************************** BEGIN ValueConverter.h ********************
+ FAUST Architecture File
+ Copyright (C) 2003-2022 GRAME, Centre National de Creation Musicale
+ ---------------------------------------------------------------------
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ EXCEPTION : As a special exception, you may create a larger work
+ that contains this FAUST architecture section and distribute
+ that work under terms of your choice, so long as this FAUST
+ architecture section is not modified.
+ ********************************************************************/
+
+#ifndef __ValueConverter__
+#define __ValueConverter__
+
+/***************************************************************************************
+ ValueConverter.h
+ (GRAME, Copyright 2015-2019)
+ Set of conversion objects used to map user interface values (for example a gui slider
+ delivering values between 0 and 1) to faust values (for example a vslider between
+ 20 and 20000) using a log scale.
+ -- Utilities
+ Range(lo,hi) : clip a value x between lo and hi
+ Interpolator(lo,hi,v1,v2) : Maps a value x between lo and hi to a value y between v1 and v2
+ Interpolator3pt(lo,mi,hi,v1,vm,v2) : Map values between lo mid hi to values between v1 vm v2
+ -- Value Converters
+ ValueConverter::ui2faust(x)
+ ValueConverter::faust2ui(x)
+ -- ValueConverters used for sliders depending of the scale
+ LinearValueConverter(umin, umax, fmin, fmax)
+ LinearValueConverter2(lo, mi, hi, v1, vm, v2) using 2 segments
+ LogValueConverter(umin, umax, fmin, fmax)
+ ExpValueConverter(umin, umax, fmin, fmax)
+ -- ValueConverters used for accelerometers based on 3 points
+ AccUpConverter(amin, amid, amax, fmin, fmid, fmax)        -- curve 0
+ AccDownConverter(amin, amid, amax, fmin, fmid, fmax)      -- curve 1
+ AccUpDownConverter(amin, amid, amax, fmin, fmid, fmax)    -- curve 2
+ AccDownUpConverter(amin, amid, amax, fmin, fmid, fmax)    -- curve 3
+ -- lists of ZoneControl are used to implement accelerometers metadata for each axes
+ ZoneControl(zone, valueConverter) : a zone with an accelerometer data converter
+ -- ZoneReader are used to implement screencolor metadata
+ ZoneReader(zone, valueConverter) : a zone with a data converter
+
+****************************************************************************************/
+
+#include <float.h>
+#include <algorithm>    // std::max
+#include <cmath>
+#include <vector>
+#include <assert.h>
+
+
+//--------------------------------------------------------------------------------------
+// Interpolator(lo,hi,v1,v2)
+// Maps a value x between lo and hi to a value y between v1 and v2
+// y = v1 + (x-lo)/(hi-lo)*(v2-v1)
+// y = v1 + (x-lo) * coef           with coef = (v2-v1)/(hi-lo)
+// y = v1 + x*coef - lo*coef
+// y = v1 - lo*coef + x*coef
+// y = offset + x*coef              with offset = v1 - lo*coef
+//--------------------------------------------------------------------------------------
+class FAUST_API Interpolator {
+    
+    private:
+
+        //--------------------------------------------------------------------------------------
+        // Range(lo,hi) clip a value between lo and hi
+        //--------------------------------------------------------------------------------------
+        struct Range
+        {
+            double fLo;
+            double fHi;
+
+            Range(double x, double y) : fLo(std::min<double>(x,y)), fHi(std::max<double>(x,y)) {}
+            double operator()(double x) { return (x<fLo) ? fLo : (x>fHi) ? fHi : x; }
+        };
+
+
+        Range fRange;
+        double fCoef;
+        double fOffset;
+
+    public:
+
+        Interpolator(double lo, double hi, double v1, double v2) : fRange(lo,hi)
+        {
+            if (hi != lo) {
+                // regular case
+                fCoef = (v2-v1)/(hi-lo);
+                fOffset = v1 - lo*fCoef;
+            } else {
+                // degenerate case, avoids division by zero
+                fCoef = 0;
+                fOffset = (v1+v2)/2;
+            }
+        }
+        double operator()(double v)
+        {
+            double x = fRange(v);
+            return  fOffset + x*fCoef;
+        }
+
+        void getLowHigh(double& amin, double& amax)
+        {
+            amin = fRange.fLo;
+            amax = fRange.fHi;
+        }
+};
+
+//--------------------------------------------------------------------------------------
+// Interpolator3pt(lo,mi,hi,v1,vm,v2)
+// Map values between lo mid hi to values between v1 vm v2
+//--------------------------------------------------------------------------------------
+class FAUST_API Interpolator3pt {
+
+    private:
+
+        Interpolator fSegment1;
+        Interpolator fSegment2;
+        double fMid;
+
+    public:
+
+        Interpolator3pt(double lo, double mi, double hi, double v1, double vm, double v2) :
+            fSegment1(lo, mi, v1, vm),
+            fSegment2(mi, hi, vm, v2),
+            fMid(mi) {}
+        double operator()(double x) { return  (x < fMid) ? fSegment1(x) : fSegment2(x); }
+
+        void getMappingValues(double& amin, double& amid, double& amax)
+        {
+            fSegment1.getLowHigh(amin, amid);
+            fSegment2.getLowHigh(amid, amax);
+        }
+};
+
+//--------------------------------------------------------------------------------------
+// Abstract ValueConverter class. Converts values between UI and Faust representations
+//--------------------------------------------------------------------------------------
+class FAUST_API ValueConverter {
+
+    public:
+
+        virtual ~ValueConverter() {}
+        virtual double ui2faust(double x) { return x; };
+        virtual double faust2ui(double x) { return x; };
+};
+
+//--------------------------------------------------------------------------------------
+// A converter than can be updated
+//--------------------------------------------------------------------------------------
+
+class FAUST_API UpdatableValueConverter : public ValueConverter {
+    
+    protected:
+        
+        bool fActive;
+        
+    public:
+        
+        UpdatableValueConverter():fActive(true)
+        {}
+        virtual ~UpdatableValueConverter()
+        {}
+        
+        virtual void setMappingValues(double amin, double amid, double amax, double min, double init, double max) = 0;
+        virtual void getMappingValues(double& amin, double& amid, double& amax) = 0;
+        
+        void setActive(bool on_off) { fActive = on_off; }
+        bool getActive() { return fActive; }
+    
+};
+
+//--------------------------------------------------------------------------------------
+// Linear conversion between ui and Faust values
+//--------------------------------------------------------------------------------------
+class FAUST_API LinearValueConverter : public ValueConverter {
+    
+    private:
+        
+        Interpolator fUI2F;
+        Interpolator fF2UI;
+        
+    public:
+        
+        LinearValueConverter(double umin, double umax, double fmin, double fmax) :
+            fUI2F(umin,umax,fmin,fmax), fF2UI(fmin,fmax,umin,umax)
+        {}
+        
+        LinearValueConverter() : fUI2F(0.,0.,0.,0.), fF2UI(0.,0.,0.,0.)
+        {}
+        virtual double ui2faust(double x) { return fUI2F(x); }
+        virtual double faust2ui(double x) { return fF2UI(x); }
+    
+};
+
+//--------------------------------------------------------------------------------------
+// Two segments linear conversion between ui and Faust values
+//--------------------------------------------------------------------------------------
+class FAUST_API LinearValueConverter2 : public UpdatableValueConverter {
+    
+    private:
+    
+        Interpolator3pt fUI2F;
+        Interpolator3pt fF2UI;
+        
+    public:
+    
+        LinearValueConverter2(double amin, double amid, double amax, double min, double init, double max) :
+            fUI2F(amin, amid, amax, min, init, max), fF2UI(min, init, max, amin, amid, amax)
+        {}
+        
+        LinearValueConverter2() : fUI2F(0.,0.,0.,0.,0.,0.), fF2UI(0.,0.,0.,0.,0.,0.)
+        {}
+    
+        virtual double ui2faust(double x) { return fUI2F(x); }
+        virtual double faust2ui(double x) { return fF2UI(x); }
+    
+        virtual void setMappingValues(double amin, double amid, double amax, double min, double init, double max)
+        {
+            fUI2F = Interpolator3pt(amin, amid, amax, min, init, max);
+            fF2UI = Interpolator3pt(min, init, max, amin, amid, amax);
+        }
+
+        virtual void getMappingValues(double& amin, double& amid, double& amax)
+        {
+            fUI2F.getMappingValues(amin, amid, amax);
+        }
+    
+};
+
+//--------------------------------------------------------------------------------------
+// Logarithmic conversion between ui and Faust values
+//--------------------------------------------------------------------------------------
+class FAUST_API LogValueConverter : public LinearValueConverter {
+
+    public:
+
+        LogValueConverter(double umin, double umax, double fmin, double fmax) :
+            LinearValueConverter(umin, umax, std::log(std::max<double>(DBL_MIN, fmin)), std::log(std::max<double>(DBL_MIN, fmax)))
+        {}
+
+        virtual double ui2faust(double x) { return std::exp(LinearValueConverter::ui2faust(x)); }
+        virtual double faust2ui(double x) { return LinearValueConverter::faust2ui(std::log(std::max<double>(x, DBL_MIN))); }
+
+};
+
+//--------------------------------------------------------------------------------------
+// Exponential conversion between ui and Faust values
+//--------------------------------------------------------------------------------------
+class FAUST_API ExpValueConverter : public LinearValueConverter {
+
+    public:
+
+        ExpValueConverter(double umin, double umax, double fmin, double fmax) :
+            LinearValueConverter(umin, umax, std::min<double>(DBL_MAX, std::exp(fmin)), std::min<double>(DBL_MAX, std::exp(fmax)))
+        {}
+
+        virtual double ui2faust(double x) { return std::log(LinearValueConverter::ui2faust(x)); }
+        virtual double faust2ui(double x) { return LinearValueConverter::faust2ui(std::min<double>(DBL_MAX, std::exp(x))); }
+
+};
+
+//--------------------------------------------------------------------------------------
+// Convert accelerometer or gyroscope values to Faust values
+// Using an Up curve (curve 0)
+//--------------------------------------------------------------------------------------
+class FAUST_API AccUpConverter : public UpdatableValueConverter {
+
+    private:
+
+        Interpolator3pt fA2F;
+        Interpolator3pt fF2A;
+
+    public:
+
+        AccUpConverter(double amin, double amid, double amax, double fmin, double fmid, double fmax) :
+            fA2F(amin,amid,amax,fmin,fmid,fmax),
+            fF2A(fmin,fmid,fmax,amin,amid,amax)
+        {}
+
+        virtual double ui2faust(double x) { return fA2F(x); }
+        virtual double faust2ui(double x) { return fF2A(x); }
+
+        virtual void setMappingValues(double amin, double amid, double amax, double fmin, double fmid, double fmax)
+        {
+            //__android_log_print(ANDROID_LOG_ERROR, "Faust", "AccUpConverter update %f %f %f %f %f %f", amin,amid,amax,fmin,fmid,fmax);
+            fA2F = Interpolator3pt(amin, amid, amax, fmin, fmid, fmax);
+            fF2A = Interpolator3pt(fmin, fmid, fmax, amin, amid, amax);
+        }
+
+        virtual void getMappingValues(double& amin, double& amid, double& amax)
+        {
+            fA2F.getMappingValues(amin, amid, amax);
+        }
+
+};
+
+//--------------------------------------------------------------------------------------
+// Convert accelerometer or gyroscope values to Faust values
+// Using a Down curve (curve 1)
+//--------------------------------------------------------------------------------------
+class FAUST_API AccDownConverter : public UpdatableValueConverter {
+
+    private:
+
+        Interpolator3pt        fA2F;
+        Interpolator3pt        fF2A;
+
+    public:
+
+        AccDownConverter(double amin, double amid, double amax, double fmin, double fmid, double fmax) :
+            fA2F(amin,amid,amax,fmax,fmid,fmin),
+            fF2A(fmin,fmid,fmax,amax,amid,amin)
+        {}
+
+        virtual double ui2faust(double x) { return fA2F(x); }
+        virtual double faust2ui(double x) { return fF2A(x); }
+
+        virtual void setMappingValues(double amin, double amid, double amax, double fmin, double fmid, double fmax)
+        {
+             //__android_log_print(ANDROID_LOG_ERROR, "Faust", "AccDownConverter update %f %f %f %f %f %f", amin,amid,amax,fmin,fmid,fmax);
+            fA2F = Interpolator3pt(amin, amid, amax, fmax, fmid, fmin);
+            fF2A = Interpolator3pt(fmin, fmid, fmax, amax, amid, amin);
+        }
+
+        virtual void getMappingValues(double& amin, double& amid, double& amax)
+        {
+            fA2F.getMappingValues(amin, amid, amax);
+        }
+};
+
+//--------------------------------------------------------------------------------------
+// Convert accelerometer or gyroscope values to Faust values
+// Using an Up-Down curve (curve 2)
+//--------------------------------------------------------------------------------------
+class FAUST_API AccUpDownConverter : public UpdatableValueConverter {
+
+    private:
+
+        Interpolator3pt        fA2F;
+        Interpolator fF2A;
+
+    public:
+
+        AccUpDownConverter(double amin, double amid, double amax, double fmin, double /* fmid */, double fmax) :
+            fA2F(amin,amid,amax,fmin,fmax,fmin),
+            fF2A(fmin,fmax,amin,amax)                          // Special, pseudo inverse of a non monotonic function
+        {}
+
+        virtual double ui2faust(double x) { return fA2F(x); }
+        virtual double faust2ui(double x) { return fF2A(x); }
+
+        virtual void setMappingValues(double amin, double amid, double amax, double fmin, double /* fmid */, double fmax)
+        {
+            //__android_log_print(ANDROID_LOG_ERROR, "Faust", "AccUpDownConverter update %f %f %f %f %f %f", amin,amid,amax,fmin,fmid,fmax);
+            fA2F = Interpolator3pt(amin, amid, amax, fmin, fmax, fmin);
+            fF2A = Interpolator(fmin, fmax, amin, amax);
+        }
+
+        virtual void getMappingValues(double& amin, double& amid, double& amax)
+        {
+            fA2F.getMappingValues(amin, amid, amax);
+        }
+};
+
+//--------------------------------------------------------------------------------------
+// Convert accelerometer or gyroscope values to Faust values
+// Using a Down-Up curve (curve 3)
+//--------------------------------------------------------------------------------------
+class FAUST_API AccDownUpConverter : public UpdatableValueConverter {
+
+    private:
+
+        Interpolator3pt        fA2F;
+        Interpolator fF2A;
+
+    public:
+
+        AccDownUpConverter(double amin, double amid, double amax, double fmin, double /* fmid */, double fmax) :
+            fA2F(amin,amid,amax,fmax,fmin,fmax),
+            fF2A(fmin,fmax,amin,amax)                          // Special, pseudo inverse of a non monotonic function
+        {}
+
+        virtual double ui2faust(double x) { return fA2F(x); }
+        virtual double faust2ui(double x) { return fF2A(x); }
+
+        virtual void setMappingValues(double amin, double amid, double amax, double fmin, double /* fmid */, double fmax)
+        {
+            //__android_log_print(ANDROID_LOG_ERROR, "Faust", "AccDownUpConverter update %f %f %f %f %f %f", amin,amid,amax,fmin,fmid,fmax);
+            fA2F = Interpolator3pt(amin, amid, amax, fmax, fmin, fmax);
+            fF2A = Interpolator(fmin, fmax, amin, amax);
+        }
+
+        virtual void getMappingValues(double& amin, double& amid, double& amax)
+        {
+            fA2F.getMappingValues(amin, amid, amax);
+        }
+};
+
+//--------------------------------------------------------------------------------------
+// Base class for ZoneControl
+//--------------------------------------------------------------------------------------
+class FAUST_API ZoneControl {
+
+    protected:
+
+        FAUSTFLOAT*    fZone;
+
+    public:
+
+        ZoneControl(FAUSTFLOAT* zone) : fZone(zone) {}
+        virtual ~ZoneControl() {}
+
+        virtual void update(double /* v */) const {}
+
+        virtual void setMappingValues(int /* curve */, double /* amin */, double /* amid */, double /* amax */, double /* min */, double /* init */, double /* max */) {}
+        virtual void getMappingValues(double& /* amin */, double& /* amid */, double& /* amax */) {}
+
+        FAUSTFLOAT* getZone() { return fZone; }
+
+        virtual void setActive(bool /* on_off */) {}
+        virtual bool getActive() { return false; }
+
+        virtual int getCurve() { return -1; }
+
+};
+
+//--------------------------------------------------------------------------------------
+//  Useful to implement accelerometers metadata as a list of ZoneControl for each axes
+//--------------------------------------------------------------------------------------
+class FAUST_API ConverterZoneControl : public ZoneControl {
+
+    protected:
+
+        ValueConverter* fValueConverter;
+
+    public:
+
+        ConverterZoneControl(FAUSTFLOAT* zone, ValueConverter* converter) : ZoneControl(zone), fValueConverter(converter) {}
+        virtual ~ConverterZoneControl() { delete fValueConverter; } // Assuming fValueConverter is not kept elsewhere...
+
+        virtual void update(double v) const { *fZone = FAUSTFLOAT(fValueConverter->ui2faust(v)); }
+
+        ValueConverter* getConverter() { return fValueConverter; }
+
+};
+
+//--------------------------------------------------------------------------------------
+// Association of a zone and a four value converter, each one for each possible curve.
+// Useful to implement accelerometers metadata as a list of ZoneControl for each axes
+//--------------------------------------------------------------------------------------
+class FAUST_API CurveZoneControl : public ZoneControl {
+
+    private:
+
+        std::vector<UpdatableValueConverter*> fValueConverters;
+        int fCurve;
+
+    public:
+
+        CurveZoneControl(FAUSTFLOAT* zone, int curve, double amin, double amid, double amax, double min, double init, double max) : ZoneControl(zone), fCurve(0)
+        {
+            assert(curve >= 0 && curve <= 3);
+            fValueConverters.push_back(new AccUpConverter(amin, amid, amax, min, init, max));
+            fValueConverters.push_back(new AccDownConverter(amin, amid, amax, min, init, max));
+            fValueConverters.push_back(new AccUpDownConverter(amin, amid, amax, min, init, max));
+            fValueConverters.push_back(new AccDownUpConverter(amin, amid, amax, min, init, max));
+            fCurve = curve;
+        }
+        virtual ~CurveZoneControl()
+        {
+            for (const auto& it : fValueConverters) { delete it; }
+        }
+        void update(double v) const { if (fValueConverters[fCurve]->getActive()) *fZone = FAUSTFLOAT(fValueConverters[fCurve]->ui2faust(v)); }
+
+        void setMappingValues(int curve, double amin, double amid, double amax, double min, double init, double max)
+        {
+            fValueConverters[curve]->setMappingValues(amin, amid, amax, min, init, max);
+            fCurve = curve;
+        }
+
+        void getMappingValues(double& amin, double& amid, double& amax)
+        {
+            fValueConverters[fCurve]->getMappingValues(amin, amid, amax);
+        }
+
+        void setActive(bool on_off)
+        {
+            for (const auto& it : fValueConverters) { it->setActive(on_off); }
+        }
+
+        int getCurve() { return fCurve; }
+};
+
+class FAUST_API ZoneReader {
+
+    private:
+
+        FAUSTFLOAT* fZone;
+        Interpolator fInterpolator;
+
+    public:
+
+        ZoneReader(FAUSTFLOAT* zone, double lo, double hi) : fZone(zone), fInterpolator(lo, hi, 0, 255) {}
+
+        virtual ~ZoneReader() {}
+
+        int getValue()
+        {
+            return (fZone != nullptr) ? int(fInterpolator(*fZone)) : 127;
+        }
+
+};
+
+#endif
+/**************************  END  ValueConverter.h **************************/
+
+typedef unsigned int uint;
+
+class APIUI : public PathBuilder, public Meta, public UI
+{
+    public:
+        enum ItemType { kButton = 0, kCheckButton, kVSlider, kHSlider, kNumEntry, kHBargraph, kVBargraph };
+        enum Type { kAcc = 0, kGyr = 1, kNoType };
+
+    protected:
+
+        enum Mapping { kLin = 0, kLog = 1, kExp = 2 };
+
+        struct Item {
+            std::string fLabel;
+            std::string fShortname;
+            std::string fPath;
+            ValueConverter* fConversion;
+            FAUSTFLOAT* fZone;
+            FAUSTFLOAT fInit;
+            FAUSTFLOAT fMin;
+            FAUSTFLOAT fMax;
+            FAUSTFLOAT fStep;
+            ItemType fItemType;
+            
+            Item(const std::string& label,
+                 const std::string& short_name,
+                 const std::string& path,
+                 ValueConverter* conversion,
+                 FAUSTFLOAT* zone,
+                 FAUSTFLOAT init,
+                 FAUSTFLOAT min,
+                 FAUSTFLOAT max,
+                 FAUSTFLOAT step,
+                 ItemType item_type)
+            :fLabel(label), fShortname(short_name), fPath(path), fConversion(conversion),
+            fZone(zone), fInit(init), fMin(min), fMax(max), fStep(step), fItemType(item_type)
+            {}
+        };
+        std::vector<Item> fItems;
+
+        std::vector<std::map<std::string, std::string> > fMetaData;
+        std::vector<ZoneControl*> fAcc[3];
+        std::vector<ZoneControl*> fGyr[3];
+
+        // Screen color control
+        // "...[screencolor:red]..." etc.
+        bool fHasScreenControl;      // true if control screen color metadata
+        ZoneReader* fRedReader;
+        ZoneReader* fGreenReader;
+        ZoneReader* fBlueReader;
+
+        // Current values controlled by metadata
+        std::string fCurrentUnit;
+        int fCurrentScale;
+        std::string fCurrentAcc;
+        std::string fCurrentGyr;
+        std::string fCurrentColor;
+        std::string fCurrentTooltip;
+        std::map<std::string, std::string> fCurrentMetadata;
+
+        // Add a generic parameter
+        virtual void addParameter(const char* label,
+                                  FAUSTFLOAT* zone,
+                                  FAUSTFLOAT init,
+                                  FAUSTFLOAT min,
+                                  FAUSTFLOAT max,
+                                  FAUSTFLOAT step,
+                                  ItemType type)
+        {
+            std::string path = buildPath(label);
+            fFullPaths.push_back(path);
+
+            // handle scale metadata
+            ValueConverter* converter = nullptr;
+            switch (fCurrentScale) {
+                case kLin:
+                    converter = new LinearValueConverter(0, 1, min, max);
+                    break;
+                case kLog:
+                    converter = new LogValueConverter(0, 1, min, max);
+                    break;
+                case kExp:
+                    converter = new ExpValueConverter(0, 1, min, max);
+                    break;
+            }
+            fCurrentScale = kLin;
+
+            fItems.push_back(Item(label, "", path, converter, zone, init, min, max, step, type));
+       
+            if (fCurrentAcc.size() > 0 && fCurrentGyr.size() > 0) {
+                fprintf(stderr, "warning : 'acc' and 'gyr' metadata used for the same %s parameter !!\n", label);
+            }
+
+            // handle acc metadata "...[acc : <axe> <curve> <amin> <amid> <amax>]..."
+            if (fCurrentAcc.size() > 0) {
+                std::istringstream iss(fCurrentAcc);
+                int axe, curve;
+                double amin, amid, amax;
+                iss >> axe >> curve >> amin >> amid >> amax;
+
+                if ((0 <= axe) && (axe < 3) &&
+                    (0 <= curve) && (curve < 4) &&
+                    (amin < amax) && (amin <= amid) && (amid <= amax))
+                {
+                    fAcc[axe].push_back(new CurveZoneControl(zone, curve, amin, amid, amax, min, init, max));
+                } else {
+                    fprintf(stderr, "incorrect acc metadata : %s \n", fCurrentAcc.c_str());
+                }
+                fCurrentAcc = "";
+            }
+
+            // handle gyr metadata "...[gyr : <axe> <curve> <amin> <amid> <amax>]..."
+            if (fCurrentGyr.size() > 0) {
+                std::istringstream iss(fCurrentGyr);
+                int axe, curve;
+                double amin, amid, amax;
+                iss >> axe >> curve >> amin >> amid >> amax;
+
+                if ((0 <= axe) && (axe < 3) &&
+                    (0 <= curve) && (curve < 4) &&
+                    (amin < amax) && (amin <= amid) && (amid <= amax))
+                {
+                    fGyr[axe].push_back(new CurveZoneControl(zone, curve, amin, amid, amax, min, init, max));
+                } else {
+                    fprintf(stderr, "incorrect gyr metadata : %s \n", fCurrentGyr.c_str());
+                }
+                fCurrentGyr = "";
+            }
+
+            // handle screencolor metadata "...[screencolor:red|green|blue|white]..."
+            if (fCurrentColor.size() > 0) {
+                if ((fCurrentColor == "red") && (fRedReader == nullptr)) {
+                    fRedReader = new ZoneReader(zone, min, max);
+                    fHasScreenControl = true;
+                } else if ((fCurrentColor == "green") && (fGreenReader == nullptr)) {
+                    fGreenReader = new ZoneReader(zone, min, max);
+                    fHasScreenControl = true;
+                } else if ((fCurrentColor == "blue") && (fBlueReader == nullptr)) {
+                    fBlueReader = new ZoneReader(zone, min, max);
+                    fHasScreenControl = true;
+                } else if ((fCurrentColor == "white") && (fRedReader == nullptr) && (fGreenReader == nullptr) && (fBlueReader == nullptr)) {
+                    fRedReader = new ZoneReader(zone, min, max);
+                    fGreenReader = new ZoneReader(zone, min, max);
+                    fBlueReader = new ZoneReader(zone, min, max);
+                    fHasScreenControl = true;
+                } else {
+                    fprintf(stderr, "incorrect screencolor metadata : %s \n", fCurrentColor.c_str());
+                }
+            }
+            fCurrentColor = "";
+
+            fMetaData.push_back(fCurrentMetadata);
+            fCurrentMetadata.clear();
+        }
+
+        int getZoneIndex(std::vector<ZoneControl*>* table, int p, int val)
+        {
+            FAUSTFLOAT* zone = fItems[uint(p)].fZone;
+            for (size_t i = 0; i < table[val].size(); i++) {
+                if (zone == table[val][i]->getZone()) return int(i);
+            }
+            return -1;
+        }
+
+        void setConverter(std::vector<ZoneControl*>* table, int p, int val, int curve, double amin, double amid, double amax)
+        {
+            int id1 = getZoneIndex(table, p, 0);
+            int id2 = getZoneIndex(table, p, 1);
+            int id3 = getZoneIndex(table, p, 2);
+
+            // Deactivates everywhere..
+            if (id1 != -1) table[0][uint(id1)]->setActive(false);
+            if (id2 != -1) table[1][uint(id2)]->setActive(false);
+            if (id3 != -1) table[2][uint(id3)]->setActive(false);
+
+            if (val == -1) { // Means: no more mapping...
+                // So stay all deactivated...
+            } else {
+                int id4 = getZoneIndex(table, p, val);
+                if (id4 != -1) {
+                    // Reactivate the one we edit...
+                  table[val][uint(id4)]->setMappingValues(curve, amin, amid, amax, fItems[uint(p)].fMin, fItems[uint(p)].fInit, fItems[uint(p)].fMax);
+                  table[val][uint(id4)]->setActive(true);
+                } else {
+                    // Allocate a new CurveZoneControl which is 'active' by default
+                    FAUSTFLOAT* zone = fItems[uint(p)].fZone;
+                    table[val].push_back(new CurveZoneControl(zone, curve, amin, amid, amax, fItems[uint(p)].fMin, fItems[uint(p)].fInit, fItems[uint(p)].fMax));
+                }
+            }
+        }
+
+        void getConverter(std::vector<ZoneControl*>* table, int p, int& val, int& curve, double& amin, double& amid, double& amax)
+        {
+            int id1 = getZoneIndex(table, p, 0);
+            int id2 = getZoneIndex(table, p, 1);
+            int id3 = getZoneIndex(table, p, 2);
+
+            if (id1 != -1) {
+                val = 0;
+                curve = table[val][uint(id1)]->getCurve();
+                table[val][uint(id1)]->getMappingValues(amin, amid, amax);
+            } else if (id2 != -1) {
+                val = 1;
+                curve = table[val][uint(id2)]->getCurve();
+                table[val][uint(id2)]->getMappingValues(amin, amid, amax);
+            } else if (id3 != -1) {
+                val = 2;
+                curve = table[val][uint(id3)]->getCurve();
+                table[val][uint(id3)]->getMappingValues(amin, amid, amax);
+            } else {
+                val = -1; // No mapping
+                curve = 0;
+                amin = -100.;
+                amid = 0.;
+                amax = 100.;
+            }
+        }
+
+    public:
+
+        APIUI() : fHasScreenControl(false), fRedReader(nullptr), fGreenReader(nullptr), fBlueReader(nullptr), fCurrentScale(kLin)
+        {}
+
+        virtual ~APIUI()
+        {
+            for (const auto& it : fItems) delete it.fConversion;
+            for (int i = 0; i < 3; i++) {
+                for (const auto& it : fAcc[i]) delete it;
+                for (const auto& it : fGyr[i]) delete it;
+            }
+            delete fRedReader;
+            delete fGreenReader;
+            delete fBlueReader;
+        }
+
+        // -- widget's layouts
+
+        virtual void openTabBox(const char* label) { pushLabel(label); }
+        virtual void openHorizontalBox(const char* label) { pushLabel(label); }
+        virtual void openVerticalBox(const char* label) { pushLabel(label); }
+        virtual void closeBox()
+        {
+            if (popLabel()) {
+                // Shortnames can be computed when all fullnames are known
+                computeShortNames();
+                // Fill 'shortname' field for each item
+                for (const auto& it : fFull2Short) {
+                    int index = getParamIndex(it.first.c_str());
+                    fItems[index].fShortname = it.second;
+                }
+            }
+        }
+
+        // -- active widgets
+
+        virtual void addButton(const char* label, FAUSTFLOAT* zone)
+        {
+            addParameter(label, zone, 0, 0, 1, 1, kButton);
+        }
+
+        virtual void addCheckButton(const char* label, FAUSTFLOAT* zone)
+        {
+            addParameter(label, zone, 0, 0, 1, 1, kCheckButton);
+        }
+
+        virtual void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step)
+        {
+            addParameter(label, zone, init, min, max, step, kVSlider);
+        }
+
+        virtual void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step)
+        {
+            addParameter(label, zone, init, min, max, step, kHSlider);
+        }
+
+        virtual void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step)
+        {
+            addParameter(label, zone, init, min, max, step, kNumEntry);
+        }
+
+        // -- passive widgets
+
+        virtual void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max)
+        {
+            addParameter(label, zone, min, min, max, (max-min)/1000.0f, kHBargraph);
+        }
+
+        virtual void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max)
+        {
+            addParameter(label, zone, min, min, max, (max-min)/1000.0f, kVBargraph);
+        }
+
+        // -- soundfiles
+
+        virtual void addSoundfile(const char* /* label */, const char* /* filename */, Soundfile** /* sf_zone */) {}
+
+        // -- metadata declarations
+
+        virtual void declare(FAUSTFLOAT* /* zone */, const char* key, const char* val)
+        {
+            // Keep metadata
+            fCurrentMetadata[key] = val;
+
+            if (strcmp(key, "scale") == 0) {
+                if (strcmp(val, "log") == 0) {
+                    fCurrentScale = kLog;
+                } else if (strcmp(val, "exp") == 0) {
+                    fCurrentScale = kExp;
+                } else {
+                    fCurrentScale = kLin;
+                }
+            } else if (strcmp(key, "unit") == 0) {
+                fCurrentUnit = val;
+            } else if (strcmp(key, "acc") == 0) {
+                fCurrentAcc = val;
+            } else if (strcmp(key, "gyr") == 0) {
+                fCurrentGyr = val;
+            } else if (strcmp(key, "screencolor") == 0) {
+                fCurrentColor = val; // val = "red", "green", "blue" or "white"
+            } else if (strcmp(key, "tooltip") == 0) {
+                fCurrentTooltip = val;
+            }
+        }
+
+        virtual void declare(const char* /* key */, const char* /* val */)
+        {}
+
+        //-------------------------------------------------------------------------------
+        // Simple API part
+        //-------------------------------------------------------------------------------
+    
+        /**
+         * Return the number of parameters in the UI.
+         *
+         * @return the number of parameters
+         */
+        int getParamsCount() { return int(fItems.size()); }
+
+        /**
+         * Return the param index.
+         *
+         * @param str - the UI parameter label/shortname/path
+         *
+         * @return the param index
+         */
+        int getParamIndex(const char* str)
+        {
+            std::string path = std::string(str);
+            auto it = find_if(fItems.begin(), fItems.end(),
+                              [=](const Item& it) { return (it.fLabel == path) || (it.fShortname == path) || (it.fPath == path); });
+            return (it != fItems.end()) ? int(it - fItems.begin()) : -1;
+        }
+    
+        /**
+         * Return the param label.
+         *
+         * @param p - the UI parameter index
+         *
+         * @return the param label
+         */
+        const char* getParamLabel(int p) { return fItems[uint(p)].fLabel.c_str(); }
+    
+        /**
+         * Return the param shortname.
+         *
+         * @param p - the UI parameter index
+         *
+         * @return the param shortname
+         */
+        const char* getParamShortname(int p) { return fItems[uint(p)].fShortname.c_str(); }
+    
+        /**
+         * Return the param path.
+         *
+         * @param p - the UI parameter index
+         *
+         * @return the param path
+         */
+        const char* getParamAddress(int p) { return fItems[uint(p)].fPath.c_str(); }
+    
+        /**
+         * Return the param metadata.
+         *
+         * @param p - the UI parameter index
+         *
+         * @return the param metadata as a map<key, value>
+         */
+        std::map<const char*, const char*> getMetadata(int p)
+        {
+            std::map<const char*, const char*> res;
+            std::map<std::string, std::string> metadata = fMetaData[uint(p)];
+            for (const auto& it : metadata) {
+                res[it.first.c_str()] = it.second.c_str();
+            }
+            return res;
+        }
+
+        /**
+         * Return the param metadata value.
+         *
+         * @param p - the UI parameter index
+         * @param key - the UI parameter index
+         *
+         * @return the param metadata value associate to the key
+         */
+        const char* getMetadata(int p, const char* key)
+        {
+            return (fMetaData[uint(p)].find(key) != fMetaData[uint(p)].end()) ? fMetaData[uint(p)][key].c_str() : "";
+        }
+    
+        /**
+         * Return the param minimum value.
+         *
+         * @param p - the UI parameter index
+         *
+         * @return the param minimum value
+         */
+        FAUSTFLOAT getParamMin(int p) { return fItems[uint(p)].fMin; }
+    
+        /**
+         * Return the param maximum value.
+         *
+         * @param p - the UI parameter index
+         *
+         * @return the param maximum value
+         */
+        FAUSTFLOAT getParamMax(int p) { return fItems[uint(p)].fMax; }
+    
+        /**
+         * Return the param step value.
+         *
+         * @param p - the UI parameter index
+         *
+         * @return the param step value
+         */
+        FAUSTFLOAT getParamStep(int p) { return fItems[uint(p)].fStep; }
+    
+        /**
+         * Return the param init value.
+         *
+         * @param p - the UI parameter index
+         *
+         * @return the param init value
+         */
+        FAUSTFLOAT getParamInit(int p) { return fItems[uint(p)].fInit; }
+
+        /**
+         * Return the param memory zone.
+         *
+         * @param p - the UI parameter index
+         *
+         * @return the param memory zone.
+         */
+        FAUSTFLOAT* getParamZone(int p) { return fItems[uint(p)].fZone; }
+
+        /**
+         * Return the param value.
+         *
+         * @param p - the UI parameter index
+         *
+         * @return the param value.
+         */
+        FAUSTFLOAT getParamValue(int p) { return *fItems[uint(p)].fZone; }
+    
+        /**
+         * Return the param value.
+         *
+         * @param str - the UI parameter label/shortname/path
+         *
+         * @return the param value.
+         */
+        FAUSTFLOAT getParamValue(const char* str)
+        {
+            int index = getParamIndex(str);
+            if (index >= 0) {
+                return getParamValue(index);
+            } else {
+                fprintf(stderr, "getParamValue : '%s' not found\n", (str == nullptr ? "NULL" : str));
+                return FAUSTFLOAT(0);
+            }
+        }
+
+        /**
+         * Set the param value.
+         *
+         * @param p - the UI parameter index
+         * @param v - the UI parameter value
+         *
+         */
+        void setParamValue(int p, FAUSTFLOAT v) { *fItems[uint(p)].fZone = v; }
+        
+        /**
+         * Set the param value.
+         *
+         * @param p - the UI parameter label/shortname/path
+         * @param v - the UI parameter value
+         *
+         */
+        void setParamValue(const char* path, FAUSTFLOAT v)
+        {
+            int index = getParamIndex(path);
+            if (index >= 0) {
+                setParamValue(index, v);
+            } else {
+                fprintf(stderr, "setParamValue : '%s' not found\n", (path == nullptr ? "NULL" : path));
+            }
+        }
+
+        double getParamRatio(int p) { return fItems[uint(p)].fConversion->faust2ui(*fItems[uint(p)].fZone); }
+        void setParamRatio(int p, double r) { *fItems[uint(p)].fZone = FAUSTFLOAT(fItems[uint(p)].fConversion->ui2faust(r)); }
+
+        double value2ratio(int p, double r)    { return fItems[uint(p)].fConversion->faust2ui(r); }
+        double ratio2value(int p, double r)    { return fItems[uint(p)].fConversion->ui2faust(r); }
+
+        /**
+         * Return the control type (kAcc, kGyr, or -1) for a given parameter.
+         *
+         * @param p - the UI parameter index
+         *
+         * @return the type
+         */
+        Type getParamType(int p)
+        {
+            if (p >= 0) {
+                if (getZoneIndex(fAcc, p, 0) != -1
+                    || getZoneIndex(fAcc, p, 1) != -1
+                    || getZoneIndex(fAcc, p, 2) != -1) {
+                    return kAcc;
+                } else if (getZoneIndex(fGyr, p, 0) != -1
+                           || getZoneIndex(fGyr, p, 1) != -1
+                           || getZoneIndex(fGyr, p, 2) != -1) {
+                    return kGyr;
+                }
+            }
+            return kNoType;
+        }
+
+        /**
+         * Return the Item type (kButton = 0, kCheckButton, kVSlider, kHSlider, kNumEntry, kHBargraph, kVBargraph) for a given parameter.
+         *
+         * @param p - the UI parameter index
+         *
+         * @return the Item type
+         */
+        ItemType getParamItemType(int p)
+        {
+            return fItems[uint(p)].fItemType;
+        }
+
+        /**
+         * Set a new value coming from an accelerometer, propagate it to all relevant FAUSTFLOAT* zones.
+         *
+         * @param acc - 0 for X accelerometer, 1 for Y accelerometer, 2 for Z accelerometer
+         * @param value - the new value
+         *
+         */
+        void propagateAcc(int acc, double value)
+        {
+            for (size_t i = 0; i < fAcc[acc].size(); i++) {
+                fAcc[acc][i]->update(value);
+            }
+        }
+
+        /**
+         * Used to edit accelerometer curves and mapping. Set curve and related mapping for a given UI parameter.
+         *
+         * @param p - the UI parameter index
+         * @param acc - 0 for X accelerometer, 1 for Y accelerometer, 2 for Z accelerometer (-1 means "no mapping")
+         * @param curve - between 0 and 3 (0: up, 1: down, 2: up and down, 2: down and up)
+         * @param amin - mapping 'min' point
+         * @param amid - mapping 'middle' point
+         * @param amax - mapping 'max' point
+         *
+         */
+        void setAccConverter(int p, int acc, int curve, double amin, double amid, double amax)
+        {
+            setConverter(fAcc, p, acc, curve, amin, amid, amax);
+        }
+
+        /**
+         * Used to edit gyroscope curves and mapping. Set curve and related mapping for a given UI parameter.
+         *
+         * @param p - the UI parameter index
+         * @param gyr - 0 for X gyroscope, 1 for Y gyroscope, 2 for Z gyroscope (-1 means "no mapping")
+         * @param curve - between 0 and 3 (0: up, 1: down, 2: up and down, 2: down and up)
+         * @param amin - mapping 'min' point
+         * @param amid - mapping 'middle' point
+         * @param amax - mapping 'max' point
+         *
+         */
+        void setGyrConverter(int p, int gyr, int curve, double amin, double amid, double amax)
+        {
+            setConverter(fGyr, p, gyr, curve, amin, amid, amax);
+        }
+
+        /**
+         * Used to edit accelerometer curves and mapping. Get curve and related mapping for a given UI parameter.
+         *
+         * @param p - the UI parameter index
+         * @param acc - the acc value to be retrieved (-1 means "no mapping")
+         * @param curve - the curve value to be retrieved (between 0 and 3)
+         * @param amin - the amin value to be retrieved
+         * @param amid - the amid value to be retrieved
+         * @param amax - the amax value to be retrieved
+         *
+         */
+        void getAccConverter(int p, int& acc, int& curve, double& amin, double& amid, double& amax)
+        {
+            getConverter(fAcc, p, acc, curve, amin, amid, amax);
+        }
+
+        /**
+         * Used to edit gyroscope curves and mapping. Get curve and related mapping for a given UI parameter.
+         *
+         * @param p - the UI parameter index
+         * @param gyr - the gyr value to be retrieved (-1 means "no mapping")
+         * @param curve - the curve value to be retrieved (between 0 and 3)
+         * @param amin - the amin value to be retrieved
+         * @param amid - the amid value to be retrieved
+         * @param amax - the amax value to be retrieved
+         *
+         */
+        void getGyrConverter(int p, int& gyr, int& curve, double& amin, double& amid, double& amax)
+        {
+            getConverter(fGyr, p, gyr, curve, amin, amid, amax);
+        }
+
+        /**
+         * Set a new value coming from an gyroscope, propagate it to all relevant FAUSTFLOAT* zones.
+         *
+         * @param gyr - 0 for X gyroscope, 1 for Y gyroscope, 2 for Z gyroscope
+         * @param value - the new value
+         *
+         */
+        void propagateGyr(int gyr, double value)
+        {
+            for (size_t i = 0; i < fGyr[gyr].size(); i++) {
+                fGyr[gyr][i]->update(value);
+            }
+        }
+
+        /**
+         * Get the number of FAUSTFLOAT* zones controlled with the accelerometer.
+         *
+         * @param acc - 0 for X accelerometer, 1 for Y accelerometer, 2 for Z accelerometer
+         * @return the number of zones
+         *
+         */
+        int getAccCount(int acc)
+        {
+            return (acc >= 0 && acc < 3) ? int(fAcc[acc].size()) : 0;
+        }
+
+        /**
+         * Get the number of FAUSTFLOAT* zones controlled with the gyroscope.
+         *
+         * @param gyr - 0 for X gyroscope, 1 for Y gyroscope, 2 for Z gyroscope
+         * @param the number of zones
+         *
+         */
+        int getGyrCount(int gyr)
+        {
+            return (gyr >= 0 && gyr < 3) ? int(fGyr[gyr].size()) : 0;
+        }
+
+        /**
+         * Get the requested screen color.
+         *
+         * -1 means no screen color control (no screencolor metadata found)
+         * otherwise return 0x00RRGGBB a ready to use color
+         *
+         */
+        int getScreenColor()
+        {
+            if (fHasScreenControl) {
+                int r = (fRedReader) ? fRedReader->getValue() : 0;
+                int g = (fGreenReader) ? fGreenReader->getValue() : 0;
+                int b = (fBlueReader) ? fBlueReader->getValue() : 0;
+                return (r<<16) | (g<<8) | b;
+            } else {
+                return -1;
+            }
+        }
+
+};
+
+#endif
+/**************************  END  APIUI.h **************************/
+
+// NOTE: "faust -scn name" changes the last line above to
+// #include <faust/name/name.h>
+
+//----------------------------------------------------------------------------
+//  FAUST Generated Code
+//----------------------------------------------------------------------------
+
+
+#ifndef FAUSTFLOAT
+#define FAUSTFLOAT float
+#endif 
+
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+
+#ifndef FAUSTCLASS 
+#define FAUSTCLASS stereotomonodsp
+#endif
+
+#ifdef __APPLE__ 
+#define exp10f __exp10f
+#define exp10 __exp10
+#endif
+
+#if defined(_WIN32)
+#define RESTRICT __restrict
+#else
+#define RESTRICT __restrict__
+#endif
+
+
+class stereotomonodsp : public dsp {
+       
+ private:
+       
+       int fSampleRate;
+       
+ public:
+       
+       void metadata(Meta* m) { 
+               m->declare("author", "Dominick Hing");
+               m->declare("compile_options", "-a faust2header.cpp -lang cpp -i -inpl -cn stereotomonodsp -es 1 -mcd 16 -single -ftz 0");
+               m->declare("description", "Stereo-to-Mono Faust Plugin for JackTrip");
+               m->declare("filename", "stereotomonodsp.dsp");
+               m->declare("license", "MIT Style STK-4.2");
+               m->declare("name", "stereo-to-mono");
+               m->declare("version", "1.0");
+       }
+
+       virtual int getNumInputs() {
+               return 2;
+       }
+       virtual int getNumOutputs() {
+               return 1;
+       }
+       
+       static void classInit(int /* sample_rate */) {
+       }
+       
+       virtual void instanceConstants(int sample_rate) {
+               fSampleRate = sample_rate;
+       }
+       
+       virtual void instanceResetUserInterface() {
+       }
+       
+       virtual void instanceClear() {
+       }
+       
+       virtual void init(int sample_rate) {
+               classInit(sample_rate);
+               instanceInit(sample_rate);
+       }
+       virtual void instanceInit(int sample_rate) {
+               instanceConstants(sample_rate);
+               instanceResetUserInterface();
+               instanceClear();
+       }
+       
+       virtual stereotomonodsp* clone() {
+               return new stereotomonodsp();
+       }
+       
+       virtual int getSampleRate() {
+               return fSampleRate;
+       }
+       
+       virtual void buildUserInterface(UI* ui_interface) {
+               ui_interface->openVerticalBox("stereo-to-mono");
+               ui_interface->closeBox();
+       }
+       
+       virtual void compute(int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) {
+               FAUSTFLOAT* input0 = inputs[0];
+               FAUSTFLOAT* input1 = inputs[1];
+               FAUSTFLOAT* output0 = outputs[0];
+               for (int i0 = 0; i0 < count; i0 = i0 + 1) {
+                       float fTemp0 = float(input0[i0]);
+                       float fTemp1 = float(input1[i0]);
+                       output0[i0] = FAUSTFLOAT(0.5f * (fTemp0 + fTemp1));
+               }
+       }
+
+};
+
+
+#endif