- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
+---
+Language: ObjC
+DisableFormat: true
...
-
--- /dev/null
+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
add_compile_definitions(RT_AUDIO)
set (qjacktrip_SRC ${qjacktrip_SRC}
src/RtAudioInterface.cpp
+ src/StereoToMono.cpp
)
endif ()
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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+pyyaml
+jinja2
--- /dev/null
+#!/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
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
### 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
```
=== "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"
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'
+```
# 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
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.
--- /dev/null
+<?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
+- 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:
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'
QT += networkauth
QT += qml
QT += quick
+ QT += quickcontrols2
QT += svg
QT += websockets
}
src/Meter.h \
src/Volume.h \
src/Tone.h \
+ src/StereoToMono.h \
src/AudioTester.h \
src/jacktrip_globals.h \
src/jacktrip_types.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 \
src/Regulator.cpp \
src/Reverb.cpp \
src/Meter.cpp \
+ src/StereoToMono.cpp \
src/Volume.cpp \
src/Tone.cpp \
src/AudioTester.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 \
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')
'src/Meter.cpp',
'src/Volume.cpp',
'src/Tone.cpp',
+ 'src/StereoToMono.cpp',
'src/Reverb.cpp',
'src/main.cpp',
'src/SslServer.cpp',
'src/JackTrip.h',
'src/ProcessPlugin.h',
'src/Meter.h',
+ 'src/StereoToMono.h',
'src/Volume.h',
'src/Tone.h',
'src/JackTripWorker.h',
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',
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',
]
moc_h += [
'src/gui/virtualstudio.h',
+ 'src/gui/vsInit.h',
'src/gui/vsDevice.h',
'src/gui/vsAudioInterface.h',
'src/gui/vsServerInfo.h',
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
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'
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')
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
- 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:
{
"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"
"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"
"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"
{
"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"
"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"
"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"
{
"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",
{
"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",
{
"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",
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)
, 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;
}
}
#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();
}
//*******************************************************************************
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];
}
}
//*******************************************************************************
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
{
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);
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
}
}
#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)
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);
<< " 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++) {
#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];
// 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(),
//#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++) {
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);
}
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)
// ----------------------------------------------------------------
fromBitToSampleConversion(
// use interleaved channel layout
//&mOutputPacket[(i*mSizeInBytesPerChannel) + (j*mBitResolutionMode)],
- &mOutputPacket[(j * mBitResolutionMode * mNumOutChans)
+ &mOutputPacket[(j * mBitResolutionMode * nChansOut)
+ (i * mBitResolutionMode)],
&tmp_sample[j], mBitResolutionMode);
}
#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],
fromBitToSampleConversion(
// use interleaved channel layout
//&mOutputPacket[(i*mSizeInBytesPerChannel) + (j*mBitResolutionMode)],
- &mAudioOutputPacket[(j * mBitResolutionMode * mNumOutChans)
+ &mAudioOutputPacket[(j * mBitResolutionMode * nChansOut)
+ (i * mBitResolutionMode)],
&tmp_sample[j], mBitResolutionMode);
}
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
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;
&tmp_result,
// use interleaved channel layout
//&mInputPacket[(i*mSizeInBytesPerChannel) + (j*mBitResolutionMode)],
- &mInputPacket[(j * mBitResolutionMode * mNumOutChans)
+ &mInputPacket[(j * mBitResolutionMode * nChansOut)
+ (i * mBitResolutionMode)],
mBitResolutionMode);
}
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],
&tmp_result,
// use interleaved channel layout
//&mInputPacket[(i*mSizeInBytesPerChannel) + (j*mBitResolutionMode)],
- &mAudioInputPacket[(j * mBitResolutionMode * mNumInChans)
+ &mAudioInputPacket[(j * mBitResolutionMode * nChansIn)
+ (i * mBitResolutionMode)],
mBitResolutionMode);
}
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 "
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 "
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) {
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);
}
}
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";
std::string AudioInterface::getDevicesErrorHelpUrl()
{
return mErrorHelpUrl;
-}
\ No newline at end of file
+}
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();
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; }
//--------------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; }
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*>
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);
#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,
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
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;
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;
}
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;
}
}
file.close();
}
+ return errorFree;
}
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:
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,
QHash<QString, QString> m_timesTable;
QString m_authFileName;
+ bool m_monitorChanges;
QFileSystemWatcher m_authFileWatcher;
};
//*******************************************************************************
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);
}
//*******************************************************************************
#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
{
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:
// 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
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
#include <QThread>
#include <QTimer>
#include <QtEndian>
+#include <QtGlobal>
#include <cstdlib>
#include <iomanip>
#include <iostream>
//*******************************************************************************
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
, mDataProtocol(DataProtocolType)
, mPacketHeaderType(PacketHeaderType)
, mAudiointerfaceMode(JackTrip::JACK)
+ , mBaseAudioChanIn(BaseChanIn)
, mNumAudioChansIn(NumChansIn)
+ , mBaseAudioChanOut(BaseChanOut)
, mNumAudioChansOut(NumChansOut)
+ , mInputMixMode(InputMixMode)
#ifdef WAIR // WAIR
, mNumNetRevChans(NumNetRevChans)
#endif // endwhere
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
#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);
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);
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
// 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)
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);
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);
}
//*******************************************************************************
-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;
}
*/
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
/// \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
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)
// 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;
}
}
#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__
#include "Meter.h"
-#include <QVector>
-
#include "jacktrip_types.h"
//*******************************************************************************
}
/* 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;
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];
}
}
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
+}
#include <QObject>
#include <QTimer>
-#include <QVector>
#include <iostream>
#include <vector>
delete meterP[i];
}
meterP.clear();
+ if (mValues) {
+ delete mValues;
+ }
+ if (mOutValues) {
+ delete mOutValues;
+ }
+ if (mBuffer) {
+ delete mBuffer;
+ }
}
void init(int samplingRate) override;
void updateNumChannels(int nChansIn, int nChansOut) override;
private:
+ void setupValues();
+
float fs;
int mNumChannels;
float threshold = -80.0;
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
{
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
}
}
pushStat->tick();
- double adjustAuto = pushStat->calcAuto(mAutoHeadroom, mFPPdurMsec);
+ double adjustAuto =
+ pushStat->calcAuto(mAutoHeadroom, mFPPdurMsec, mPeerFPPdurMsec);
// qDebug() << adjustAuto;
if (mAuto && (pushStat->lastTime > AutoInitDur))
mMsecTolerance = adjustAuto;
//*******************************************************************************
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);
}
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);
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;
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;
};
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;
int mModSeqNumPeer;
double mAutoHeadroom;
double mFPPdurMsec;
+ double mPeerFPPdurMsec;
void changeGlobal(double);
void changeGlobal_2(int);
void changeGlobal_3(int);
#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;
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();
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) {
}
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
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"
std::cout << e.getMessage() << '\n' << std::endl;
throw std::runtime_error(e.getMessage());
}
-
- // Setup parent class
- AudioInterface::setup(verbose);
}
//*******************************************************************************
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);
}
//*******************************************************************************
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 {
}
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 != "") {
categories->append(QStringLiteral(""));
#endif
}
+ if (channels != NULL) {
+ if (isInput) {
+ channels->append(defaultDeviceInfo.inputChannels);
+ } else {
+ channels->append(defaultDeviceInfo.outputChannels);
+ }
+ }
}
std::vector<RtAudio::Api> apis;
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) {
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;
*index = -1;
*api = "";
return;
-}
\ No newline at end of file
+}
#include <QQueue>
#include "AudioInterface.h"
+#include "StereoToMono.h"
#include "jacktrip_globals.h"
class JackTrip; // Forward declaration
{
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();
/// \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);
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__
#include "Settings.h"
#include "LoopBack.h"
-//#include "NetKS.h"
+// #include "NetKS.h"
#include "Effects.h"
#ifdef WAIR // wair
#include "ap8x2.dsp.h"
#endif // endwhere
-//#include "JackTripWorker.h"
+// #include "JackTripWorker.h"
#include <getopt.h> // for command line parsing
#include <cassert>
#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;
OPT_LISTDEVICES,
OPT_AUDIODEVICE,
OPT_AUDIOINPUTDEVICE,
- OPT_AUDIOOUTPUTDEVICE
+ OPT_AUDIOOUTPUTDEVICE,
+ OPT_GUI,
+ OPT_DEEPLINK
};
//*******************************************************************************
// 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);
}
{"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,
{"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
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
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)) {
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
//-------------------------------------------------------
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
//-------------------------------------------------------
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':
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);
case 'G': // IO Stat log file
//-------------------------------------------------------
{
+ mGuiIgnoresArguments = true;
+ if (mGuiEnabled) {
+ break;
+ }
std::ofstream* outStream = new std::ofstream(optarg);
if (!outStream->is_open()) {
printUsage();
}
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) {
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);
}
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);
}
case 'f': { // --effects (-f) effectsSpecArg
//-------------------------------------------------------
+ mGuiIgnoresArguments = true;
+ if (mGuiEnabled) {
+ break;
+ }
char cmd[]{"--effects (-f)"};
int returnCode = mEffects.parseEffectsOptArg(cmd, optarg);
if (returnCode > 1) {
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);
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");
break;
}
}
+ }
// Warn user if undefined options where entered
//----------------------------------------------------------------------------
}
// 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) {
"(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;
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"
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
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
jackTrip->setDeviceID(mDeviceID);
}
- // Change default Buffer Size
- if (mChangeDefaultBS) {
- jackTrip->setAudioBufferSizeInSamples(mAudioBufferSize);
- }
-
// Set device names
jackTrip->setInputDevice(mInputDeviceName);
jackTrip->setOutputDevice(mOutputDeviceName);
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;
+ }
+}
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)
{
}
#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;
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;
--- /dev/null
+//*****************************************************************
+/*
+ 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
--- /dev/null
+//*****************************************************************
+/*
+ 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
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");
}
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) {
}
}
#else
-
+
// OLD CODE WITHOUT REDUNDANCY----------------------------------------------------
/*
// This is blocking until we get a packet...
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;
--- /dev/null
+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)
+ ? ` <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
import QtQuick 2.12
import QtQuick.Controls 2.12
-import QtGraphicalEffects 1.12
Item {
width: parent.width; height: parent.height
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;
flagImage: bannerURL ? bannerURL : flag
studioName: name
publicStudio: isPublic
- manageable: isManageable
+ admin: isAdmin
available: canConnect
connected: false
studioId: id ? id : ""
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
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"
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"
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 : ""
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
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
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
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
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)
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
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
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
}
}
+ 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)
+ ? ` <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
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
anchors.top: waitingText0.bottom
anchors.topMargin: 16 * virtualstudio.uiScale
anchors.bottomMargin: 16 * virtualstudio.uiScale
- visible: parent.isManageable
+ visible: parent.isAdmin
height: 64 * virtualstudio.uiScale
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
import QtQuick 2.12
import QtQuick.Controls 2.12
-import QtGraphicalEffects 1.12
Item {
width: parent.width; height: parent.height
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 {
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
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
import QtQuick 2.12
import QtQuick.Controls 2.12
-import QtGraphicalEffects 1.12
Item {
width: parent.width; height: parent.height
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"
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
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
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"
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
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
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
import QtQuick 2.12
import QtQuick.Controls 2.12
+import QtGraphicalEffects 1.12
Item {
width: parent.width; height: parent.height
color: backgroundColour
}
- property int fontBig: 28
+ property int fontBig: 20
property int fontMedium: 13
property int fontSmall: 11
property int fontExtraSmall: 8
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"
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 {
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)
color: textColour
}
}
-
+
background: Rectangle {
width: parent.width
}
}
- 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)
- ? ` <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
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 {
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
}
onClicked: {
virtualstudio.windowState = "browse";
- inputCombo.currentIndex = virtualstudio.previousInput;
- outputCombo.currentIndex = virtualstudio.previousOutput;
+ inputCurrIndex = virtualstudio.previousInput;
+ outputCurrIndex = virtualstudio.previousOutput;
virtualstudio.revertSettings()
}
anchors.verticalCenter: parent.verticalCenter
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
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"
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
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
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
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
}
}
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") {
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
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
}
}
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
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
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
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)
- ? ` <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 {
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() }
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
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
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
}
}
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"
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
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"
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
}
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
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 }
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
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
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
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
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)
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
}
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
}
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
}
}
-<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
--- /dev/null
+<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
--- /dev/null
+<?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
-<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
-<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
#include <QFileDialog>
#include <QHostAddress>
#include <QMessageBox>
+#include <QProcess>
#include <QSettings>
#include <QVector>
#include <cstdlib>
#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))
, m_jackTripRunning(false)
, m_isExiting(false)
, m_exitSent(false)
- , m_hasIPv4Reply(false)
- , m_argc(argc)
, m_hideWarning(false)
{
m_ui->setupUi(this);
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.
}
#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));
// 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);
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;
settings.setValue(QStringLiteral("UsingFallback"), false);
}
settings.endGroup();
-#endif // PSI
#else // RT_AUDIO
QMessageBox msgBox;
msgBox.setText(
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;
}
}
} 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();
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)
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"));
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,
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);
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) {
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();
}
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;
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()) {
}
}
-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
}
}
-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
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);
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();
}
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(
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());
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());
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()
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();
}
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));
}
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.)
#include <QTemporaryFile>
#include "../JackTrip.h"
+#include "../Settings.h"
#include "../UdpHubListener.h"
#include "messageDialog.h"
#include "vuMeter.h"
Q_OBJECT
public:
- explicit QJackTrip(int argc = 0, bool suppressCommandlineWarning = false,
+ explicit QJackTrip(Settings* settings, bool suppressCommandlineWarning = false,
QWidget* parent = nullptr);
~QJackTrip() override;
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
void enableUi(bool enabled);
void advancedOptionsForHubServer(bool isHubServer);
void migrateSettings();
- void loadSettings();
+ void loadSettings(Settings* cliSettings = nullptr);
void saveSettings();
#ifdef RT_AUDIO
QString commandLineFromCurrentOptions();
void showCommandLineMessageBox();
+ JackTrip::hubConnectionModeT hubModeFromPatchType(patchTypeT patchType);
+
QScopedPointer<Ui::QJackTrip> m_ui;
QScopedPointer<UdpHubListener> m_udpHub;
QScopedPointer<JackTrip> m_jackTrip;
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;
<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>
</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 &connection</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QCheckBox" name="disconnectScriptCheckBox">
+ <property name="text">
+ <string>Execute script on &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">
<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"/>
<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>
--- /dev/null
+<?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
--- /dev/null
+<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
-<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
/**
* \file virtualstudio.cpp
- * \author Aaron Wyatt
+ * \author Matt Horton, based on code by Aaron Wyatt
* \date March 2022
*/
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();
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();
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);
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(
m_view.requestActivate(); // Raise to top
}
+bool VirtualStudio::vsModeActive()
+{
+ return m_vsModeActive;
+}
+
bool VirtualStudio::showFirstRun()
{
return m_showFirstRun;
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
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
}
{
#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
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
}
if (!m_standardWindow.isNull()) {
m_view.hide();
m_standardWindow->show();
+ m_vsModeActive = false;
}
QSettings settings;
settings.setValue(QStringLiteral("UiMode"), QJackTrip::STANDARD);
(*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);
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(""));
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()
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);
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;
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
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 {
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);
&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);
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)
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();
return;
}
-void VirtualStudio::updatedInputVuMeasurements(const QVector<float>& valuesInDecibels)
+void VirtualStudio::updatedInputVuMeasurements(const float* valuesInDecibels,
+ int numChannels)
{
QJsonArray uiValues;
bool detectedClip = false;
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]);
}
}
}
+#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;
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]);
}
detectedClip = true;
}
}
-
+#ifdef RT_AUDIO
+ if (m_numOutputChannels == 1) {
+ uiValues[1] = uiValues[0];
+ }
+#endif
m_view.engine()->rootContext()->setContextProperty(QStringLiteral("outputMeterModel"),
QVariant::fromValue(uiValues));
}
} else if (stage == QAbstractOAuth2::Stage::RequestingAuthorization) {
parameters->insert(QStringLiteral("audience"),
QStringLiteral("https://api.jacktrip.org"));
- parameters->insert(QStringLiteral("prompt"), QStringLiteral("login"));
}
});
"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, "
+ " <a href=\"https://app.jacktrip.org/studios/create\">click "
+ "here</a> to create your first studio.</p>\n"
"</div>\n"));
m_authenticator->setReplyHandler(replyHandler);
connect(m_authenticator.data(), &QOAuth2AuthorizationCodeFlow::granted, this,
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;
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(
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);
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);
&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);
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();
}
#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();
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();
#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;
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)));
}
}
delete m_inputMeter;
delete m_outputMeter;
+ delete m_outputVolumePlugin;
+ delete m_inputVolumePlugin;
delete m_inputTestMeter;
delete m_studioSocket;
/**
* \file virtualstudio.h
- * \author Aaron Wyatt
+ * \author Matt Horton, based on code by Aaron Wyatt
* \date March 2022
*/
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
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
void setStandardWindow(QSharedPointer<QJackTrip> window);
void show();
void raiseToTop();
+ bool vsModeActive();
bool showFirstRun();
void setShowFirstRun(bool show);
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();
void logout();
void refreshStudios(int index, bool signalRefresh = false);
void refreshDevices();
+ void validateDevicesState();
+ void validateInputDevicesState();
+ void validateOutputDevicesState();
void playOutputAudio();
void revertSettings();
void applySettings();
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);
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();
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;
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)
{
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";
}
}
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"));
#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();
emit errorToProcess(QString::fromUtf8(e.what()));
}
m_audioInterface.clear();
- m_numAudioChansIn = gDefaultNumInChannels;
- m_numAudioChansOut = gDefaultNumOutChannels;
- m_deviceID = gDefaultDeviceID;
+ m_deviceID = gDefaultDeviceID;
}
}
}
}
-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)
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)
{
{
// 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());
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,
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);
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);
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();
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
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;
}
// 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) {
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);
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;
+}
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);
private:
void registerJTAsDevice();
bool enabled();
+ int selectBindPort();
QString randomString(int stringLength);
VsPinger* m_pinger = NULL;
--- /dev/null
+//*****************************************************************
+/*
+ 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);
+ }
+ }
+}
--- /dev/null
+//*****************************************************************
+/*
+ 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__
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
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)
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;
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;
"loopback": true,
"stereo": true,
"type": "JackTrip",
- "managed": true,
"size": "c5.large",
"mixBranch": "main",
"mixCode": "SimpleMix(~maxClients).masterVolume_(1).connect.start;",
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)
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);
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
#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
#ifndef NO_GUI
#include <QApplication>
-#include <QCommandLineParser>
#ifndef NO_UPDATER
#include "dblsqd/feed.h"
#ifndef NO_VS
#include <QDebug>
#include <QFile>
-#include <QLocalServer>
-#include <QLocalSocket>
#include <QQmlEngine>
#include <QQuickView>
#include <QSettings>
#include "JTApplication.h"
#include "gui/virtualstudio.h"
+#include "gui/vsInit.h"
#include "gui/vsQmlClipboard.h"
#include "gui/vsUrlHandler.h"
#endif
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
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);
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);
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
} 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);
--- /dev/null
+/* ------------------------------------------------------------
+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