diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..aac5192 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +liberapay: gdb +custom: 'https://www.paypal.me/gdbau' diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md new file mode 100644 index 0000000..f12d9a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue.md @@ -0,0 +1,54 @@ +--- +name: Issue +about: Open an issue for help, to report a bug, or request a feature +title: '' +labels: '' +assignees: '' + +--- + +# `alsa-scarlett-gui` Issue Template + +Thank you for taking the time to contribute to the `alsa-scarlett-gui` project. Before you submit your issue, please ensure you have checked the FAQ and provide the necessary information below. + +## Confirmation +- [ ] I confirm that I have read the [FAQ](https://github.com/geoffreybennett/alsa-scarlett-gui/blob/master/FAQ.md). + +## Issue Category +Please select the category that best describes your issue: +- [ ] Help Request +- [ ] Bug Report +- [ ] Feature Request + +## Environment Details +Please provide the following details about your environment. + +### Linux Distribution and Version +(paste output from `cat /etc/redhat-release` or `cat /etc/lsb_release` here) +- Distribution: +- Version: + +### Kernel Version +(paste output from `uname -r` here) +- Kernel version: + +### Kernel Messages +(paste output from `dmesg | grep -A 5 -B 5 -i focusrite` here) + +### Focusrite Interface Series and Model +(maybe shown in kernel messages, or paste output from `lsusb -d1235:` if unsure) +- Series (e.g., Scarlett 2nd/3rd/4th Gen, Clarett USB, Clarett+): +- Model (e.g., Solo, 2i2, 4i4, etc.): + +### Audio System +(use `ps aux | grep -E "pulseaudio|jackd|pipewire"` to check) +- [ ] PulseAudio +- [ ] JACK +- [ ] PipeWire + +## Issue Description +Please provide a detailed description of the issue or feature request, including steps to reproduce (if applicable), expected behavior, and actual behavior: + +--- + +Thank you for helping improve `alsa-scarlett-gui`! diff --git a/.github/workflows/build-debian-package.yml b/.github/workflows/build-debian-package.yml index 336d281..550fea6 100644 --- a/.github/workflows/build-debian-package.yml +++ b/.github/workflows/build-debian-package.yml @@ -19,7 +19,7 @@ jobs: - name: Install build dependencies run: | sudo apt -y update - sudo apt -y install git make gcc libgtk-4-dev libasound2-dev + sudo apt -y install git make gcc libgtk-4-dev libasound2-dev libssl-dev - name: Build from sources run: | @@ -33,7 +33,7 @@ jobs: ${{ github.workspace }}/deb-workspace/usr/share/doc/${{ env.APP_NAME }}-${{ env.APP_VERSION }} cp src/alsa-scarlett-gui ${{ github.workspace }}/deb-workspace/usr/bin/ cp src/vu.b4.alsa-scarlett-gui.desktop ${{ github.workspace }}/deb-workspace/usr/share/applications/ - cp src/img/alsa-scarlett-gui.png ${{ github.workspace }}/deb-workspace/usr/share/icons/hicolor/256x256/apps/ + cp src/img/vu.b4.alsa-scarlett-gui.png ${{ github.workspace }}/deb-workspace/usr/share/icons/hicolor/256x256/apps/ cp -r *.md img demo ${{ github.workspace }}/deb-workspace/usr/share/doc/${{ env.APP_NAME }}-${{ env.APP_VERSION }}/ - name: Build debian package diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000..9b3c198 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,88 @@ +# FAQ for the Scarlett2 Mixer Driver and `alsa-scarlett-gui` + +## What is this? + +The Scarlett2 Protocol Driver (also known as the Scarlett2 Mixer +Driver) is a part of the Linux kernel, enhancing the ALSA kernel +driver with additional controls for Focusrite Scarlett, Clarett, and +Vocaster interfaces. + +To check if your kernel is already up-to-date, and how to upgrade if +not, see the [Control Panel Installation Prerequisites — Linux +Kernel](https://github.com/geoffreybennett/alsa-scarlett-gui/blob/master/INSTALL.md). + +`alsa-scarlett-gui` is an easy-to-use application to adjust those +controls. + +## Do I need the driver for my Focusrite interface? + +In order to get audio working? No. Focusrite USB interfaces are +“plug-and-play” — they are USB Audio Class Compliant, meaning they +work out-of-the-box with the standard ALSA USB audio driver (to get +full functionality on Scarlett 3rd/4th Gen/Vocaster interfaces, first +deactivate MSD mode by holding down the 48V button while powering it +on). + +## MSD Mode? + +“MSD Mode” is the “Mass Storage Device Mode” that the Scarlett 3rd and +4th Gen interfaces ship in. + +If MSD Mode is enabled, you need to disable it and restart your +interface to get access to its full functionality. + +When you plug the interface in, there’ll be a tiny read-only virtual +disk that has a link to the Focusrite product registration page; until +you turn off MSD Mode not all features of the interface will be +available. + +You can turn off MSD Mode by holding down the 48V button while +powering on the interface, or by clicking the button in +`alsa-scarlett-gui` and rebooting it. + +## What is the purpose of the driver if it’s not needed for audio? + +This driver is for users who want more control over their interface. +It allows for detailed manipulation of internal audio routing and +settings specific to Scarlett, Clarett, and Vocaster devices, beyond +the basic audio I/O functionality. Also, being able to monitor the +audio levels seen by the interface is really useful. + +## What interfaces are supported? + +- All Scarlett 2nd Gen interfaces with software controls (there are no + software controls on the 2nd Gen Solo and 2i2, so the mixer driver + is irrelevant). + +- All Scarlett 3rd Gen interfaces. + +- Scarlett 4th Gen Solo, 2i2, and 4i4. + +- All Clarett USB and Clarett+ interfaces. + +- Vocaster One and Vocaster Two. + +## Where are the options to set the sample rate and buffer size? + +It’s important to note that the Scarlett2 driver and +`alsa-scarlett-gui` have nothing to do with audio input/output to and +from the device. This task is managed by the generic part of the ALSA +USB soundcard driver. + +Audio settings like the sample rate and buffer size are chosen by the +application which is using the soundcard. In most cases, that is a +sound server such as PulseAudio, JACK, or PipeWire. + +Note that not all features are available at higher sample rates; refer +to the user manual of your interface for more information. + +## Help?! + +For help with the driver: +https://github.com/geoffreybennett/scarlett-gen2/issues + +For help with `alsa-scarlett-gui`: +https://github.com/geoffreybennett/alsa-scarlett-gui/issues + +For general Linux audio help: +https://linuxmusicians.com diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100644 index 8d06eac..0000000 --- a/INSTALL.md +++ /dev/null @@ -1,158 +0,0 @@ -# ALSA Scarlett Gen 2/3 Control Panel Installation - -## Prerequisites - -Linux Kernel with the ALSA Scarlett2 Protocol Driver. - -- Use at least version 5.14 for Scarlett Gen 3 support and bug fixes - for the Gen 2 support. -- For Clarett+ 8Pre support, you need 6.1. -- For the other Clarett USB and Clarett+ models, you need 6.7. -- For the level meters to work, you need 6.7. - -If you don't have 6.7, you can get the driver from here and build it -for your current kernel: - -https://github.com/geoffreybennett/scarlett-gen2/releases/tag/v6.5.11c1 - -## Enabling the Driver - -As of Linux 6.7 the driver is enabled by default and you can skip this -section. - -If you're running a kernel before 6.7, the driver needs to be enabled -at module load time with the `device_setup=1` option to -insmod/modprobe. Create a file /etc/modprobe.d/scarlett.conf -containing the appropriate line for your device: - -Scarlett Gen 2: - -- 6i6: `options snd_usb_audio vid=0x1235 pid=0x8203 device_setup=1` -- 18i8: `options snd_usb_audio vid=0x1235 pid=0x8204 device_setup=1` -- 18i20: `options snd_usb_audio vid=0x1235 pid=0x8201 device_setup=1` - -Scarlett Gen 3: - -- Solo: `options snd_usb_audio vid=0x1235 pid=0x8211 device_setup=1` -- 2i2: `options snd_usb_audio vid=0x1235 pid=0x8210 device_setup=1` -- 4i4: `options snd_usb_audio vid=0x1235 pid=0x8212 device_setup=1` -- 8i6: `options snd_usb_audio vid=0x1235 pid=0x8213 device_setup=1` -- 18i8: `options snd_usb_audio vid=0x1235 pid=0x8214 device_setup=1` -- 18i20: `options snd_usb_audio vid=0x1235 pid=0x8215 device_setup=1` - -Clarett+: - -- 8Pre: `options snd_usb_audio vid=0x1235 pid=0x820c device_setup=1` - -Or you can use a sledgehammer: -``` -options snd_usb_audio device_setup=1,1,1,1 -``` -to pass that option to the first 4 USB audio devices. - -To see if the driver is present and enabled: `dmesg | grep -i -A 5 -B -5 focusrite` should display information like: - -``` -New USB device found, idVendor=1235, idProduct=8215, bcdDevice= 6.0b -Product: Scarlett 18i20 USB -Focusrite Scarlett Gen 2/3 Mixer Driver enabled pid=0x8215 -``` - -If the driver is disabled you’ll see a message like: - -``` -Focusrite Scarlett Gen 2/3 Mixer Driver disabled; use options -snd_usb_audio vid=0x1235 pid=0x8215 device_setup=1 to enable and -report any issues to g@b4.vu", -``` - -## Building and Running - -On Fedora, the packages `alsa-lib-devel` and `gtk4-devel` need to be -installed: - -``` -sudo dnf -y install alsa-lib-devel gtk4-devel -``` - -On Ubuntu 22.04: - -``` -sudo apt -y install git make gcc libgtk-4-dev libasound2-dev -``` - -To download from github: - -``` -git clone https://github.com/geoffreybennett/alsa-scarlett-gui -cd alsa-scarlett-gui -``` - -To build: - -``` -cd src -make -j4 -``` - -To run: - -``` -./alsa-scarlett-gui -``` - -You can install it into `/usr/local` (binary, desktop file, and icon) -with: - -``` -sudo make install -``` - -And uninstall with: - -``` -sudo make uninstall -``` - -Continue on to reading [USAGE.md](USAGE.md) for usage information and -known issues. - -## Flatpak - -With Flatpak, in any distro: - -``` -flatpak-builder --user --install --force-clean flatpak-build \ - vu.b4.alsa-scarlett-gui.yml -``` - -Be sure to use `flatpak-build` as the directory where the flatpak is -built or hence you risk bundling the artifacts when comitting! - -If you get messages like these: - -``` -Failed to init: Unable to find sdk org.gnome.Sdk version 45 -Failed to init: Unable to find runtime org.gnome.Platform version 45 -``` - -Then install them: - -``` -flatpak install org.gnome.Sdk -flatpak install org.gnome.Platform -``` - -If you get: - -``` -Looking for matches… -error: No remote refs found for ‘org.gnome.Sdk’ -``` - -Then: - -``` -flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo -``` diff --git a/INTERFACES.md b/INTERFACES.md deleted file mode 100644 index 88fd3aa..0000000 --- a/INTERFACES.md +++ /dev/null @@ -1,413 +0,0 @@ -# Focusrite Scarlett Interface Features by Model - -The information here was compiled carefully and is believed accurate -but there might still be mistakes. Please independently confirm before -relying on anything here. - -## 6i6 Gen 2 - -- 6× Hardware Inputs - - Analogue In 1–2: Mic/Line/Inst In 1–2 (Pad) - - Analogue In 3–4: Line In 3–4 - - S/PDIF 1–2 - -- Hardware Input Controls - - 2× Input Gain knobs for Analogue In 1–2 - - Fixed Input Gain for Analogue In 3–4 - - Phantom Power for Analogue 1–2 linked - -- 6× Hardware Outputs - - Analogue 1: Line 1 Out (Monitor L) + Headphone 1 Left - - Analogue 2: Line 2 Out (Monitor R) + Headphone 1 Right - - Analogue 3: Line 3 Out + Headphone 2 Left - - Analogue 4: Line 4 Out + Headphone 2 Right - - S/PDIF 1–2 - -- Hardware Output Controls - - Monitor volume knob controlling Line 1+2 - - No physical control for Line 3+4 output volume - - Headphone 1 volume knob controlling Headphone 1 Volume - - Headphone 2 volume knob controlling Headphone 2 Volume - - 4× Mute + Line Out Gain controls −127dB to 0dB - - 1–2 control Line 1–2 Out and Headphone 1 - - 3–4 control Line 3–4 Out and Headphone 2 - -- Mixer: 18 input (1–18), 10 output (A–J) - - 18×10 gain controls −80dB to +6dB - - Each input assignable to any of the assignable outputs - -- 6× PCM Inputs (USB device to host) - -- 6× PCM Outputs (USB host to device) - -- Assignable outputs to the hardware outputs, mixer, and PCM Inputs: - - Off, Analogue Inputs 1–4, S/PDIF 1–2, Mix A–J, PCM Outputs 1–6 - -- Sync Status - -- Clock Source: Internal or S/PDIF - -## 18i8 Gen 2 - -- 18× Hardware Inputs - - Analogue 1–2: Mic/Line/Inst In 1–2 (Pad) - - Analogue 3–4: Mic/Line In 3–4 (Pad) - - Analogue 5–8: Line In 5–8 - - S/PDIF 1–2 - - ADAT 1–8 - -- Hardware Input Controls - - 4× Input Gain knobs for Analogue In 1–4 - - Fixed Input Gain for Analogue In 5–8 - - Phantom Power for Analogue 1–2 linked - - Phantom Power for Analogue 3–4 linked - -- 8× Hardware Outputs - - Analogue 1: Line 1 Out (Monitor L) - - Analogue 2: Line 2 Out (Monitor R) - - Analogue 3: Headphone 1 Left - - Analogue 4: Headphone 1 Right - - Analogue 5: Headphone 2 Left - - Analogue 6: Headphone 2 Right - - S/PDIF 1–2 - -- Hardware Output Controls - - Monitor volume knob controlling Line 1+2 - - Headphone 1 volume knob controlling Headphone 1 Volume - - Headphone 2 volume knob controlling Headphone 2 Volume - - 6× Mute + Line Out Gain controls −127dB to 0dB - - 1–2 control Line 1–2 Out - - 3–4 control Headphone 1 - - 5–6 control Headphone 2 - -- Mixer: 20 input (1–20), 10 output (A–J) - - 20×10 gain controls −80dB to +6dB - - Each input assignable to any of the assignable outputs - -- 20× PCM Inputs (USB device to host) - -- 8× PCM Outputs (USB host to device) - -- Assignable outputs to the hardware outputs, mixer, and PCM Inputs: - - Off, Analogue Inputs 1–8, S/PDIF 1–2, ADAT 1–8, Mix A–J, PCM - Outputs 1–8 - -- Sync Status - -- Clock Source: Internal, S/PDIF, or ADAT - -## 18i20 Gen 2 - -- 18× Hardware Inputs - - Analogue 1–2: Mic/Line/Inst In 1–2 (Pad) - - Analogue 3–8: Mic/Line In 3–8 - - S/PDIF 1–2 - - ADAT 1–8 - -- Hardware Input Controls - - 8× Input Gain knobs for Analogue In 1–8 - - Phantom Power for Analogue 1–4 linked - - Phantom Power for Analogue 5–8 linked - -- 20× Hardware Outputs - - Analogue 1: Line 1 Out (Monitor L) - - Analogue 2: Line 2 Out (Monitor R) - - Analogue 3: Line 3 Out - - Analogue 4: Line 4 Out - - Analogue 5: Line 5 Out - - Analogue 6: Line 6 Out - - Analogue 7: Line 7 Out + Headphone 1 Left - - Analogue 8: Line 8 Out + Headphone 1 Right - - Analogue 9: Line 9 Out + Headphone 2 Left - - Analogue 10: Line 10 Out + Headphone 2 Right - - S/PDIF 1–2 - - ADAT 1–8 - -- Hardware Output Controls - - For Analogue 1–8 Outputs: - - 8× SW/HW Volume Control Switch - - 8× SW Line Out Gain controls −127dB to 0dB - - 8× SW Mute - - Monitor volume knob controlling volume of Analogue 1–8 (selected - by SW/HW Volume Control Switches) - - Global Mute and Dim controlling Analogue 1–8 (enabled per-channel - if SW/HW Volume Control Switch set to HW) - - 2× Headphone volume knob controlling Headphone volume (applied in - addition to the SW/HW Volume Control) - -- Mixer: 20 input (1–20), 10 output (A–J) - - 20×10 gain controls −80dB to +6dB - - Each input assignable to any of the assignable outputs - -- 20× PCM Inputs (USB device to host) - -- 18× PCM Outputs (USB host to device) - -- Assignable outputs to the hardware outputs, mixer, and PCM Inputs: - - Off, Analogue Inputs 1–8, S/PDIF 1–2, ADAT 1–8, Mix A–J, PCM - Outputs 1–8 - -- Sync Status - -- Clock Source: Internal, S/PDIF, or ADAT - -## Solo Gen 3 - -- 2× Hardware Inputs - - Analogue In 1: Mic In (Air, Phantom Power) - - Analogue In 2: Line/Inst In - -- Hardware Input Controls - - 2× Input Gain knobs for Analogue 1–2 - - Phantom Power Persistence - -- 2× Hardware Outputs - - Analogue Out 1: Line Out Left + Headphone Left - - Analogue Out 2: Line Out Right + Headphone Right - -- Hardware Output Controls - - Monitor volume knob controls all outputs together - -- 2× PCM Inputs (USB device to host) - - Fixed to Analogue Inputs 1–2 - -- 2× PCM Outputs (USB host to device) - - Fixed to Analogue Outputs 1–2 - -- Direct Monitor: - - On: mixes Analogue 1+2 Inputs into both Analogue 1+2 Outputs - -## 2i2 Gen 3 - -- 2× Hardware Inputs - - Analogue In 1–2: Mic/Line/Inst In 1–2 (Air) - -- Hardware Input Controls - - 2× Input Gain knobs for Analogue 1–2 - - Phantom Power for Analogue 1–2 linked - - Phantom Power Persistence - -- 2× Hardware Outputs - - Analogue Out 1: Line Out Left + Headphone Left - - Analogue Out 2: Line Out Right + Headphone Right - -- Hardware Output Controls - - Monitor volume knob for Line Out Left and Right - - Headphone volume knob for Headphone - -- 2× PCM Inputs (USB device to host) - - Fixed to Analogue Inputs 1–2 - -- 2× PCM Outputs (USB host to device) - - Fixed to Analogue Outputs 1–2 - -- Direct Monitor: - - Mono: mixes both Analogue 1+2 Inputs into both Analogue 1+2 - Outputs - - Stereo: mixes Analogue 1+2 Inputs into Analogue 1+2 Outputs - respectively - -## 4i4 Gen 3 - -- 4× Hardware Inputs - - Analogue In 1–2: Mic/Line/Inst In 1–2 (Air, Pad) - - Analogue In 3–4: Line In 3–4 - -- Hardware Input Controls - - 2× Input Gain knobs for Analogue In 1–2 - - Fixed Input Gain for Analogue In 3–4 - - Phantom Power for Analogue 1–2 linked - - Phantom Power Persistence - -- 4× Hardware Outputs - - Analogue 1: Line 1 Out (Monitor L) - - Analogue 2: Line 2 Out (Monitor R) - - Analogue 3: Line 3 Out + Headphone Left - - Analogue 4: Line 4 Out + Headphone Right - -- Hardware Output Controls - - Monitor volume knob controlling Line 1+2 - - Headphone volume knob controlling Headphone volume - - No physical control for Line 3+4 output volume - - 4× Mute + Line Out Gain controls −127dB to 0dB - - 1–2 control Line 1–2 Out - - 3–4 control Line 3–4 Out and Headphone - -- Mixer: 8 input (1–8), 6 output (A–F) - - 8×6 gain controls −80dB to +6dB - - Each input assignable to any of the assignable outputs - -- 6× PCM Inputs (USB device to host) - -- 4× PCM Outputs (USB host to device) - -- Assignable outputs to the hardware outputs, mixer, and PCM Inputs: - - Off, Analogue Inputs 1–4, Mix A–F, PCM Outputs 1–4 - -- Sync Status - -## 8i6 Gen 3 - -- 8× Hardware Inputs - - Analogue 1–2: Mic/Line/Inst In 1–2 (Air, Pad) - - Analogue 3–6: Line In 3–6 - - S/PDIF 1–2 - -- Hardware Input Controls - - 2× Input Gain knobs for Analogue In 1–2 - - Fixed Input Gain for Analogue In 3–6 - - Phantom Power for Analogue 1–2 linked - - Phantom Power Persistence - -- 6× Hardware Outputs - - Analogue 1: Line 1 Out (Monitor L) + Headphone 1 Left - - Analogue 2: Line 2 Out (Monitor R) + Headphone 1 Right - - Analogue 3: Line 3 Out + Headphone 2 Left - - Analogue 4: Line 4 Out + Headphone 2 Right - - S/PDIF 1–2 - -- Hardware Output Controls - - Monitor volume knob controlling Line 1+2 - - No physical control for Line 3+4 output volume - - Headphone 1 volume knob controlling Headphone 1 Volume - - Headphone 2 volume knob controlling Headphone 2 Volume - - 4× Line Out Gain controls −127dB to 0dB - - 1–2 control Line 1–2 Out and Headphone 1 - - 3–4 control Line 3–4 Out and Headphone 2 - -- Mixer: 8 input (1–8), 8 output (A–H) - - 8×8 gain controls −80dB to +6dB - - Each input assignable to any of the assignable outputs - -- 10× PCM Inputs (USB device to host) - -- 6× PCM Outputs (USB host to device) - -- Assignable outputs to the hardware outputs, mixer, and PCM Inputs: - - Off, Analogue Inputs 1–6, S/PDIF 1–2, Mix A–H, PCM Outputs 1–6 - -- Sync Status - -- Clock Source: Internal or S/PDIF - -## 18i8 Gen 3 - -- 18× Hardware Inputs - - Analogue 1–2: Mic/Line/Inst In 1–2 (Air, Pad) - - Analogue 3–4: Mic/Line In 3–4 (Air, Pad) - - Analogue 5–8: Line In 5–8 - - S/PDIF 1–2 - - ADAT 1–8 - -- Hardware Input Controls - - 4× Input Gain knobs for Analogue In 1–4 - - Fixed Input Gain for Analogue In 5–8 - - Phantom Power for Analogue 1–2 linked - - Phantom Power for Analogue 3–4 linked - - Phantom Power Persistence - -- 10× Hardware Outputs - - Analogue 1: Line 1 Out (Monitor L) - - Analogue 2: Line 2 Out (Monitor R) - - Analogue 3: Line 3 Out (Alt Monitor L) - - Analogue 4: Line 4 Out (Alt Monitor R) - - Analogue 5: Headphone 1 Left - - Analogue 6: Headphone 1 Right - - Analogue 7: Headphone 2 Left - - Analogue 8: Headphone 2 Right - - S/PDIF 1–2 - -Note: The Headphones outputs are internally Analogue 3–6 and the rear -Line 3/4 outputs (Alt Monitor) are internally Analogue 7/8, but the -driver hides this from you. - -- Hardware Output Controls - - For Analogue 1–8 Outputs: - - 8× SW/HW Volume Control Switch - - 8× SW Line Out Gain controls −127dB to 0dB - - 8× SW Mute - - Monitor volume knob controlling volume of Analogue 1–8 (selected - by SW/HW Volume Control Switches) - - Global mute and dim controlling Analogue 1–8 (enabled per-channel - if SW/HW Volume Control Switch set to HW) - - 2× Headphone volume knob controlling Headphone volume (applied in - addition to the SW/HW Volume Control) - -- Mixer: 18 input (1–18), 10 output (A–J) - - 18×10 gain controls −80dB to +6dB - - Each input assignable to any of the assignable outputs - -- 18× PCM Inputs (USB device to host) - -- 8× PCM Outputs (USB host to device) - -- Assignable outputs to the hardware outputs, mixer, and PCM Inputs: - - Off, Analogue Inputs 1–8, S/PDIF 1–2, ADAT 1–8, Mix A–J, PCM - Outputs 1–20 - -- Speaker Switching - -- Sync Status - -- Clock Source: Internal, S/PDIF, or ADAT - -## 18i20 Gen 3 - -- 19× Hardware Inputs - - Analogue 1–2: Mic/Line/Inst In 1–2 (Air, Pad) - - Analogue 3–8: Mic/Line In 3–8 (Air, Pad) - - Analogue 9: Talkback Mic - - S/PDIF 1–2 - - ADAT 1–8 - -- Hardware Input Controls - - 8× Input Gain knobs for Analogue In 1–8 - - Phantom Power for Analogue 1–4 linked - - Phantom Power for Analogue 5–8 linked - - Phantom Power Persistence - -- 20× Hardware Outputs - - Analogue 1: Line 1 Out (Monitor L) - - Analogue 2: Line 2 Out (Monitor R) - - Analogue 3: Line 3 Out - - Analogue 4: Line 4 Out - - Analogue 5: Line 5 Out - - Analogue 6: Line 6 Out - - Analogue 7: Line 7 Out + Headphone 1 Left - - Analogue 8: Line 8 Out + Headphone 1 Right - - Analogue 9: Line 9 Out + Headphone 2 Left - - Analogue 10: Line 10 Out + Headphone 2 Right - - S/PDIF 1–2 - - ADAT 1–8 - -- Hardware Output Controls - - For Analogue 1–8 Outputs: - - 8× SW/HW Volume Control Switch - - 8× SW Line Out Gain controls −127dB to 0dB - - 8× SW Mute - - Monitor volume knob controlling volume of Analogue 1–8 (selected - by SW/HW Volume Control Switches) - - Global Mute and Dim controlling Analogue 1–8 (enabled per-channel - if SW/HW Volume Control Switch set to HW) - - 2× Headphone volume knob controlling Headphone volume (applied in - addition to the SW/HW Volume Control) - -- Mixer: 25 input (1–25), 12 output (A–L) - - 25×12 gain controls −80dB to +6dB - - Each input assignable to any of the assignable outputs - -- 20× PCM Inputs (USB device to host) - -- 20× PCM Outputs (USB host to device) - -- Assignable outputs to the hardware outputs, mixer, and PCM Inputs: - - Off, Analogue Inputs 1–8, S/PDIF 1–2, ADAT 1–8, Mix A–L, PCM - Outputs 1–8 - -- Speaker Switching - -- Talkback Mic - -- Sync Status - -- Clock Source: Internal, S/PDIF, or ADAT diff --git a/README.md b/README.md index 79ef35f..bc28f71 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,15 @@ -# ALSA Scarlett Gen 2/3 Control Panel (`alsa-scarlett-gui`) +# ALSA Scarlett2 Control Panel (`alsa-scarlett-gui`) `alsa-scarlett-gui` is a Gtk4 GUI for the ALSA controls presented by -the Linux kernel Focusrite Scarlett Gen 2/3/Clarett USB/Clarett+ Mixer -Driver. +the Linux kernel Focusrite Scarlett2 USB Protocol Mixer Driver. + +Supported interfaces: +- Scarlett 2nd Gen 6i6, 18i8, 18i20 +- Scarlett 3rd Gen Solo, 2i2, 4i4, 8i6, 18i8, 18i20 +- Scarlett 4th Gen Solo, 2i2, 4i4 +- Clarett 2Pre, 4Pre, 8Pre USB +- Clarett+ 2Pre, 4Pre, 8Pre +- Vocaster One and Vocaster Two ## About @@ -10,71 +17,53 @@ Driver. The Focusrite USB audio interfaces are class compliant meaning that they work “out of the box” on Linux as audio and MIDI interfaces -(although on Gen 3/4 you need to disable MSD mode first for full -functionality). However, except for some of the smallest models, they -have a bunch of proprietary functionality that required a kernel +(although on Gen 3/4/Vocaster you need to disable MSD mode first for +full functionality). However, except for some of the smallest models, +they have a bunch of proprietary functionality that required a kernel driver to be written specifically for those devices. -Linux kernel support (“ALSA Focusrite Scarlett Gen 2/3 Mixer Driver”) -for the proprietary functionality was first added in: -- Scarlett Gen 2: Linux 5.4 (bugs fixed in Linux 5.14) -- Scarlett Gen 3: Linux 5.14 -- Clarett+ 8Pre: Linux 6.1 -- Clarett 2Pre/4Pre/8Pre USB, Clarett+ 2Pre/4Pre: Linux 6.7 - Unfortunately, actually using this functionality used to be quite an awful experience. The existing applications like `alsamixer` and `qasmixer` become completely user-hostile with the hundreds of controls presented for the Gen 3 18i20. Even the smallest Gen 3 4i4 interface at last count had 84 ALSA controls. -Announcing the ALSA Scarlett Gen 2/3 (and Clarett USB/Clarett+!) -Control Panel! +Announcing the ALSA Scarlett2 Control Panel, now supporting Scarlett +Gen 2, 3, 4, Clarett, and Vocaster! ![Demonstration](img/demo.gif) -The GUI supports all features presented by the driver (if not, please -report a bug). - ## Documentation -Refer to [INSTALL.md](INSTALL.md) for prerequisites, how to build, -install, and run. +Refer to [INSTALL.md](docs/INSTALL.md) for prerequisites, how to +build, install, and run. -Refer to [USAGE.md](USAGE.md) for usage information and known issues. +Refer to [USAGE.md](docs/USAGE.md) for general usage information and +known issues. + +Information specific to various models: + +- [Scarlett 3rd Gen Solo and 2i2](docs/iface-small.md) + +- [Scarlett 2nd Gen 6i6+, 3rd Gen 4i4+, Clarett USB, and + Clarett+](docs/iface-large.md) + +- [Scarlett 4th Gen](docs/iface-4th-gen.md) ## Donations This program is Free Software, developed using my personal resources, over hundreds of hours. -If you like this software, please consider a donation to say thank you -as it was expensive to purchase one of each model for development and -testing! Any donation is appreciated. +If you like this software, please consider a donation to say thank +you! Any donation is appreciated. - https://liberapay.com/gdb - https://paypal.me/gdbau -## Scarlett Gen 4 Support - -Focusrite recently released 3 new "Generation 4" interfaces: Solo, -2i2, and 4i4. Thanks to all the Linux Musicians who donated so I could -purchase one of each Gen 4 interface: https://gofund.me/ae997781, -support for these is coming soon. - -The overwhelming response to the GoFundMe also got the attention of -Focusrite. They offered to send me any devices that I didn't already -have, and also said that for any future product releases, they will do -their utmost to send me devices in advance. - -## Vocaster Support - -Vocaster One and Two support will be coming once I've completed the -Scarlett 4th Gen support. - ## License -Copyright 2022-2023 Geoffrey D. Bennett +Copyright 2022-2024 Geoffrey D. Bennett 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 diff --git a/USAGE.md b/USAGE.md deleted file mode 100644 index e24992a..0000000 --- a/USAGE.md +++ /dev/null @@ -1,381 +0,0 @@ -# ALSA Scarlett Gen 2/3 Control Panel Usage - -Refer to [INSTALL.md](INSTALL.md) for prerequisites, how to build, -install, and run. - -For usage instructions, read on... - -## No interface connected - -If no interface is detected (usually because there isn’t one -connected!) you’ll see this window: - -![No Interface Connected](img/iface-none.png) - -Plug in an interface or select the menu option File → Interface -Simulation and load a demo file to make more interesting things -happen. - -## MSD (Mass Storage Device) Mode - -If MSD Mode is enabled (as it is from the factory), you need to -disable it and restart your interface to get access to its full -functionality. - -![MSD Mode](img/iface-msd.png) - -## Using on Small Interfaces - -For the small Gen 3 interfaces (Solo and 2i2), there’s just a few -buttons to control the Air, Line, Phantom Power, and Direct Monitor -settings. Mostly nothing that you can’t access from the front panel -anyway. - -![Gen 3 Small Interfaces](img/iface-small-gen3.png) - -The Line/Inst (Level), Air, and 48V controls are described below in -the Analogue Input Controls section. - -Direct Monitor sends the analogue input signals to the analogue -outputs for zero-latency monitoring. On the 2i2, you have the choice -of Mono or Stereo monitoring. Mono sends both inputs to the left and -right outputs. Stereo sends input 1 to the left, and input 2 to the -right output. - -The one control not accessible from the front panel is “Phantom Power -Persistence” (menu option View → Startup) which controls the Phantom -Power state when the interface is powered on. - -## Gen 2 6i6+, Gen 3 4i4+, Clarett USB, and Clarett+ Interfaces - -The Gen 2 6i6+, Gen 3 4i4+, and Clarett interfaces have many controls -available. The controls are split between 4 windows, 3 of which are by -default hidden. - -The main window has: -- Global Controls -- Analogue Input Controls -- Analogue Output Controls - -![Main Window](img/window-main.png) - -The View menu option on the main window lets you open three other -windows which contain the other controls: -- Routing -- Mixer -- Startup - -### Global Controls - -Global controls affect the operation of the interface as a whole. - -#### Clock Source - -Clock Source selects where the interface receives its digital clock -from. If you aren’t using S/PDIF or ADAT inputs, set this to Internal. - -#### Sync Status - -Sync Status indicates if the interface is locked to a valid digital -clock. If you aren’t using S/PDIF or ADAT inputs and the Sync Status -is Unlocked, change the Clock Source to Internal. - -#### Speaker Switching (18i8 Gen 3 and 18i20 Gen 3 only) - -Speaker Switching lets you swap between two pairs of monitoring -speakers very easily. - -When enabled (Main or Alt): - -- Line Out 1–4 Volume Control Switches are locked to HW -- Line Out 3/4 routing is saved -- Line Out 3/4 routing is set to the Line Out 1/2 routing - -When set to Main, Line outputs 3 and 4 are muted. - -When set to Alt, Line outputs 1 and 2 are muted. - -When disabled (Off): -- Global mute is activated -- Line Out 1–4 Volume Control Switches are unlocked -- Line Out 3/4 routing is restored to the saved values - -#### Talkback (18i20 Gen 3 only) - -Talkback lets you add another channel (usually the talkback mic) to a -mix with a button push, usually to talk to musicians, and without -using an additional mic channel. - -The Talkback feature has a few parts: - -- Talkback Microphone connected to Analogue Input 9 -- Talkback Disable/Enable internal switch -- Talkback Off/On physical switch -- Talkback Mix (one switch per mix) -- Mix Input 25 - -To set up the talkback feature, set Mix Input 25 to the talkback -source (usually Analogue Input 9), enable the Talkback Mix switches -for the mixes you want the talkback input to be heard on, and change -the Talkback control from Disabled to Off. Leave the Mix Input 25 gain -controls at zero (−127dB), otherwise the talkback inputs will be heard -even when talkback is disabled/off. - -Pressing the Talkback switch on the device will then lower the volume -of the other inputs on the mixes for which talkback is enabled and -unmute Mix Input 25 on those mixes. - -Talkback can also be activated by changing the Talkback control from -Off to On. - -The talkback microphone can also be used just the same as any of the -other analogue inputs and routed to a physical output, PCM input, or -mixer input. - -### Analogue Input Controls - -This is applicable to all interfaces except the Gen 2 18i20 which has -hardware-only buttons for these features. - -#### Level - -The Level buttons are used to select between Mic/Line and Instrument -level/impedance. When plugging in microphones or line-level equipment -to the input, set it to “Line”. The “Inst” setting is for instrument -with pickups such as guitars. - -#### Air (Gen 3, Clarett USB, and Clarett+ only) - -Enabling Air will transform your recordings and inspire you while -making music. - -#### Pad - -Enabling Pad engages an attenuator in the channel, giving you more -headroom for very hot signals. - -#### Phantom Power (48V) - -Gen 2 devices have a hardware button for controlling phantom power. - -Gen 3 devices have hardware and software control of phantom power. -Turning the “48V” switch on sends “Phantom Power” to the XLR -microphone input. This is required for some microphones (such as -condensor microphones), and damaging to some microphones (particularly -vintage ribbon microphones). - -On Gen 3 device, phantom power is turned off by default when the -interface is turned on. This can be changed in the startup -configuration (menu option View → Startup). - -### Analogue Output Controls - -The analogue output controls let you set the output volume (gain) on -the analogue line out and headphone outputs. All interfaces support -setting the gain and muting individual channels. - -Click and drag up/down to change the volume, or use your mouse scroll -wheel. You can also double-click on the volume dial to quickly toggle -the volume between the minimum value and 0dB. - -The bigger interfaces: Gen 2 18i20, Gen 3 18i8, and Gen 3 18i20 have a -switchable hardware/software volume control. The position of the big -volume knob on the front of the interface is indicated by the “HW” -dial in the GUI. The analogue outputs can have their volume set either -by the knob (“HW” setting of of the HW/SW button) or by the dials on -each output (“SW” setting of the HW/SW button). - -When set to HW, the mute/volume status for those channels is -controlled by the hardware volume knob and the global dim/mute -controls and the software volume dial and mute button for those -channels are disabled. - -There are “mute” and “dim” (reduce volume) buttons below the “HW” dial -which affect only the outputs with “HW” control enabled. The Gen 3 -18i8 doesn’t have physical buttons or indicator lights for these -controls, but the 18i20 devices do. - -On the other (smaller) interfaces, the big volume knob on the front of -the interface controls the volume of the Line 1 and 2 outputs. This is -in addition to the software volume control, therefore both must be -turned up in order to hear anything. The other (line 3+) analogue -outputs are only controlled by the software controls. - -The volume controls for the headphone outputs on each interface -operate in addition to any other hardware or software volume controls -for those channels. When using headphones, the volumes for those -channels would usually be set to 0dB and the actual volume controlled -with the physical headphone volume control(s). - -### Routing - -The routing window allows complete control of signal routing between -the hardware inputs/outputs, internal mixer, and PCM (USB) -inputs/outputs. - -![Routing Window](img/window-routing.png) - -To manage the routing connections: - -- Click and drag from a source to a destination or a destination to a - source to connect them. Audio from the source will then be sent to - that destination. - -- Click on a source or a destination to clear the links connected to - that source/destination. - -Note that a destination can only be connected to one source, but one -source can be connected to many destinations. If you want a -destination to receive input from more than one source, use the mixer -inputs and outputs. - -The Presets menu can be used to clear all connections, or to set up -common configurations: - -- The “Direct” preset sets up the usual configuration using the - interface as an audio interface by connecting: - - - all Hardware Inputs to PCM Inputs - - all PCM Outputs to Hardware Outputs - -- The “Preamp” preset connects all Hardware Inputs to Hardware Outputs. - -- The “Stereo Out” preset connects PCM 1 and 2 Outputs to pairs of - Hardware Outputs. - -The Direct routing configuration is the simplest most-generally-useful -configuration: - -![Direct Routing](img/routing-direct.png) - -#### Loopback - -Gen 2, Clarett USB, and Clarett+ interfaces have as many PCM Inputs as -Hardware Inputs. Gen 3 interfaces have two more PCM Inputs which -Focusrite Control uses as “Loopback” inputs. - -The “Loopback” feature advertised for Gen 3 devices is actually a -limitation of the proprietary Focusrite Control software. All devices -(except Solo/2i2) support full reassignment of the PCM Inputs, so you -can have any PCM Input as a “Loopback” or assigned to any other -source. - -#### Talkback - -The Gen 3 18i20 talkback microphone is Analogue Input 9 and can be -routed like any other source. If you want to record using it, there is -no need for the loopback hack suggested by the vendor. Just route it -to a PCM Input. - -### Mixer - -If you use the Routing window to connect Sources to Mixer Inputs and -Mixer Outputs to Destinations, then you can use the Mixer window to -set the amount of each Mixer Input that is sent to each Mixer Output -using a matrix of controls: - -![Mixer Window](img/window-mixer.png) - -Click and drag up/down on the gain controls to adjust, or use your -mouse scroll wheel. You can also double-click on the dial to quickly -toggle between the minimum value and 0dB. - -### Startup - -The Startup window is used to configure settings that only take effect -when the interface is powered on. - -![Startup Window](img/window-startup.png) - -#### Standalone - -When Standalone mode is enabled, the interface will continue to route -audio as per the previous routing and mixer settings after it has been -disconnected from a computer. By configuring the routing between the -hardware and mixer inputs and outputs appropriately, the interface can -act as a standalone preamp or mixer. - -Standalone mode is supported on all devices supported by the kernel -driver. Even the 4i4 Gen 3 (which is bus-powered) will operate in -standalone mode. - -#### Phantom Power Persistence (Gen 3 only) - -When Phantom Power Persistence is enabled, the interface will restore -the previous Phantom Power/48V setting when the interface is turned -on. For the safety of microphones which can be damaged by phantom -power, the interface defaults to having phantom power disabled when it -is turned on. - -#### MSD (Mass Storage Device) Mode (Gen 3 only) - -When MSD Mode is enabled (as it is from the factory), the interface -has reduced functionality. You’ll want to have this disabled. On the -other hand, when MSD Mode is enabled, the interface presents itself as -a Mass Storage Device (like a USB stick), containing a link to the -Focusrite web site encouraging you to register your product and -download the proprietary drivers which can’t be used on Linux. - -By default, once MSD Mode is disabled, the control for it is hidden. -If for some reason you want to re-enable MSD Mode, you can set the -`device_setup` option to 3 to get the control back. - -## Load/Save Configuration - -The entire state of the interface can be loaded and saved using the -File → Load Configuration and File → Save Configuration menu options. - -Internally, this uses `alsactl`: - -- Load: `alsactl restore USB -f ` -- Save: `alsactl store USB -f ` - -The saved state files can be used to simulate an interface if you -don’t have one attached. The `demo` directory in the distribution -contains a sample file for every supported model. - -## Interface Simulation Mode - -The GUI can load an `alsactl` state file saved from a real interface -and display a GUI as if the corresponding interface was connected. - -This is useful if you don’t have an interface connected and want to -try, develop, or debug the GUI. - -Either specify the `.state` filename on the command line or select the -menu option File → Interface Simulation to load. - -## Known Bugs/Issues - -- The linear-dB scale of the volume controls doesn’t work well. Lower - volumes (e.g. below −30dB) don’t need as much fine control as higher - volumes. - -- Can’t select (focus) the gain/volume controls or use a keyboard to - adjust them. - -- Level meters don’t work if you're not running the driver from Linux - 6.7. - -- Load/Save uses `alsactl` which will be confused if the ALSA - interface name (e.g. `USB`) changes. - -- Load/Save is not implemented for simulated interfaces. - -- Lots of “couldn't find weak ref” warnings are emitted when loading a - state file for simulation. - -- The read-only status of controls in interface simulation mode does - not change when the HW/SW button is clicked. - -- When there’s more than one main window open, closing one of them - doesn’t free and close everything related to that card. - -- There is no facility to group channels into stereo pairs (needs - kernel support to save this information in the interface). - -- There is no facility to give channels custom names (needs kernel - support to save this information in the interface). - -- No keyboard accelerators (e.g. Ctrl-Q to quit) have been - implemented. diff --git a/demo/Scarlett Gen 4 2i2.state b/demo/Scarlett Gen 4 2i2.state new file mode 100644 index 0000000..fdd272d --- /dev/null +++ b/demo/Scarlett Gen 4 2i2.state @@ -0,0 +1,997 @@ +state.Gen { + control.1 { + iface PCM + name 'Playback Channel Map' + value.0 0 + value.1 0 + comment { + access read + type INTEGER + count 2 + range '0 - 36' + } + } + control.2 { + iface PCM + name 'Capture Channel Map' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + comment { + access read + type INTEGER + count 4 + range '0 - 36' + } + } + control.3 { + iface CARD + name 'USB Internal Validity' + value true + comment { + access read + type BOOLEAN + count 1 + } + } + control.4 { + iface CARD + name 'Firmware Version' + value 2115 + comment { + access read + type INTEGER + count 1 + range '0 - 0' + } + } + control.5 { + iface CARD + name 'Minimum Firmware Version' + value 2115 + comment { + access read + type INTEGER + count 1 + range '0 - 0' + } + } + control.6 { + iface MIXER + name 'MSD Mode Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.7 { + iface MIXER + name 'Line In 1 Level Capture Enum' + value Line + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Line + item.1 Inst + } + } + control.8 { + iface MIXER + name 'Line In 2 Level Capture Enum' + value Line + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Line + item.1 Inst + } + } + control.9 { + iface MIXER + name 'Line In 1 Air Capture Enum' + value Off + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 Presence + item.2 'Presence + Drive' + } + } + control.10 { + iface MIXER + name 'Line In 2 Air Capture Enum' + value Off + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 Presence + item.2 'Presence + Drive' + } + } + control.11 { + iface MIXER + name 'Line In 1-2 Phantom Power Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.12 { + iface MIXER + name 'Input Select Capture Enum' + value 'Input 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 'Input 1' + item.1 'Input 2' + } + } + control.13 { + iface MIXER + name 'Line In 1 Gain Capture Volume' + value 9 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 70 (step 1)' + dbmin -7000 + dbmax 0 + dbvalue.0 -6100 + } + } + control.14 { + iface MIXER + name 'Line In 1 Autogain Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.15 { + iface MIXER + name 'Line In 1 Autogain Status Capture Enum' + value Stopped + comment { + access read + type ENUMERATED + count 1 + item.0 Stopped + item.1 Running + item.2 Failed + item.3 Cancelled + item.4 Unknown + } + } + control.16 { + iface MIXER + name 'Line In 1 Safe Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.17 { + iface MIXER + name 'Line In 1-2 Link Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.18 { + iface MIXER + name 'Line In 2 Gain Capture Volume' + value 9 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 70 (step 1)' + dbmin -7000 + dbmax 0 + dbvalue.0 -6100 + } + } + control.19 { + iface MIXER + name 'Line In 2 Autogain Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.20 { + iface MIXER + name 'Line In 2 Autogain Status Capture Enum' + value Stopped + comment { + access read + type ENUMERATED + count 1 + item.0 Stopped + item.1 Running + item.2 Failed + item.3 Cancelled + item.4 Unknown + } + } + control.21 { + iface MIXER + name 'Line In 2 Safe Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.22 { + iface MIXER + name 'Analogue Output 01 Playback Enum' + value 'Mix A' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'DSP 1' + item.8 'DSP 2' + item.9 'PCM 1' + item.10 'PCM 2' + } + } + control.23 { + iface MIXER + name 'Analogue Output 02 Playback Enum' + value 'Mix B' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'DSP 1' + item.8 'DSP 2' + item.9 'PCM 1' + item.10 'PCM 2' + } + } + control.24 { + iface MIXER + name 'Mixer Input 01 Capture Enum' + value 'PCM 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'DSP 1' + item.8 'DSP 2' + item.9 'PCM 1' + item.10 'PCM 2' + } + } + control.25 { + iface MIXER + name 'Mixer Input 02 Capture Enum' + value 'PCM 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'DSP 1' + item.8 'DSP 2' + item.9 'PCM 1' + item.10 'PCM 2' + } + } + control.26 { + iface MIXER + name 'Mixer Input 03 Capture Enum' + value 'DSP 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'DSP 1' + item.8 'DSP 2' + item.9 'PCM 1' + item.10 'PCM 2' + } + } + control.27 { + iface MIXER + name 'Mixer Input 04 Capture Enum' + value 'DSP 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'DSP 1' + item.8 'DSP 2' + item.9 'PCM 1' + item.10 'PCM 2' + } + } + control.28 { + iface MIXER + name 'DSP Input 1 Capture Enum' + value 'Analogue 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'DSP 1' + item.8 'DSP 2' + item.9 'PCM 1' + item.10 'PCM 2' + } + } + control.29 { + iface MIXER + name 'DSP Input 2 Capture Enum' + value 'Analogue 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'DSP 1' + item.8 'DSP 2' + item.9 'PCM 1' + item.10 'PCM 2' + } + } + control.30 { + iface MIXER + name 'PCM 01 Capture Enum' + value 'DSP 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'DSP 1' + item.8 'DSP 2' + item.9 'PCM 1' + item.10 'PCM 2' + } + } + control.31 { + iface MIXER + name 'PCM 02 Capture Enum' + value 'DSP 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'DSP 1' + item.8 'DSP 2' + item.9 'PCM 1' + item.10 'PCM 2' + } + } + control.32 { + iface MIXER + name 'PCM 03 Capture Enum' + value 'Mix C' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'DSP 1' + item.8 'DSP 2' + item.9 'PCM 1' + item.10 'PCM 2' + } + } + control.33 { + iface MIXER + name 'PCM 04 Capture Enum' + value 'Mix D' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'DSP 1' + item.8 'DSP 2' + item.9 'PCM 1' + item.10 'PCM 2' + } + } + control.34 { + iface MIXER + name 'Mix A Input 01 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.35 { + iface MIXER + name 'Mix A Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.36 { + iface MIXER + name 'Mix A Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.37 { + iface MIXER + name 'Mix A Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.38 { + iface MIXER + name 'Mix B Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.39 { + iface MIXER + name 'Mix B Input 02 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.40 { + iface MIXER + name 'Mix B Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.41 { + iface MIXER + name 'Mix B Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.42 { + iface MIXER + name 'Mix C Input 01 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.43 { + iface MIXER + name 'Mix C Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.44 { + iface MIXER + name 'Mix C Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.45 { + iface MIXER + name 'Mix C Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.46 { + iface MIXER + name 'Mix D Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.47 { + iface MIXER + name 'Mix D Input 02 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.48 { + iface MIXER + name 'Mix D Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.49 { + iface MIXER + name 'Mix D Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.50 { + iface PCM + name 'Level Meter' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + value.4 0 + value.5 0 + value.6 0 + value.7 0 + value.8 0 + value.9 0 + value.10 0 + value.11 0 + comment { + access 'read volatile' + type INTEGER + count 12 + range '0 - 4095 (step 1)' + } + } + control.51 { + iface MIXER + name 'Sync Status' + value Locked + comment { + access read + type ENUMERATED + count 1 + item.0 Unlocked + item.1 Locked + } + } + control.52 { + iface MIXER + name 'Direct Monitor Playback Enum' + value Off + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 Mono + item.2 Stereo + } + } + control.53 { + iface MIXER + name 'Monitor 1 Mix A Input 01 Playback Volume' + value 150 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -500 + } + } + control.54 { + iface MIXER + name 'Monitor 1 Mix A Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.55 { + iface MIXER + name 'Monitor 1 Mix A Input 03 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.56 { + iface MIXER + name 'Monitor 1 Mix A Input 04 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.57 { + iface MIXER + name 'Monitor 1 Mix B Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.58 { + iface MIXER + name 'Monitor 1 Mix B Input 02 Playback Volume' + value 150 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -500 + } + } + control.59 { + iface MIXER + name 'Monitor 1 Mix B Input 03 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.60 { + iface MIXER + name 'Monitor 1 Mix B Input 04 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.61 { + iface MIXER + name 'Monitor 2 Mix A Input 01 Playback Volume' + value 150 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -500 + } + } + control.62 { + iface MIXER + name 'Monitor 2 Mix A Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.63 { + iface MIXER + name 'Monitor 2 Mix A Input 03 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.64 { + iface MIXER + name 'Monitor 2 Mix A Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.65 { + iface MIXER + name 'Monitor 2 Mix B Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.66 { + iface MIXER + name 'Monitor 2 Mix B Input 02 Playback Volume' + value 150 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -500 + } + } + control.67 { + iface MIXER + name 'Monitor 2 Mix B Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.68 { + iface MIXER + name 'Monitor 2 Mix B Input 04 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } +} diff --git a/demo/Scarlett Gen 4 4i4.state b/demo/Scarlett Gen 4 4i4.state new file mode 100644 index 0000000..a0123d8 --- /dev/null +++ b/demo/Scarlett Gen 4 4i4.state @@ -0,0 +1,3768 @@ +state.Gen { + control.1 { + iface PCM + name 'Playback Channel Map' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + value.4 0 + value.5 0 + comment { + access read + type INTEGER + count 6 + range '0 - 36' + } + } + control.2 { + iface PCM + name 'Capture Channel Map' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + value.4 0 + value.5 0 + comment { + access read + type INTEGER + count 6 + range '0 - 36' + } + } + control.3 { + iface CARD + name 'USB Internal Validity' + value true + comment { + access read + type BOOLEAN + count 1 + } + } + control.4 { + iface CARD + name 'Firmware Version' + value 2089 + comment { + access read + type INTEGER + count 1 + range '0 - 0' + } + } + control.5 { + iface CARD + name 'Minimum Firmware Version' + value 2089 + comment { + access read + type INTEGER + count 1 + range '0 - 0' + } + } + control.6 { + iface MIXER + name 'Master HW Playback Volume' + value 0 + comment { + access read + type INTEGER + count 1 + range '0 - 127 (step 1)' + dbmin -12700 + dbmax 0 + dbvalue.0 -12700 + } + } + control.7 { + iface MIXER + name 'Headphone Playback Volume' + value 0 + comment { + access read + type INTEGER + count 1 + range '0 - 127 (step 1)' + dbmin -12700 + dbmax 0 + dbvalue.0 -12700 + } + } + control.8 { + iface MIXER + name 'Line In 1 Level Capture Enum' + value Line + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Line + item.1 Inst + } + } + control.9 { + iface MIXER + name 'Line In 2 Level Capture Enum' + value Line + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Line + item.1 Inst + } + } + control.10 { + iface MIXER + name 'Line In 1 Air Capture Enum' + value Off + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 Presence + item.2 'Presence + Drive' + } + } + control.11 { + iface MIXER + name 'Line In 2 Air Capture Enum' + value Off + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 Presence + item.2 'Presence + Drive' + } + } + control.12 { + iface MIXER + name 'Line In 1 Phantom Power Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.13 { + iface MIXER + name 'Line In 2 Phantom Power Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.14 { + iface MIXER + name 'Input Select Capture Enum' + value 'Input 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 'Input 1' + item.1 'Input 2' + } + } + control.15 { + iface MIXER + name 'Line In 1 Gain Capture Volume' + value 70 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 70 (step 1)' + dbmin -7000 + dbmax 0 + dbvalue.0 0 + } + } + control.16 { + iface MIXER + name 'Line In 1 Autogain Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.17 { + iface MIXER + name 'Line In 1 Autogain Status Capture Enum' + value Stopped + comment { + access read + type ENUMERATED + count 1 + item.0 Stopped + item.1 Running + item.2 Failed + item.3 Cancelled + item.4 Unknown + } + } + control.18 { + iface MIXER + name 'Line In 1 Safe Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.19 { + iface MIXER + name 'Line In 1-2 Link Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.20 { + iface MIXER + name 'Line In 2 Gain Capture Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 70 (step 1)' + dbmin -7000 + dbmax 0 + dbvalue.0 -7000 + } + } + control.21 { + iface MIXER + name 'Line In 2 Autogain Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.22 { + iface MIXER + name 'Line In 2 Autogain Status Capture Enum' + value Stopped + comment { + access read + type ENUMERATED + count 1 + item.0 Stopped + item.1 Running + item.2 Failed + item.3 Cancelled + item.4 Unknown + } + } + control.23 { + iface MIXER + name 'Line In 2 Safe Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.24 { + iface MIXER + name 'Analogue Output 01 Playback Enum' + value 'Mix A' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.25 { + iface MIXER + name 'Analogue Output 02 Playback Enum' + value 'Mix B' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.26 { + iface MIXER + name 'Analogue Output 03 Playback Enum' + value 'Mix C' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.27 { + iface MIXER + name 'Analogue Output 04 Playback Enum' + value 'Mix D' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.28 { + iface MIXER + name 'Analogue Output 05 Playback Enum' + value 'Mix E' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.29 { + iface MIXER + name 'Analogue Output 06 Playback Enum' + value 'Mix F' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.30 { + iface MIXER + name 'Mixer Input 01 Capture Enum' + value 'DSP 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.31 { + iface MIXER + name 'Mixer Input 02 Capture Enum' + value 'DSP 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.32 { + iface MIXER + name 'Mixer Input 03 Capture Enum' + value 'Analogue 3' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.33 { + iface MIXER + name 'Mixer Input 04 Capture Enum' + value 'Analogue 4' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.34 { + iface MIXER + name 'Mixer Input 05 Capture Enum' + value 'PCM 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.35 { + iface MIXER + name 'Mixer Input 06 Capture Enum' + value 'PCM 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.36 { + iface MIXER + name 'Mixer Input 07 Capture Enum' + value 'PCM 3' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.37 { + iface MIXER + name 'Mixer Input 08 Capture Enum' + value 'PCM 4' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.38 { + iface MIXER + name 'Mixer Input 09 Capture Enum' + value 'PCM 5' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.39 { + iface MIXER + name 'Mixer Input 10 Capture Enum' + value 'PCM 6' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.40 { + iface MIXER + name 'DSP Input 1 Capture Enum' + value 'Analogue 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.41 { + iface MIXER + name 'DSP Input 2 Capture Enum' + value 'Analogue 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.42 { + iface MIXER + name 'PCM 01 Capture Enum' + value 'DSP 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.43 { + iface MIXER + name 'PCM 02 Capture Enum' + value 'DSP 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.44 { + iface MIXER + name 'PCM 03 Capture Enum' + value 'Analogue 3' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.45 { + iface MIXER + name 'PCM 04 Capture Enum' + value 'Analogue 4' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.46 { + iface MIXER + name 'PCM 05 Capture Enum' + value 'Mix E' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.47 { + iface MIXER + name 'PCM 06 Capture Enum' + value 'Mix F' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.48 { + iface MIXER + name 'Mix A Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.49 { + iface MIXER + name 'Mix A Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.50 { + iface MIXER + name 'Mix A Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.51 { + iface MIXER + name 'Mix A Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.52 { + iface MIXER + name 'Mix A Input 05 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.53 { + iface MIXER + name 'Mix A Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.54 { + iface MIXER + name 'Mix A Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.55 { + iface MIXER + name 'Mix A Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.56 { + iface MIXER + name 'Mix A Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.57 { + iface MIXER + name 'Mix A Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.58 { + iface MIXER + name 'Mix B Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.59 { + iface MIXER + name 'Mix B Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.60 { + iface MIXER + name 'Mix B Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.61 { + iface MIXER + name 'Mix B Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.62 { + iface MIXER + name 'Mix B Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.63 { + iface MIXER + name 'Mix B Input 06 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.64 { + iface MIXER + name 'Mix B Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.65 { + iface MIXER + name 'Mix B Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.66 { + iface MIXER + name 'Mix B Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.67 { + iface MIXER + name 'Mix B Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.68 { + iface MIXER + name 'Mix C Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.69 { + iface MIXER + name 'Mix C Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.70 { + iface MIXER + name 'Mix C Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.71 { + iface MIXER + name 'Mix C Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.72 { + iface MIXER + name 'Mix C Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.73 { + iface MIXER + name 'Mix C Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.74 { + iface MIXER + name 'Mix C Input 07 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.75 { + iface MIXER + name 'Mix C Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.76 { + iface MIXER + name 'Mix C Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.77 { + iface MIXER + name 'Mix C Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.78 { + iface MIXER + name 'Mix D Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.79 { + iface MIXER + name 'Mix D Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.80 { + iface MIXER + name 'Mix D Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.81 { + iface MIXER + name 'Mix D Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.82 { + iface MIXER + name 'Mix D Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.83 { + iface MIXER + name 'Mix D Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.84 { + iface MIXER + name 'Mix D Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.85 { + iface MIXER + name 'Mix D Input 08 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.86 { + iface MIXER + name 'Mix D Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.87 { + iface MIXER + name 'Mix D Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.88 { + iface MIXER + name 'Mix E Input 01 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.89 { + iface MIXER + name 'Mix E Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.90 { + iface MIXER + name 'Mix E Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.91 { + iface MIXER + name 'Mix E Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.92 { + iface MIXER + name 'Mix E Input 05 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.93 { + iface MIXER + name 'Mix E Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.94 { + iface MIXER + name 'Mix E Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.95 { + iface MIXER + name 'Mix E Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.96 { + iface MIXER + name 'Mix E Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.97 { + iface MIXER + name 'Mix E Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.98 { + iface MIXER + name 'Mix F Input 01 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.99 { + iface MIXER + name 'Mix F Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.100 { + iface MIXER + name 'Mix F Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.101 { + iface MIXER + name 'Mix F Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.102 { + iface MIXER + name 'Mix F Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.103 { + iface MIXER + name 'Mix F Input 06 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.104 { + iface MIXER + name 'Mix F Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.105 { + iface MIXER + name 'Mix F Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.106 { + iface MIXER + name 'Mix F Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.107 { + iface MIXER + name 'Mix F Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.108 { + iface PCM + name 'Level Meter' + value.0 3313 + value.1 3418 + value.2 0 + value.3 0 + value.4 3362 + value.5 3450 + value.6 1876 + value.7 0 + value.8 0 + value.9 0 + value.10 3313 + value.11 3418 + value.12 0 + value.13 0 + value.14 0 + value.15 0 + value.16 1876 + value.17 0 + value.18 1876 + value.19 0 + value.20 0 + value.21 0 + value.22 3362 + value.23 3450 + comment { + access 'read volatile' + type INTEGER + count 24 + range '0 - 4095 (step 1)' + } + } + control.109 { + iface MIXER + name 'Sync Status' + value Locked + comment { + access read + type ENUMERATED + count 1 + item.0 Unlocked + item.1 Locked + } + } + control.110 { + iface CARD + name 'Power Status Card Enum' + value Fail + comment { + access read + type ENUMERATED + count 1 + item.0 External + item.1 Bus + item.2 Fail + } + } +} +state.Gen_1 { + control.1 { + iface PCM + name 'Playback Channel Map' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + value.4 0 + value.5 0 + comment { + access read + type INTEGER + count 6 + range '0 - 36' + } + } + control.2 { + iface PCM + name 'Capture Channel Map' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + value.4 0 + value.5 0 + comment { + access read + type INTEGER + count 6 + range '0 - 36' + } + } + control.3 { + iface CARD + name 'USB Internal Validity' + value true + comment { + access read + type BOOLEAN + count 1 + } + } + control.4 { + iface CARD + name 'Firmware Version' + value 2089 + comment { + access read + type INTEGER + count 1 + range '0 - 0' + } + } + control.5 { + iface CARD + name 'Minimum Firmware Version' + value 2089 + comment { + access read + type INTEGER + count 1 + range '0 - 0' + } + } + control.6 { + iface MIXER + name 'MSD Mode Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.7 { + iface MIXER + name 'Master HW Playback Volume' + value 88 + comment { + access read + type INTEGER + count 1 + range '0 - 127 (step 1)' + dbmin -12700 + dbmax 0 + dbvalue.0 -3900 + } + } + control.8 { + iface MIXER + name 'Headphone Playback Volume' + value 0 + comment { + access read + type INTEGER + count 1 + range '0 - 127 (step 1)' + dbmin -12700 + dbmax 0 + dbvalue.0 -12700 + } + } + control.9 { + iface MIXER + name 'Line In 1 Level Capture Enum' + value Line + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Line + item.1 Inst + } + } + control.10 { + iface MIXER + name 'Line In 2 Level Capture Enum' + value Line + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Line + item.1 Inst + } + } + control.11 { + iface MIXER + name 'Line In 1 Air Capture Enum' + value Off + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 Presence + item.2 'Presence + Drive' + } + } + control.12 { + iface MIXER + name 'Line In 2 Air Capture Enum' + value Off + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 Presence + item.2 'Presence + Drive' + } + } + control.13 { + iface MIXER + name 'Line In 1 Phantom Power Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.14 { + iface MIXER + name 'Line In 2 Phantom Power Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.15 { + iface MIXER + name 'Input Select Capture Enum' + value 'Input 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 'Input 1' + item.1 'Input 2' + } + } + control.16 { + iface MIXER + name 'Line In 1 Gain Capture Volume' + value 70 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 70 (step 1)' + dbmin -7000 + dbmax 0 + dbvalue.0 0 + } + } + control.17 { + iface MIXER + name 'Line In 1 Autogain Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.18 { + iface MIXER + name 'Line In 1 Autogain Status Capture Enum' + value Cancelled + comment { + access read + type ENUMERATED + count 1 + item.0 Stopped + item.1 Running + item.2 Failed + item.3 Cancelled + item.4 Unknown + } + } + control.19 { + iface MIXER + name 'Line In 1 Safe Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.20 { + iface MIXER + name 'Line In 1-2 Link Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.21 { + iface MIXER + name 'Line In 2 Gain Capture Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 70 (step 1)' + dbmin -7000 + dbmax 0 + dbvalue.0 -7000 + } + } + control.22 { + iface MIXER + name 'Line In 2 Autogain Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.23 { + iface MIXER + name 'Line In 2 Autogain Status Capture Enum' + value Stopped + comment { + access read + type ENUMERATED + count 1 + item.0 Stopped + item.1 Running + item.2 Failed + item.3 Cancelled + item.4 Unknown + } + } + control.24 { + iface MIXER + name 'Line In 2 Safe Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.25 { + iface MIXER + name 'Analogue Output 01 Playback Enum' + value 'Mix A' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.26 { + iface MIXER + name 'Analogue Output 02 Playback Enum' + value 'Mix B' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.27 { + iface MIXER + name 'Analogue Output 03 Playback Enum' + value 'Mix C' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.28 { + iface MIXER + name 'Analogue Output 04 Playback Enum' + value 'Mix D' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.29 { + iface MIXER + name 'Analogue Output 05 Playback Enum' + value 'Mix A' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.30 { + iface MIXER + name 'Analogue Output 06 Playback Enum' + value 'Mix B' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.31 { + iface MIXER + name 'Mixer Input 01 Capture Enum' + value 'DSP 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.32 { + iface MIXER + name 'Mixer Input 02 Capture Enum' + value 'DSP 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.33 { + iface MIXER + name 'Mixer Input 03 Capture Enum' + value 'Analogue 3' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.34 { + iface MIXER + name 'Mixer Input 04 Capture Enum' + value 'Analogue 4' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.35 { + iface MIXER + name 'Mixer Input 05 Capture Enum' + value 'PCM 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.36 { + iface MIXER + name 'Mixer Input 06 Capture Enum' + value 'PCM 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.37 { + iface MIXER + name 'Mixer Input 07 Capture Enum' + value 'PCM 3' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.38 { + iface MIXER + name 'Mixer Input 08 Capture Enum' + value 'PCM 4' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.39 { + iface MIXER + name 'Mixer Input 09 Capture Enum' + value 'PCM 5' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.40 { + iface MIXER + name 'Mixer Input 10 Capture Enum' + value 'PCM 6' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.41 { + iface MIXER + name 'DSP Input 1 Capture Enum' + value 'Analogue 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.42 { + iface MIXER + name 'DSP Input 2 Capture Enum' + value 'Analogue 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.43 { + iface MIXER + name 'PCM 01 Capture Enum' + value 'DSP 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.44 { + iface MIXER + name 'PCM 02 Capture Enum' + value 'DSP 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.45 { + iface MIXER + name 'PCM 03 Capture Enum' + value 'Analogue 3' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.46 { + iface MIXER + name 'PCM 04 Capture Enum' + value 'Analogue 4' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.47 { + iface MIXER + name 'PCM 05 Capture Enum' + value 'Mix E' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.48 { + iface MIXER + name 'PCM 06 Capture Enum' + value 'Mix F' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Mix A' + item.6 'Mix B' + item.7 'Mix C' + item.8 'Mix D' + item.9 'Mix E' + item.10 'Mix F' + item.11 'DSP 1' + item.12 'DSP 2' + item.13 'PCM 1' + item.14 'PCM 2' + item.15 'PCM 3' + item.16 'PCM 4' + item.17 'PCM 5' + item.18 'PCM 6' + } + } + control.49 { + iface MIXER + name 'Mix A Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.50 { + iface MIXER + name 'Mix A Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.51 { + iface MIXER + name 'Mix A Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.52 { + iface MIXER + name 'Mix A Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.53 { + iface MIXER + name 'Mix A Input 05 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.54 { + iface MIXER + name 'Mix A Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.55 { + iface MIXER + name 'Mix A Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.56 { + iface MIXER + name 'Mix A Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.57 { + iface MIXER + name 'Mix A Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.58 { + iface MIXER + name 'Mix A Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.59 { + iface MIXER + name 'Mix B Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.60 { + iface MIXER + name 'Mix B Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.61 { + iface MIXER + name 'Mix B Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.62 { + iface MIXER + name 'Mix B Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.63 { + iface MIXER + name 'Mix B Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.64 { + iface MIXER + name 'Mix B Input 06 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.65 { + iface MIXER + name 'Mix B Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.66 { + iface MIXER + name 'Mix B Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.67 { + iface MIXER + name 'Mix B Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.68 { + iface MIXER + name 'Mix B Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.69 { + iface MIXER + name 'Mix C Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.70 { + iface MIXER + name 'Mix C Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.71 { + iface MIXER + name 'Mix C Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.72 { + iface MIXER + name 'Mix C Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.73 { + iface MIXER + name 'Mix C Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.74 { + iface MIXER + name 'Mix C Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.75 { + iface MIXER + name 'Mix C Input 07 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.76 { + iface MIXER + name 'Mix C Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.77 { + iface MIXER + name 'Mix C Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.78 { + iface MIXER + name 'Mix C Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.79 { + iface MIXER + name 'Mix D Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.80 { + iface MIXER + name 'Mix D Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.81 { + iface MIXER + name 'Mix D Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.82 { + iface MIXER + name 'Mix D Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.83 { + iface MIXER + name 'Mix D Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.84 { + iface MIXER + name 'Mix D Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.85 { + iface MIXER + name 'Mix D Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.86 { + iface MIXER + name 'Mix D Input 08 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.87 { + iface MIXER + name 'Mix D Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.88 { + iface MIXER + name 'Mix D Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.89 { + iface MIXER + name 'Mix E Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.90 { + iface MIXER + name 'Mix E Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.91 { + iface MIXER + name 'Mix E Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.92 { + iface MIXER + name 'Mix E Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.93 { + iface MIXER + name 'Mix E Input 05 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.94 { + iface MIXER + name 'Mix E Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.95 { + iface MIXER + name 'Mix E Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.96 { + iface MIXER + name 'Mix E Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.97 { + iface MIXER + name 'Mix E Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.98 { + iface MIXER + name 'Mix E Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.99 { + iface MIXER + name 'Mix F Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.100 { + iface MIXER + name 'Mix F Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.101 { + iface MIXER + name 'Mix F Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.102 { + iface MIXER + name 'Mix F Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.103 { + iface MIXER + name 'Mix F Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.104 { + iface MIXER + name 'Mix F Input 06 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.105 { + iface MIXER + name 'Mix F Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.106 { + iface MIXER + name 'Mix F Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.107 { + iface MIXER + name 'Mix F Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.108 { + iface MIXER + name 'Mix F Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.109 { + iface PCM + name 'Level Meter' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + value.4 0 + value.5 0 + value.6 14 + value.7 0 + value.8 0 + value.9 0 + value.10 0 + value.11 0 + value.12 0 + value.13 0 + value.14 0 + value.15 0 + value.16 14 + value.17 0 + value.18 14 + value.19 0 + value.20 0 + value.21 0 + value.22 0 + value.23 0 + comment { + access 'read volatile' + type INTEGER + count 24 + range '0 - 4095 (step 1)' + } + } + control.110 { + iface MIXER + name 'Sync Status' + value Locked + comment { + access read + type ENUMERATED + count 1 + item.0 Unlocked + item.1 Locked + } + } + control.111 { + iface CARD + name 'Power Status Card Enum' + value Bus + comment { + access read + type ENUMERATED + count 1 + item.0 External + item.1 Bus + item.2 Fail + } + } +} diff --git a/demo/Scarlett Gen 4 Solo.state b/demo/Scarlett Gen 4 Solo.state new file mode 100644 index 0000000..5eda00e --- /dev/null +++ b/demo/Scarlett Gen 4 Solo.state @@ -0,0 +1,885 @@ +state.Gen { + control.1 { + iface PCM + name 'Playback Channel Map' + value.0 0 + value.1 0 + comment { + access read + type INTEGER + count 2 + range '0 - 36' + } + } + control.2 { + iface PCM + name 'Capture Channel Map' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + comment { + access read + type INTEGER + count 4 + range '0 - 36' + } + } + control.3 { + iface CARD + name 'USB Internal Validity' + value true + comment { + access read + type BOOLEAN + count 1 + } + } + control.4 { + iface CARD + name 'Firmware Version' + value 2115 + comment { + access read + type INTEGER + count 1 + range '0 - 0' + } + } + control.5 { + iface CARD + name 'Minimum Firmware Version' + value 2115 + comment { + access read + type INTEGER + count 1 + range '0 - 0' + } + } + control.6 { + iface MIXER + name 'MSD Mode Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.7 { + iface MIXER + name 'Line In 1 Level Capture Enum' + value Line + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Line + item.1 Inst + } + } + control.8 { + iface MIXER + name 'Line In 2 Air Capture Enum' + value Off + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 Presence + item.2 'Presence + Drive' + } + } + control.9 { + iface MIXER + name 'Line In 2 Phantom Power Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.10 { + iface MIXER + name 'PCM Input Capture Switch' + value Direct + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Direct + item.1 Mixer + } + } + control.11 { + iface MIXER + name 'Analogue Output 01 Playback Enum' + value 'Mix A' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'DSP 1' + item.10 'DSP 2' + item.11 'PCM 1' + item.12 'PCM 2' + } + } + control.12 { + iface MIXER + name 'Analogue Output 02 Playback Enum' + value 'Mix B' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'DSP 1' + item.10 'DSP 2' + item.11 'PCM 1' + item.12 'PCM 2' + } + } + control.13 { + iface MIXER + name 'Mixer Input 01 Capture Enum' + value 'PCM 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'DSP 1' + item.10 'DSP 2' + item.11 'PCM 1' + item.12 'PCM 2' + } + } + control.14 { + iface MIXER + name 'Mixer Input 02 Capture Enum' + value 'PCM 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'DSP 1' + item.10 'DSP 2' + item.11 'PCM 1' + item.12 'PCM 2' + } + } + control.15 { + iface MIXER + name 'Mixer Input 03 Capture Enum' + value 'DSP 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'DSP 1' + item.10 'DSP 2' + item.11 'PCM 1' + item.12 'PCM 2' + } + } + control.16 { + iface MIXER + name 'Mixer Input 04 Capture Enum' + value 'DSP 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'DSP 1' + item.10 'DSP 2' + item.11 'PCM 1' + item.12 'PCM 2' + } + } + control.17 { + iface MIXER + name 'DSP Input 1 Capture Enum' + value 'Analogue 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'DSP 1' + item.10 'DSP 2' + item.11 'PCM 1' + item.12 'PCM 2' + } + } + control.18 { + iface MIXER + name 'DSP Input 2 Capture Enum' + value 'Analogue 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'DSP 1' + item.10 'DSP 2' + item.11 'PCM 1' + item.12 'PCM 2' + } + } + control.19 { + iface MIXER + name 'PCM 01 Capture Enum' + value 'DSP 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'DSP 1' + item.10 'DSP 2' + item.11 'PCM 1' + item.12 'PCM 2' + } + } + control.20 { + iface MIXER + name 'PCM 02 Capture Enum' + value 'DSP 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'DSP 1' + item.10 'DSP 2' + item.11 'PCM 1' + item.12 'PCM 2' + } + } + control.21 { + iface MIXER + name 'PCM 03 Capture Enum' + value 'Mix C' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'DSP 1' + item.10 'DSP 2' + item.11 'PCM 1' + item.12 'PCM 2' + } + } + control.22 { + iface MIXER + name 'PCM 04 Capture Enum' + value 'Mix D' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'DSP 1' + item.10 'DSP 2' + item.11 'PCM 1' + item.12 'PCM 2' + } + } + control.23 { + iface MIXER + name 'Mix A Input 01 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.24 { + iface MIXER + name 'Mix A Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.25 { + iface MIXER + name 'Mix A Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.26 { + iface MIXER + name 'Mix A Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.27 { + iface MIXER + name 'Mix B Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.28 { + iface MIXER + name 'Mix B Input 02 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.29 { + iface MIXER + name 'Mix B Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.30 { + iface MIXER + name 'Mix B Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.31 { + iface MIXER + name 'Mix C Input 01 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.32 { + iface MIXER + name 'Mix C Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.33 { + iface MIXER + name 'Mix C Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.34 { + iface MIXER + name 'Mix C Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.35 { + iface MIXER + name 'Mix D Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.36 { + iface MIXER + name 'Mix D Input 02 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.37 { + iface MIXER + name 'Mix D Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.38 { + iface MIXER + name 'Mix D Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.39 { + iface MIXER + name 'Mix E Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.40 { + iface MIXER + name 'Mix E Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.41 { + iface MIXER + name 'Mix E Input 03 Playback Volume' + value 144 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -800 + } + } + control.42 { + iface MIXER + name 'Mix E Input 04 Playback Volume' + value 144 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -800 + } + } + control.43 { + iface MIXER + name 'Mix F Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.44 { + iface MIXER + name 'Mix F Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.45 { + iface MIXER + name 'Mix F Input 03 Playback Volume' + value 144 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -800 + } + } + control.46 { + iface MIXER + name 'Mix F Input 04 Playback Volume' + value 144 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -800 + } + } + control.47 { + iface PCM + name 'Level Meter' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + value.4 0 + value.5 3 + value.6 0 + value.7 3 + value.8 0 + value.9 3 + value.10 0 + value.11 0 + comment { + access 'read volatile' + type INTEGER + count 12 + range '0 - 4095 (step 1)' + } + } + control.48 { + iface MIXER + name 'Sync Status' + value Locked + comment { + access read + type ENUMERATED + count 1 + item.0 Unlocked + item.1 Locked + } + } + control.49 { + iface MIXER + name 'Direct Monitor Playback Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.50 { + iface MIXER + name 'Monitor Mix A Input 01 Playback Volume' + value 150 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -500 + } + } + control.51 { + iface MIXER + name 'Monitor Mix A Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.52 { + iface MIXER + name 'Monitor Mix A Input 03 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.53 { + iface MIXER + name 'Monitor Mix A Input 04 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.54 { + iface MIXER + name 'Monitor Mix B Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.55 { + iface MIXER + name 'Monitor Mix B Input 02 Playback Volume' + value 150 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -500 + } + } + control.56 { + iface MIXER + name 'Monitor Mix B Input 03 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.57 { + iface MIXER + name 'Monitor Mix B Input 04 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } +} diff --git a/demo/Vocaster One.state b/demo/Vocaster One.state new file mode 100644 index 0000000..f39cdfb --- /dev/null +++ b/demo/Vocaster One.state @@ -0,0 +1,1861 @@ +state.USB_1 { + control.1 { + iface PCM + name 'Playback Channel Map' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + comment { + access read + type INTEGER + count 4 + range '0 - 36' + } + } + control.2 { + iface PCM + name 'Capture Channel Map' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + value.4 0 + value.5 0 + value.6 0 + value.7 0 + value.8 0 + value.9 0 + comment { + access read + type INTEGER + count 10 + range '0 - 36' + } + } + control.3 { + iface CARD + name 'USB Internal Validity' + value true + comment { + access read + type BOOLEAN + count 1 + } + } + control.4 { + iface CARD + name 'Firmware Version' + value 1769 + comment { + access read + type INTEGER + count 1 + range '0 - 0' + } + } + control.5 { + iface CARD + name 'Minimum Firmware Version' + value 1769 + comment { + access read + type INTEGER + count 1 + range '0 - 0' + } + } + control.6 { + iface MIXER + name 'MSD Mode Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.7 { + iface MIXER + name 'Line In 1 DSP Capture Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.8 { + iface CARD + name 'Line In 1 Compressor Enable' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.9 { + iface CARD + name 'Line In 1 Compressor Threshold' + value -22 + comment { + access 'read write' + type INTEGER + count 1 + range '-40 - 0 (step 1)' + } + } + control.10 { + iface CARD + name 'Line In 1 Compressor Ratio' + value 8 + comment { + access 'read write' + type INTEGER + count 1 + range '2 - 100 (step 1)' + } + } + control.11 { + iface CARD + name 'Line In 1 Compressor Knee Width' + value 3 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 10 (step 1)' + } + } + control.12 { + iface CARD + name 'Line In 1 Compressor Attack' + value 30 + comment { + access 'read write' + type INTEGER + count 1 + range '30 - 255 (step 1)' + } + } + control.13 { + iface CARD + name 'Line In 1 Compressor Release' + value 30 + comment { + access 'read write' + type INTEGER + count 1 + range '30 - 255 (step 1)' + } + } + control.14 { + iface CARD + name 'Line In 1 Compressor Makeup Gain' + value 5 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 24 (step 1)' + } + } + control.15 { + iface CARD + name 'Line In 1 Pre-Comp Filter Enable' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.16 { + iface CARD + name 'Line In 1 PEQ Filter Enable' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.17 { + iface CARD + name 'Line In 1 Pre-Comp Coefficients 1' + value.0 265694923 + value.1 -531389846 + value.2 265694923 + value.3 532986969 + value.4 -264567952 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.18 { + iface CARD + name 'Line In 1 Pre-Comp Coefficients 2' + value.0 268435456 + value.1 -536870912 + value.2 268435456 + value.3 535245642 + value.4 -266826695 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.19 { + iface CARD + name 'Line In 1 PEQ Coefficients 1' + value.0 268940007 + value.1 -533799575 + value.2 264905304 + value.3 533799575 + value.4 -265409855 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.20 { + iface CARD + name 'Line In 1 PEQ Coefficients 2' + value.0 264329859 + value.1 -480949433 + value.2 220769668 + value.3 480949433 + value.4 -216664071 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.21 { + iface CARD + name 'Line In 1 PEQ Coefficients 3' + value.0 305086343 + value.1 -305965130 + value.2 115171522 + value.3 248373882 + value.4 -94231161 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.22 { + iface MIXER + name 'Line In 1 Mute Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.23 { + iface MIXER + name 'Line In 1 Phantom Power Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.24 { + iface MIXER + name 'Line In 1 Gain Capture Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 70 (step 1)' + dbmin 0 + dbmax 7000 + dbvalue.0 0 + } + } + control.25 { + iface MIXER + name 'Line In 1 Autogain Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.26 { + iface MIXER + name 'Line In 1 Autogain Status Capture Enum' + value FailClipped + comment { + access read + type ENUMERATED + count 1 + item.0 Running + item.1 Success + item.2 SuccessDRover + item.3 WarnMinGainLimit + item.4 FailDRunder + item.5 FailMaxGainLimit + item.6 FailClipped + item.7 Cancelled + item.8 Invalid + } + } + control.27 { + iface MIXER + name 'Analogue Output 01 Playback Enum' + value 'Mix A' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.28 { + iface MIXER + name 'Analogue Output 02 Playback Enum' + value 'Mix B' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.29 { + iface MIXER + name 'Analogue Output 03 Playback Enum' + value 'Mix C' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.30 { + iface MIXER + name 'Analogue Output 04 Playback Enum' + value 'Mix D' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.31 { + iface MIXER + name 'Mixer Input 01 Capture Enum' + value 'DSP 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.32 { + iface MIXER + name 'Mixer Input 02 Capture Enum' + value 'Analogue 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.33 { + iface MIXER + name 'Mixer Input 03 Capture Enum' + value 'PCM 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.34 { + iface MIXER + name 'Mixer Input 04 Capture Enum' + value 'PCM 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.35 { + iface MIXER + name 'Mixer Input 05 Capture Enum' + value 'PCM 3' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.36 { + iface MIXER + name 'Mixer Input 06 Capture Enum' + value 'PCM 4' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.37 { + iface MIXER + name 'Mixer Input 07 Capture Enum' + value 'Mix A' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.38 { + iface MIXER + name 'Mixer Input 08 Capture Enum' + value 'Mix B' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.39 { + iface MIXER + name 'DSP Input 1 Capture Enum' + value 'Analogue 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.40 { + iface MIXER + name 'PCM 01 Capture Enum' + value 'Mix E' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.41 { + iface MIXER + name 'PCM 02 Capture Enum' + value 'Mix F' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.42 { + iface MIXER + name 'PCM 03 Capture Enum' + value 'Mix G' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.43 { + iface MIXER + name 'PCM 04 Capture Enum' + value 'Mix H' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.44 { + iface MIXER + name 'PCM 05 Capture Enum' + value 'DSP 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.45 { + iface MIXER + name 'PCM 06 Capture Enum' + value 'Analogue 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.46 { + iface MIXER + name 'PCM 07 Capture Enum' + value 'PCM 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.47 { + iface MIXER + name 'PCM 08 Capture Enum' + value 'PCM 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.48 { + iface MIXER + name 'PCM 09 Capture Enum' + value 'PCM 3' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.49 { + iface MIXER + name 'PCM 10 Capture Enum' + value 'PCM 4' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Mix A' + item.4 'Mix B' + item.5 'Mix C' + item.6 'Mix D' + item.7 'Mix E' + item.8 'Mix F' + item.9 'Mix G' + item.10 'Mix H' + item.11 'DSP 1' + item.12 'PCM 1' + item.13 'PCM 2' + item.14 'PCM 3' + item.15 'PCM 4' + } + } + control.50 { + iface MIXER + name 'Mix A Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.51 { + iface MIXER + name 'Mix A Input 02 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.52 { + iface MIXER + name 'Mix A Input 03 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.53 { + iface MIXER + name 'Mix A Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.54 { + iface MIXER + name 'Mix A Input 05 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.55 { + iface MIXER + name 'Mix A Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.56 { + iface MIXER + name 'Mix A Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.57 { + iface MIXER + name 'Mix A Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.58 { + iface MIXER + name 'Mix B Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.59 { + iface MIXER + name 'Mix B Input 02 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.60 { + iface MIXER + name 'Mix B Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.61 { + iface MIXER + name 'Mix B Input 04 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.62 { + iface MIXER + name 'Mix B Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.63 { + iface MIXER + name 'Mix B Input 06 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.64 { + iface MIXER + name 'Mix B Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.65 { + iface MIXER + name 'Mix B Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.66 { + iface MIXER + name 'Mix C Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.67 { + iface MIXER + name 'Mix C Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.68 { + iface MIXER + name 'Mix C Input 03 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.69 { + iface MIXER + name 'Mix C Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.70 { + iface MIXER + name 'Mix C Input 05 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.71 { + iface MIXER + name 'Mix C Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.72 { + iface MIXER + name 'Mix C Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.73 { + iface MIXER + name 'Mix C Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.74 { + iface MIXER + name 'Mix D Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.75 { + iface MIXER + name 'Mix D Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.76 { + iface MIXER + name 'Mix D Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.77 { + iface MIXER + name 'Mix D Input 04 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.78 { + iface MIXER + name 'Mix D Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.79 { + iface MIXER + name 'Mix D Input 06 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.80 { + iface MIXER + name 'Mix D Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.81 { + iface MIXER + name 'Mix D Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.82 { + iface MIXER + name 'Mix E Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.83 { + iface MIXER + name 'Mix E Input 02 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.84 { + iface MIXER + name 'Mix E Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.85 { + iface MIXER + name 'Mix E Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.86 { + iface MIXER + name 'Mix E Input 05 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.87 { + iface MIXER + name 'Mix E Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.88 { + iface MIXER + name 'Mix E Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.89 { + iface MIXER + name 'Mix E Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.90 { + iface MIXER + name 'Mix F Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.91 { + iface MIXER + name 'Mix F Input 02 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.92 { + iface MIXER + name 'Mix F Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.93 { + iface MIXER + name 'Mix F Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.94 { + iface MIXER + name 'Mix F Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.95 { + iface MIXER + name 'Mix F Input 06 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.96 { + iface MIXER + name 'Mix F Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.97 { + iface MIXER + name 'Mix F Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.98 { + iface MIXER + name 'Mix G Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.99 { + iface MIXER + name 'Mix G Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.100 { + iface MIXER + name 'Mix G Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.101 { + iface MIXER + name 'Mix G Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.102 { + iface MIXER + name 'Mix G Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.103 { + iface MIXER + name 'Mix G Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.104 { + iface MIXER + name 'Mix G Input 07 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.105 { + iface MIXER + name 'Mix G Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.106 { + iface MIXER + name 'Mix H Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.107 { + iface MIXER + name 'Mix H Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.108 { + iface MIXER + name 'Mix H Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.109 { + iface MIXER + name 'Mix H Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.110 { + iface MIXER + name 'Mix H Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.111 { + iface MIXER + name 'Mix H Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.112 { + iface MIXER + name 'Mix H Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.113 { + iface MIXER + name 'Mix H Input 08 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.114 { + iface PCM + name 'Level Meter' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + value.4 0 + value.5 0 + value.6 0 + value.7 0 + value.8 0 + value.9 0 + value.10 0 + value.11 0 + value.12 0 + value.13 0 + value.14 0 + value.15 0 + value.16 0 + value.17 0 + value.18 0 + value.19 0 + value.20 0 + value.21 0 + value.22 0 + comment { + access 'read volatile' + type INTEGER + count 23 + range '0 - 4095 (step 1)' + } + } + control.115 { + iface MIXER + name 'Sync Status' + value Locked + comment { + access read + type ENUMERATED + count 1 + item.0 Unlocked + item.1 Locked + } + } +} diff --git a/demo/Vocaster Two.state b/demo/Vocaster Two.state new file mode 100644 index 0000000..ca1ea9a --- /dev/null +++ b/demo/Vocaster Two.state @@ -0,0 +1,3428 @@ +state.USB_1 { + control.1 { + iface PCM + name 'Playback Channel Map' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + comment { + access read + type INTEGER + count 4 + range '0 - 36' + } + } + control.2 { + iface PCM + name 'Capture Channel Map' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + value.4 0 + value.5 0 + value.6 0 + value.7 0 + value.8 0 + value.9 0 + value.10 0 + value.11 0 + value.12 0 + value.13 0 + comment { + access read + type INTEGER + count 14 + range '0 - 36' + } + } + control.3 { + iface CARD + name 'USB Internal Validity' + value true + comment { + access read + type BOOLEAN + count 1 + } + } + control.4 { + iface CARD + name 'Firmware Version' + value 1769 + comment { + access read + type INTEGER + count 1 + range '0 - 0' + } + } + control.5 { + iface CARD + name 'Minimum Firmware Version' + value 1769 + comment { + access read + type INTEGER + count 1 + range '0 - 0' + } + } + control.6 { + iface MIXER + name 'MSD Mode Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.7 { + iface MIXER + name 'Line In 1 DSP Capture Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.8 { + iface CARD + name 'Line In 1 Compressor Enable' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.9 { + iface CARD + name 'Line In 1 Compressor Threshold' + value -22 + comment { + access 'read write' + type INTEGER + count 1 + range '-40 - 0 (step 1)' + } + } + control.10 { + iface CARD + name 'Line In 1 Compressor Ratio' + value 8 + comment { + access 'read write' + type INTEGER + count 1 + range '2 - 100 (step 1)' + } + } + control.11 { + iface CARD + name 'Line In 1 Compressor Knee Width' + value 3 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 10 (step 1)' + } + } + control.12 { + iface CARD + name 'Line In 1 Compressor Attack' + value 30 + comment { + access 'read write' + type INTEGER + count 1 + range '30 - 255 (step 1)' + } + } + control.13 { + iface CARD + name 'Line In 1 Compressor Release' + value 30 + comment { + access 'read write' + type INTEGER + count 1 + range '30 - 255 (step 1)' + } + } + control.14 { + iface CARD + name 'Line In 1 Compressor Makeup Gain' + value 5 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 24 (step 1)' + } + } + control.15 { + iface CARD + name 'Line In 1 Pre-Comp Filter Enable' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.16 { + iface CARD + name 'Line In 1 PEQ Filter Enable' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.17 { + iface CARD + name 'Line In 1 Pre-Comp Coefficients 1' + value.0 265694923 + value.1 -531389846 + value.2 265694923 + value.3 532986969 + value.4 -264567952 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.18 { + iface CARD + name 'Line In 1 Pre-Comp Coefficients 2' + value.0 268435456 + value.1 -536870912 + value.2 268435456 + value.3 535245642 + value.4 -266826695 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.19 { + iface CARD + name 'Line In 1 PEQ Coefficients 1' + value.0 268940007 + value.1 -533799575 + value.2 264905304 + value.3 533799575 + value.4 -265409855 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.20 { + iface CARD + name 'Line In 1 PEQ Coefficients 2' + value.0 264329859 + value.1 -480949433 + value.2 220769668 + value.3 480949433 + value.4 -216664071 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.21 { + iface CARD + name 'Line In 1 PEQ Coefficients 3' + value.0 305086343 + value.1 -305965130 + value.2 115171522 + value.3 248373882 + value.4 -94231161 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.22 { + iface MIXER + name 'Line In 2 DSP Capture Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.23 { + iface CARD + name 'Line In 2 Compressor Enable' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.24 { + iface CARD + name 'Line In 2 Compressor Threshold' + value -22 + comment { + access 'read write' + type INTEGER + count 1 + range '-40 - 0 (step 1)' + } + } + control.25 { + iface CARD + name 'Line In 2 Compressor Ratio' + value 8 + comment { + access 'read write' + type INTEGER + count 1 + range '2 - 100 (step 1)' + } + } + control.26 { + iface CARD + name 'Line In 2 Compressor Knee Width' + value 3 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 10 (step 1)' + } + } + control.27 { + iface CARD + name 'Line In 2 Compressor Attack' + value 30 + comment { + access 'read write' + type INTEGER + count 1 + range '30 - 255 (step 1)' + } + } + control.28 { + iface CARD + name 'Line In 2 Compressor Release' + value 30 + comment { + access 'read write' + type INTEGER + count 1 + range '30 - 255 (step 1)' + } + } + control.29 { + iface CARD + name 'Line In 2 Compressor Makeup Gain' + value 5 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 24 (step 1)' + } + } + control.30 { + iface CARD + name 'Line In 2 Pre-Comp Filter Enable' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.31 { + iface CARD + name 'Line In 2 PEQ Filter Enable' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.32 { + iface CARD + name 'Line In 2 Pre-Comp Coefficients 1' + value.0 265694923 + value.1 -531389846 + value.2 265694923 + value.3 532986969 + value.4 -264567952 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.33 { + iface CARD + name 'Line In 2 Pre-Comp Coefficients 2' + value.0 268435456 + value.1 -536870912 + value.2 268435456 + value.3 535245642 + value.4 -266826695 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.34 { + iface CARD + name 'Line In 2 PEQ Coefficients 1' + value.0 268940007 + value.1 -533799575 + value.2 264905304 + value.3 533799575 + value.4 -265409855 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.35 { + iface CARD + name 'Line In 2 PEQ Coefficients 2' + value.0 264329859 + value.1 -480949433 + value.2 220769668 + value.3 480949433 + value.4 -216664071 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.36 { + iface CARD + name 'Line In 2 PEQ Coefficients 3' + value.0 305086343 + value.1 -305965130 + value.2 115171522 + value.3 248373882 + value.4 -94231161 + comment { + access 'read write' + type INTEGER + count 5 + range '-2147483648 - 2147483647 (step 1)' + } + } + control.37 { + iface MIXER + name 'Line In 1 Mute Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.38 { + iface MIXER + name 'Line In 2 Mute Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.39 { + iface MIXER + name 'Line In 1 Phantom Power Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.40 { + iface MIXER + name 'Line In 2 Phantom Power Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.41 { + iface MIXER + name 'Line In 1 Gain Capture Volume' + value 9 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 70 (step 1)' + dbmin 0 + dbmax 7000 + dbvalue.0 900 + } + } + control.42 { + iface MIXER + name 'Line In 1 Autogain Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.43 { + iface MIXER + name 'Line In 1 Autogain Status Capture Enum' + value Success + comment { + access read + type ENUMERATED + count 1 + item.0 Running + item.1 Success + item.2 SuccessDRover + item.3 WarnMinGainLimit + item.4 FailDRunder + item.5 FailMaxGainLimit + item.6 FailClipped + item.7 Cancelled + item.8 Invalid + } + } + control.44 { + iface MIXER + name 'Line In 2 Gain Capture Volume' + value 9 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 70 (step 1)' + dbmin 0 + dbmax 7000 + dbvalue.0 900 + } + } + control.45 { + iface MIXER + name 'Line In 2 Autogain Capture Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.46 { + iface MIXER + name 'Line In 2 Autogain Status Capture Enum' + value Success + comment { + access read + type ENUMERATED + count 1 + item.0 Running + item.1 Success + item.2 SuccessDRover + item.3 WarnMinGainLimit + item.4 FailDRunder + item.5 FailMaxGainLimit + item.6 FailClipped + item.7 Cancelled + item.8 Invalid + } + } + control.47 { + iface MIXER + name 'Analogue Output 01 Playback Enum' + value 'Mix A' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.48 { + iface MIXER + name 'Analogue Output 02 Playback Enum' + value 'Mix B' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.49 { + iface MIXER + name 'Analogue Output 03 Playback Enum' + value 'Mix C' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.50 { + iface MIXER + name 'Analogue Output 04 Playback Enum' + value 'Mix D' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.51 { + iface MIXER + name 'Analogue Output 05 Playback Enum' + value 'Mix E' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.52 { + iface MIXER + name 'Analogue Output 06 Playback Enum' + value 'Mix F' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.53 { + iface MIXER + name 'Mixer Input 01 Capture Enum' + value 'DSP 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.54 { + iface MIXER + name 'Mixer Input 02 Capture Enum' + value 'DSP 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.55 { + iface MIXER + name 'Mixer Input 03 Capture Enum' + value 'Analogue 3' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.56 { + iface MIXER + name 'Mixer Input 04 Capture Enum' + value 'Analogue 4' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.57 { + iface MIXER + name 'Mixer Input 05 Capture Enum' + value 'Analogue 5' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.58 { + iface MIXER + name 'Mixer Input 06 Capture Enum' + value 'Analogue 6' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.59 { + iface MIXER + name 'Mixer Input 07 Capture Enum' + value 'PCM 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.60 { + iface MIXER + name 'Mixer Input 08 Capture Enum' + value 'PCM 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.61 { + iface MIXER + name 'Mixer Input 09 Capture Enum' + value 'PCM 3' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.62 { + iface MIXER + name 'Mixer Input 10 Capture Enum' + value 'PCM 4' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.63 { + iface MIXER + name 'Mixer Input 11 Capture Enum' + value 'Mix A' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.64 { + iface MIXER + name 'Mixer Input 12 Capture Enum' + value 'Mix B' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.65 { + iface MIXER + name 'DSP Input 1 Capture Enum' + value 'Analogue 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.66 { + iface MIXER + name 'DSP Input 2 Capture Enum' + value 'Analogue 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.67 { + iface MIXER + name 'PCM 01 Capture Enum' + value 'Mix G' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.68 { + iface MIXER + name 'PCM 02 Capture Enum' + value 'Mix H' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.69 { + iface MIXER + name 'PCM 03 Capture Enum' + value 'Mix I' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.70 { + iface MIXER + name 'PCM 04 Capture Enum' + value 'Mix J' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.71 { + iface MIXER + name 'PCM 05 Capture Enum' + value 'DSP 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.72 { + iface MIXER + name 'PCM 06 Capture Enum' + value 'DSP 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.73 { + iface MIXER + name 'PCM 07 Capture Enum' + value 'Analogue 3' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.74 { + iface MIXER + name 'PCM 08 Capture Enum' + value 'Analogue 4' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.75 { + iface MIXER + name 'PCM 09 Capture Enum' + value 'Analogue 5' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.76 { + iface MIXER + name 'PCM 10 Capture Enum' + value 'Analogue 6' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.77 { + iface MIXER + name 'PCM 11 Capture Enum' + value 'PCM 1' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.78 { + iface MIXER + name 'PCM 12 Capture Enum' + value 'PCM 2' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.79 { + iface MIXER + name 'PCM 13 Capture Enum' + value 'PCM 3' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.80 { + iface MIXER + name 'PCM 14 Capture Enum' + value 'PCM 4' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 'Analogue 1' + item.2 'Analogue 2' + item.3 'Analogue 3' + item.4 'Analogue 4' + item.5 'Analogue 5' + item.6 'Analogue 6' + item.7 'Mix A' + item.8 'Mix B' + item.9 'Mix C' + item.10 'Mix D' + item.11 'Mix E' + item.12 'Mix F' + item.13 'Mix G' + item.14 'Mix H' + item.15 'Mix I' + item.16 'Mix J' + item.17 'DSP 1' + item.18 'DSP 2' + item.19 'PCM 1' + item.20 'PCM 2' + item.21 'PCM 3' + item.22 'PCM 4' + } + } + control.81 { + iface MIXER + name 'Mix A Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.82 { + iface MIXER + name 'Mix A Input 02 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.83 { + iface MIXER + name 'Mix A Input 03 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.84 { + iface MIXER + name 'Mix A Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.85 { + iface MIXER + name 'Mix A Input 05 Playback Volume' + value 122 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -1900 + } + } + control.86 { + iface MIXER + name 'Mix A Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.87 { + iface MIXER + name 'Mix A Input 07 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.88 { + iface MIXER + name 'Mix A Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.89 { + iface MIXER + name 'Mix A Input 09 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.90 { + iface MIXER + name 'Mix A Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.91 { + iface MIXER + name 'Mix A Input 11 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.92 { + iface MIXER + name 'Mix A Input 12 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.93 { + iface MIXER + name 'Mix B Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.94 { + iface MIXER + name 'Mix B Input 02 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.95 { + iface MIXER + name 'Mix B Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.96 { + iface MIXER + name 'Mix B Input 04 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.97 { + iface MIXER + name 'Mix B Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.98 { + iface MIXER + name 'Mix B Input 06 Playback Volume' + value 122 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -1900 + } + } + control.99 { + iface MIXER + name 'Mix B Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.100 { + iface MIXER + name 'Mix B Input 08 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.101 { + iface MIXER + name 'Mix B Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.102 { + iface MIXER + name 'Mix B Input 10 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.103 { + iface MIXER + name 'Mix B Input 11 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.104 { + iface MIXER + name 'Mix B Input 12 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.105 { + iface MIXER + name 'Mix C Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.106 { + iface MIXER + name 'Mix C Input 02 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.107 { + iface MIXER + name 'Mix C Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.108 { + iface MIXER + name 'Mix C Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.109 { + iface MIXER + name 'Mix C Input 05 Playback Volume' + value 122 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -1900 + } + } + control.110 { + iface MIXER + name 'Mix C Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.111 { + iface MIXER + name 'Mix C Input 07 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.112 { + iface MIXER + name 'Mix C Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.113 { + iface MIXER + name 'Mix C Input 09 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.114 { + iface MIXER + name 'Mix C Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.115 { + iface MIXER + name 'Mix C Input 11 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.116 { + iface MIXER + name 'Mix C Input 12 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.117 { + iface MIXER + name 'Mix D Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.118 { + iface MIXER + name 'Mix D Input 02 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.119 { + iface MIXER + name 'Mix D Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.120 { + iface MIXER + name 'Mix D Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.121 { + iface MIXER + name 'Mix D Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.122 { + iface MIXER + name 'Mix D Input 06 Playback Volume' + value 122 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -1900 + } + } + control.123 { + iface MIXER + name 'Mix D Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.124 { + iface MIXER + name 'Mix D Input 08 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.125 { + iface MIXER + name 'Mix D Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.126 { + iface MIXER + name 'Mix D Input 10 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.127 { + iface MIXER + name 'Mix D Input 11 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.128 { + iface MIXER + name 'Mix D Input 12 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.129 { + iface MIXER + name 'Mix E Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.130 { + iface MIXER + name 'Mix E Input 02 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.131 { + iface MIXER + name 'Mix E Input 03 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.132 { + iface MIXER + name 'Mix E Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.133 { + iface MIXER + name 'Mix E Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.134 { + iface MIXER + name 'Mix E Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.135 { + iface MIXER + name 'Mix E Input 07 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.136 { + iface MIXER + name 'Mix E Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.137 { + iface MIXER + name 'Mix E Input 09 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.138 { + iface MIXER + name 'Mix E Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.139 { + iface MIXER + name 'Mix E Input 11 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.140 { + iface MIXER + name 'Mix E Input 12 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.141 { + iface MIXER + name 'Mix F Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.142 { + iface MIXER + name 'Mix F Input 02 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.143 { + iface MIXER + name 'Mix F Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.144 { + iface MIXER + name 'Mix F Input 04 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.145 { + iface MIXER + name 'Mix F Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.146 { + iface MIXER + name 'Mix F Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.147 { + iface MIXER + name 'Mix F Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.148 { + iface MIXER + name 'Mix F Input 08 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.149 { + iface MIXER + name 'Mix F Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.150 { + iface MIXER + name 'Mix F Input 10 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.151 { + iface MIXER + name 'Mix F Input 11 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.152 { + iface MIXER + name 'Mix F Input 12 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.153 { + iface MIXER + name 'Mix G Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.154 { + iface MIXER + name 'Mix G Input 02 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.155 { + iface MIXER + name 'Mix G Input 03 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.156 { + iface MIXER + name 'Mix G Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.157 { + iface MIXER + name 'Mix G Input 05 Playback Volume' + value 122 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -1900 + } + } + control.158 { + iface MIXER + name 'Mix G Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.159 { + iface MIXER + name 'Mix G Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.160 { + iface MIXER + name 'Mix G Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.161 { + iface MIXER + name 'Mix G Input 09 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.162 { + iface MIXER + name 'Mix G Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.163 { + iface MIXER + name 'Mix G Input 11 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.164 { + iface MIXER + name 'Mix G Input 12 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.165 { + iface MIXER + name 'Mix H Input 01 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.166 { + iface MIXER + name 'Mix H Input 02 Playback Volume' + value 154 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -300 + } + } + control.167 { + iface MIXER + name 'Mix H Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.168 { + iface MIXER + name 'Mix H Input 04 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.169 { + iface MIXER + name 'Mix H Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.170 { + iface MIXER + name 'Mix H Input 06 Playback Volume' + value 122 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -1900 + } + } + control.171 { + iface MIXER + name 'Mix H Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.172 { + iface MIXER + name 'Mix H Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.173 { + iface MIXER + name 'Mix H Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.174 { + iface MIXER + name 'Mix H Input 10 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.175 { + iface MIXER + name 'Mix H Input 11 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.176 { + iface MIXER + name 'Mix H Input 12 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.177 { + iface MIXER + name 'Mix I Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.178 { + iface MIXER + name 'Mix I Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.179 { + iface MIXER + name 'Mix I Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.180 { + iface MIXER + name 'Mix I Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.181 { + iface MIXER + name 'Mix I Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.182 { + iface MIXER + name 'Mix I Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.183 { + iface MIXER + name 'Mix I Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.184 { + iface MIXER + name 'Mix I Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.185 { + iface MIXER + name 'Mix I Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.186 { + iface MIXER + name 'Mix I Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.187 { + iface MIXER + name 'Mix I Input 11 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.188 { + iface MIXER + name 'Mix I Input 12 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.189 { + iface MIXER + name 'Mix J Input 01 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.190 { + iface MIXER + name 'Mix J Input 02 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.191 { + iface MIXER + name 'Mix J Input 03 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.192 { + iface MIXER + name 'Mix J Input 04 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.193 { + iface MIXER + name 'Mix J Input 05 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.194 { + iface MIXER + name 'Mix J Input 06 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.195 { + iface MIXER + name 'Mix J Input 07 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.196 { + iface MIXER + name 'Mix J Input 08 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.197 { + iface MIXER + name 'Mix J Input 09 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.198 { + iface MIXER + name 'Mix J Input 10 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.199 { + iface MIXER + name 'Mix J Input 11 Playback Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 -8000 + } + } + control.200 { + iface MIXER + name 'Mix J Input 12 Playback Volume' + value 160 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 172 (step 1)' + dbmin -8000 + dbmax 600 + dbvalue.0 0 + } + } + control.201 { + iface PCM + name 'Level Meter' + value.0 0 + value.1 0 + value.2 0 + value.3 0 + value.4 0 + value.5 0 + value.6 0 + value.7 0 + value.8 0 + value.9 0 + value.10 0 + value.11 0 + value.12 0 + value.13 0 + value.14 0 + value.15 0 + value.16 0 + value.17 0 + value.18 0 + value.19 0 + value.20 0 + value.21 0 + value.22 0 + value.23 0 + value.24 0 + value.25 0 + value.26 0 + value.27 0 + value.28 0 + value.29 0 + value.30 0 + value.31 0 + value.32 0 + value.33 0 + comment { + access 'read volatile' + type INTEGER + count 34 + range '0 - 4095 (step 1)' + } + } + control.202 { + iface MIXER + name 'Sync Status' + value Locked + comment { + access read + type ENUMERATED + count 1 + item.0 Unlocked + item.1 Locked + } + } +} diff --git a/docs/INSTALL.md b/docs/INSTALL.md new file mode 100644 index 0000000..445a561 --- /dev/null +++ b/docs/INSTALL.md @@ -0,0 +1,154 @@ +# ALSA Scarlett2 Control Panel Installation + +## Prerequisites + +### Linux Kernel + +You need to be running a Linux Kernel that has the ALSA Scarlett2 +Protocol Driver. Use `uname -r` to check what kernel version you are +running. + +- For reasonable functionality of Scarlett 2nd and 3rd Gen and Clarett + interfaces, you need at least Linux kernel version 6.7 +- For Scarlett 4th Gen support and firmware updates from Linux, you + need at least 6.8 +- For Vocaster support, you’ll need to build an updated + `snd-usb-audio` driver (or wait for 6.10) + +If you’ve got a Vocaster, or if your distribution doesn’t include a +recent-enough kernel for your interface, you can get the latest driver +from here and build it for your current kernel: + +https://github.com/geoffreybennett/scarlett-gen2/releases + +#### Enabling the Driver + +As of Linux 6.7 the driver is enabled by default. Check the driver +status (after plugging your interface in) with this command: + +``` +dmesg | grep -i -A 5 -B 5 focusrite +``` + +If all is good you’ll see messages like this: + +``` +New USB device found, idVendor=1235, idProduct=8215, bcdDevice= 6.0b +Product: Scarlett 18i20 USB +Focusrite Scarlett Gen 3 Mixer Driver enabled (pid=0x8215); report +any issues to https://github.com/geoffreybennett/scarlett-gen2/issues +``` + +If you don’t see the “Mixer Driver” message or if it shows “disabled” +then check the [OLDKERNEL.md](OLDKERNEL.md) instructions. + +### Gtk4 + +You need a Linux distribution with Gtk4 development libraries. If it +doesn’t have them natively, try the Flatpak instructions below. + +### Firmware + +As of Linux 6.8, firmware updates of all the supported interfaces can +be done through Linux. This is mandatory for Scarlett 4th Gen and +Vocaster interfaces (unless you’ve already updated it using the +manufacturer’s software), and optional for Scarlett 2nd and 3rd Gen, +Clarett USB, and Clarett+ interfaces. + +Download the firmware from +https://github.com/geoffreybennett/scarlett2-firmware and place it in +`/usr/lib/firmware/scarlett2` or use the RPM/deb package. + +## Building and Running + +On Fedora, these packages need to be installed: + +``` +sudo dnf -y install alsa-lib-devel gtk4-devel openssl-devel +``` + +On OpenSUSE: + +``` +sudo zypper in git alsa-devel gtk4-devel libopenssl-devel +``` + +On Ubuntu: + +``` +sudo apt -y install git make gcc libgtk-4-dev libasound2-dev libssl-dev +``` + +To download from github: + +``` +git clone https://github.com/geoffreybennett/alsa-scarlett-gui +cd alsa-scarlett-gui +``` + +To build: + +``` +cd src +make -j4 +``` + +To run: + +``` +./alsa-scarlett-gui +``` + +You can install it into `/usr/local` (binary, desktop file, and icon) +with: + +``` +sudo make install +``` + +And uninstall with: + +``` +sudo make uninstall +``` + +Continue on to reading [USAGE.md](USAGE.md) for how to use the GUI. + +## Flatpak + +With Flatpak, in any distro: + +``` +flatpak-builder --user --install --force-clean flatpak-build \ + vu.b4.alsa-scarlett-gui.yml +``` + +Be sure to use `flatpak-build` as the directory where the flatpak is +built or hence you risk bundling the artifacts when committing! + +If you get messages like these: + +``` +Failed to init: Unable to find sdk org.gnome.Sdk version 45 +Failed to init: Unable to find runtime org.gnome.Platform version 45 +``` + +Then install them: + +``` +flatpak install org.gnome.Sdk +flatpak install org.gnome.Platform +``` + +If you get: + +``` +Looking for matches… +error: No remote refs found for ‘org.gnome.Sdk’ +``` + +Then: + +``` +flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo +``` diff --git a/docs/OLDKERNEL.md b/docs/OLDKERNEL.md new file mode 100644 index 0000000..9735d33 --- /dev/null +++ b/docs/OLDKERNEL.md @@ -0,0 +1,73 @@ +# ALSA Scarlett2 Usage With Old Kernels + +Linux kernel 6.7 (check your version with `uname -r`) was the first +kernel version with this driver enabled by default. It’s recommended +that you run 6.7 or later, or build the backported driver for your +kernel. If you do, then these instructions aren’t relevant; continue +with [INSTALL.md](INSTALL.md) for prerequisites, how to build, +install, and run `alsa-scarlett-gui`. + +If you’ve got a Scarlett Gen 2 or 3 or a Clarett+ 8Pre and don’t mind +the level meters not working, then the minimum kernel versions are: + +- **Scarlett Gen 2**: Linux 5.4 (bugs fixed in Linux 5.14) +- **Scarlett Gen 3**: Linux 5.14 +- **Clarett+ 8Pre**: Linux 6.1 + +## Linux Kernel with Backported Driver (recommended) + +Install the latest version of the backported driver from here: + +https://github.com/geoffreybennett/scarlett-gen2/releases + +then you can ignore the instructions below. + +## Linux Kernel before 6.7 without Backported Driver + +If you’re running a kernel before 6.7 without the backported driver, +you need to enable it at module load time with the `device_setup=1` +option to insmod/modprobe. Create a file +`/etc/modprobe.d/scarlett.conf` containing the appropriate line for +your device: + +Scarlett Gen 2: + +- **6i6**: `options snd_usb_audio vid=0x1235 pid=0x8203 device_setup=1` +- **18i8**: `options snd_usb_audio vid=0x1235 pid=0x8204 device_setup=1` +- **18i20**: `options snd_usb_audio vid=0x1235 pid=0x8201 device_setup=1` + +Scarlett Gen 3: + +- **Solo**: `options snd_usb_audio vid=0x1235 pid=0x8211 device_setup=1` +- **2i2**: `options snd_usb_audio vid=0x1235 pid=0x8210 device_setup=1` +- **4i4**: `options snd_usb_audio vid=0x1235 pid=0x8212 device_setup=1` +- **8i6**: `options snd_usb_audio vid=0x1235 pid=0x8213 device_setup=1` +- **18i8**: `options snd_usb_audio vid=0x1235 pid=0x8214 device_setup=1` +- **18i20**: `options snd_usb_audio vid=0x1235 pid=0x8215 device_setup=1` + +Clarett+: + +- **8Pre**: `options snd_usb_audio vid=0x1235 pid=0x820c device_setup=1` + +Or you can use a sledgehammer: +``` +options snd_usb_audio device_setup=1,1,1,1 +``` +to pass that option to the first 4 USB audio devices. + +To see if the driver is present and enabled: `dmesg | grep -i -A 5 -B +5 focusrite` should display information like: + +``` +New USB device found, idVendor=1235, idProduct=8215, bcdDevice= 6.0b +Product: Scarlett 18i20 USB +Focusrite Scarlett Gen 2/3 Mixer Driver enabled pid=0x8215 +``` + +If the driver is disabled you’ll see a message like: + +``` +Focusrite Scarlett Gen 2/3 Mixer Driver disabled; use options +snd_usb_audio vid=0x1235 pid=0x8215 device_setup=1 to enable and +report any issues to g@b4.vu +``` diff --git a/docs/USAGE.md b/docs/USAGE.md new file mode 100644 index 0000000..c64fb82 --- /dev/null +++ b/docs/USAGE.md @@ -0,0 +1,144 @@ +# ALSA Scarlett2 Control Panel Usage + +Refer to [INSTALL.md](INSTALL.md) for prerequisites, how to build, +install, and run. + +## No interface connected + +If no interface is detected (usually because there isn’t one +connected!) you’ll see this window: + +![No Interface Connected](../img/iface-none.png) + +Plug in an interface or select the menu option File → Interface +Simulation and load a demo file to make more interesting things +happen. + +## First Time Usage + +If your interface is fresh out of the box (or you haven’t updated it +using the manufacturer’s software), you may need to update the +firmware and/or disable MSD Mode first. + +### Firmware Update Required + +Some interfaces require a firmware update before all their +functionality is available. If the firmware is not available on your +system, you’ll see this window: + +![Firmware Update Required (Firmware +Missing)](../img/firmware-missing.png) + +In this case, click on the link, download and install the firmware +package, then restart `alsa-scarlett-gui`. + +If a firmware update is required and the firmware is available, you’ll +see this window: + +![Firmware Update Required](../img/firmware-update-required.png) + +Click “Update”, then “Yes” to update the firmware. + +![Firmware Update Progress](../img/firmware-updating.png) + +The update will take about 15 seconds, and then your interface will +restart, showing the main window. + +### MSD (Mass Storage Device/Quick Start/Easy Start) Mode + +If MSD Mode is enabled (as it is from the factory) and a firmware +update is not available or required, then you’ll see this window: + +![MSD Mode](../img/iface-msd.png) + +Click the “Enabled” button to disable MSD Mode, then click “Reboot” to +restart the interface, and in a moment the main window will appear. + +## Startup Controls + +The View → Startup menu option opens a window to configure settings +that only take effect when the interface is powered on. + +The options common to all interfaces are: + +- **Reset Configuration**: this will reset the configuration to the + factory defaults. This is particularly useful with the 4th Gen and + Vocaster interfaces if you’ve made a mess of the configuration and + want to start again. + +- **Update Firmware**: if a firmware update is found in the + `/usr/share/firmware/scarlett2` directory, then an option to update + the firmware will be available here. + +## File Menu + +The File menu contains options to load and save the configuration, +load a configuration in simulation mode, and to exit the application. + +### Load/Save Configuration + +The entire state of the interface can be loaded and saved using the +File → Load Configuration and File → Save Configuration menu options. + +Internally, this uses `alsactl`: + +- **Load**: `alsactl restore USB -f ` +- **Save**: `alsactl store USB -f ` + +The saved state files can be used to simulate an interface if you +don’t have one attached. The `demo` directory in the distribution +contains a sample file for every supported model. + +### Interface Simulation Mode + +The GUI can load an `alsactl` state file saved from a real interface +and display a GUI as if the corresponding interface was connected. + +This is useful if you don’t have an interface connected and want to +try, develop, or debug the GUI. + +Either specify the `.state` filename on the command line or select the +menu option File → Interface Simulation to load. + +## Interface Controls + +The controls and menu items which are available vary widely, depending +on your specific interface. + +There are three broad categories of interfaces with different +capabilities; each category of interface is described in a separate +ocument: + +- [Scarlett 3rd Gen Solo and 2i2](iface-small.md) + + Minimal number of controls, and they mostly accessible through + hardware buttons anyway. Not very interesting. + +- [Scarlett 2nd Gen 6i6+, 3rd Gen 4i4+, Clarett USB, and + Clarett+](iface-large.md) + + Full routing and mixing capabilities. + +- [Scarlett 4th Gen](iface-4th-gen.md) + + Full routing and mixing capabilities, remote-controlled input gain, + but no output controls. + +## Known Bugs/Issues + +- Load/Save uses `alsactl` which will be confused if the ALSA + interface name (e.g. `USB`) changes. + +- Load/Save is not implemented for simulated interfaces. + +- The read-only status of controls in interface simulation mode does + not change when the HW/SW button is clicked. + +- When there’s more than one main window open, closing one of them + doesn’t free and close everything related to that card. + +- There is no facility to group channels into stereo pairs (needs + kernel support to save this information in the interface). + +- There is no facility to give channels custom names (needs kernel + support to save this information in the interface). diff --git a/docs/iface-4th-gen.md b/docs/iface-4th-gen.md new file mode 100644 index 0000000..2256507 --- /dev/null +++ b/docs/iface-4th-gen.md @@ -0,0 +1,349 @@ +# ALSA Scarlett2 Control Panel + +## Scarlett 4th Gen Interfaces + +This document describes how to use the ALSA Scarlett2 Control Panel +with the Scarlett 4th Gen interfaces: + +- Scarlett 4th Gen Solo, 2i2, and 4i4 + +### Comparison with earlier Scarlett and Clarett Interfaces + +If you are familiar with the Scarlett 2nd and 3rd Gen interfaces or +the Clarett interfaces, the major differences to the 4th Gen +interfaces from the point of view of this software are: + +- The 4th Gen Solo and 2i2 interfaces have the full routing and mixing + capabilities of the larger 2nd and 3rd Gen and Clarett interfaces + (although the line outputs and the headphone outputs are still + linked). + +- The 4th Gen 2i2 and 4i4 interfaces have software-controllable + (“remote”) input gain controls. + +- The 4th Gen interfaces don’t have the output volume and mute + controls that the 2nd and 3rd Gen and Clarett interfaces have. + +- The Air mode with Presence+Drive is implemented with a DSP which is + separately routable. + +## Main Window + +The main window is divided into three sections: +- Global Controls +- Analogue Input Controls +- Analogue Output Controls + +The main window for the Solo and 2i2 interfaces is shown below; the +4i4 interface is similar to the 2i2, but doesn’t have the Direct +Monitor control, and can show the position of the front panel volume +knobs. + +![Main Window](../img/iface-4th-gen.png) + +### Global Controls + +#### Sync Status + +Sync Status indicates if the interface is locked to a valid digital +clock. This should only ever briefly show “Unlocked” when the sample +rate is changed as these interfaces can only use their internal clock. + +#### Power + +The 4i4 has a “Power” control that displays the power status. It can +be “Fail”, “Bus”, or “External”. “Fail” means that the interface is +not receiving sufficient power; please see the Scarlett 4i4 4th Gen +User Guide for more information. “Bus” vs. “External” indicates +whether the interface is receiving power from the second USB-C port +(“External”) or not (“Bus”). + +#### Sample Rate + +Sample Rate is informative only, and displays the current sample rate +if the interface is currently in use. In ALSA, the sample rate is set +by the application using the interface, which is usually a sound +server such as PulseAudio, JACK, or PipeWire. + +### Analogue Input Controls + +The analogue input controls available depend on the interface model: + +- **Instrument, Air, and Phantom Power**: All models +- **Mix**: Solo only (described later in the [Solo Mix + Control](#solo-mix-control) section) +- **Input Select, Link, Gain, Autogain, and Safe**: 2i2 and 4i4 + +#### Instrument + +The Inst button(s) are used to select between Mic/Line and Instrument +level/impedance. When plugging in microphones or line-level equipment +(such as a synthesizer, external preamp, or effects processor) to the +input, set it to “Line”. The “Inst” setting is for instruments with +pickups such as guitars. + +#### Air + +The Scarlett 3rd Gen introduced Air mode which transformed your +recordings and inspired you while making music by boosting the +signal’s high-end. The 4th Gen interfaces now call that “Air Presence” +and add a new mode “Air Presence+Drive” which boosts mid-range +harmonics in your sound. + +#### Phantom Power (48V) + +Turning the “48V” switch on sends “Phantom Power” to the XLR +microphone input. This is required for some microphones (such as +condensor microphones), and damaging to some microphones (particularly +vintage ribbon microphones). + +The 2i2 has a single 48V switch that controls both channels, and the +4i4 has an independent 48V switch for each channel. + +#### Input Select + +The 2i2 and 4i4 interfaces have hardware buttons for 48V, Inst, Air, +Auto, and Safe. The “Input Select” control allows you to choose which +channel those buttons control. + +#### Link + +The “Link” control links the 48V, Inst, Air, Auto, and Safe controls +together so that they control both channels simultaneously. + +#### Gain + +The “Gain” controls adjust the input gain for the selected channel. +Click and drag up/down on the control to adjust the gain, use your +mouse scroll wheel, or click the control to select it and use the +arrow keys, Page Up, Page Down, Home, and End keys. + +#### Autogain + +When the “Autogain” control is enabled, the interface will listen to +the input signal for ten seconds and automatically adjust the gain to +get the best signal level. When autogain is not running, the +most-recent autogain exit status is shown below the “Autogain” +control. + +#### Safe + +“Safe” mode is a feature that automatically reduces the gain if the +signal is too loud. This can be useful to prevent clipping. + +### Analogue Output Controls + +The analogue output controls available depend on the interface model: + +- **Direct Monitor**: Solo and 2i2 +- **Volume Knobs**: 4i4 + +#### Direct Monitor + +Enabling Direct Monitor sends the analogue input signals to the +analogue outputs (speakers/headphones) for zero-latency monitoring. + +On the 2i2, you have the choice of Mono or Stereo monitoring when you +click the button: + +- **Mono** sends both inputs to the left and right outputs +- **Stereo** sends input 1 to the left, and input 2 to the right + output. + +As the 4th Gen Solo and 2i2 interfaces have the full routing and +mixing capabilities of the larger 2nd and 3rd Gen interfaces, the +Direct Monitor levels can be [adjusted in the +mixer](#solo-direct-monitor). + +The 4i4 has no Direct Monitor button, but that functionality can be +achieved with [appropriate configuration in the routing and mixing +windows](#4i4-sample-direct-monitor-configuration). + +#### Volume Knobs + +The 4i4 interface has volume knobs on the front panel, the position of +which is shown in the main window. + +## Routing and Mixing + +The routing and mixing capabilities of the 4th Gen interfaces are the +same in concept as the 2nd and 3rd Gen interfaces, but there is a DSP +which is separately routable, and the default routing uses the mixer +extensively. + +From the main window, open the Routing window with the View → Routing +menu option or pressing Ctrl-R: + +![4th Gen 2i2 Routing](../img/scarlett-4th-gen-2i2-routing.png) + +To understand the signal flow, note the following: +1. The Analogue 1 & 2 Inputs (i.e. the Mic/Line/Inst inputs) are + routed to the DSP Inputs. +2. The DSP Outputs are routed to the PCM 1 & 2 Inputs (that’s what + ALSA sees as the first two inputs from the interface for + recording). +3. The PCM Outputs (that’s what ALSA sees as the interface outputs for + playback) and the DSP Outputs are all connected to the Mixer + Inputs. +4. The Mixer A & B Outputs are connected to the Hardware Analogue + outputs (i.e. your speakers/headphones) so you can hear any mix of + the PCM and DSP Outputs (this is how the Direct Monitor function + works). +5. The Mixer C & D Outputs are connected to the PCM 3 & 4 Inputs (this + is referred to as Loopback, for recording audio from your computer, + but can be used for another purpose if you want). + +Important Notes: +- The “Presets” are generally not useful with the 4th Gen interfaces + as they are designed for the 2nd and 3rd Gen interfaces. If you try + these out, you’ll probably want to reset back to the factory + defaults afterwards. +- Besides Air Mode, the DSP is also used for the gain halo level + meters and autogain, so if you route something else to the DSP + Inputs, those features will work “rather differently”. +- The Focusrite Control 2 software can’t control most of this routing, + so if you make changes here and then want to use Focusrite Control + 2, you’ll probably need to reset the routing back to the factory + default settings. There’s currently no way to reset to factory + default settings from the Focusrite Control 2 software; you’ll need + to use the [Reset Configuration](USAGE.md#startup-controls) option + in this software, or the `scarlett2` utility. + +To adjust the routing: + +- Click and drag from a source to a sink or a sink to a source to + connect them. Audio from the source will then be sent to that sink. + +- Click on a source or a sink to clear the links connected to that + source/sink. + +Note that a sink can only be connected to one source, but one source +can be connected to many sinks. + +To adjust the mixer output levels: + +1) Open the mixer window with the main window View → Mixer menu + option, or press Ctrl-M. + +2) Mixer levels can be adjusted with your keyboard or mouse in the + same way as the [Gain Controls](#gain). + +### Solo Direct Monitor + +When you enable or disable Direct Monitor on the Solo interface, the +interface will update the Mix A and B Outputs so that the DSP 1 & 2 +Outputs are mixed in (or not) with the PCM 1 & 2 Outputs. Note how the +volume of the PCM outputs is also reduced when Direct Monitor is +enabled so that you can hear the DSP outputs (i.e. your Analogue +inputs) more clearly. + +![4th Gen Solo Direct Monitor](../img/scarlett-4th-gen-solo-monitor.gif) + +If you customise the Mix A/B mixer levels while Direct Monitor is +enabled, the new settings will be saved and used when Direct Monitor +is enabled again. + +### 2i2 Direct Monitor + +Similarly to the Solo interface, the 2i2 interface will update the Mix +A and B Outputs when you enable or disable Direct Monitor, but the 2i2 +has Mono and Stereo options: + +![4th Gen 2i2 Direct Monitor](../img/scarlett-4th-gen-2i2-monitor.gif) + +Note how in Mono mode: + +- the DSP 1 & 2 Outputs are mixed to both the left and right outputs + +and in Stereo mode: + +- DSP 1 (i.e. Analogue Input 1) is sent to the left output (Mix A), + and +- DSP 2 (i.e. Analogue Input 2) is sent to the right output (Mix B). + +### Solo Mix Control + +The Mix control is only available on the Solo interface. It switches +the source for the PCM 1 & 2 Inputs between the DSP Outputs and the +Mixer E & F Outputs. + +![4th Gen Solo Mix Control](../img/scarlett-4th-gen-solo-mix.gif) + +By default, enabling this control will mix the Analogue 1 & 2 Inputs +together before they are sent to the PCM 1 & 2 Inputs: + +![4th Gen Solo Mixer E & F Outputs](../img/scarlett-4th-gen-solo-mix-e-f.png) + +This can be useful if you want to treat the PCM 1 & 2 Inputs as a +stereo pair, and not have the line/instrument input panned hard left +and the microphone input panned hard right. + +The mixer levels for the Mix E & F Outputs can adjusted to suit. + +### 4i4 Routing and Mixing + +Although the 4th Gen 4i4 has no explicit Direct Monitor control, it is +far more flexible because it has 6 PCM inputs, 6 PCM outputs, a 10×6 +mixer, and 6 Analogue Hardware outputs. + +Analogue Outputs 1–4 correspond to the Line Outputs 1–4 on the back of +the interface, and Analogue Outputs 5–6 correspond to the Headphone +Output on the front of the interface. + +The default routing and mix for the 4i4 is shown below: + +![4th Gen 4i4 Routing](../img/scarlett-4th-gen-4i4-routing.png) + +Note that with the default routing/mix settings: +- The Analogue Inputs 1–4 are routed to the PCM Inputs 1–4 (the first + two going via the DSP). +- PCM Inputs 5–6 are used for Loopback (recording audio from your + computer). +- All the Hardware Inputs and PCM Outputs are connected to the Mixer + Inputs. +- PCM Outputs 1–4 are connected to the Analogue Outputs 1–4 (via the + mixer). +- The Line 1–2 Outputs (Analogue Outputs 1–2) and the Headphones + (Analogue Outputs 5–6) share the Mixer Outputs A & B. + +#### 4i4 Sample Direct Monitor Configuration + +A common configuration for the 4i4 is to send the PCM 1 & 2 Outputs +mixed with the Analogue Inputs 1 & 2 to the Headphones, while leaving +the Analogue Outputs 1–2 as they are. This is an advanced version of +the direct monitoring feature that is available on the Solo and 2i2. +It can be implemented by: + +1) Route Mixer Outputs E & F to Analogue Outputs 5 & 6. +2) Turn up Mix E & F DSP 1 & 2 levels in the mixer (see the mixer + example above for [2i2 Direct Monitor](#2i2-direct-monitor)). + +As there are only 6 Mixer Outputs, the PCM 5 & 6 Inputs (Loopback) are +now shared with the headphones. If you want to retain the Loopback +functionality without having the Analogue Inputs mixed in, you could: +- Route the PCM 1 & 2 Outputs directly to the PCM 5 & 6 Inputs, rather + than going via the mixer, or +- Free up Mixer Outputs A & B for Loopback by routing PCM Outputs 1 & + 2 directly to Analogue Outputs 1 & 2. + +Besides Direct Monitor, there are many other possibilities for +routing/mixing with the 4i4. For example, by using the additional PCM +Outputs and Inputs you could set up a mix-minus configuration for a +podcast/video call. + +## Levels + +The meters show the levels seen by the interface at every routing +sink: Hardware Outputs, Mixer Inputs, DSP Inputs, and PCM Inputs. Open +this window by selecting the View → Levels menu option or pressing +Ctrl-L. + +![Levels](../img/window-levels-4th-gen.gif) + +Look at this in conjunction with the routing window to understand +which meter corresponds to which source or sink. + +Thanks for reading this far! If you appreciate the hundreds of hours +of work that went into the kernel driver, the control panel, and this +documentation, please consider supporting the author with a +[donation](../README.md#donations). diff --git a/docs/iface-large.md b/docs/iface-large.md new file mode 100644 index 0000000..79e421c --- /dev/null +++ b/docs/iface-large.md @@ -0,0 +1,321 @@ +# ALSA Scarlett2 Control Panel + +## Large Scarlett 2nd and 3rd Gen and Clarett Interfaces + +This document describes how to use the ALSA Scarlett2 Control Panel +with the larger Scarlett 2nd Gen, 3rd Gen, and Clarett USB interfaces: + +- Scarlett 2nd Gen 6i6, 18i8, 18i20 +- Scarlett 3rd Gen 4i4, 8i6, 18i8, 18i20 +- Clarett 2Pre, 4Pre, 8Pre USB +- Clarett+ 2Pre, 4Pre, 8Pre + +## Main Window + +The main window is divided into three sections: + +- Global Controls +- Analogue Input Controls +- Analogue Output Controls + +The particular controls available depend on the interface model; the +3rd Gen 18i20 has all the controls so is shown here: + +![Main Window](../img/window-main.png) + +Note that the View menu option lets you open three other windows which +contain additional controls, described in the following sections: +- [Routing](#routing) +- [Mixer](#mixer) +- [Levels](#levels) +- [Startup](#startup) + +### Global Controls + +Global controls relate to the operation of the interface as a whole. + +![Global Controls](../img/main-global.png) + +#### Clock Source (interfaces with S/PDIF or ADAT inputs only) + +Clock Source selects where the interface receives its digital clock +from. If you aren’t using S/PDIF or ADAT inputs, set this to Internal. + +#### Sync Status + +Sync Status indicates if the interface is locked to a valid digital +clock. If you aren’t using S/PDIF or ADAT inputs and the Sync Status +is Unlocked, change the Clock Source to Internal. + +#### Sample Rate + +Sample Rate is informative only, and displays the current sample rate +if the interface is currently in use. In ALSA, the sample rate is set +by the application using the interface, which is usually a sound +server such as PulseAudio, JACK, or PipeWire. + +#### Speaker Switching (Scarlett 3rd Gen 18i8 and 18i20 only) + +Speaker Switching lets you swap between two pairs of monitoring +speakers very easily. + +When enabled (Main or Alt): + +- Line Out 1–4 Volume Control Switches are locked to HW +- Line Out 3/4 routing is saved +- Line Out 3/4 routing is set to the Line Out 1/2 routing + +When set to Main, Line outputs 3 and 4 are muted. + +When set to Alt, Line outputs 1 and 2 are muted. + +When disabled (Off): +- Global mute is activated ⭐ +- Line Out 1–4 Volume Control Switches are unlocked +- Line Out 3/4 routing is restored to the saved values + +⭐ You likely won’t expect this to happen. Make sure to unmute the +outputs after disabling speaker switching if you want to hear +something again. + +#### Talkback (Scarlett 3rd Gen 18i20 only) + +Talkback lets you add another channel (usually the talkback mic) to a +mix with a button push, usually to talk to musicians, and without +using an additional mic channel. + +The Talkback feature has a few parts: + +- Talkback Microphone connected to Analogue Input 9 +- Talkback Disable/Enable and Off/On software switches +- Talkback Off/On physical switch +- Talkback Mix (one switch per mix) +- Mix Input 25 + +To set up the talkback feature, set Mix Input 25 to the talkback +source (usually Analogue Input 9), enable the Talkback Mix switches +for the mixes you want the talkback input to be heard on, and change +the Talkback control from Disabled to Off. Leave the Mix Input 25 gain +controls at zero (−127dB), otherwise the talkback inputs will be heard +even when talkback is disabled/off. + +Pressing the Talkback switch on the device will then lower the volume +of the other inputs on the mixes for which talkback is enabled and +unmute Mix Input 25 on those mixes. + +Talkback can also be activated by changing the Talkback control from +Off to On. + +The talkback microphone can also be used just the same as any of the +other analogue inputs and routed to a physical output, PCM input, or +mixer input. + +### Analogue Input Controls + +This section is applicable to all interfaces except the Scarlett 2nd +Gen 18i20 which has hardware-only buttons for these features. + +![Analogue Input Controls](../img/main-inputs.png) + +#### Inst + +The Inst buttons are used to select between Mic/Line and Instrument +level/impedance. When plugging in microphones or line-level equipment +(such as a synthesizer, external preamp, or effects processor) to the +input, set it to “Line”. The “Inst” setting is for instruments with +pickups such as guitars. + +#### Air (Scarlett 3rd Gen and Clarett only) + +Enabling Air will transform your recordings and inspire you while +making music. + +#### Pad + +Enabling Pad engages a 10dB attenuator in the channel, giving you more +headroom for very hot signals. + +#### Phantom Power (48V) + +Scarlett 2nd Gen and Clarett devices have a hardware button for +controlling phantom power. + +Scarlett 3rd Gen devices have hardware and software control of phantom +power. Turning the “48V” switch on sends “Phantom Power” to the XLR +microphone input. This is required for some microphones (such as +condensor microphones), and damaging to some microphones (particularly +vintage ribbon microphones). + +On Scarlett 3rd Gen devices, phantom power is turned off by default +when the interface is turned on. This can be changed in the startup +configuration (menu option View → Startup). + +### Analogue Output Controls + +The analogue output controls let you set the output volume (gain) on +the analogue line out and headphone outputs. All interfaces support +setting the gain and muting individual channels. + +![Analogue Output Controls](../img/main-outputs.png) + +Click and drag up/down on the volume dial to change the volume, use +your arrow keys, Home/End/PgUp/PgDn keys, or use your mouse scroll +wheel to adjust. You can also double-click on it to quickly toggle the +volume between off and 0dB. + +The biggest interfaces: Scarlett 2nd Gen 18i20, 3rd Gen 18i8, and 3rd +Gen 18i20 have a switchable hardware/software volume control. The +position of the big volume knob on the front of the interface is +indicated by the “HW” dial in the GUI. The analogue outputs can have +their volume set either by the knob (“HW” setting of of the HW/SW +button) or by the dials on each output (“SW” setting of the HW/SW +button). + +When set to HW, the mute/volume status for those channels is +controlled by the hardware volume knob and the global dim/mute +controls and the software volume dial and mute button for those +channels are disabled. + +There are “mute” and “dim” (reduce volume) buttons below the “HW” dial +which affect only the outputs with “HW” control enabled. The 3rd Gen +18i8 doesn’t have physical buttons or indicator lights for these +controls, but the 18i20 devices do. + +On the other (smaller) interfaces, the big volume knob on the front of +the interface controls the volume of the Line 1 and 2 outputs. This is +in addition to the software volume control, therefore both must be +turned up in order to hear anything. The other (line 3+) analogue +outputs are only controlled by the software controls. + +The volume controls for the headphone outputs on each interface +operate in addition to any other hardware or software volume controls +for those channels. When using headphones, the volumes for those +channels would usually be set to 0dB and the actual volume controlled +with the physical headphone volume control(s). + +## Routing + +The routing window allows complete control of signal routing between +the hardware inputs/outputs, internal mixer, and PCM (USB) +inputs/outputs. + +![Routing Window](../img/window-routing.png) + +To manage the routing connections: + +- Click and drag from a source to a sink or a sink to a source to + connect them. Audio from the source will then be sent to that sink. + +- Click on a source or a sink to clear the links connected to that + source/sink. + +Note that a sink can only be connected to one source, but one source +can be connected to many sinks. If you want a sink to receive input +from more than one source, use the mixer inputs and outputs: + +- Connect the sources that you want to mix together to mixer inputs +- Connect mixer outputs to the sinks that you want to receive the + mixed audio +- Use the Mixer window to set the amount of each mixer input that is + sent to each mixer output + +The Presets menu can be used to clear all connections, or to set up +common configurations: + +- The “Direct” preset sets up the usual configuration using the + interface as a regular audio interface by connecting: + + - all Hardware Inputs to PCM Inputs + - all PCM Outputs to Hardware Outputs + +- The “Preamp” preset connects all Hardware Inputs to Hardware + Outputs. + +- The “Stereo Out” preset connects PCM 1 and 2 Outputs to pairs of + Hardware Outputs. + +The Direct routing configuration is the simplest most-generally-useful +configuration: + +![Direct Routing](../img/routing-direct.png) + +### Loopback + +Scarlett 2nd Gen, Clarett USB, and Clarett+ interfaces have as many +PCM Inputs as Hardware Inputs. Scarlett 3rd Gen interfaces have two +more PCM Inputs which Focusrite Control uses as “Loopback” inputs. + +The “Loopback” feature advertised for Scarlett 3rd Gen devices is +actually a limitation of the proprietary Focusrite Control software. +All supported devices with a mixer (that’s all but the 2nd and 3rd Gen +Solo/2i2 interfaces) support full reassignment of the PCM Inputs, so +you can have any PCM Input as a “Loopback” or assigned to any other +source. + +### Talkback + +The Scarlett 3rd Gen 18i20 talkback microphone is Analogue Input 9 and +can be routed like any other source. If you want to record using it, +there is no need for the loopback hack suggested by the manufacturer. +Just route it to a PCM Input. + +## Mixer + +If you use the Routing window to connect Sources to Mixer Inputs and +Mixer Outputs to Sinks, then you can use the Mixer window to set the +amount of each Mixer Input that is sent to each Mixer Output using a +matrix of controls: + +![Mixer Window](../img/window-mixer.png) + +Click and drag up/down on the gain controls to adjust, or use your +mouse scroll wheel. You can also double-click on the control to +quickly toggle between off and 0dB. + +## Levels + +The Levels window shows the current levels of the hardware outputs, the +mixer inputs, and the PCM inputs. + +![Levels Window](../img/window-levels-3rd-gen.png) + +Look at this in conjunction with the routing window to understand +which meter corresponds to which source or sink. + +## Startup + +The Startup window is used to configure settings that are +applied/relevant when the interface is powered on. + +![Startup Window](../img/window-startup.png) + +### Standalone + +When Standalone mode is enabled, the interface will continue to route +audio as per the previous routing and mixer settings after it has been +disconnected from a computer. By configuring the routing between the +hardware and mixer inputs and outputs appropriately, the interface can +act as a standalone preamp or mixer. + +Standalone mode is supported on all devices supported by the kernel +driver. Even the Scarlett 3rd Gen 4i4 (which is bus-powered) will +operate in standalone mode. + +### Phantom Power Persistence (Scarlett 3rd Gen only) + +When Phantom Power Persistence is enabled, the interface will restore +the previous Phantom Power/48V setting when the interface is turned +on. For the safety of microphones which can be damaged by phantom +power, the interface defaults to having phantom power disabled when it +is turned on. + +### Reset Configuration + +This will reset the configuration of the interface to the factory +defaults (except for MSD mode which is left off). + +### Update Firmware + +If a firmware update is found in the `/usr/share/firmware/scarlett2` +directory, then an option to update the firmware will be available +here. diff --git a/docs/iface-small.md b/docs/iface-small.md new file mode 100644 index 0000000..b0a1d40 --- /dev/null +++ b/docs/iface-small.md @@ -0,0 +1,56 @@ +# ALSA Scarlett2 Control Panel + +## Small Scarlett 3rd Gen Interfaces + +The Scarlett 3rd Gen Solo and 2i2 interfaces have just a few buttons to control +the Air, Line, Phantom Power, and Direct Monitor settings. Mostly +nothing that you can’t access from the front panel anyway. + +![Gen 3 Small Interfaces](../img/iface-small-gen3.png) + +## Input Controls + +### Air + +Enabling Air will transform your recordings and inspire you while +making music. + +### Inst + +The Inst buttons are used to select between Mic/Line and Instrument +level/impedance. When plugging in microphones or line-level equipment +(such as a synthesizer, external preamp, or effects processor) to the +input, set it to “Line”. The “Inst” setting is for instruments with +pickups such as guitars. + +### 48V (Phantom Power) + +Turning the “48V” switch on sends “Phantom Power” to the XLR +microphone input(s). This is required for some microphones (such as +condensor microphones), and damaging to some microphones (particularly +vintage ribbon microphones). + +## Output Controls + +### Direct Monitor + +Direct Monitor sends the analogue input signals to the analogue +outputs for zero-latency monitoring. + +On the 2i2, you have the choice of Mono or Stereo monitoring when you +click the button. Mono sends both inputs to the left and right +outputs. Stereo sends input 1 to the left, and input 2 to the right +output. + +## Startup Controls + +#### Phantom Power Persistence + +By default, phantom power is turned off when the interface is turned +on. This can be changed in the startup configuration (menu option View +→ Startup). + +The one control not accessible from the front panel is “Phantom Power +Persistence” (menu option View → Startup) which controls the Phantom +Power state when the interface is powered on. + diff --git a/img/demo.gif b/img/demo.gif index b40f5d3..028f912 100644 Binary files a/img/demo.gif and b/img/demo.gif differ diff --git a/img/firmware-missing.png b/img/firmware-missing.png new file mode 100644 index 0000000..667b5de Binary files /dev/null and b/img/firmware-missing.png differ diff --git a/img/firmware-update-required.png b/img/firmware-update-required.png new file mode 100644 index 0000000..a598b84 Binary files /dev/null and b/img/firmware-update-required.png differ diff --git a/img/firmware-updating.png b/img/firmware-updating.png new file mode 100644 index 0000000..a0723aa Binary files /dev/null and b/img/firmware-updating.png differ diff --git a/img/iface-4th-gen.png b/img/iface-4th-gen.png new file mode 100644 index 0000000..77d8952 Binary files /dev/null and b/img/iface-4th-gen.png differ diff --git a/img/iface-msd.png b/img/iface-msd.png index d7db9fc..02420b5 100644 Binary files a/img/iface-msd.png and b/img/iface-msd.png differ diff --git a/img/iface-none.png b/img/iface-none.png index df0dbe7..025e23d 100644 Binary files a/img/iface-none.png and b/img/iface-none.png differ diff --git a/img/iface-small-gen3.png b/img/iface-small-gen3.png index e9f63e4..503bf0d 100644 Binary files a/img/iface-small-gen3.png and b/img/iface-small-gen3.png differ diff --git a/img/main-global.png b/img/main-global.png new file mode 100644 index 0000000..a307af9 Binary files /dev/null and b/img/main-global.png differ diff --git a/img/main-inputs.png b/img/main-inputs.png new file mode 100644 index 0000000..1b823fe Binary files /dev/null and b/img/main-inputs.png differ diff --git a/img/main-outputs.png b/img/main-outputs.png new file mode 100644 index 0000000..c203773 Binary files /dev/null and b/img/main-outputs.png differ diff --git a/img/routing-direct.png b/img/routing-direct.png index 66192c4..b43badb 100644 Binary files a/img/routing-direct.png and b/img/routing-direct.png differ diff --git a/img/scarlett-4th-gen-2i2-monitor.gif b/img/scarlett-4th-gen-2i2-monitor.gif new file mode 100644 index 0000000..985191a Binary files /dev/null and b/img/scarlett-4th-gen-2i2-monitor.gif differ diff --git a/img/scarlett-4th-gen-2i2-routing.png b/img/scarlett-4th-gen-2i2-routing.png new file mode 100644 index 0000000..60f48d1 Binary files /dev/null and b/img/scarlett-4th-gen-2i2-routing.png differ diff --git a/img/scarlett-4th-gen-4i4-routing.png b/img/scarlett-4th-gen-4i4-routing.png new file mode 100644 index 0000000..0f52437 Binary files /dev/null and b/img/scarlett-4th-gen-4i4-routing.png differ diff --git a/img/scarlett-4th-gen-solo-mix-e-f.png b/img/scarlett-4th-gen-solo-mix-e-f.png new file mode 100644 index 0000000..cfcba88 Binary files /dev/null and b/img/scarlett-4th-gen-solo-mix-e-f.png differ diff --git a/img/scarlett-4th-gen-solo-mix.gif b/img/scarlett-4th-gen-solo-mix.gif new file mode 100644 index 0000000..03cfd3b Binary files /dev/null and b/img/scarlett-4th-gen-solo-mix.gif differ diff --git a/img/scarlett-4th-gen-solo-monitor.gif b/img/scarlett-4th-gen-solo-monitor.gif new file mode 100644 index 0000000..0d6b389 Binary files /dev/null and b/img/scarlett-4th-gen-solo-monitor.gif differ diff --git a/img/window-levels-3rd-gen.png b/img/window-levels-3rd-gen.png new file mode 100644 index 0000000..fdd2f7f Binary files /dev/null and b/img/window-levels-3rd-gen.png differ diff --git a/img/window-levels-4th-gen.gif b/img/window-levels-4th-gen.gif new file mode 100644 index 0000000..3bbc579 Binary files /dev/null and b/img/window-levels-4th-gen.gif differ diff --git a/img/window-main.png b/img/window-main.png index 9123a4a..fda19d2 100644 Binary files a/img/window-main.png and b/img/window-main.png differ diff --git a/img/window-mixer.png b/img/window-mixer.png index b333c5a..00b8f7e 100644 Binary files a/img/window-mixer.png and b/img/window-mixer.png differ diff --git a/img/window-routing.png b/img/window-routing.png index 7bd87c2..5b3a315 100644 Binary files a/img/window-routing.png and b/img/window-routing.png differ diff --git a/img/window-startup.png b/img/window-startup.png index 2907f34..617f767 100644 Binary files a/img/window-startup.png and b/img/window-startup.png differ diff --git a/src/Makefile b/src/Makefile index e5564f8..4ccf95d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,14 +1,19 @@ -# SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +# SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett # SPDX-License-Identifier: GPL-3.0-or-later # Credit to Tom Tromey and Paul D. Smith: # http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/ -VERSION := $(shell git describe --abbrev=4 --dirty --always --tags) +VERSION := $(shell \ + git describe --abbrev=4 --dirty --always --tags 2>/dev/null || \ + echo $${APP_VERSION:-Unknown} \ +) + DEPDIR := .deps DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d -CFLAGS := -Wall -Werror -ggdb -fno-omit-frame-pointer -O2 -D_FORTIFY_SOURCE=2 +CFLAGS ?= -ggdb -fno-omit-frame-pointer -O2 +CFLAGS += -Wall -Werror -D_FORTIFY_SOURCE=2 CFLAGS += -DVERSION=\"$(VERSION)\" CFLAGS += -Wno-error=deprecated-declarations @@ -21,6 +26,7 @@ CFLAGS += $(shell $(PKG_CONFIG) --cflags alsa) LDFLAGS += $(shell $(PKG_CONFIG) --libs glib-2.0) LDFLAGS += $(shell $(PKG_CONFIG) --libs gtk4) LDFLAGS += $(shell $(PKG_CONFIG) --libs alsa) +LDFLAGS += -lm -lcrypto COMPILE.c = $(CC) $(DEPFLAGS) $(CFLAGS) -c @@ -60,7 +66,7 @@ $(DEPFILES): include $(wildcard $(DEPFILES)) $(TARGET): $(OBJS) - cc -o $(TARGET) $(OBJS) ${LDFLAGS} -lm + cc -o $(TARGET) $(OBJS) ${LDFLAGS} ifeq ($(PREFIX),) PREFIX := /usr/local diff --git a/src/about.c b/src/about.c index 17d2b61..633eb2f 100644 --- a/src/about.c +++ b/src/about.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "about.h" @@ -11,20 +11,22 @@ void activate_about( GtkWindow *w = GTK_WINDOW(data); const char *authors[] = { - "Geoffrey D. Bennett", + "Geoffrey D. Bennett ", NULL }; gtk_show_about_dialog( w, - "program-name", "ALSA Scarlett Gen 2/3 Control Panel", + "program-name", "ALSA Scarlett2 Control Panel", "version", "Version " VERSION, - "comments", "GTK4 interface to the ALSA Scarlett Gen 2/3 Mixer controls", + "comments", + "Gtk4 GUI for the ALSA controls presented by the\n" + "Linux kernel Focusrite Scarlett2 Mixer Driver", "website", "https://github.com/geoffreybennett/alsa-scarlett-gui", - "copyright", "Copyright 2022 Geoffrey D. Bennett", + "copyright", "Copyright 2022-2024 Geoffrey D. Bennett", "license-type", GTK_LICENSE_GPL_3_0, "logo-icon-name", "alsa-scarlett-gui-logo", - "title", "About ALSA Scarlett Mixer Interface", + "title", "About ALSA Scarlett2 Mixer Interface", "authors", authors, NULL ); diff --git a/src/about.h b/src/about.h index 2b17b91..61aa585 100644 --- a/src/about.h +++ b/src/about.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/alsa-scarlett-gui.css b/src/alsa-scarlett-gui.css index 1fe1b18..0fc75fd 100644 --- a/src/alsa-scarlett-gui.css +++ b/src/alsa-scarlett-gui.css @@ -1,18 +1,335 @@ +/* Top-level window frame */ +.window-frame { + background: black; + color: white; + padding: 15px; + border-radius: 0px; + border: none; +} + +/* Top-level window content */ +.window-content { + padding: 15px; + border: 2px solid #800000; + border-radius: 20px; +} + +/* Title of the window */ +.window-title { + font-size: large; +} + +/* Links */ +.linktext { + color: #89CFF0; +} + +/* Label above controls-content */ +.controls-label { + font-size: smaller; + margin-top: -4px; +} + +/* controls-content boxes */ +.controls-content { + background: #141414; + padding: 10px; + border: 1px solid #a00000; + border-radius: 5px; + color: #d0d0d0; +} + +/* Tighten up routing groups and make the background a little lighter */ +.window-routing .controls-content { + background: #181818; + padding: 5px; +} + +/* Used when the controls content is at the top level */ +.top-level-content { + background: #141414; +} + .route-label { font-size: smaller; border-radius: 3px; } .route-label:hover { - background: @theme_selected_bg_color; - outline: 2px solid @theme_selected_bg_color; + background: #801010; + outline: 2px solid #801010; } .route-label:drop(active) { box-shadow: none; - background: @theme_selected_bg_color; + background: #801010; } -button { +label.gain { + font-size: smaller; +} + +/* Default button style */ +.window-frame button { + border: 1px solid #303030; + background: linear-gradient(175deg, #202020, #282828); + box-shadow: none; + font-weight: bold; + color: #ffffff; +} + +.window-frame button.toggle { + color: #808080; +} + +.window-frame button:focus:focus-visible { + outline-color: #801010; +} + +/* padding doesn't work when selected with .window-frame, so use + * .toggle instead + */ +button.toggle { padding: 0px 5px 0px 5px; } + +.window-frame button:checked { + color: #ffffff; + border: 1px solid #404040; +} + +.window-frame button:hover { + background: #303030; +} + +.window-frame button:disabled { + background: #202020; + color: #505050; +} + +/* Stop text shadows on buttons from being applied to the popup menu */ +.window-frame button > label > * { + text-shadow: none; +} + +/* Button controls that are always disabled because they indicate status */ +.window-frame button.fixed { + color: #ffffff; + filter: none; +} + +/* Combobox controls that are always disabled because they indicate status */ +.window-frame combobox.fixed > box > button { + color: #ffffff; +} + +/* Buttons that glow when on */ +.window-frame button.sync-status { + text-shadow: 0 0 5px #a00000, 0 0 15px #800000; +} + +.window-frame button.sync-status:checked { + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +.window-frame button.input-select:checked { + color: #ffffff; + text-shadow: 0 0 5px #00ff00, 0 0 10px #00ff00, 0 0 15px #00ff00; + filter: none; +} + +.window-frame button.input-link:checked { + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +.window-frame button.autogain:checked { + text-shadow: 0 0 5px #0000ff, 0 0 15px #0000ff; +} + +/* orange */ +.window-frame .vocaster button.autogain:checked { + text-shadow: 0 0 5px #ffc000, 0 0 15px #ffc000; +} + +.window-frame button.safe:checked { + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +.window-frame button.safe:checked:disabled { + text-shadow: 0 0 5px #005000, 0 0 15px #005000; +} + +.window-frame button.inst:checked { + text-shadow: 0 0 5px #ff0000, 0 0 15px #ff0000; +} + +.window-frame .gen4 button.inst:checked { + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +.window-frame .gen4 button.inst:checked:disabled { + text-shadow: 0 0 5px #005000, 0 0 15px #005000; +} + +.window-frame button.pcm-input-mix:checked { + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +/* Air Checked (Gen 3) */ +.window-frame button.air:checked { + text-shadow: 0 0 5px #ffc000, 0 0 15px #ffc000; +} + +/* Air Selections (Gen 4) */ +.window-frame button.air.selected-presence > label { + color: #ffffff; + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +.window-frame button.air.selected-presencedrive > label { + color: #ffffff; + text-shadow: 0 0 5px #ffc000, 0 0 15px #ffc000; +} + +.window-frame button.air.selected-presence:disabled > label { + color: #505050; + text-shadow: 0 0 5px #005000, 0 0 15px #005000; +} + +.window-frame button.air.selected-presencedrive:disabled > label { + color: #505050; + text-shadow: 0 0 5px #503c00, 0 0 15px #503c00; +} + +.window-frame button.pad:checked { + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +.window-frame button.phantom:checked { + text-shadow: 0 0 5px #ff0000, 0 0 15px #c00000; +} + +.window-frame .gen4 button.phantom:checked { + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +.window-frame .gen4 button.phantom:checked:disabled { + text-shadow: 0 0 5px #005000, 0 0 15px #005000; +} + +.window-frame button.input-mute:checked { + text-shadow: 0 0 5px #ff0000, 0 0 15px #c00000; +} + +.window-frame button.dsp:checked { + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +/* Direct Monitor Checked (Solo) */ +.window-frame .direct-monitor:checked { + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +/* Direct Monitor Selections (2i2) */ +.window-frame button.direct-monitor.selected-mono > label { + color: #ffffff; + text-shadow: 0 0 5px #c0c0c0, 0 0 15px #c0c0c0; +} + +.window-frame button.direct-monitor.selected-stereo > label { + color: #ffffff; + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +/* Sample Rates */ +.window-frame button.sample-rate.sample-rate-44100 { + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +.window-frame button.sample-rate.sample-rate-48000 { + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +.window-frame button.sample-rate.sample-rate-88200 { + text-shadow: 0 0 5px #ff8000, 0 0 15px #ff8000; +} + +.window-frame button.sample-rate.sample-rate-96000 { + text-shadow: 0 0 5px #ff8000, 0 0 15px #ff8000; +} + +.window-frame button.sample-rate.sample-rate-176400 { + text-shadow: 0 0 5px #ff0000, 0 0 15px #c00000; +} + +.window-frame button.sample-rate.sample-rate-192000 { + text-shadow: 0 0 5px #ff0000, 0 0 15px #c00000; +} + +/* Button controls where checked is dimmer */ + +/* Mute button */ +.window-frame button.mute { + color: #ffffff; + -gtk-icon-shadow: 0 0 5px #00c000, 0 0 15px #00c000; + border-color: #404040; +} + +.window-frame button.mute:checked { + -gtk-icon-shadow: 0 0 5px #ff0000, 0 0 15px #c00000; + border-color: #303030; +} + +/* Dim button */ +.window-frame button.dim { + color: #ffffff; + -gtk-icon-shadow: 0 0 5px #00c000, 0 0 15px #00c000; + border-color: #404040; +} + +.window-frame button.dim:checked { + -gtk-icon-shadow: 0 0 5px #ffc000, 0 0 15px #ffc000; + border-color: #303030; +} + +/* SW/HW button */ +.window-frame button.sw-hw { + color: #ffffff; + -gtk-icon-shadow: 0 0 5px #00c000, 0 0 15px #00c000; + border-color: #404040; +} + +.window-frame button.sw-hw:checked { + color: #808080; + -gtk-icon-shadow: 0 0 5px #ffc000, 0 0 15px #ffc000; + border-color: #303030; +} + +/* Textview used for long descriptions in the startup window */ +.window-frame textview { + color: #ffffff; + background: none; +} + +.window-frame textview > text { + background: none; +} + +/* Bigger buttons in the startup window */ +.window-frame .window-startup button { + padding: 5px; +} + +/* Separators */ +.window-frame separator { + background: #800000; +} + +.window-frame .big-padding { + padding: 50px; +} + +/* Bigger buttons in confirmation dialogs */ +.window-frame .big-padding button { + padding: 5px 30px; +} diff --git a/src/alsa-sim.c b/src/alsa-sim.c index 5af315b..f965657 100644 --- a/src/alsa-sim.c +++ b/src/alsa-sim.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "alsa.h" @@ -151,6 +151,42 @@ static void alsa_parse_comment_node( elem->type = SND_CTL_ELEM_TYPE_INTEGER; } else if (strcmp(key, "item") == 0) { alsa_parse_enum_items(node, elem); + } else if (strcmp(key, "range") == 0) { + if (type != SND_CONFIG_TYPE_STRING) { + printf("range type not string\n"); + return; + } + const char *range; + err = snd_config_get_string(node, &range); + if (err < 0) + fatal_alsa_error("snd_config_get_string error", err); + + // Parse the range string and update elem->min_val and elem->max_val + int min_val, max_val; + if (sscanf(range, "%d - %d", &min_val, &max_val) == 2) { + elem->min_val = min_val; + elem->max_val = max_val; + } + } else if (strcmp(key, "dbmin") == 0) { + if (type != SND_CONFIG_TYPE_INTEGER) { + printf("dbmin type not integer\n"); + return; + } + long dbmin; + err = snd_config_get_integer(node, &dbmin); + if (err < 0) + fatal_alsa_error("snd_config_get_integer error", err); + elem->min_dB = dbmin / 100; + } else if (strcmp(key, "dbmax") == 0) { + if (type != SND_CONFIG_TYPE_INTEGER) { + printf("dbmax type not integer\n"); + return; + } + long dbmax; + err = snd_config_get_integer(node, &dbmax); + if (err < 0) + fatal_alsa_error("snd_config_get_integer error", err); + elem->max_dB = dbmax / 100; } } } @@ -249,12 +285,13 @@ static int alsa_config_to_new_elem( } } - // check iface value; only interested in MIXER and PCM + // check iface value; only interested in CARD, MIXER, and PCM if (!iface) { printf("missing iface node in control id %d\n", id); goto fail; } - if (strcmp(iface, "MIXER") != 0 && + if (strcmp(iface, "CARD") != 0 && + strcmp(iface, "MIXER") != 0 && strcmp(iface, "PCM") != 0) goto fail; diff --git a/src/alsa-sim.h b/src/alsa-sim.h index 8a79c74..0c2b0ae 100644 --- a/src/alsa-sim.h +++ b/src/alsa-sim.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/alsa.c b/src/alsa.c index 67c616b..94bcb55 100644 --- a/src/alsa.c +++ b/src/alsa.c @@ -1,25 +1,37 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include #include "alsa.h" +#include "scarlett2-firmware.h" #include "stringhelper.h" #include "window-iface.h" +#define MAX_TLV_RANGE_SIZE 256 + // names for the port categories const char *port_category_names[PC_COUNT] = { "Hardware Outputs", "Mixer Inputs", + "DSP Inputs", "PCM Inputs" }; // global array of cards -GArray *alsa_cards; +static GArray *alsa_cards; // static fd and wd for ALSA inotify static int inotify_fd, inotify_wd; +struct reopen_callback { + ReOpenCallback *callback; + void *data; +}; + +// hash table for cards being rebooted +GHashTable *reopen_callbacks; + // forward declaration static void alsa_elem_change(struct alsa_elem *elem); @@ -94,15 +106,18 @@ int get_max_elem_by_name(GArray *elems, char *prefix, char *needle) { return max; } -// return true if the element is an routing destination enum, e.g.: +// return true if the element is an routing sink enum, e.g.: // PCM xx Capture Enum // Mixer Input xx Capture Enum // Analogue Output xx Playback Enum // S/PDIF Output xx Playback Enum // ADAT Output xx Playback Enum -int is_elem_routing_dst(struct alsa_elem *elem) { - if (strstr(elem->name, "Capture Enum") && - !strstr(elem->name, "Level")) +int is_elem_routing_snk(struct alsa_elem *elem) { + if (strstr(elem->name, "Capture Enum") && ( + strncmp(elem->name, "PCM ", 4) == 0 || + strncmp(elem->name, "Mixer Input ", 12) == 0 || + strncmp(elem->name, "DSP Input ", 10) == 0 + )) return 1; if (strstr(elem->name, "Output") && strstr(elem->name, "Playback Enum")) @@ -110,6 +125,20 @@ int is_elem_routing_dst(struct alsa_elem *elem) { return 0; } +// add a callback to the list of callbacks for this element +void alsa_elem_add_callback( + struct alsa_elem *elem, + AlsaElemCallback *callback, + void *data +) { + struct alsa_elem_callback *cb = calloc(1, sizeof(struct alsa_elem_callback)); + + cb->callback = callback; + cb->data = data; + + elem->callbacks = g_list_append(elem->callbacks, cb); +} + // // alsa snd_ctl_elem_*() mediation functions // for simulated elements, fake the ALSA element @@ -336,6 +365,54 @@ static void alsa_get_elem_list(struct alsa_card *card) { if (strstr(alsa_elem.name, "Channel Map")) continue; + // get TLV info if it's a volume control + if (alsa_elem.type == SND_CTL_ELEM_TYPE_INTEGER) { + snd_ctl_elem_info_t *elem_info; + + snd_ctl_elem_info_alloca(&elem_info); + snd_ctl_elem_info_set_numid(elem_info, alsa_elem.numid); + snd_ctl_elem_info(card->handle, elem_info); + + if (snd_ctl_elem_info_is_tlv_readable(elem_info)) { + snd_ctl_elem_id_t *elem_id; + unsigned int tlv[MAX_TLV_RANGE_SIZE]; + unsigned int *dbrec; + int ret; + long min_dB, max_dB; + + snd_ctl_elem_id_alloca(&elem_id); + snd_ctl_elem_id_set_numid(elem_id, alsa_elem.numid); + + ret = snd_ctl_elem_tlv_read( + card->handle, elem_id, tlv, sizeof(tlv) + ); + if (ret < 0) { + fprintf(stderr, "TLV read error %d\n", ret); + continue; + } + + ret = snd_tlv_parse_dB_info(tlv, sizeof(tlv), &dbrec); + if (ret <= 0) { + fprintf(stderr, "TLV parse error %d\n", ret); + continue; + } + + int min_val = snd_ctl_elem_info_get_min(elem_info); + int max_val = snd_ctl_elem_info_get_max(elem_info); + + ret = snd_tlv_get_dB_range(tlv, min_val, max_val, &min_dB, &max_dB); + if (ret != 0) { + fprintf(stderr, "TLV range error %d\n", ret); + continue; + } + + alsa_elem.min_val = min_val; + alsa_elem.max_val = max_val; + alsa_elem.min_dB = min_dB / 100; + alsa_elem.max_dB = max_dB / 100; + } + } + if (card->elems->len <= alsa_elem.numid) g_array_set_size(card->elems, alsa_elem.numid + 1); g_array_index(card->elems, struct alsa_elem, alsa_elem.numid) = alsa_elem; @@ -347,11 +424,17 @@ static void alsa_get_elem_list(struct alsa_card *card) { } static void alsa_elem_change(struct alsa_elem *elem) { - if (!elem->widget) + if (!elem || !elem->callbacks) return; - if (!elem->widget_callback) - return; - elem->widget_callback(elem); + + for (GList *l = elem->callbacks; l; l = l->next) { + struct alsa_elem_callback *cb = (struct alsa_elem_callback *)l->data; + + if (!cb || !cb->callback) + continue; + + cb->callback(elem, cb->data); + } } static gboolean alsa_card_callback( @@ -449,6 +532,7 @@ static void card_destroy_callback(void *data) { // TODO: there is more to free free(card->device); + free(card->serial); free(card->name); free(card); @@ -481,7 +565,174 @@ static void alsa_subscribe(struct alsa_card *card) { snd_ctl_poll_descriptors(card->handle, &card->pfd, 1); } -void alsa_scan_cards(void) { +static void alsa_get_usbid(struct alsa_card *card) { + char path[256]; + snprintf(path, 256, "/proc/asound/card%d/usbid", card->num); + + FILE *f = fopen(path, "r"); + if (!f) { + fprintf(stderr, "can't open %s: %s\n", path, strerror(errno)); + return; + } + + int vid, pid; + int result = fscanf(f, "%04x:%04x", &vid, &pid); + fclose(f); + + if (result != 2) { + fprintf(stderr, "can't read %s\n", path); + return; + } + + if (vid != 0x1235) { + fprintf(stderr, "VID %04x != expected 0x1235 for Focusrite\n", vid); + return; + } + + card->pid = pid; +} + +// get the bus and device numbers from /proc/asound/cardxx/usbbus +// format is XXX/YYY +static int alsa_get_usbbus(struct alsa_card *card, int *bus, int *dev) { + char path[256]; + snprintf(path, 256, "/proc/asound/card%d/usbbus", card->num); + FILE *f = fopen(path, "r"); + if (!f) { + fprintf(stderr, "can't open %s\n", path); + return 0; + } + + int result = fscanf(f, "%d/%d", bus, dev); + fclose(f); + + if (result != 2) { + fprintf(stderr, "can't read %s\n", path); + return 0; + } + + return 1; +} + +// read the devnum file in bus_path +// /sys/bus/usb/devices/usbBUS/BUS-PORT/devnum +// and return the value within +static int usb_get_devnum(const char *bus_path) { + char devnum_path[512]; + snprintf(devnum_path, 512, "%s/devnum", bus_path); + + FILE *f = fopen(devnum_path, "r"); + if (!f) { + if (errno == ENOENT) + return -1; + + fprintf(stderr, "can't open %s: %s\n", devnum_path, strerror(errno)); + return -1; + } + + int devnum; + int result = fscanf(f, "%d", &devnum); + int err = errno; + fclose(f); + + if (result != 1) { + fprintf(stderr, "can't read %s: %s\n", devnum_path, strerror(err)); + return -1; + } + + return devnum; +} + +// recursively search for the device with the given dev number +// in the /sys/bus/usb/devices/usbX/Y-Z hierarchy +// and return the path to the port +static int usb_find_device_port( + const char *bus_path, + int bus, + int dev, + char *port_path, + size_t port_path_size +) { + if (usb_get_devnum(bus_path) == dev) { + snprintf(port_path, port_path_size, "%s", bus_path); + return 1; + } + + DIR *dir = opendir(bus_path); + if (!dir) { + fprintf(stderr, "can't open %s: %s\n", bus_path, strerror(errno)); + return 0; + } + + // looking for d_name beginning with the bus number followed by a "-" + char prefix[20]; + snprintf(prefix, 20, "%d-", bus); + + struct dirent *entry; + while ((entry = readdir(dir))) { + if (entry->d_type != DT_DIR) + continue; + + if (strncmp(entry->d_name, prefix, strlen(prefix)) != 0) + continue; + + char next_path[512]; + snprintf(next_path, 512, "%s/%s", bus_path, entry->d_name); + + if (usb_find_device_port(next_path, bus, dev, port_path, port_path_size)) { + closedir(dir); + return 1; + } + } + + closedir(dir); + return 0; +} + +static void alsa_get_serial_number(struct alsa_card *card) { + + int result, bus, dev; + if (!alsa_get_usbbus(card, &bus, &dev)) + return; + + // recurse through /sys/bus/usb/devices/usbBUS/BUS-.../devnum + // to find the device with the matching dev number + char bus_path[80]; + snprintf(bus_path, 80, "/sys/bus/usb/devices/usb%d", bus); + + char port_path[512]; + + if (!usb_find_device_port(bus_path, bus, dev, port_path, sizeof(port_path))) { + fprintf( + stderr, + "can't find port name in %s for dev %d (%s)\n", + bus_path, dev, card->name + ); + return; + } + + // read the serial number + char serial_path[520]; + snprintf(serial_path, 520, "%s/serial", port_path); + FILE *f = fopen(serial_path, "r"); + if (!f) { + fprintf(stderr, "can't open %s\n", serial_path); + return; + } + + char serial[40]; + result = fscanf(f, "%39s", serial); + fclose(f); + + if (result != 1) { + fprintf(stderr, "can't read %s\n", serial_path); + return; + } + + card->serial = strdup(serial); +} + +static void alsa_scan_cards(void) { snd_ctl_card_info_t *info; snd_ctl_t *ctl; int card_num = -1; @@ -506,7 +757,8 @@ void alsa_scan_cards(void) { goto next; if (strncmp(snd_ctl_card_info_get_name(info), "Scarlett", 8) != 0 && - strncmp(snd_ctl_card_info_get_name(info), "Clarett", 7) != 0) + strncmp(snd_ctl_card_info_get_name(info), "Clarett", 7) != 0 && + strncmp(snd_ctl_card_info_get_name(info), "Vocaster", 8) != 0) goto next; // is there already an entry for this card in alsa_cards? @@ -525,6 +777,21 @@ void alsa_scan_cards(void) { alsa_get_elem_list(card); alsa_subscribe(card); + alsa_get_usbid(card); + alsa_get_serial_number(card); + card->best_firmware_version = scarlett2_get_best_firmware_version(card->pid); + + if (card->serial) { + + // call the callbacks for this card + struct reopen_callback *rc = g_hash_table_lookup( + reopen_callbacks, card->serial + ); + if (rc) + rc->callback(rc->data); + + g_hash_table_remove(reopen_callbacks, card->serial); + } create_card_window(card); alsa_add_card_callback(card); @@ -571,7 +838,7 @@ static gboolean inotify_callback( return TRUE; } -void alsa_inotify_init(void) { +static void alsa_inotify_init(void) { GIOChannel *io_channel; inotify_fd = inotify_init(); @@ -583,3 +850,32 @@ void alsa_inotify_init(void) { inotify_callback, NULL, NULL ); } + +void alsa_init(void) { + alsa_cards = g_array_new(FALSE, TRUE, sizeof(struct alsa_card *)); + reopen_callbacks = g_hash_table_new_full( + g_str_hash, g_str_equal, g_free, g_free + ); + alsa_inotify_init(); + alsa_scan_cards(); +} + +void alsa_register_reopen_callback( + const char *serial, + ReOpenCallback *callback, + void *data +) { + struct reopen_callback *rc = g_new0(struct reopen_callback, 1); + rc->callback = callback; + rc->data = data; + + g_hash_table_insert(reopen_callbacks, g_strdup(serial), rc); +} + +void alsa_unregister_reopen_callback(const char *serial) { + g_hash_table_remove(reopen_callbacks, serial); +} + +int alsa_has_reopen_callbacks(void) { + return g_hash_table_size(reopen_callbacks); +} diff --git a/src/alsa.h b/src/alsa.h index 26408d6..688311d 100644 --- a/src/alsa.h +++ b/src/alsa.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -17,9 +17,9 @@ struct alsa_card; // typedef for callbacks to update widgets when the alsa element // notifies of a change -typedef void (AlsaElemCallback)(struct alsa_elem *); +typedef void (AlsaElemCallback)(struct alsa_elem *, void *); -// port categories for routing_src and routing_dst entries +// port categories for routing_src and routing_snk entries // must match the level meter ordering from the driver enum { // Hardware inputs/outputs @@ -28,22 +28,25 @@ enum { // Mixer inputs/outputs PC_MIX = 1, + // DSP inputs/outputs + PC_DSP = 2, + // PCM inputs/outputs - PC_PCM = 2, + PC_PCM = 3, // number of port categories - PC_COUNT = 3 + PC_COUNT = 4 }; // names for the port categories extern const char *port_category_names[PC_COUNT]; // is a drag active, and whether dragging from a routing source or a -// routing destination +// routing sink enum { DRAG_TYPE_NONE = 0, DRAG_TYPE_SRC = 1, - DRAG_TYPE_DST = 2, + DRAG_TYPE_SNK = 2, }; // entry in alsa_card routing_srcs (routing sources) array @@ -56,7 +59,7 @@ struct routing_src { // the enum id of the alsa item int id; - // PC_MIX, PC_PCM, or PC_HW + // PC_DSP, PC_MIX, PC_PCM, or PC_HW int port_category; // 0-based count within port_category @@ -76,12 +79,12 @@ struct routing_src { GtkWidget *widget2; }; -// entry in alsa_card routing_dsts (routing destinations) array -// for alsa elements that are routing destinations like Analogue -// Output 01 Playback Enum -// port_category is set to PC_MIX, PC_PCM, PC_HW +// entry in alsa_card routing_snks (routing sinks) array for alsa +// elements that are routing sinks like Analogue Output 01 Playback +// Enum +// port_category is set to PC_DSP, PC_MIX, PC_PCM, PC_HW // port_num is a count (0-based) within that category -struct routing_dst { +struct routing_snk { // location within the array int idx; @@ -89,17 +92,29 @@ struct routing_dst { // pointer back to the element this entry is associated with struct alsa_elem *elem; - // PC_MIX, PC_PCM, or PC_HW + // box widget on the routing page + GtkWidget *box_widget; + + // socket widget on the routing page + GtkWidget *socket_widget; + + // PC_DSP, PC_MIX, PC_PCM, or PC_HW int port_category; // 0-based count within port_category int port_num; - // the mixer label widgets for this destination + // the mixer label widgets for this sink GtkWidget *mixer_label_top; GtkWidget *mixer_label_bottom; }; +// hold one callback & its data +struct alsa_elem_callback { + AlsaElemCallback *callback; + void *data; +}; + // entry in alsa_card elems (ALSA control elements) array struct alsa_elem { @@ -112,23 +127,18 @@ struct alsa_elem { int type; int count; + // for gain/volume elements, the dB range and step + int min_val; + int max_val; + int min_dB; + int max_dB; + // for the number (or translated letter; A = 1) in the item name - // TODO: move this to struct routing_dst? + // TODO: move this to struct routing_snk? int lr_num; - // the primary GTK widget and callback function for this ALSA - // control element - GtkWidget *widget; - AlsaElemCallback *widget_callback; - - // text label for volume controls - // handle for routing controls - // second button for dual controls - GtkWidget *widget2; - - // for boolean buttons, the two possible texts - // for dual buttons, the four possible texts - const char *bool_text[4]; + // the callback functions for this ALSA control element + GList *callbacks; // for simulated elements, the current state int writable; @@ -142,14 +152,17 @@ struct alsa_elem { struct alsa_card { int num; char *device; + uint32_t pid; + char *serial; char *name; + int best_firmware_version; snd_ctl_t *handle; struct pollfd pfd; GArray *elems; struct alsa_elem *sample_capture_elem; struct alsa_elem *level_meter_elem; GArray *routing_srcs; - GArray *routing_dsts; + GArray *routing_snks; GIOChannel *io_channel; guint event_source_id; GtkWidget *window_main; @@ -157,6 +170,7 @@ struct alsa_card { GtkWidget *window_mixer; GtkWidget *window_levels; GtkWidget *window_startup; + GtkWidget *window_modal; GtkWidget *window_main_contents; GtkWidget *routing_grid; GtkWidget *routing_lines; @@ -164,6 +178,8 @@ struct alsa_card { GtkWidget *routing_hw_out_grid; GtkWidget *routing_pcm_in_grid; GtkWidget *routing_pcm_out_grid; + GtkWidget *routing_dsp_in_grid; + GtkWidget *routing_dsp_out_grid; GtkWidget *routing_mixer_in_grid; GtkWidget *routing_mixer_out_grid; GtkWidget *meters[MAX_METERS]; @@ -176,13 +192,10 @@ struct alsa_card { GtkWidget *drag_line; int drag_type; struct routing_src *src_drag; - struct routing_dst *dst_drag; + struct routing_snk *snk_drag; double drag_x, drag_y; }; -// global array of cards -extern GArray *alsa_cards; - // utility void fatal_alsa_error(const char *msg, int err); @@ -190,7 +203,14 @@ void fatal_alsa_error(const char *msg, int err); struct alsa_elem *get_elem_by_name(GArray *elems, char *name); struct alsa_elem *get_elem_by_prefix(GArray *elems, char *prefix); int get_max_elem_by_name(GArray *elems, char *prefix, char *needle); -int is_elem_routing_dst(struct alsa_elem *elem); +int is_elem_routing_snk(struct alsa_elem *elem); + +// add callback to alsa_elem callback list +void alsa_elem_add_callback( + struct alsa_elem *elem, + AlsaElemCallback *callback, + void *data +); // alsa snd_ctl_elem_*() functions int alsa_get_elem_type(struct alsa_elem *elem); @@ -206,6 +226,15 @@ char *alsa_get_item_name(struct alsa_elem *elem, int i); // add to alsa_cards array struct alsa_card *card_create(int card_num); -// scan/rescan for cards -void alsa_scan_cards(void); -void alsa_inotify_init(void); +// init +void alsa_init(void); + +// register re-open callback +typedef void (ReOpenCallback)(void *); +void alsa_register_reopen_callback( + const char *serial, + ReOpenCallback *callback, + void *data +); +void alsa_unregister_reopen_callback(const char *serial); +int alsa_has_reopen_callbacks(void); diff --git a/src/const.h b/src/const.h index 09d73f7..df3b094 100644 --- a/src/const.h +++ b/src/const.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/device-reset-config.c b/src/device-reset-config.c new file mode 100644 index 0000000..089eb67 --- /dev/null +++ b/src/device-reset-config.c @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "device-reset-config.h" +#include "scarlett2.h" +#include "scarlett2-ioctls.h" +#include "window-modal.h" + +static gpointer update_progress( + struct modal_data *modal_data, + char *text, + int progress +) { + struct progress_data *progress_data = g_new0(struct progress_data, 1); + progress_data->modal_data = modal_data; + progress_data->text = text; + progress_data->progress = progress; + + g_main_context_invoke(NULL, modal_update_progress, progress_data); + return NULL; +} + +#define fail(msg) { \ + if (hwdep) \ + scarlett2_close(hwdep); \ + return update_progress(modal_data, msg, -1); \ +} + +#define failsndmsg(msg) g_strdup_printf(msg, snd_strerror(err)) + +gpointer reset_config_thread(gpointer user_data) { + struct modal_data *modal_data = user_data; + + update_progress(modal_data, g_strdup("Resetting configuration..."), 0); + + snd_hwdep_t *hwdep; + + int err = scarlett2_open_card(modal_data->card->device, &hwdep); + if (err < 0) + fail(failsndmsg("Unable to open hwdep interface: %s")); + + err = scarlett2_erase_config(hwdep); + if (err < 0) + fail(failsndmsg("Unable to reset configuration: %s")); + + while (1) { + g_usleep(50000); + + err = scarlett2_get_erase_progress(hwdep); + if (err < 0) + fail(failsndmsg("Unable to get erase progress: %s")); + if (err == 255) + break; + + update_progress(modal_data, NULL, err); + } + + g_main_context_invoke(NULL, modal_start_reboot_progress, modal_data); + scarlett2_reboot(hwdep); + scarlett2_close(hwdep); + + return NULL; +} + +static void join_thread(gpointer thread) { + g_thread_join(thread); +} + +static void reset_config_yes_callback(struct modal_data *modal_data) { + GThread *thread = g_thread_new( + "reset_config_thread", reset_config_thread, modal_data + ); + g_object_set_data_full( + G_OBJECT(modal_data->button_box), "thread", thread, join_thread + ); +} + +void create_reset_config_window(GtkWidget *w, struct alsa_card *card) { + create_modal_window( + w, card, + "Confirm Reset Configuration", + "Resetting Configuration", + "Are you sure you want to reset the configuration?", + reset_config_yes_callback + ); +} diff --git a/src/device-reset-config.h b/src/device-reset-config.h new file mode 100644 index 0000000..c406306 --- /dev/null +++ b/src/device-reset-config.h @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include "alsa.h" + +void create_reset_config_window(GtkWidget *w, struct alsa_card *card); diff --git a/src/device-update-firmware.c b/src/device-update-firmware.c new file mode 100644 index 0000000..2b02814 --- /dev/null +++ b/src/device-update-firmware.c @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "device-reset-config.h" +#include "scarlett2.h" +#include "scarlett2-firmware.h" +#include "scarlett2-ioctls.h" +#include "window-modal.h" + +static gpointer update_progress( + struct modal_data *modal_data, + char *text, + int progress +) { + struct progress_data *progress_data = g_new0(struct progress_data, 1); + progress_data->modal_data = modal_data; + progress_data->text = text; + progress_data->progress = progress; + + g_main_context_invoke(NULL, modal_update_progress, progress_data); + return NULL; +} + +#define fail(msg) { \ + if (hwdep) \ + scarlett2_close(hwdep); \ + if (firmware) \ + scarlett2_free_firmware_file(firmware); \ + return update_progress(modal_data, msg, -1); \ +} + +#define failsndmsg(msg) g_strdup_printf(msg, snd_strerror(err)) + +gpointer update_firmware_thread(gpointer user_data) { + struct modal_data *modal_data = user_data; + struct alsa_card *card = modal_data->card; + + int err = 0; + snd_hwdep_t *hwdep = NULL; + + // read the firmware file + update_progress(modal_data, g_strdup("Checking firmware..."), 0); + struct scarlett2_firmware_file *firmware = + scarlett2_get_best_firmware(card->pid); + + // if no firmware, fail + if (!firmware) + fail(failsndmsg("No update firmware found for device: %s")); + + if (firmware->header.usb_pid != card->pid) + fail(g_strdup("Firmware file does not match device")); + + update_progress(modal_data, g_strdup("Resetting configuration..."), 0); + + err = scarlett2_open_card(card->device, &hwdep); + if (err < 0) + fail(failsndmsg("Unable to open hwdep interface: %s")); + + err = scarlett2_erase_config(hwdep); + if (err < 0) + fail(failsndmsg("Unable to reset configuration: %s")); + + while (1) { + g_usleep(50000); + + err = scarlett2_get_erase_progress(hwdep); + if (err < 0) + fail(failsndmsg("Unable to get erase progress: %s")); + if (err == 255) + break; + + update_progress(modal_data, NULL, err); + } + + update_progress(modal_data, g_strdup("Erasing flash..."), 0); + + err = scarlett2_erase_firmware(hwdep); + if (err < 0) + fail(failsndmsg("Unable to erase upgrade firmware: %s")); + + while (1) { + g_usleep(50000); + + err = scarlett2_get_erase_progress(hwdep); + if (err < 0) + fail(failsndmsg("Unable to get erase progress: %s")); + if (err == 255) + break; + + update_progress(modal_data, NULL, err); + } + + update_progress(modal_data, g_strdup("Writing firmware..."), 0); + + size_t offset = 0; + size_t len = firmware->header.firmware_length; + unsigned char *buf = firmware->firmware_data; + + while (offset < len) { + err = snd_hwdep_write(hwdep, buf + offset, len - offset); + if (err < 0) + fail(failsndmsg("Unable to write firmware: %s")); + + offset += err; + + update_progress(modal_data, NULL, (offset * 100) / len); + } + + g_main_context_invoke(NULL, modal_start_reboot_progress, modal_data); + scarlett2_reboot(hwdep); + scarlett2_close(hwdep); + + return NULL; +} + +static void join_thread(gpointer thread) { + g_thread_join(thread); +} + +static void update_firmware_yes_callback(struct modal_data *modal_data) { + GThread *thread = g_thread_new( + "update_firmware_thread", update_firmware_thread, modal_data + ); + g_object_set_data_full( + G_OBJECT(modal_data->button_box), "thread", thread, join_thread + ); +} + +void create_update_firmware_window(GtkWidget *w, struct alsa_card *card) { + create_modal_window( + w, card, + "Confirm Update Firmware", + "Updating Firmware", + "The firmware update process will take about 15 seconds.\n" + "Please do not disconnect the device while updating.\n" + "Ready to proceed?", + update_firmware_yes_callback + ); +} diff --git a/src/device-update-firmware.h b/src/device-update-firmware.h new file mode 100644 index 0000000..127fef6 --- /dev/null +++ b/src/device-update-firmware.h @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include "alsa.h" + +void create_update_firmware_window(GtkWidget *w, struct alsa_card *card); diff --git a/src/error.c b/src/error.c index b8d5c56..99add42 100644 --- a/src/error.c +++ b/src/error.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "error.h" @@ -17,7 +17,7 @@ void show_error(GtkWindow *w, char *s) { "%s", s ); - gtk_widget_show(dialog); + gtk_widget_set_visible(dialog, TRUE); g_signal_connect(dialog, "response", G_CALLBACK(gtk_window_destroy), NULL); } diff --git a/src/error.h b/src/error.h index 0cf8e3d..09e6300 100644 --- a/src/error.h +++ b/src/error.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/file.c b/src/file.c index a37d945..f43a62a 100644 --- a/src/file.c +++ b/src/file.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "alsa.h" diff --git a/src/file.h b/src/file.h index 9b8e181..f7253b7 100644 --- a/src/file.h +++ b/src/file.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include diff --git a/src/gtkdial.c b/src/gtkdial.c index ce06515..438f648 100644 --- a/src/gtkdial.c +++ b/src/gtkdial.c @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: 2021 Stiliyan Varbanov -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: LGPL-3.0-or-later /* @@ -18,49 +18,62 @@ #include "gtkdial.h" -static void set_value(GtkDial *dial, double newval); +#define DIAL_MIN_WIDTH 50 +#define DIAL_MAX_WIDTH 70 -static void gtk_dial_set_property(GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void gtk_dial_get_property(GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); +static int set_value(GtkDial *dial, double newval); -static void gtk_dial_move_slider(GtkDial *dial, - GtkScrollType scroll); -static void -gtk_dial_drag_gesture_begin (GtkGestureDrag *gesture, - double offset_x, - double offset_y, - GtkDial *dial); +static void gtk_dial_set_property( + GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec +); -static void -gtk_dial_drag_gesture_update (GtkGestureDrag *gesture, - double offset_x, - double offset_y, - GtkDial *dial); +static void gtk_dial_get_property( + GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec +); -static void -gtk_dial_drag_gesture_end (GtkGestureDrag *gesture, - double offset_x, - double offset_y, - GtkDial *dial); +static void gtk_dial_move_slider(GtkDial *dial, GtkScrollType scroll); -static void -gtk_dial_click_gesture_pressed (GtkGestureClick *gesture, - int n_press, - double x, - double y, - GtkDial *dial); +static void gtk_dial_drag_gesture_begin( + GtkGestureDrag *gesture, + double offset_x, + double offset_y, + GtkDial *dial +); -static gboolean -gtk_dial_scroll_controller_scroll (GtkEventControllerScroll *scroll, - double dx, - double dy, - GtkDial *dial); +static void gtk_dial_drag_gesture_update( + GtkGestureDrag *gesture, + double offset_x, + double offset_y, + GtkDial *dial +); + +static void gtk_dial_drag_gesture_end( + GtkGestureDrag *gesture, + double offset_x, + double offset_y, + GtkDial *dial +); + +static void gtk_dial_click_gesture_pressed( + GtkGestureClick *gesture, + int n_press, + double x, + double y, + GtkDial *dial +); + +static gboolean gtk_dial_scroll_controller_scroll( + GtkEventControllerScroll *scroll, + double dx, + double dy, + GtkDial *dial +); static void gtk_dial_dispose(GObject *o); @@ -74,6 +87,9 @@ enum { PROP_ADJUSTMENT, PROP_ROUND_DIGITS, PROP_ZERO_DB, + PROP_OFF_DB, + PROP_TAPER, + PROP_CAN_CONTROL, LAST_PROP }; @@ -87,16 +103,6 @@ enum { static guint signals[LAST_SIGNAL]; static GParamSpec *properties[LAST_PROP]; -typedef unsigned char guint8; -typedef size_t gsize; - -struct DialColors { - GdkRGBA trough_border, - trough_bg, - trough_fill, - pointer; -}; - struct _GtkDial { GtkWidget parent_instance; GtkAdjustment *adj; @@ -104,204 +110,445 @@ struct _GtkDial { GtkGesture *drag_gesture, *click_gesture; GtkEventController *scroll_controller; e_grab grab; - - struct DialColors colors; + double dvalp; int round_digits; double zero_db; + double off_db; + int taper; + gboolean can_control; - double slider_cx, slider_cy, dvalp; + int properties_updated; + + // linear taper breakpoints array + double *taper_breakpoints; + double *taper_outputs; + int taper_breakpoints_count; + + // level meter colour breakpoints array + const int *level_breakpoints; + const double *level_colours; + int level_breakpoints_count; + + // variables derived from the widget's dynamic properties (size and + // configuration, excluding the value) + int dim; + double w; + double h; + double radius; + double slider_thickness; + double knob_radius; + double slider_radius; + double background_radius; + double cx; + double cy; + double zero_db_x; + double zero_db_y; + double *level_breakpoint_angles; + + // cairo patterns dependent on the above + cairo_pattern_t *fill_pattern[2][2]; + cairo_pattern_t *outline_pattern[2]; + + // variables derived from the dial value + double valp; + double angle; + double slider_cx; + double slider_cy; }; -G_DEFINE_TYPE (GtkDial, gtk_dial, GTK_TYPE_WIDGET) +G_DEFINE_TYPE(GtkDial, gtk_dial, GTK_TYPE_WIDGET) -static void dial_snapshot (GtkWidget *widget, GtkSnapshot *snapshot); -static void dial_measure(GtkWidget *widget, - GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline); +static void dial_snapshot(GtkWidget *widget, GtkSnapshot *snapshot); +static void dial_measure( + GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline +); #define add_slider_binding(w_class, binding_set, keyval, mask, scroll) \ - gtk_widget_class_add_binding_signal (w_class, \ - keyval, mask, \ - "move-slider", \ - "(i)", scroll) + gtk_widget_class_add_binding_signal(w_class, \ + keyval, mask, \ + "move-slider", \ + "(i)", scroll) -//BEGIN SECTION HELPERS +// BEGIN SECTION HELPERS + +#define TOTAL_ROTATION_DEGREES 290 +#define TOTAL_ROTATION (2 * M_PI * TOTAL_ROTATION_DEGREES / 360) +#define ANGLE_START (-M_PI / 2 - TOTAL_ROTATION / 2) +#define ANGLE_END (-M_PI / 2 + TOTAL_ROTATION / 2) -#define V1x 0.7316888688738209 -#define V1y 0.6816387600233341 -#define RAD_START (-M_PI-0.75) -#define RAD_END 0.75 -#define RAD_SE_DIFF2 ( (2*M_PI+3)/2 ) #define DRAG_FACTOR 0.5 -static inline double calc_valp(double val, double mn, double mx) -{ - if (val <= mn) - return 0.0; - if (val >= mx) - return 1.0; +// convert val from mn..mx to 0..1 with clamp +static double calc_valp(double val, double mn, double mx) { + if (val <= mn) + return 0.0; + if (val >= mx) + return 1.0; - // convert val from mn..mx to 0..1 - val = (val - mn)/(mx-mn); - - // 10^(val - 1) converts it to 0.1..1 with a nice curve - val = pow(10, val - 1); - - // convert to 0..1 again - return (val - 0.1) / 0.9; + return (val - mn) / (mx - mn); } -static inline double calc_val(double valp, double mn, double mx) -{ - return (mx-mn)*valp+mn; +static double taper_linear(double val, double *bp, double *out, int count) { + if (count < 3) + return val; + + if (val <= bp[0]) + return out[0]; + + for (int i = 0; i < count - 1; i++) { + if (val > bp[i + 1]) + continue; + + double scale = (out[i + 1] - out[i]) / (bp[i + 1] - bp[i]); + return out[i] + scale * (val - bp[i]); + } + + return out[count - 1]; } -struct dial_properties -{ - double w; - double h; - double radius; - double thickness; - double cx; - double cy; - double valp; - double slider_radius; - double slider_cx; - double slider_cy; - double start_x; - double start_y; - double end_x; - double end_y; -}; +static double taper_log(double val) { -static void get_dial_properties(GtkDial *dial, - struct dial_properties *props) -{ - props->w = gtk_widget_get_width (GTK_WIDGET(dial) ); - props->h = gtk_widget_get_height (GTK_WIDGET(dial) ); + // 10^(val - 1) converts it to 0.1..1 with a nice curve + val = pow(10, val - 1); - props->cx = props->w / 2; - props->cy = props->h / 2; - props->radius = props->h < props->w ? props->h / 2 - 2 : props->w / 2 - 2; - props->thickness = 10; - props->slider_radius = props->thickness * 1.5; - props->radius -= props->slider_radius / 2; - - double mn = dial->adj ? gtk_adjustment_get_lower(dial->adj) : 0; - double mx = dial->adj ? gtk_adjustment_get_upper(dial->adj) : 1; - double value = dial->adj ? gtk_adjustment_get_value(dial->adj) : 0.25; - props->valp = calc_valp(value, mn, mx); - - double SIN = sin( (RAD_SE_DIFF2*(props->valp) ) ); - double COS = cos( (RAD_SE_DIFF2*(props->valp) ) ); - - props->slider_cx = (-V1y*SIN - V1x*COS)*(2*(props->radius)-(props->thickness) )/2 + (props->cx); - props->slider_cy = (V1y*COS - V1x*SIN)*(2*(props->radius)-(props->thickness) )/2 + (props->cy); - - props->start_x = V1x*(2*(props->radius)-(props->thickness) )/2 + (props->cx); - props->start_y = V1y*(2*(props->radius)-(props->thickness) )/2 + (props->cy); - - SIN = -0.9974949866040545; - COS = -0.07073720166770303; - - props->end_x = (-V1y*SIN - V1x*COS)*(2*(props->radius)-(props->thickness) )/2 + (props->cx); - props->end_y = (V1y*COS - V1x*SIN)*(2*(props->radius)-(props->thickness) )/2 + (props->cy); + // convert to 0..1 again + return (val - 0.1) / 0.9; } -static inline double pdist2(double x1, double y1, double x2, double y2) -{ - double dx = x2 - x1; - double dy = y2 - y1; +static double calc_taper(GtkDial *dial, double val) { + double mn = gtk_adjustment_get_lower(dial->adj); + double mx = gtk_adjustment_get_upper(dial->adj); + double off_db = gtk_dial_get_off_db(dial); - return dx*dx + dy*dy; + // if off_db is set, then values below it are considered as + // almost-silence, so we clamp them to 0.01 + if (off_db > mn) { + if (val == mn) + val = 0; + else if (val <= off_db) + val = 0.01; + else + val = calc_valp(val, off_db, mx) * 0.99 + 0.01; + } else { + val = calc_valp(val, mn, mx); + } + + if (dial->taper == GTK_DIAL_TAPER_LINEAR) + return taper_linear( + val, + dial->taper_breakpoints, + dial->taper_outputs, + dial->taper_breakpoints_count + ); + + if (dial->taper == GTK_DIAL_TAPER_LOG) + return taper_log(val); + + g_warning("Invalid taper value: %d", dial->taper); + + return val; } -static inline gboolean circle_contains_point(double cx, double cy, double r, double px, double py) -{ - return pdist2(cx,cy, px,py) <= r*r; +static double calc_val(double valp, double mn, double mx) { + return (mx - mn) * valp + mn; } -//END SECTION HELPERS -static void gtk_dial_class_init(GtkDialClass *klass) -{ - GtkWidgetClass *w_class = GTK_WIDGET_CLASS(klass); - GObjectClass *g_class = G_OBJECT_CLASS(klass); - GtkWidgetClass *p_class = GTK_WIDGET_CLASS(gtk_dial_parent_class); +static int calculate_dial_height(int width) { + double radius = width / 2; + double angle = (360 - TOTAL_ROTATION_DEGREES) / 2 * M_PI / 180; + double height = radius + radius * cos(angle); - g_class->set_property = >k_dial_set_property; - g_class->get_property = >k_dial_get_property; - g_class->dispose = >k_dial_dispose; + return ceil(height); +} - w_class->size_allocate = p_class->size_allocate; - w_class->measure = &dial_measure; - w_class->snapshot = &dial_snapshot; - w_class->grab_focus = p_class->grab_focus; - w_class->focus = p_class->focus; +static double calculate_dial_radius_from_height(int height) { + double angle = (360 - TOTAL_ROTATION_DEGREES) / 2.0 * M_PI / 180.0; + return height / (1 + cos(angle)); +} - klass->move_slider = >k_dial_move_slider; - klass->value_changed = NULL; +// internal replacement for cairo_pattern_add_color_stop_rgb() that +// dims the color if the widget is insensitive and brightens it by +// focus_mult +static void cairo_add_stop_rgb_dim( + cairo_pattern_t *pat, + double offset, + double r, + double g, + double b, + int dim, + double focus_mult +) { + double x = dim ? 0.5 : 1.0; + x *= focus_mult; + + cairo_pattern_add_color_stop_rgb(pat, offset, r * x, g * x, b * x); +} + +static int update_dial_properties(GtkDial *dial) { + + // always update + dial->dim = !gtk_widget_is_sensitive(GTK_WIDGET(dial)) && dial->can_control; + + // the rest of the values only depend on the widget size and + // configuration + double width = gtk_widget_get_width(GTK_WIDGET(dial)); + double height = gtk_widget_get_height(GTK_WIDGET(dial)); + + if (dial->w == width && dial->h == height && !dial->properties_updated) + return 0; + + dial->w = width; + dial->h = height; + dial->properties_updated = 0; + + // calculate size of dial to fit within the given space + if (width > DIAL_MAX_WIDTH) + width = DIAL_MAX_WIDTH; + + double max_height = calculate_dial_height(DIAL_MAX_WIDTH); + if (height > max_height) + height = max_height; + + // calculate dial radius + double radius_from_width = width / 2; + double radius_from_height = calculate_dial_radius_from_height(height); + + dial->radius = radius_from_width < radius_from_height ? + radius_from_width : radius_from_height; + dial->radius -= 0.5; + + // calculate center of dial + double angle = (360 - TOTAL_ROTATION_DEGREES) / 2.0 * M_PI / 180.0; + double y_offset = dial->radius * cos(angle); + + dial->cx = dial->w / 2; + dial->cy = (dial->h / 2.0) + (dial->radius - y_offset) / 2.0 - 0.5; + + dial->slider_thickness = dial->radius / 2.2; + dial->knob_radius = dial->radius - dial->slider_thickness; + dial->slider_radius = dial->radius - dial->slider_thickness / 2; + dial->background_radius = dial->slider_radius + dial->slider_thickness / 4; + + // calculate zero_db marker position + double zero_db = gtk_dial_get_zero_db(dial); + + if (zero_db != -G_MAXDOUBLE) { + double zero_db_valp = calc_taper(dial, zero_db); + double zero_db_angle = calc_val(zero_db_valp, ANGLE_START, ANGLE_END); + + dial->zero_db_x = cos(zero_db_angle) * dial->slider_radius + dial->cx; + dial->zero_db_y = sin(zero_db_angle) * dial->slider_radius + dial->cy; + } + + // generate cairo fill patterns + for (int focus = 0; focus <= 1; focus++) { + for (int dim = 0; dim <= 1; dim++) { + if (dial->fill_pattern[focus][dim]) + cairo_pattern_destroy(dial->fill_pattern[focus][dim]); + + cairo_pattern_t *pat = cairo_pattern_create_radial( + dial->cx + 5, dial->cy + 5, 0, dial->cx, dial->cy, dial->radius + ); + cairo_add_stop_rgb_dim(pat, 0.0, 0.18, 0.18, 0.20, dim, focus ? 1.65 : 1); + cairo_add_stop_rgb_dim(pat, 0.4, 0.18, 0.18, 0.20, dim, focus ? 1.65 : 1); + cairo_add_stop_rgb_dim(pat, 1.0, 0.40, 0.40, 0.42, dim, focus ? 1.25 : 1); + + dial->fill_pattern[focus][dim] = pat; + } + } + + // generate cairo outline pattern + for (int dim = 0; dim <= 1; dim++) { + if (dial->outline_pattern[dim]) + cairo_pattern_destroy(dial->outline_pattern[dim]); + + cairo_pattern_t *pat = cairo_pattern_create_linear( + dial->cx - dial->radius / 2, + dial->cy - dial->radius / 2, + dial->cx + dial->radius / 2, + dial->cy + dial->radius / 2 + ); + cairo_add_stop_rgb_dim(pat, 0, 0.6, 0.6, 0.6, dim, 1); + cairo_add_stop_rgb_dim(pat, 1, 0.2, 0.2, 0.2, dim, 1); + + dial->outline_pattern[dim] = pat; + } + + // calculate level meter breakpoint angles + if (dial->level_breakpoint_angles) + free(dial->level_breakpoint_angles); + + if (dial->level_breakpoints_count) { + dial->level_breakpoint_angles = malloc( + dial->level_breakpoints_count * sizeof(double) + ); + for (int i = 0; i < dial->level_breakpoints_count; i++) { + double valp = calc_taper(dial, dial->level_breakpoints[i]); + dial->level_breakpoint_angles[i] = + calc_val(valp, ANGLE_START, ANGLE_END); + } + } + + return 1; +} + +static void update_dial_values(GtkDial *dial) { + dial->valp = calc_taper(dial, gtk_adjustment_get_value(dial->adj)); + dial->angle = calc_val(dial->valp, ANGLE_START, ANGLE_END); + dial->slider_cx = cos(dial->angle) * dial->slider_radius + dial->cx; + dial->slider_cy = sin(dial->angle) * dial->slider_radius + dial->cy; +} + +static double pdist2(double x1, double y1, double x2, double y2) { + double dx = x2 - x1; + double dy = y2 - y1; + + return dx * dx + dy * dy; +} + +static gboolean circle_contains_point( + double cx, + double cy, + double r, + double px, + double py +) { + return pdist2(cx, cy, px, py) <= r * r; +} + +// END SECTION HELPERS + +static void gtk_dial_class_init(GtkDialClass *klass) { + GtkWidgetClass *w_class = GTK_WIDGET_CLASS(klass); + GObjectClass *g_class = G_OBJECT_CLASS(klass); + GtkWidgetClass *p_class = GTK_WIDGET_CLASS(gtk_dial_parent_class); + + g_class->set_property = >k_dial_set_property; + g_class->get_property = >k_dial_get_property; + g_class->dispose = >k_dial_dispose; + + w_class->size_allocate = p_class->size_allocate; + w_class->measure = &dial_measure; + w_class->snapshot = &dial_snapshot; + w_class->grab_focus = p_class->grab_focus; + w_class->focus = p_class->focus; + + klass->move_slider = >k_dial_move_slider; + klass->value_changed = NULL; + + gtk_widget_class_set_css_name(w_class, "dial"); /** * GtkDial:adjustment: (attributes org.gtk.Method.get=gtk_dial_get_adjustment org.gtk.Method.set=gtk_dial_set_adjustment) * - * The GtkAdjustment that contains the current value of this range object. + * The GtkAdjustment that contains the current value of this dial object. */ - properties[PROP_ADJUSTMENT] = - g_param_spec_object ("adjustment", - "Adjustment", - "The GtkAdjustment that contains the current value of this range object", - GTK_TYPE_ADJUSTMENT, - G_PARAM_READWRITE|G_PARAM_CONSTRUCT); + properties[PROP_ADJUSTMENT] = g_param_spec_object( + "adjustment", + "Adjustment", + "The GtkAdjustment that contains the current value of this dial object", + GTK_TYPE_ADJUSTMENT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT + ); /** * GtkDial:round_digits: (attributes org.gtk.Method.get=gtk_dial_get_round_digits org.gtk.Method.set=gtk_dial_set_round_digits) * * Limits the number of decimal points this GtkDial will store (default 0: integers). */ - properties[PROP_ROUND_DIGITS] = - g_param_spec_int("round_digits", - "RoundDigits", - "Limits the number of decimal points this GtkDial will store", - -1, 1000, - -1, - G_PARAM_READWRITE|G_PARAM_CONSTRUCT); + properties[PROP_ROUND_DIGITS] = g_param_spec_int( + "round_digits", + "RoundDigits", + "Limits the number of decimal points this GtkDial will store", + -1, 1000, + -1, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT + ); /** * GtkDial:zero_db: (attributes org.gtk.Method.get=gtk_dial_get_zero_db org.gtk.Method.set=gtk_dial_set_zero_db) * - * Limits the number of decimal points this GtkDial will store (default 0: integers). + * The zero-dB value of the dial. */ - properties[PROP_ZERO_DB] = - g_param_spec_double("zero_db", - "ZerodB", - "The zero-dB value of the dial", - -G_MAXDOUBLE, G_MAXDOUBLE, - 0.0, - G_PARAM_READWRITE|G_PARAM_CONSTRUCT); - - g_object_class_install_properties(g_class, LAST_PROP, properties); + properties[PROP_ZERO_DB] = g_param_spec_double( + "zero_db", + "ZerodB", + "The zero-dB value of the dial", + -G_MAXDOUBLE, G_MAXDOUBLE, + -G_MAXDOUBLE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT + ); /** - * GtkRange::value-changed: - * @range: the `GtkRange` that received the signal + * GtkDial:off_db: (attributes org.gtk.Method.get=gtk_dial_get_off_db org.gtk.Method.set=gtk_dial_set_off_db) * - * Emitted when the range value changes. + * Values above the lower value of the adjustment up to this value + * will be considered as the minimum value + 1 (so will display as + * just-above-zero). */ - signals[VALUE_CHANGED] = - g_signal_new ("value-changed", - G_TYPE_FROM_CLASS (g_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GtkDialClass, value_changed), - NULL, NULL, - NULL, - G_TYPE_NONE, 0); + properties[PROP_OFF_DB] = g_param_spec_double( + "off_db", + "OffdB", + "Values up to this value will be considered as almost-silence", + -G_MAXDOUBLE, G_MAXDOUBLE, + -G_MAXDOUBLE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT + ); - /** + /** + * GtkDial:taper: (attributes org.gtk.Method.get=gtk_dial_get_taper org.gtk.Method.set=gtk_dial_set_taper) + * + * The taper of the dial. + */ + properties[PROP_TAPER] = g_param_spec_int( + "taper", + "Taper", + "The taper of the dial", + GTK_DIAL_TAPER_LINEAR, GTK_DIAL_TAPER_LOG, + GTK_DIAL_TAPER_LINEAR, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT + ); + + /** + * GtkDial:can-control: (attributes org.gtk.Method.get=gtk_dial_get_can_control org.gtk.Method.set=gtk_dial_set_can_control) + * + * Whether the dial can be controlled by the user (even though it + * might sometimes be insensitive). + */ + properties[PROP_CAN_CONTROL] = g_param_spec_boolean( + "can-control", + "CanControl", + "Whether the dial can be controlled by the user", + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT + ); + + g_object_class_install_properties(g_class, LAST_PROP, properties); + + /** + * GtkDial::value-changed: + * @dial: the `GtkDial` that received the signal + * + * Emitted when the dial value changes. + */ + signals[VALUE_CHANGED] = g_signal_new( + "value-changed", + G_TYPE_FROM_CLASS(g_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GtkDialClass, value_changed), + NULL, NULL, + NULL, + G_TYPE_NONE, 0 + ); + + /** * GtkDial::move-slider: * @Dial: the `GtkDial` that received the signal * @step: how to move the slider @@ -310,543 +557,798 @@ static void gtk_dial_class_init(GtkDialClass *klass) * * Used for keybindings. */ - signals[MOVE_SLIDER] = - g_signal_new ("move-slider", - G_TYPE_FROM_CLASS (g_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkDialClass, move_slider), - NULL, NULL, - NULL, - G_TYPE_NONE, 1, - GTK_TYPE_SCROLL_TYPE); + signals[MOVE_SLIDER] = g_signal_new( + "move-slider", + G_TYPE_FROM_CLASS(g_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(GtkDialClass, move_slider), + NULL, NULL, + NULL, + G_TYPE_NONE, 1, + GTK_TYPE_SCROLL_TYPE + ); - add_slider_binding (w_class, binding_set, GDK_KEY_Left, 0, - GTK_SCROLL_STEP_LEFT); - add_slider_binding (w_class, binding_set, GDK_KEY_Down, 0, - GTK_SCROLL_STEP_LEFT); - add_slider_binding (w_class, binding_set, GDK_KEY_Right, 0, - GTK_SCROLL_STEP_RIGHT); - add_slider_binding (w_class, binding_set, GDK_KEY_Up, 0, - GTK_SCROLL_STEP_RIGHT); + add_slider_binding(w_class, binding_set, GDK_KEY_Left, 0, GTK_SCROLL_STEP_LEFT); + add_slider_binding(w_class, binding_set, GDK_KEY_Down, 0, GTK_SCROLL_STEP_LEFT); + add_slider_binding(w_class, binding_set, GDK_KEY_Right, 0, GTK_SCROLL_STEP_RIGHT); + add_slider_binding(w_class, binding_set, GDK_KEY_Up, 0, GTK_SCROLL_STEP_RIGHT); + add_slider_binding(w_class, binding_set, GDK_KEY_Page_Up, 0, GTK_SCROLL_PAGE_RIGHT); + add_slider_binding(w_class, binding_set, GDK_KEY_Page_Down, 0, GTK_SCROLL_PAGE_LEFT); + add_slider_binding(w_class, binding_set, GDK_KEY_Home, 0, GTK_SCROLL_START); + add_slider_binding(w_class, binding_set, GDK_KEY_End, 0, GTK_SCROLL_END); } -static void gtk_dial_init(GtkDial *dial) -{ -// gtk_dial_set_style(dial, "#cdc7c2", "white", "#3584e4"); - gtk_dial_set_style(dial, "#cdc7c2", "#f0f0f0", "#3584e4", "#808080"); - //gdk_rgba_parse(&dial->colors.trough_border, "#cdc7c2"); - gtk_widget_set_focusable (GTK_WIDGET (dial), TRUE); - //gtk_widget_set_parent(dial->slider_container, GTK_WIDGET(dial) ); - - dial->adj = NULL; - - dial->slider_cx = gtk_widget_get_width(GTK_WIDGET(dial) ) / 2.0; - dial->slider_cy = 0; - - dial->grab = GRAB_NONE; - dial->drag_gesture = gtk_gesture_drag_new(); - gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (dial->drag_gesture), 0); - g_signal_connect (dial->drag_gesture, "drag-begin", - G_CALLBACK (gtk_dial_drag_gesture_begin), dial); - g_signal_connect (dial->drag_gesture, "drag-update", - G_CALLBACK (gtk_dial_drag_gesture_update), dial); - g_signal_connect (dial->drag_gesture, "drag-end", - G_CALLBACK (gtk_dial_drag_gesture_end), dial); - gtk_widget_add_controller (GTK_WIDGET (dial), GTK_EVENT_CONTROLLER (dial->drag_gesture) ); - - dial->click_gesture = gtk_gesture_click_new(); - //gtk_gesture_long_press_set_delay_factor (GTK_GESTURE_LONG_PRESS (dial->click_gesture), 0.1); - gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (dial->click_gesture), 0); - g_signal_connect (dial->click_gesture, "pressed", - G_CALLBACK (gtk_dial_click_gesture_pressed), dial); - gtk_widget_add_controller (GTK_WIDGET (dial), GTK_EVENT_CONTROLLER (dial->click_gesture) ); - gtk_gesture_group (dial->click_gesture, dial->drag_gesture); - - dial->scroll_controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES); - g_signal_connect (dial->scroll_controller, "scroll", - G_CALLBACK (gtk_dial_scroll_controller_scroll), dial); - gtk_widget_add_controller (GTK_WIDGET (dial), dial->scroll_controller); +static void gtk_dial_focus_change_cb( + GtkEventControllerFocus *controller, GtkDial *dial +) { + gtk_widget_queue_draw(GTK_WIDGET(dial)); } -static void dial_measure(GtkWidget *widget, - GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline) -{ - *minimum = 50; - *natural = 50; - *minimum_baseline = for_size; - *natural_baseline = for_size; +static void gtk_dial_notify_sensitive_cb( + GObject *object, + GParamSpec *pspec, + GtkDial *dial +) { + gtk_widget_queue_draw(GTK_WIDGET(dial)); } -static void dial_snapshot(GtkWidget *widget, GtkSnapshot *snapshot) -{ - GtkDial *dial = GTK_DIAL(widget); +static void gtk_dial_init(GtkDial *dial) { + gtk_widget_set_focusable(GTK_WIDGET(dial), TRUE); - struct dial_properties p; - get_dial_properties(dial, &p); - p.valp = CLAMP(p.valp, 0.0001, 1.0); + dial->adj = NULL; - cairo_t *cr = gtk_snapshot_append_cairo(snapshot, &GRAPHENE_RECT_INIT(0, 0, p.w, p.h) ); + dial->grab = GRAB_NONE; + dial->drag_gesture = gtk_gesture_drag_new(); + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(dial->drag_gesture), 0); + g_signal_connect( + dial->drag_gesture, "drag-begin", + G_CALLBACK(gtk_dial_drag_gesture_begin), dial + ); + g_signal_connect( + dial->drag_gesture, "drag-update", + G_CALLBACK(gtk_dial_drag_gesture_update), dial + ); + g_signal_connect( + dial->drag_gesture, "drag-end", + G_CALLBACK(gtk_dial_drag_gesture_end), dial + ); + gtk_widget_add_controller( + GTK_WIDGET(dial), GTK_EVENT_CONTROLLER(dial->drag_gesture) + ); - // draw border + dial->click_gesture = gtk_gesture_click_new(); + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(dial->click_gesture), 0); + g_signal_connect( + dial->click_gesture, "pressed", + G_CALLBACK(gtk_dial_click_gesture_pressed), dial + ); + gtk_widget_add_controller( + GTK_WIDGET(dial), GTK_EVENT_CONTROLLER(dial->click_gesture) + ); + gtk_gesture_group(dial->click_gesture, dial->drag_gesture); + + dial->scroll_controller = gtk_event_controller_scroll_new( + GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES + ); + g_signal_connect( + dial->scroll_controller, "scroll", + G_CALLBACK(gtk_dial_scroll_controller_scroll), dial + ); + gtk_widget_add_controller(GTK_WIDGET(dial), dial->scroll_controller); + + GtkEventController *controller = gtk_event_controller_focus_new(); + g_signal_connect( + controller, "enter", G_CALLBACK(gtk_dial_focus_change_cb), dial + ); + g_signal_connect( + controller, "leave", G_CALLBACK(gtk_dial_focus_change_cb), dial + ); + gtk_widget_add_controller(GTK_WIDGET(dial), controller); + + g_signal_connect( + dial, "notify::sensitive", G_CALLBACK(gtk_dial_notify_sensitive_cb), dial + ); +} + +static void dial_measure( + GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline +) { + if (orientation == GTK_ORIENTATION_HORIZONTAL) { + *minimum = DIAL_MIN_WIDTH; + *natural = DIAL_MAX_WIDTH; + } else { + *minimum = calculate_dial_height(DIAL_MIN_WIDTH); + *natural = calculate_dial_height(DIAL_MAX_WIDTH); + } + *minimum_baseline = -1; + *natural_baseline = -1; +} + +// internal replacement for cairo_set_source_rgba() that dims the +// color if the widget is insensitive +static void cairo_set_source_rgba_dim( + cairo_t *cr, + double r, + double g, + double b, + double a, + int dim +) { + if (dim) + cairo_set_source_rgba(cr, r * 0.5, g * 0.5, b * 0.5, a); + else + cairo_set_source_rgba(cr, r, g, b, a); +} + +static void draw_slider( + GtkDial *dial, + cairo_t *cr, + double radius, + double thickness, + double alpha +) { + cairo_set_line_width(cr, thickness); + + int count = dial->level_breakpoints_count; + + if (!count) { + cairo_arc(cr, dial->cx, dial->cy, radius, ANGLE_START, dial->angle); + cairo_set_source_rgba_dim(cr, 1, 1, 1, alpha, dial->dim); + cairo_stroke(cr); + return; + } + + // if the last breakpoint is at the upper limit, then the maximum + // value is displayed with the whole slider that colours + if (dial->level_breakpoint_angles[count - 1] == ANGLE_END && + dial->angle == ANGLE_END) { + const double *colours = &dial->level_colours[(count - 1) * 3]; + + cairo_set_source_rgba_dim( + cr, + colours[0], colours[1], colours[2], + alpha, + dial->dim + ); + + cairo_arc(cr, dial->cx, dial->cy, radius, ANGLE_START, ANGLE_END); + cairo_stroke(cr); + return; + } + + for (int i = 0; i < count; i++) { + const double *colours = &dial->level_colours[i * 3]; + + cairo_set_source_rgba_dim( + cr, + colours[0], colours[1], colours[2], + alpha, + dial->dim + ); + + double angle_start = dial->level_breakpoint_angles[i]; + double angle_end = + i == count - 1 + ? ANGLE_END + : dial->level_breakpoint_angles[i + 1]; + + if (dial->angle < angle_end) { + cairo_arc(cr, dial->cx, dial->cy, radius, angle_start, dial->angle); + cairo_stroke(cr); + return; + } + + cairo_arc(cr, dial->cx, dial->cy, radius, angle_start, angle_end); + cairo_stroke(cr); + } +} + +static void dial_snapshot(GtkWidget *widget, GtkSnapshot *snapshot) { + GtkDial *dial = GTK_DIAL(widget); + + if (update_dial_properties(dial)) + update_dial_values(dial); + + cairo_t *cr = gtk_snapshot_append_cairo( + snapshot, + &GRAPHENE_RECT_INIT(0, 0, dial->w, dial->h) + ); + + cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + + // background line + cairo_arc( + cr, dial->cx, dial->cy, dial->slider_radius, ANGLE_START, ANGLE_END + ); + cairo_set_line_width(cr, 2); + cairo_set_source_rgba_dim(cr, 1, 1, 1, 0.17, dial->dim); + cairo_stroke(cr); + + if (dial->valp > 0.0) { + // outside value shadow + draw_slider( + dial, cr, dial->background_radius, dial->slider_thickness / 2, 0.1 + ); + + // value blur 2 + draw_slider(dial, cr, dial->slider_radius, 6, 0.3); + } + + // draw line to zero db + double zero_db = gtk_dial_get_zero_db(dial); + if (zero_db != -G_MAXDOUBLE) { + cairo_move_to(cr, dial->cx, dial->cy); + cairo_line_to(cr, dial->zero_db_x, dial->zero_db_y); cairo_set_line_width(cr, 2); - gdk_cairo_set_source_rgba(cr, &dial->colors.trough_border); - cairo_arc(cr, p.cx, p.cy, p.radius-p.thickness, RAD_START, RAD_END/*8*M_PI/5*/); - cairo_line_to(cr, V1x*(p.radius-p.thickness) + p.cx, V1y*(p.radius-p.thickness) + p.cy); - cairo_arc_negative(cr, p.cx, p.cy, p.radius, RAD_END, RAD_START/*8*M_PI/5*/); - cairo_close_path(cr); + cairo_set_source_rgba_dim(cr, 1, 1, 1, 0.17, dial->dim); cairo_stroke(cr); + } - // bg trough - cairo_arc(cr, p.cx, p.cy, (2*p.radius-p.thickness)/2.0, RAD_START, RAD_END/*8*M_PI/5*/); - cairo_set_line_width(cr, p.thickness); - gdk_cairo_set_source_rgba(cr, &dial->colors.trough_bg); - cairo_stroke(cr); - - // fill trough - cairo_arc(cr, p.cx, p.cy, (2*p.radius-p.thickness)/2.0, RAD_START, RAD_END - (1.0-p.valp)*(RAD_END-RAD_START)/*8*M_PI/5*/); - cairo_set_line_width(cr, p.thickness); - gdk_cairo_set_source_rgba(cr, &dial->colors.trough_fill); - cairo_stroke(cr); - - // pointer - gdk_cairo_set_source_rgba(cr, &dial->colors.pointer); + // marker when at min or max + if (gtk_dial_get_value(dial) == gtk_adjustment_get_lower(dial->adj) || + gtk_dial_get_value(dial) == gtk_adjustment_get_upper(dial->adj)) { + cairo_move_to(cr, dial->cx, dial->cy); + cairo_line_to(cr, dial->slider_cx, dial->slider_cy); cairo_set_line_width(cr, 2); - cairo_move_to(cr, p.cx, p.cy); - cairo_line_to(cr, p.slider_cx, p.slider_cy); + cairo_set_source_rgba_dim(cr, 1, 1, 1, 0.5, dial->dim); cairo_stroke(cr); + } - cairo_destroy(cr); + if (dial->valp > 0.0) { + // value blur 1 + draw_slider(dial, cr, dial->slider_radius, 4, 0.5); -// //FILL SLIDER -// cairo_arc(cr, p.slider_cx, p.slider_cy, p.slider_radius, 0, 2*M_PI); -// gdk_cairo_set_source_rgba(cr, &dial->colors.trough_bg); -// cairo_fill(cr); -// -// //STROKE SLIDER -// GdkRGBA *scolor = dial->grab != GRAB_SLIDER ? &dial->colors.trough_border : &dial->colors.trough_fill; -// cairo_arc(cr, p.slider_cx, p.slider_cy, p.slider_radius, 0, 2*M_PI); -// cairo_set_line_width(cr, 1); -// gdk_cairo_set_source_rgba(cr, scolor); -// cairo_stroke(cr); + // value + draw_slider(dial, cr, dial->slider_radius, 2, 1); + } + + // fill the circle + int has_focus = gtk_widget_has_focus(GTK_WIDGET(dial)); + cairo_set_source(cr, dial->fill_pattern[has_focus][dial->dim]); + cairo_arc(cr, dial->cx, dial->cy, dial->knob_radius, 0, 2 * M_PI); + cairo_fill(cr); + + // draw the circle + cairo_set_source(cr, dial->outline_pattern[dial->dim]); + cairo_arc(cr, dial->cx, dial->cy, dial->knob_radius, 0, 2 * M_PI); + cairo_set_line_width(cr, 2); + cairo_stroke(cr); + + // if focussed + if (has_focus) { + cairo_set_source_rgba(cr, 1, 0.125, 0.125, 0.5); + cairo_set_line_width(cr, 2); + cairo_arc(cr, dial->cx, dial->cy, dial->knob_radius + 2, 0, 2 * M_PI); + cairo_stroke(cr); + } + + cairo_destroy(cr); } -GtkWidget* gtk_dial_new(GtkAdjustment *adjustment) -{ - g_return_val_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment), - NULL); +GtkWidget *gtk_dial_new(GtkAdjustment *adjustment) { + g_return_val_if_fail( + adjustment == NULL || GTK_IS_ADJUSTMENT(adjustment), + NULL + ); - return g_object_new (GTK_TYPE_DIAL, - "adjustment", adjustment, - NULL); + return g_object_new( + GTK_TYPE_DIAL, + "adjustment", adjustment, + NULL + ); } -GtkWidget * -gtk_dial_new_with_range ( double min, - double max, - double step) -{ +GtkWidget *gtk_dial_new_with_range( + double min, + double max, + double step, + double page +) { GtkAdjustment *adj; int digits; - g_return_val_if_fail (min < max, NULL); - g_return_val_if_fail (step != 0.0, NULL); + g_return_val_if_fail(min < max, NULL); - adj = gtk_adjustment_new(min, min, max, step, 10 * step, 0); + adj = gtk_adjustment_new(min, min, max, step, page, 0); - if (fabs(step) >= 1.0 || step == 0.0) - { - digits = 0; - } - else - { - digits = abs ( (int) floor(log10(fabs(step) ) ) ); - if (digits > 5) - digits = 5; - } - return g_object_new (GTK_TYPE_DIAL, - "adjustment", adj, - "round_digits", 0, - NULL); -} - -static void gtk_dial_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - GtkDial *dial = GTK_DIAL(object); - - switch(prop_id) - { - case PROP_ADJUSTMENT: - gtk_dial_set_adjustment(dial, g_value_get_object(value) ); - break; - case PROP_ROUND_DIGITS: - gtk_dial_set_round_digits(dial, g_value_get_int(value) ); - break; - case PROP_ZERO_DB: - gtk_dial_set_zero_db(dial, g_value_get_double(value) ); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void gtk_dial_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - GtkDial *dial = GTK_DIAL(object); - - switch(prop_id) - { - case PROP_ADJUSTMENT: - g_value_set_object(value, dial->adj); - break; - case PROP_ROUND_DIGITS: - g_value_set_int(value, dial->round_digits); - break; - case PROP_ZERO_DB: - g_value_set_double(value, dial->zero_db); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -double gtk_dial_get_value (GtkDial *dial) -{ - return gtk_adjustment_get_value(dial->adj); -} - -void gtk_dial_set_value (GtkDial *dial, - double value) -{ - set_value(dial, value); - gtk_widget_queue_draw(GTK_WIDGET(dial) ); -} - -void gtk_dial_set_round_digits (GtkDial *dial, - int round_digits) -{ - dial->round_digits = round_digits; - gtk_dial_set_value(dial, gtk_dial_get_value(dial) ); -} - -int gtk_dial_get_round_digits (GtkDial *dial) -{ - return dial->round_digits; -} - -void gtk_dial_set_zero_db(GtkDial *dial, double zero_db) -{ - dial->zero_db = zero_db; -} - -double gtk_dial_get_zero_db(GtkDial *dial) -{ - return dial->zero_db; -} - -gboolean gtk_dial_set_style(GtkDial *dial, - const char *trough_border, - const char *trough_bg, - const char *trough_fill, - const char *pointer) -{ - gboolean out = TRUE; - if (trough_border) - out = out && gdk_rgba_parse(&dial->colors.trough_border, trough_border); - if (trough_bg) - out = out && gdk_rgba_parse(&dial->colors.trough_bg, trough_bg); - if (trough_fill) - out = out && gdk_rgba_parse(&dial->colors.trough_fill, trough_fill); - if (pointer) - out = out && gdk_rgba_parse(&dial->colors.pointer, pointer); - - return out; -} - -void gtk_dial_set_adjustment (GtkDial *dial, - GtkAdjustment *adj) -{ - if (!(adj == NULL || GTK_IS_ADJUSTMENT (adj) ) ) - return; - if (dial->adj) - g_object_unref(dial->adj); - dial->adj = adj; - g_object_ref_sink(dial->adj); - g_signal_emit(dial, signals[VALUE_CHANGED], 0); - gtk_widget_queue_draw(GTK_WIDGET(dial) ); -} - -GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial) -{ - return dial->adj; -} - -static void -set_value (GtkDial *dial, double newval) -{ - if (dial->round_digits >= 0) - { - double power; - int i; - - i = dial->round_digits; - power = 1; - while (i--) - power *= 10; - - newval = floor( (newval * power) + 0.5) / power; + if (step == 0.0) { + digits = -1; + } else if (fabs(step) >= 1.0) { + digits = 0; + } else { + digits = abs((int)floor(log10(fabs(step)))); + if (digits > 5) + digits = 5; } + + return g_object_new( + GTK_TYPE_DIAL, + "adjustment", adj, + "round_digits", digits, + NULL + ); +} + +static void gtk_dial_set_property( + GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec +) { + GtkDial *dial = GTK_DIAL(object); + + switch (prop_id) { + case PROP_ADJUSTMENT: + gtk_dial_set_adjustment(dial, g_value_get_object(value)); + break; + case PROP_ROUND_DIGITS: + gtk_dial_set_round_digits(dial, g_value_get_int(value)); + break; + case PROP_ZERO_DB: + gtk_dial_set_zero_db(dial, g_value_get_double(value)); + break; + case PROP_OFF_DB: + gtk_dial_set_off_db(dial, g_value_get_double(value)); + break; + case PROP_TAPER: + gtk_dial_set_taper(dial, g_value_get_int(value)); + break; + case PROP_CAN_CONTROL: + gtk_dial_set_can_control(dial, g_value_get_boolean(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gtk_dial_get_property( + GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec +) { + GtkDial *dial = GTK_DIAL(object); + + switch (prop_id) { + case PROP_ADJUSTMENT: + g_value_set_object(value, dial->adj); + break; + case PROP_ROUND_DIGITS: + g_value_set_int(value, dial->round_digits); + break; + case PROP_ZERO_DB: + g_value_set_double(value, dial->zero_db); + break; + case PROP_OFF_DB: + g_value_set_double(value, dial->off_db); + break; + case PROP_TAPER: + g_value_set_int(value, dial->taper); + break; + case PROP_CAN_CONTROL: + g_value_set_boolean(value, dial->can_control); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +double gtk_dial_get_value(GtkDial *dial) { + return gtk_adjustment_get_value(dial->adj); +} + +void gtk_dial_set_value(GtkDial *dial, double value) { + if (set_value(dial, value)) + gtk_widget_queue_draw(GTK_WIDGET(dial)); +} + +void gtk_dial_set_round_digits(GtkDial *dial, int round_digits) { + dial->round_digits = round_digits; + gtk_dial_set_value(dial, gtk_dial_get_value(dial)); +} + +int gtk_dial_get_round_digits(GtkDial *dial) { + return dial->round_digits; +} + +void gtk_dial_set_zero_db(GtkDial *dial, double zero_db) { + dial->zero_db = zero_db; + dial->properties_updated = 1; +} + +double gtk_dial_get_zero_db(GtkDial *dial) { + return dial->zero_db; +} + +void gtk_dial_set_off_db(GtkDial *dial, double off_db) { + dial->off_db = off_db; + dial->properties_updated = 1; +} + +double gtk_dial_get_off_db(GtkDial *dial) { + return dial->off_db; +} + +void gtk_dial_set_taper(GtkDial *dial, int taper) { + dial->taper = taper; + dial->properties_updated = 1; +} + +int gtk_dial_get_taper(GtkDial *dial) { + return dial->taper; +} + +void gtk_dial_set_taper_linear_breakpoints( + GtkDial *dial, + const double *breakpoints, + const double *outputs, + int count +) { + dial->properties_updated = 1; + + free(dial->taper_breakpoints); + free(dial->taper_outputs); + dial->taper_breakpoints = NULL; + dial->taper_outputs = NULL; + dial->taper_breakpoints_count = 0; + + if (count < 1) + return; + + int total_count = count + 2; + + dial->taper_breakpoints = malloc(total_count * sizeof(double)); + dial->taper_outputs = malloc(total_count * sizeof(double)); + + dial->taper_breakpoints[0] = 0; + dial->taper_outputs[0] = 0; + + for (int i = 0; i < count; i++) { + dial->taper_breakpoints[i + 1] = breakpoints[i]; + dial->taper_outputs[i + 1] = outputs[i]; + } + + dial->taper_breakpoints[total_count - 1] = 1; + dial->taper_outputs[total_count - 1] = 1; + + dial->taper_breakpoints_count = total_count; +} + +void gtk_dial_set_can_control(GtkDial *dial, gboolean can_control) { + dial->can_control = can_control; + dial->properties_updated = 1; +} + +gboolean gtk_dial_get_can_control(GtkDial *dial) { + return dial->can_control; +} + +void gtk_dial_set_level_meter_colours( + GtkDial *dial, + const int *breakpoints, + const double *colours, + int count +) { + dial->level_breakpoints = breakpoints; + dial->level_colours = colours; + dial->level_breakpoints_count = count; + dial->properties_updated = 1; +} + +void gtk_dial_set_adjustment(GtkDial *dial, GtkAdjustment *adj) { + if (!(adj == NULL || GTK_IS_ADJUSTMENT(adj))) + return; + if (dial->adj) + g_object_unref(dial->adj); + dial->adj = adj; + g_object_ref_sink(dial->adj); + g_signal_emit(dial, signals[VALUE_CHANGED], 0); + gtk_widget_queue_draw(GTK_WIDGET(dial)); +} + +GtkAdjustment *gtk_dial_get_adjustment(GtkDial *dial) { + return dial->adj; +} + +static int set_value(GtkDial *dial, double newval) { + if (dial->round_digits >= 0) { + double power; + int i; + + i = dial->round_digits; + power = 1; + while (i--) + power *= 10; + + newval = floor((newval * power) + 0.5) / power; + } + + if (newval < gtk_adjustment_get_lower(dial->adj)) + newval = gtk_adjustment_get_lower(dial->adj); + else if (newval > gtk_adjustment_get_upper(dial->adj)) + newval = gtk_adjustment_get_upper(dial->adj); + + double oldval = gtk_adjustment_get_value(dial->adj); + + if (oldval == newval) + return 0; + gtk_adjustment_set_value(dial->adj, newval); g_signal_emit(dial, signals[VALUE_CHANGED], 0); + + double old_valp = dial->valp; + update_dial_values(dial); + + return old_valp != dial->valp; } -static void -step_back (GtkDial *dial) -{ +static void step_back(GtkDial *dial) { double newval; - newval = gtk_adjustment_get_value (dial->adj) - gtk_adjustment_get_step_increment (dial->adj); + newval = gtk_adjustment_get_value(dial->adj) - gtk_adjustment_get_step_increment(dial->adj); set_value(dial, newval); } -static void -step_forward (GtkDial *dial) -{ +static void step_forward(GtkDial *dial) { double newval; - newval = gtk_adjustment_get_value (dial->adj) + gtk_adjustment_get_step_increment (dial->adj); + newval = gtk_adjustment_get_value(dial->adj) + gtk_adjustment_get_step_increment(dial->adj); set_value(dial, newval); } -static void -page_back (GtkDial *dial) -{ +static void page_back(GtkDial *dial) { double newval; - newval = gtk_adjustment_get_value (dial->adj) - gtk_adjustment_get_page_increment (dial->adj); + newval = gtk_adjustment_get_value(dial->adj) - gtk_adjustment_get_page_increment(dial->adj); set_value(dial, newval); } -static void -page_forward (GtkDial *dial) -{ +static void page_forward(GtkDial *dial) { double newval; - newval = gtk_adjustment_get_value (dial->adj) + gtk_adjustment_get_page_increment (dial->adj); + newval = gtk_adjustment_get_value(dial->adj) + gtk_adjustment_get_page_increment(dial->adj); set_value(dial, newval); } -static void -scroll_begin (GtkDial *dial) -{ +static void scroll_begin(GtkDial *dial) { + double newval = gtk_adjustment_get_lower(dial->adj); + + set_value(dial, newval); } -static void scroll_end(GtkDial *dial) -{ - double newval = gtk_adjustment_get_upper (dial->adj) - gtk_adjustment_get_page_size (dial->adj); - set_value(dial, newval); +static void scroll_end(GtkDial *dial) { + double newval = gtk_adjustment_get_upper(dial->adj) - gtk_adjustment_get_page_size(dial->adj); + + set_value(dial, newval); } -static gboolean should_invert_move(GtkDial *dial, GtkOrientation o) -{ - return FALSE; +static gboolean should_invert_move(GtkDial *dial, GtkOrientation o) { + return FALSE; } -static void gtk_dial_move_slider(GtkDial *dial, - GtkScrollType scroll) -{ +static void gtk_dial_move_slider(GtkDial *dial, GtkScrollType scroll) { switch (scroll) { case GTK_SCROLL_STEP_LEFT: - if (should_invert_move (dial, GTK_ORIENTATION_HORIZONTAL)) - step_forward (dial); + if (should_invert_move(dial, GTK_ORIENTATION_HORIZONTAL)) + step_forward(dial); else - step_back (dial); + step_back(dial); break; case GTK_SCROLL_STEP_UP: - if (should_invert_move (dial, GTK_ORIENTATION_VERTICAL)) - step_forward (dial); + if (should_invert_move(dial, GTK_ORIENTATION_VERTICAL)) + step_forward(dial); else - step_back (dial); + step_back(dial); break; case GTK_SCROLL_STEP_RIGHT: - if (should_invert_move (dial, GTK_ORIENTATION_HORIZONTAL)) - step_back (dial); + if (should_invert_move(dial, GTK_ORIENTATION_HORIZONTAL)) + step_back(dial); else - step_forward (dial); + step_forward(dial); break; case GTK_SCROLL_STEP_DOWN: - if (should_invert_move (dial, GTK_ORIENTATION_VERTICAL)) - step_back (dial); + if (should_invert_move(dial, GTK_ORIENTATION_VERTICAL)) + step_back(dial); else - step_forward (dial); + step_forward(dial); break; case GTK_SCROLL_STEP_BACKWARD: - step_back (dial); + step_back(dial); break; case GTK_SCROLL_STEP_FORWARD: - step_forward (dial); + step_forward(dial); break; case GTK_SCROLL_PAGE_LEFT: - if (should_invert_move (dial, GTK_ORIENTATION_HORIZONTAL)) - page_forward (dial); + if (should_invert_move(dial, GTK_ORIENTATION_HORIZONTAL)) + page_forward(dial); else - page_back (dial); + page_back(dial); break; case GTK_SCROLL_PAGE_UP: - if (should_invert_move (dial, GTK_ORIENTATION_VERTICAL)) - page_forward (dial); + if (should_invert_move(dial, GTK_ORIENTATION_VERTICAL)) + page_forward(dial); else - page_back (dial); + page_back(dial); break; case GTK_SCROLL_PAGE_RIGHT: - if (should_invert_move (dial, GTK_ORIENTATION_HORIZONTAL)) - page_back (dial); + if (should_invert_move(dial, GTK_ORIENTATION_HORIZONTAL)) + page_back(dial); else - page_forward (dial); + page_forward(dial); break; case GTK_SCROLL_PAGE_DOWN: - if (should_invert_move (dial, GTK_ORIENTATION_VERTICAL)) - page_back (dial); + if (should_invert_move(dial, GTK_ORIENTATION_VERTICAL)) + page_back(dial); else - page_forward (dial); + page_forward(dial); break; case GTK_SCROLL_PAGE_BACKWARD: - page_back (dial); + page_back(dial); break; case GTK_SCROLL_PAGE_FORWARD: - page_forward (dial); + page_forward(dial); break; case GTK_SCROLL_START: - scroll_begin (dial); + scroll_begin(dial); break; case GTK_SCROLL_END: - scroll_end (dial); + scroll_end(dial); break; case GTK_SCROLL_JUMP: case GTK_SCROLL_NONE: default: break; - } + } - gtk_widget_queue_draw(GTK_WIDGET(dial) ); + gtk_widget_queue_draw(GTK_WIDGET(dial)); } -static void -gtk_dial_drag_gesture_begin (GtkGestureDrag *gesture, - double offset_x, - double offset_y, - GtkDial *dial) -{ - dial->dvalp = calc_valp(gtk_dial_get_value(dial), gtk_adjustment_get_lower(dial->adj), gtk_adjustment_get_upper(dial->adj) ); - gtk_gesture_set_state(dial->drag_gesture, GTK_EVENT_SEQUENCE_CLAIMED); +static void gtk_dial_drag_gesture_begin( + GtkGestureDrag *gesture, + double offset_x, + double offset_y, + GtkDial *dial +) { + dial->dvalp = calc_valp( + gtk_dial_get_value(dial), + gtk_adjustment_get_lower(dial->adj), + gtk_adjustment_get_upper(dial->adj) + ); + gtk_gesture_set_state(dial->drag_gesture, GTK_EVENT_SEQUENCE_CLAIMED); } -static void -gtk_dial_drag_gesture_update (GtkGestureDrag *gesture, - double offset_x, - double offset_y, - GtkDial *dial) -{ - double start_x, start_y; +static void gtk_dial_drag_gesture_update( + GtkGestureDrag *gesture, + double offset_x, + double offset_y, + GtkDial *dial +) { + double start_x, start_y; - gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y); + gtk_gesture_drag_get_start_point(gesture, &start_x, &start_y); - struct dial_properties p; - get_dial_properties(dial, &p); + double valp = dial->dvalp - DRAG_FACTOR * (offset_y / dial->h); + valp = CLAMP(valp, 0.0, 1.0); - double valp = dial->dvalp - DRAG_FACTOR*(offset_y/p.h); - valp = CLAMP(valp, 0.0, 1.0); + double val = calc_val( + valp, + gtk_adjustment_get_lower(dial->adj), + gtk_adjustment_get_upper(dial->adj) + ); - double val = calc_val(valp, gtk_adjustment_get_lower(dial->adj), gtk_adjustment_get_upper(dial->adj) ); - - set_value(dial, val); - gtk_widget_queue_draw(GTK_WIDGET(dial) ); + set_value(dial, val); + gtk_widget_queue_draw(GTK_WIDGET(dial)); } -static void -gtk_dial_drag_gesture_end (GtkGestureDrag *gesture, - double offset_x, - double offset_y, - GtkDial *dial) -{ - dial->grab = GRAB_NONE; - gtk_widget_queue_draw(GTK_WIDGET(dial) ); +static void gtk_dial_drag_gesture_end( + GtkGestureDrag *gesture, + double offset_x, + double offset_y, + GtkDial *dial +) { + dial->grab = GRAB_NONE; + gtk_widget_queue_draw(GTK_WIDGET(dial)); } -static void -gtk_dial_click_gesture_pressed (GtkGestureClick *gesture, - int n_press, - double x, - double y, - GtkDial *dial) -{ - // on double (or more) click, toggle between lower and zero_db value - if (n_press >= 2) { - double lower = gtk_adjustment_get_lower(dial->adj); - if (gtk_dial_get_value(dial) != lower) - set_value(dial, lower); - else - set_value(dial, dial->zero_db); - return; - } +static void gtk_dial_click_gesture_pressed( + GtkGestureClick *gesture, + int n_press, + double x, + double y, + GtkDial *dial +) { - struct dial_properties p; - get_dial_properties(dial, &p); - if (circle_contains_point(p.slider_cx, p.slider_cy, p.slider_radius, x, y) ) - dial->grab = GRAB_SLIDER; + // on double (or more) click, toggle between lower and zero_db value + if (n_press >= 2) { + double lower = gtk_adjustment_get_lower(dial->adj); + + if (gtk_dial_get_value(dial) != lower) + set_value(dial, lower); else - dial->grab = GRAB_NONE; - gtk_widget_queue_draw(GTK_WIDGET(dial) ); - gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_CLAIMED); + set_value(dial, dial->zero_db); + + return; + } + + if (gtk_widget_get_focus_on_click(GTK_WIDGET(dial)) && + !gtk_widget_has_focus(GTK_WIDGET(dial))) + gtk_widget_grab_focus(GTK_WIDGET(dial)); + + if (circle_contains_point( + dial->slider_cx, dial->slider_cy, dial->radius, x, y + )) + dial->grab = GRAB_SLIDER; + else + dial->grab = GRAB_NONE; + + gtk_widget_queue_draw(GTK_WIDGET(dial)); + gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_CLAIMED); } -static gboolean -gtk_dial_scroll_controller_scroll (GtkEventControllerScroll *scroll, - double dx, - double dy, - GtkDial *dial) -{ - double delta = dx ? dx : dy; - if (abs(delta) > 1) - delta *= abs(delta); - double step = -gtk_adjustment_get_step_increment(dial->adj)*delta; +static gboolean gtk_dial_scroll_controller_scroll( + GtkEventControllerScroll *scroll, + double dx, + double dy, + GtkDial *dial +) { + double delta = dx ? dx : dy; + double absolute_delta = fabs(delta); - set_value(dial, gtk_adjustment_get_value(dial->adj) + step); - gtk_widget_queue_draw(GTK_WIDGET(dial) ); + if (absolute_delta > 1) + delta *= absolute_delta; - return GDK_EVENT_STOP; + double step = -gtk_adjustment_get_step_increment(dial->adj) * delta; + + set_value(dial, gtk_adjustment_get_value(dial->adj) + step); + gtk_widget_queue_draw(GTK_WIDGET(dial)); + + return GDK_EVENT_STOP; } -void gtk_dial_dispose(GObject *o) -{ - GtkDial *dial = GTK_DIAL(o); - g_object_unref(dial->adj); - dial->adj = NULL; - G_OBJECT_CLASS (gtk_dial_parent_class)->dispose(o); +void gtk_dial_dispose(GObject *o) { + GtkDial *dial = GTK_DIAL(o); + + free(dial->taper_breakpoints); + dial->taper_breakpoints = NULL; + free(dial->taper_outputs); + dial->taper_outputs = NULL; + dial->taper_breakpoints_count = 0; + free(dial->level_breakpoint_angles); + dial->level_breakpoint_angles = NULL; + + for (int focus = 0; focus <= 1; focus++) + for (int dim = 0; dim <= 1; dim++) + if (dial->fill_pattern[focus][dim]) + cairo_pattern_destroy(dial->fill_pattern[focus][dim]); + + for (int dim = 0; dim <= 1; dim++) + if (dial->outline_pattern[dim]) + cairo_pattern_destroy(dial->outline_pattern[dim]); + + g_object_unref(dial->adj); + dial->adj = NULL; + G_OBJECT_CLASS(gtk_dial_parent_class)->dispose(o); } diff --git a/src/gtkdial.h b/src/gtkdial.h index 38174fa..b0c26c9 100644 --- a/src/gtkdial.h +++ b/src/gtkdial.h @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: 2021 Stiliyan Varbanov -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: LGPL-3.0-or-later /* @@ -14,92 +14,86 @@ G_BEGIN_DECLS -#define GTK_TYPE_DIAL (gtk_dial_get_type ()) -#define GTK_DIAL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_DIAL, GtkDial)) -#define GTK_DIAL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_DIAL, GtkDialClass)) -#define GTK_IS_DIAL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_DIAL)) -#define GTK_IS_DIAL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_DIAL)) -#define GTK_DIAL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_DIAL, GtkDialClass)) +#define GTK_TYPE_DIAL (gtk_dial_get_type()) +#define GTK_DIAL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_DIAL, GtkDial)) +#define GTK_DIAL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_DIAL, GtkDialClass)) +#define GTK_IS_DIAL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_DIAL)) +#define GTK_IS_DIAL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_DIAL)) +#define GTK_DIAL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_DIAL, GtkDialClass)) -typedef struct _GtkDial GtkDial; -typedef struct _GtkDialClass GtkDialClass; +typedef struct _GtkDial GtkDial; +typedef struct _GtkDialClass GtkDialClass; -struct _GtkDialClass -{ +struct _GtkDialClass { GtkWidgetClass parent_class; - void (* value_changed) (GtkDial *dial); + void (*value_changed)(GtkDial *dial); /* action signals for keybindings */ - void (* move_slider) (GtkDial *dial, - GtkScrollType scroll); + void (*move_slider)(GtkDial *dial, GtkScrollType scroll); - gboolean (*change_value) (GtkDial *dial, - GtkScrollType scroll, - double new_value); + gboolean (*change_value)( + GtkDial *dial, + GtkScrollType scroll, + double new_value + ); }; -typedef char * (*GtkDialFormatValueFunc) (GtkDial *dial, - double value, - gpointer user_data); +GType gtk_dial_get_type(void) G_GNUC_CONST; -GDK_AVAILABLE_IN_ALL -GType gtk_dial_get_type (void) G_GNUC_CONST; -GDK_AVAILABLE_IN_ALL -GtkWidget * gtk_dial_new (GtkAdjustment *adjustment); -GDK_AVAILABLE_IN_ALL -GtkWidget * gtk_dial_new_with_range (double min, - double max, - double step); -GDK_AVAILABLE_IN_ALL -void gtk_dial_set_has_origin (GtkDial *dial, - gboolean has_origin); -GDK_AVAILABLE_IN_ALL -gboolean gtk_dial_get_has_origin (GtkDial *dial); +GtkWidget *gtk_dial_new(GtkAdjustment *adjustment); -GDK_AVAILABLE_IN_ALL -void gtk_dial_set_adjustment (GtkDial *dial, - GtkAdjustment *adj); +GtkWidget *gtk_dial_new_with_range( + double min, + double max, + double step, + double page +); -GDK_AVAILABLE_IN_ALL -GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial); +void gtk_dial_set_has_origin(GtkDial *dial, gboolean has_origin); +gboolean gtk_dial_get_has_origin(GtkDial *dial); -GDK_AVAILABLE_IN_ALL -double gtk_dial_get_value (GtkDial *dial); +void gtk_dial_set_adjustment(GtkDial *dial, GtkAdjustment *adj); +GtkAdjustment *gtk_dial_get_adjustment(GtkDial *dial); -GDK_AVAILABLE_IN_ALL -void gtk_dial_set_value (GtkDial *dial, - double value); -GDK_AVAILABLE_IN_ALL -void gtk_dial_set_round_digits (GtkDial *dial, - int round_digits); -GDK_AVAILABLE_IN_ALL -int gtk_dial_get_round_digits (GtkDial *range); -GDK_AVAILABLE_IN_ALL -void gtk_dial_set_zero_db (GtkDial *dial, - double zero_db); -GDK_AVAILABLE_IN_ALL -double gtk_dial_get_zero_db (GtkDial *range); +double gtk_dial_get_value(GtkDial *dial); +void gtk_dial_set_value(GtkDial *dial, double value); + +void gtk_dial_set_round_digits(GtkDial *dial, int round_digits); +int gtk_dial_get_round_digits(GtkDial *dial); + +void gtk_dial_set_zero_db(GtkDial *dial, double zero_db); +double gtk_dial_get_zero_db(GtkDial *dial); + +void gtk_dial_set_off_db(GtkDial *dial, double off_db); +double gtk_dial_get_off_db(GtkDial *dial); + +// taper functions +enum { + GTK_DIAL_TAPER_LINEAR, + GTK_DIAL_TAPER_LOG +}; + +void gtk_dial_set_taper(GtkDial *dial, int taper); +int gtk_dial_get_taper(GtkDial *dial); + +void gtk_dial_set_taper_linear_breakpoints( + GtkDial *dial, + const double *breakpoints, + const double *outputs, + int count +); + +void gtk_dial_set_can_control(GtkDial *dial, gboolean can_control); +gboolean gtk_dial_get_can_control(GtkDial *dial); + +void gtk_dial_set_level_meter_colours( + GtkDial *dial, + const int *breakpoints, + const double *colours, + int count +); -/** - * @brief Set the colors which this dial uses. String codes can be one of the following: - * A standard name (Taken from the X11 rgb.txt file) - * A hexadecimal value in the form “#rgb”, “#rrggbb”, “#rrrgggbbb” or ”#rrrrggggbbbb” - * A RGB color in the form “rgb(r,g,b)” (In this case the color will have full opacity) - * A RGBA color in the form “rgba(r,g,b,a)” - * NULL if the color is to remain unchanged - * - * @param dial: The dial - * @param trough_border: String code for trough border color - * @param trough_bg: String code for trough background color - * @param trough_fill: String code for trough fill color - * @return TRUE if all the colors were set successfully, FALSE otherwise - */ -gboolean gtk_dial_set_style(GtkDial *dial, - const char *trough_border, - const char *trough_bg, - const char *trough_fill, - const char *pointer); G_END_DECLS #endif diff --git a/src/gtkhelper.c b/src/gtkhelper.c index d81ae3f..a0c54c0 100644 --- a/src/gtkhelper.c +++ b/src/gtkhelper.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "gtkhelper.h" @@ -25,7 +25,15 @@ void gtk_grid_set_spacing(GtkGrid *grid, int spacing) { gtk_grid_set_column_spacing(grid, spacing); } -void gtk_widget_add_class(GtkWidget *w, const char *class) { - GtkStyleContext *style_context = gtk_widget_get_style_context(w); - gtk_style_context_add_class(style_context, class); +void gtk_widget_remove_css_classes_by_prefix( + GtkWidget *w, + const char *prefix +) { + char **classes = gtk_widget_get_css_classes(w); + + for (char **i = classes; *i != NULL; i++) + if (strncmp(*i, prefix, strlen(prefix)) == 0) + gtk_widget_remove_css_class(w, *i); + + g_strfreev(classes); } diff --git a/src/gtkhelper.h b/src/gtkhelper.h index dc9f282..407e67c 100644 --- a/src/gtkhelper.h +++ b/src/gtkhelper.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -9,4 +9,4 @@ void gtk_widget_set_margin(GtkWidget *w, int margin); void gtk_widget_set_expand(GtkWidget *w, gboolean expand); void gtk_widget_set_align(GtkWidget *w, GtkAlign x, GtkAlign y); void gtk_grid_set_spacing(GtkGrid *grid, int spacing); -void gtk_widget_add_class(GtkWidget *w, const char *class); +void gtk_widget_remove_css_classes_by_prefix(GtkWidget *w, const char *prefix); diff --git a/src/hardware.c b/src/hardware.c new file mode 100644 index 0000000..e33e4a2 --- /dev/null +++ b/src/hardware.c @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "hardware.h" + +struct scarlett2_device scarlett2_supported[] = { + { 0x8203, "Scarlett 2nd Gen 6i6" }, + { 0x8204, "Scarlett 2nd Gen 18i8" }, + { 0x8201, "Scarlett 2nd Gen 18i20" }, + { 0x8211, "Scarlett 3rd Gen Solo" }, + { 0x8210, "Scarlett 3rd Gen 2i2" }, + { 0x8212, "Scarlett 3rd Gen 4i4" }, + { 0x8213, "Scarlett 3rd Gen 8i6" }, + { 0x8214, "Scarlett 3rd Gen 18i8" }, + { 0x8215, "Scarlett 3rd Gen 18i20" }, + { 0x8216, "Vocaster One" }, + { 0x8217, "Vocaster Two" }, + { 0x8218, "Scarlett 4th Gen Solo" }, + { 0x8219, "Scarlett 4th Gen 2i2" }, + { 0x821a, "Scarlett 4th Gen 4i4" }, + { 0x8206, "Clarett USB 2Pre" }, + { 0x8207, "Clarett USB 4Pre" }, + { 0x8208, "Clarett USB 8Pre" }, + { 0x820a, "Clarett+ 2Pre" }, + { 0x820b, "Clarett+ 4Pre" }, + { 0x820c, "Clarett+ 8Pre" }, + { 0, NULL } +}; + +struct scarlett2_device *get_device_for_pid(int pid) { + for (int i = 0; scarlett2_supported[i].name; i++) + if (scarlett2_supported[i].pid == pid) + return &scarlett2_supported[i]; + + return NULL; +} diff --git a/src/hardware.h b/src/hardware.h new file mode 100644 index 0000000..cde3eec --- /dev/null +++ b/src/hardware.h @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +// Supported devices +struct scarlett2_device { + int pid; + const char *name; +}; + +struct scarlett2_device *get_device_for_pid(int pid); diff --git a/src/iface-mixer.c b/src/iface-mixer.c index d94b09f..cf8ac55 100644 --- a/src/iface-mixer.c +++ b/src/iface-mixer.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "gtkhelper.h" @@ -6,9 +6,12 @@ #include "stringhelper.h" #include "tooltips.h" #include "widget-boolean.h" -#include "widget-combo.h" +#include "widget-drop-down.h" #include "widget-dual.h" -#include "widget-volume.h" +#include "widget-gain.h" +#include "widget-input-select.h" +#include "widget-label.h" +#include "widget-sample-rate.h" #include "window-helper.h" #include "window-levels.h" #include "window-mixer.h" @@ -36,7 +39,9 @@ static void add_clock_source_control( gtk_box_append(GTK_BOX(global_controls), b); GtkWidget *l = gtk_label_new("Clock Source"); - GtkWidget *w = make_combo_box_alsa_elem(clock_source); + GtkWidget *w = make_drop_down_alsa_elem(clock_source, NULL); + gtk_widget_add_css_class(w, "clock-source"); + gtk_widget_add_css_class(w, "fixed"); gtk_box_append(GTK_BOX(b), l); gtk_box_append(GTK_BOX(b), w); @@ -75,6 +80,62 @@ static void add_sync_status_control( GtkWidget *l = gtk_label_new("Sync Status"); gtk_box_append(GTK_BOX(b), l); GtkWidget *w = make_boolean_alsa_elem(sync_status, "Unlocked", "Locked"); + gtk_widget_add_css_class(w, "sync-status"); + gtk_widget_add_css_class(w, "fixed"); + gtk_box_append(GTK_BOX(b), w); +} + +static void add_power_status_control( + struct alsa_card *card, + GtkWidget *global_controls +) { + GArray *elems = card->elems; + + struct alsa_elem *power_status = get_elem_by_name( + elems, "Power Status Card Enum" + ); + + if (!power_status) + return; + + GtkWidget *b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + gtk_widget_set_tooltip_text( + b, + "Power indicates if the interface is being powered by the USB " + "bus, an external power supply, or if there is insufficient power " + "available and the interface has shut down." + ); + gtk_box_append(GTK_BOX(global_controls), b); + + GtkWidget *l = gtk_label_new("Power"); + gtk_box_append(GTK_BOX(b), l); + GtkWidget *w = make_drop_down_alsa_elem(power_status, NULL); + gtk_widget_add_css_class(w, "power-status"); + gtk_widget_add_css_class(w, "fixed"); + gtk_box_append(GTK_BOX(b), w); +} + +static void add_sample_rate_control( + struct alsa_card *card, + GtkWidget *global_controls +) { + GtkWidget *b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + gtk_widget_set_tooltip_text( + b, + "The Sample Rate cannot be changed here because it is set by the " + "application which is using the interface, usually a sound " + "server like PulseAudio, JACK, or PipeWire. If this shows N/A, " + "no application is currently using the interface.\n\n" + "Note that not all features are available on all interfaces at " + "sample rates above 48kHz. Please refer to the user guide for " + "your interface for more information." + ); + gtk_box_append(GTK_BOX(global_controls), b); + + GtkWidget *l = gtk_label_new("Sample Rate"); + gtk_box_append(GTK_BOX(b), l); + GtkWidget *w = make_sample_rate_widget(card); + gtk_widget_add_css_class(w, "sample-rate"); gtk_box_append(GTK_BOX(b), w); } @@ -91,18 +152,19 @@ static void add_speaker_switching_controls( if (!speaker_switching) return; - make_dual_boolean_alsa_elems(speaker_switching, "Off", "On", "Main", "Alt"); - GtkWidget *b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + GtkWidget *w = make_dual_boolean_alsa_elems( + speaker_switching, + "Speaker Switching", + "Off", "On", "Main", "Alt" + ); + gtk_widget_set_tooltip_text( - b, + w, "Speaker Switching lets you swap between two pairs of " "monitoring speakers very easily." ); - GtkWidget *l = gtk_label_new("Speaker Switching"); - gtk_box_append(GTK_BOX(global_controls), b); - gtk_box_append(GTK_BOX(b), l); - gtk_box_append(GTK_BOX(b), speaker_switching->widget); - gtk_box_append(GTK_BOX(b), speaker_switching->widget2); + + gtk_box_append(GTK_BOX(global_controls), w); } static void add_talkback_controls( @@ -118,36 +180,299 @@ static void add_talkback_controls( if (!talkback) return; - make_dual_boolean_alsa_elems(talkback, "Disabled", "Enabled", "Off", "On"); - GtkWidget *b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + GtkWidget *w = make_dual_boolean_alsa_elems( + talkback, + "Talkback", + "Disabled", "Enabled", "Off", "On" + ); + gtk_widget_set_tooltip_text( - b, + w, "Talkback lets you add another channel (usually the talkback " "mic) to a mix with a button push, usually to talk to " "musicians, and without using an additional mic channel." ); - GtkWidget *l = gtk_label_new("Talkback"); - gtk_box_append(GTK_BOX(global_controls), b); - gtk_box_append(GTK_BOX(b), l); - gtk_box_append(GTK_BOX(b), talkback->widget); - gtk_box_append(GTK_BOX(b), talkback->widget2); + + gtk_box_append(GTK_BOX(global_controls), w); } static GtkWidget *create_global_box(GtkWidget *grid, int *x, int orient) { - GtkWidget *label = gtk_label_new("Global"); - GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); - GtkWidget *controls = gtk_box_new(orient, 15); - gtk_widget_set_margin(controls, 10); + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + gtk_widget_set_vexpand(box, TRUE); - gtk_grid_attach(GTK_GRID(grid), label, *x, 0, 1, 1); - gtk_grid_attach(GTK_GRID(grid), sep, *x, 1, 1, 1); - gtk_grid_attach(GTK_GRID(grid), controls, *x, 2, 1, 1); + GtkWidget *label = gtk_label_new("Global"); + gtk_widget_add_css_class(label, "controls-label"); + gtk_widget_set_halign(label, GTK_ALIGN_START); + GtkWidget *controls = gtk_box_new(orient, 15); + gtk_widget_add_css_class(controls, "controls-content"); + gtk_widget_set_vexpand(controls, TRUE); + + gtk_box_append(GTK_BOX(box), label); + gtk_box_append(GTK_BOX(box), controls); + + gtk_grid_attach(GTK_GRID(grid), box, *x, 0, 1, 1); (*x)++; return controls; } +/* 4th Gen Solo Mix switch */ +static void create_input_select_control( + GArray *elems, + GtkWidget *input_grid, + int *current_row +) { + struct alsa_elem *elem = get_elem_by_name(elems, "PCM Input Capture Switch"); + + if (!elem) + return; + + GtkWidget *w = make_boolean_alsa_elem(elem, "Mix", "Mix"); + gtk_widget_add_css_class(w, "pcm-input-mix"); + gtk_widget_set_tooltip_text( + w, + "Enabling Input Mix selects Mix E/F as the input source for " + "the PCM 1/2 Inputs rather than the DSP 1/2 Inputs. This is " + "useful to get a mono mix of both input channels." + ); + gtk_grid_attach(GTK_GRID(input_grid), w, 0, *current_row, 2, 1); + + (*current_row)++; +} + +static void create_input_link_control( + struct alsa_elem *elem, + GtkWidget *grid, + int current_row, + int column_num +) { + GtkWidget *w = make_boolean_alsa_elem(elem, "Link", NULL); + gtk_widget_add_css_class(w, "input-link"); + gtk_widget_set_hexpand(w, TRUE); + + int from, to; + get_two_num_from_string(elem->name, &from, &to); + if (to == -1) + to = from; + + gtk_grid_attach(GTK_GRID(grid), w, from - 1, current_row, to - from + 1, 1); +} + +static void create_input_gain_control( + struct alsa_elem *elem, + GtkWidget *grid, + int current_row, + int column_num +) { + GtkWidget *w = make_gain_alsa_elem(elem, 0, WIDGET_GAIN_TAPER_LINEAR, 1); + + gtk_grid_attach(GTK_GRID(grid), w, column_num, current_row, 1, 1); +} + +static void create_input_autogain_control( + struct alsa_elem *elem, + GtkWidget *grid, + int current_row, + int column_num +) { + GtkWidget *w = make_boolean_alsa_elem(elem, "Autogain", NULL); + gtk_widget_add_css_class(w, "autogain"); + gtk_widget_set_hexpand(w, TRUE); + gtk_widget_set_tooltip_text( + w, + "Autogain will listen to the input signal for 10 seconds and " + "automatically set the gain of the input channel to get the " + "best signal level." + ); + + gtk_grid_attach(GTK_GRID(grid), w, column_num, current_row, 1, 1); +} + +static void create_input_autogain_status_control( + struct alsa_elem *elem, + GtkWidget *grid, + int current_row, + int column_num +) { + GtkWidget *w = make_label_alsa_elem(elem); + + gtk_grid_attach(GTK_GRID(grid), w, column_num, current_row, 1, 1); +} + +static void create_input_safe_control( + struct alsa_elem *elem, + GtkWidget *grid, + int current_row, + int column_num +) { + GtkWidget *w = make_boolean_alsa_elem(elem, "Safe", NULL); + gtk_widget_add_css_class(w, "safe"); + gtk_widget_set_hexpand(w, TRUE); + gtk_widget_set_tooltip_text( + w, + "Enabling Safe Mode prevents the input from clipping by " + "automatically reducing the gain if the signal is too hot." + ); + + gtk_grid_attach(GTK_GRID(grid), w, column_num, current_row, 1, 1); +} + +static void create_input_level_control( + struct alsa_elem *elem, + GtkWidget *grid, + int current_row, + int column_num +) { + GtkWidget *w = make_boolean_alsa_elem(elem, "Inst", NULL); + gtk_widget_add_css_class(w, "inst"); + gtk_widget_set_hexpand(w, TRUE); + gtk_widget_set_tooltip_text(w, level_descr); + + gtk_grid_attach(GTK_GRID(grid), w, column_num, current_row, 1, 1); +} + +static void create_input_air_switch_control( + struct alsa_elem *elem, + GtkWidget *grid, + int current_row, + int column_num +) { + GtkWidget *w = make_boolean_alsa_elem(elem, "Air", NULL); + gtk_widget_add_css_class(w, "air"); + gtk_widget_set_hexpand(w, TRUE); + gtk_widget_set_tooltip_text(w, air_descr); + + gtk_grid_attach(GTK_GRID(grid), w, column_num, current_row, 1, 1); +} + +static void create_input_air_enum_control( + struct alsa_elem *elem, + GtkWidget *grid, + int current_row, + int column_num +) { + GtkWidget *w = make_drop_down_alsa_elem(elem, "Air"); + gtk_widget_add_css_class(w, "air"); + gtk_widget_set_hexpand(w, TRUE); + gtk_widget_set_tooltip_text(w, air_descr); + + gtk_grid_attach(GTK_GRID(grid), w, column_num, current_row, 1, 1); +} + +static void create_input_dsp_switch_control( + struct alsa_elem *elem, + GtkWidget *grid, + int current_row, + int column_num +) { + GtkWidget *w = make_boolean_alsa_elem(elem, "Enhance", NULL); + gtk_widget_add_css_class(w, "dsp"); + gtk_widget_set_hexpand(w, TRUE); +// gtk_widget_set_tooltip_text(w, dsp_descr); + + gtk_grid_attach(GTK_GRID(grid), w, column_num, current_row, 1, 1); +} + +static void create_input_dsp_preset_control( + struct alsa_elem *elem, + GtkWidget *grid, + int current_row, + int column_num +) { + GtkWidget *w = make_drop_down_alsa_elem(elem, NULL); + gtk_widget_add_css_class(w, "dsp-preset"); + gtk_widget_set_hexpand(w, TRUE); +// gtk_widget_set_tooltip_text(w, dsp_descr); + + gtk_grid_attach(GTK_GRID(grid), w, column_num, current_row, 1, 1); +} + +static void create_input_mute_switch_control( + struct alsa_elem *elem, + GtkWidget *grid, + int current_row, + int column_num +) { + GtkWidget *w = make_boolean_alsa_elem(elem, "Mute", NULL); + gtk_widget_add_css_class(w, "input-mute"); + gtk_widget_set_hexpand(w, TRUE); +// gtk_widget_set_tooltip_text(w, dsp_descr); + + gtk_grid_attach(GTK_GRID(grid), w, column_num, current_row, 1, 1); +} + +static void create_input_pad_control( + struct alsa_elem *elem, + GtkWidget *grid, + int current_row, + int column_num +) { + GtkWidget *w = make_boolean_alsa_elem(elem, "Pad", NULL); + gtk_widget_add_css_class(w, "pad"); + gtk_widget_set_hexpand(w, TRUE); + gtk_widget_set_tooltip_text( + w, + "Enabling Pad engages a 10dB attenuator in the channel, giving " + "you more headroom for very hot signals." + ); + + gtk_grid_attach(GTK_GRID(grid), w, column_num, current_row, 1, 1); +} + +static void create_input_phantom_control( + struct alsa_elem *elem, + GtkWidget *grid, + int current_row, + int column_num +) { + GtkWidget *w = make_boolean_alsa_elem(elem, "48V", NULL); + gtk_widget_add_css_class(w, "phantom"); + gtk_widget_set_hexpand(w, TRUE); + gtk_widget_set_tooltip_text(w, phantom_descr); + + int from, to; + get_two_num_from_string(elem->name, &from, &to); + if (to == -1) + to = from; + + gtk_grid_attach(GTK_GRID(grid), w, from - 1, current_row, to - from + 1, 1); +} + +static void create_input_controls_by_type( + GArray *elems, + GtkWidget *grid, + int *current_row, + char *control, + void (*create_func)(struct alsa_elem *, GtkWidget *, int, int) +) { + int count = 0; + + for (int i = 0; i < elems->len; i++) { + struct alsa_elem *elem = &g_array_index(elems, struct alsa_elem, i); + + // if no card entry, it's an empty slot + if (!elem->card) + continue; + + if (!strstr(elem->name, control)) + continue; + + int column_num = get_num_from_string(elem->name) - 1; + create_func(elem, grid, *current_row, column_num); + + count++; + } + + // Don't increment row for 4th Gen Solo Inst control so Air control + // goes next to it + if (!strcmp(control, "Level Capture Enum") && count == 1) + return; + + if (count) + (*current_row)++; +} + static void create_input_controls( struct alsa_card *card, GtkWidget *top, @@ -162,79 +487,96 @@ static void create_input_controls( if (!input_count) return; - GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL); - gtk_widget_set_halign(sep, GTK_ALIGN_CENTER); - gtk_grid_attach(GTK_GRID(top), sep, (*x)++, 0, 1, 3); + struct alsa_elem *input_select_elem = + get_elem_by_name(elems, "Input Select Capture Enum"); + + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); GtkWidget *label_ic = gtk_label_new("Analogue Inputs"); - gtk_grid_attach(GTK_GRID(top), label_ic, *x, 0, 1, 1); - - GtkWidget *horiz_input_sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); - gtk_grid_attach(GTK_GRID(top), horiz_input_sep, *x, 1, 1, 1); + gtk_widget_add_css_class(label_ic, "controls-label"); + gtk_widget_set_halign(label_ic, GTK_ALIGN_START); + gtk_box_append(GTK_BOX(box), label_ic); GtkWidget *input_grid = gtk_grid_new(); + gtk_widget_add_css_class(input_grid, "controls-content"); gtk_grid_set_spacing(GTK_GRID(input_grid), 10); - gtk_grid_attach(GTK_GRID(top), input_grid, *x, 2, 1, 1); + gtk_widget_set_hexpand(input_grid, TRUE); + gtk_widget_set_halign(input_grid, GTK_ALIGN_FILL); + gtk_widget_set_vexpand(input_grid, TRUE); + gtk_box_append(GTK_BOX(box), input_grid); + + gtk_grid_attach(GTK_GRID(top), box, *x, 0, 1, 1); for (int i = 1; i <= input_count; i++) { - char s[20]; - snprintf(s, 20, "%d", i); - GtkWidget *label = gtk_label_new(s); - gtk_grid_attach(GTK_GRID(input_grid), label, i, 0, 1, 1); - } + GtkWidget *label; - GtkWidget *level_label = NULL; - GtkWidget *air_label = NULL; - GtkWidget *pad_label = NULL; - - for (int i = 0; i < elems->len; i++) { - struct alsa_elem *elem = &g_array_index(elems, struct alsa_elem, i); - GtkWidget *w; - - // if no card entry, it's an empty slot - if (!elem->card) - continue; - - int line_num = get_num_from_string(elem->name); - - // input controls - if (strstr(elem->name, "Level Capture Enum")) { - if (!level_label) { - level_label = gtk_label_new("Level"); - gtk_grid_attach(GTK_GRID(input_grid), level_label, 0, 1, 1, 1); - } - w = make_boolean_alsa_elem(elem, "Line", "Inst"); - gtk_widget_set_tooltip_text(w, level_descr); - gtk_grid_attach(GTK_GRID(input_grid), w, line_num, 1, 1, 1); - } else if (strstr(elem->name, "Air Capture Switch")) { - if (!air_label) { - air_label = gtk_label_new("Air"); - gtk_grid_attach(GTK_GRID(input_grid), air_label, 0, 2, 1, 1); - } - w = make_boolean_alsa_elem(elem, "Off", "On"); - gtk_widget_set_tooltip_text(w, air_descr); - gtk_grid_attach(GTK_GRID(input_grid), w, line_num, 2, 1, 1); - } else if (strstr(elem->name, "Pad Capture Switch")) { - if (!pad_label) { - pad_label = gtk_label_new("Pad"); - gtk_grid_attach(GTK_GRID(input_grid), pad_label, 0, 3, 1, 1); - } - w = make_boolean_alsa_elem(elem, "Off", "On"); - gtk_widget_set_tooltip_text( - w, - "Enabling Pad engages an attenuator in the channel, giving " - "you more headroom for very hot signals." - ); - gtk_grid_attach(GTK_GRID(input_grid), w, line_num, 3, 1, 1); - } else if (strstr(elem->name, "Phantom Power Capture Switch")) { - int from, to; - get_two_num_from_string(elem->name, &from, &to); - w = make_boolean_alsa_elem(elem, "48V Off", "48V On"); - gtk_widget_set_tooltip_text(w, phantom_descr); - gtk_grid_attach(GTK_GRID(input_grid), w, from, 4, to - from + 1, 1); + if (input_select_elem) { + label = make_input_select_alsa_elem(input_select_elem, i); + } else { + char s[20]; + snprintf(s, 20, "%d", i); + label = gtk_label_new(s); } + gtk_grid_attach(GTK_GRID(input_grid), label, i - 1, 0, 1, 1); } + int current_row = 1; + + create_input_select_control(elems, input_grid, ¤t_row); + + create_input_controls_by_type( + elems, input_grid, ¤t_row, + "Link Capture Switch", create_input_link_control + ); + create_input_controls_by_type( + elems, input_grid, ¤t_row, + "Gain Capture Volume", create_input_gain_control + ); + create_input_controls_by_type( + elems, input_grid, ¤t_row, + "Autogain Capture Switch", create_input_autogain_control + ); + create_input_controls_by_type( + elems, input_grid, ¤t_row, + "Autogain Status Capture Enum", create_input_autogain_status_control + ); + create_input_controls_by_type( + elems, input_grid, ¤t_row, + "Safe Capture Switch", create_input_safe_control + ); + create_input_controls_by_type( + elems, input_grid, ¤t_row, + "Level Capture Enum", create_input_level_control + ); + create_input_controls_by_type( + elems, input_grid, ¤t_row, + "Air Capture Switch", create_input_air_switch_control + ); + create_input_controls_by_type( + elems, input_grid, ¤t_row, + "Air Capture Enum", create_input_air_enum_control + ); + create_input_controls_by_type( + elems, input_grid, ¤t_row, + "DSP Capture Switch", create_input_dsp_switch_control + ); + create_input_controls_by_type( + elems, input_grid, ¤t_row, + "DSP Preset Capture Enum", create_input_dsp_preset_control + ); + create_input_controls_by_type( + elems, input_grid, ¤t_row, + "Mute Capture Switch", create_input_mute_switch_control + ); + create_input_controls_by_type( + elems, input_grid, ¤t_row, + "Pad Capture Switch", create_input_pad_control + ); + create_input_controls_by_type( + elems, input_grid, ¤t_row, + "Phantom Power Capture Switch", create_input_phantom_control + ); + (*x)++; } @@ -247,24 +589,68 @@ static void create_output_controls( ) { GArray *elems = card->elems; - if (*x) { - GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL); - gtk_grid_attach(GTK_GRID(top), sep, (*x)++, y, x_span, 3); - } + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); GtkWidget *label_oc = gtk_label_new("Analogue Outputs"); - gtk_grid_attach(GTK_GRID(top), label_oc, *x, y, x_span, 1); - - GtkWidget *horiz_output_sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL); - gtk_grid_attach(GTK_GRID(top), horiz_output_sep, *x, y + 1, x_span, 1); + gtk_widget_add_css_class(label_oc, "controls-label"); + gtk_widget_set_halign(label_oc, GTK_ALIGN_START); + gtk_box_append(GTK_BOX(box), label_oc); GtkWidget *output_grid = gtk_grid_new(); + gtk_widget_add_css_class(output_grid, "controls-content"); gtk_grid_set_spacing(GTK_GRID(output_grid), 10); - gtk_grid_attach(GTK_GRID(top), output_grid, *x, y + 2, x_span, 1); gtk_widget_set_hexpand(output_grid, TRUE); + gtk_widget_set_vexpand(output_grid, TRUE); + gtk_box_append(GTK_BOX(box), output_grid); + + gtk_grid_attach(GTK_GRID(top), box, *x, y, x_span, 1); int output_count = get_max_elem_by_name(elems, "Line", "Playback Volume"); + /* 4th Gen Solo/2i2 */ + if (get_elem_by_prefix(elems, "Direct Monitor Playback")) { + struct alsa_elem *elem; + + for (int i = 0; i < 2; i++) { + char s[20]; + snprintf(s, 20, "%d", i + 1); + GtkWidget *label = gtk_label_new(s); + gtk_grid_attach(GTK_GRID(output_grid), label, i, 0, 1, 1); + } + + /* Solo */ + + elem = get_elem_by_name(elems, "Direct Monitor Playback Switch"); + + if (elem) { + GtkWidget *w = make_boolean_alsa_elem(elem, "Direct Monitor", NULL); + gtk_widget_add_css_class(w, "direct-monitor"); + gtk_widget_set_tooltip_text( + w, + "Direct Monitor sends the analogue input signals to the " + "analogue outputs for zero-latency monitoring." + ); + gtk_grid_attach(GTK_GRID(output_grid), w, 0, 1, 2, 1); + } + + /* 2i2 */ + + elem = get_elem_by_name(elems, "Direct Monitor Playback Enum"); + + if (elem) { + GtkWidget *w = make_drop_down_alsa_elem(elem, "Direct Monitor"); + gtk_widget_add_css_class(w, "direct-monitor"); + gtk_widget_set_tooltip_text( + w, + "Direct Monitor sends the analogue input signals to the " + "analogue outputs for zero-latency monitoring." + ); + gtk_grid_attach(GTK_GRID(output_grid), w, 0, 1, 2, 1); + } + + return; + } + int has_hw_vol = !!get_elem_by_name(elems, "Master HW Playback Volume"); int line_1_col = has_hw_vol; @@ -288,7 +674,7 @@ static void create_output_controls( // output controls if (strncmp(elem->name, "Line", 4) == 0) { if (strstr(elem->name, "Playback Volume")) { - w = make_volume_alsa_elem(elem); + w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_LOG, 1); gtk_grid_attach( GTK_GRID(output_grid), w, line_num - 1 + line_1_col, 1, 1, 1 ); @@ -296,6 +682,7 @@ static void create_output_controls( w = make_boolean_alsa_elem( elem, "*audio-volume-high", "*audio-volume-muted" ); + gtk_widget_add_css_class(w, "mute"); if (has_hw_vol) { gtk_widget_set_tooltip_text( w, @@ -309,6 +696,7 @@ static void create_output_controls( ); } else if (strstr(elem->name, "Volume Control Playback Enum")) { w = make_boolean_alsa_elem(elem, "SW", "HW"); + gtk_widget_add_css_class(w, "sw-hw"); gtk_widget_set_tooltip_text( w, "Set software-controlled (SW) or hardware-controlled (HW) " @@ -321,26 +709,47 @@ static void create_output_controls( // master output controls } else if (strcmp(elem->name, "Master HW Playback Volume") == 0) { - GtkWidget *l = gtk_label_new("HW"); + int gen4 = !!strstr(card->name, "4th Gen"); + + GtkWidget *l = gtk_label_new(gen4 ? "Line 1–2" : "HW"); + gtk_grid_attach(GTK_GRID(output_grid), l, 0, 0, 1, 1); + if (gen4) { + w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_GEN4_VOLUME, 0); + } else { + w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_LOG, 0); + } + gtk_widget_set_tooltip_text( + w, + gen4 + ? "This control shows the setting of the master volume " + "knob, which controls the volume of the analogue line " + "outputs 1 and 2." + : "This control shows the setting of the physical " + "(hardware) volume knob, which controls the volume of " + "the analogue outputs which have been set to “HW”." + ); + gtk_grid_attach(GTK_GRID(output_grid), w, 0, 1, 1, 1); + } else if (strcmp(elem->name, "Headphone Playback Volume") == 0) { + GtkWidget *l = gtk_label_new("Headphones"); gtk_widget_set_tooltip_text( l, - "This control shows the setting of the physical (hardware) " - "volume knob, which controls the volume of the analogue " - "outputs which have been set to “HW”." + "This control shows the setting of the headphone volume knob." ); - gtk_grid_attach(GTK_GRID(output_grid), l, 0, 0, 1, 1); - w = make_volume_alsa_elem(elem); - gtk_grid_attach(GTK_GRID(output_grid), w, 0, 1, 1, 1); + gtk_grid_attach(GTK_GRID(output_grid), l, 1, 0, 1, 1); + w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_GEN4_VOLUME, 0); + gtk_grid_attach(GTK_GRID(output_grid), w, 1, 1, 1, 1); } else if (strcmp(elem->name, "Mute Playback Switch") == 0) { w = make_boolean_alsa_elem( elem, "*audio-volume-high", "*audio-volume-muted" ); + gtk_widget_add_css_class(w, "mute"); gtk_widget_set_tooltip_text(w, "Mute HW controlled outputs"); - gtk_grid_attach(GTK_GRID(output_grid), elem->widget, 0, 2, 1, 1); + gtk_grid_attach(GTK_GRID(output_grid), w, 0, 2, 1, 1); } else if (strcmp(elem->name, "Dim Playback Switch") == 0) { w = make_boolean_alsa_elem( elem, "*audio-volume-medium", "*audio-volume-low" ); + gtk_widget_add_css_class(w, "dim"); gtk_widget_set_tooltip_text( w, "Dim (lower volume) of HW controlled outputs" ); @@ -360,37 +769,59 @@ static void create_global_controls( ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL; GtkWidget *global_controls = create_global_box(top, x, orient); - GtkWidget *left = global_controls; - GtkWidget *right = global_controls; + GtkWidget *column[3]; + + for (int i = 0; i < 3; i++) + column[i] = global_controls; if (card->has_speaker_switching) { - left = gtk_box_new(GTK_ORIENTATION_VERTICAL, 15); - right = gtk_box_new(GTK_ORIENTATION_VERTICAL, 15); - gtk_box_append(GTK_BOX(global_controls), left); - gtk_box_append(GTK_BOX(global_controls), right); + for (int i = 0; i < 3; i++) { + column[i] = gtk_box_new(GTK_ORIENTATION_VERTICAL, 15); + gtk_box_append(GTK_BOX(global_controls), column[i]); + } } - add_clock_source_control(card, left); - add_sync_status_control(card, right); - add_speaker_switching_controls(card, left); - add_talkback_controls(card, right); + add_clock_source_control(card, column[0]); + add_sync_status_control(card, column[1]); + add_power_status_control(card, column[1]); + add_sample_rate_control(card, column[2]); + add_speaker_switching_controls(card, column[0]); + add_talkback_controls(card, column[1]); } static GtkWidget *create_main_window_controls(struct alsa_card *card) { int x = 0; GtkWidget *top = gtk_grid_new(); - gtk_widget_set_margin(top, 10); - gtk_grid_set_spacing(GTK_GRID(top), 10); + gtk_widget_add_css_class(top, "window-content"); + gtk_widget_add_css_class(top, "iface-mixer"); + + if (strstr(card->name, "4th Gen") || + strstr(card->name, "Gen 4")) { + gtk_widget_add_css_class(top, "gen4"); + } else if (strstr(card->name, "Scarlett")) { + gtk_widget_add_css_class(top, "scarlett"); + } else if (strstr(card->name, "Clarett")) { + gtk_widget_add_css_class(top, "clarett"); + } else if (strstr(card->name, "Vocaster")) { + gtk_widget_add_css_class(top, "vocaster"); + } + + gtk_grid_set_spacing(GTK_GRID(top), 15); + + int input_count = get_max_elem_by_name( + card->elems, "Line", "Capture Switch" + ); + int output_count = get_max_elem_by_name( + card->elems, "Line", "Playback Volume" + ); create_global_controls(card, top, &x); create_input_controls(card, top, &x); - if (card->has_speaker_switching) { - x = 0; - GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); - gtk_grid_attach(GTK_GRID(top), sep, 0, 3, 3, 1); - create_output_controls(card, top, &x, 4, 3); + if (input_count + output_count >= 12) { + x = 0; + create_output_controls(card, top, &x, 1, 2); } else { create_output_controls(card, top, &x, 0, 1); } @@ -425,13 +856,38 @@ static gboolean window_levels_close_request(GtkWindow *w, gpointer data) { return true; } +// wrap a scrolled window around the controls +static void create_scrollable_window(GtkWidget *window, GtkWidget *controls) { + GtkWidget *scrolled_window = gtk_scrolled_window_new(); + + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC + ); + gtk_scrolled_window_set_child( + GTK_SCROLLED_WINDOW(scrolled_window), controls + ); + gtk_scrolled_window_set_propagate_natural_height( + GTK_SCROLLED_WINDOW(scrolled_window), TRUE + ); + gtk_scrolled_window_set_propagate_natural_width( + GTK_SCROLLED_WINDOW(scrolled_window), TRUE + ); + + gtk_window_set_child(GTK_WINDOW(window), scrolled_window); + gtk_window_set_resizable(GTK_WINDOW(window), TRUE); +} + GtkWidget *create_iface_mixer_main(struct alsa_card *card) { card->has_speaker_switching = !!get_elem_by_name(card->elems, "Speaker Switching Playback Enum"); card->has_talkback = !!get_elem_by_name(card->elems, "Talkback Playback Enum"); - GtkWidget *top = create_main_window_controls(card); + GtkWidget *top = gtk_frame_new(NULL); + gtk_widget_add_css_class(top, "window-frame"); + GtkWidget *contents = create_main_window_controls(card); + gtk_frame_set_child(GTK_FRAME(top), contents); GtkWidget *routing_top = create_routing_controls(card); if (!routing_top) @@ -441,7 +897,7 @@ GtkWidget *create_iface_mixer_main(struct alsa_card *card) { card, "Routing", G_CALLBACK(window_routing_close_request) ); - gtk_window_set_child(GTK_WINDOW(card->window_routing), routing_top); + create_scrollable_window(card->window_routing, routing_top); GtkWidget *mixer_top = create_mixer_controls(card); @@ -449,7 +905,7 @@ GtkWidget *create_iface_mixer_main(struct alsa_card *card) { card, "Mixer", G_CALLBACK(window_mixer_close_request) ); - gtk_window_set_child(GTK_WINDOW(card->window_mixer), mixer_top); + create_scrollable_window(card->window_mixer, mixer_top); GtkWidget *levels_top = create_levels_controls(card); diff --git a/src/iface-mixer.h b/src/iface-mixer.h index 3fa47ac..89585d2 100644 --- a/src/iface-mixer.h +++ b/src/iface-mixer.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/iface-no-mixer.c b/src/iface-no-mixer.c index bec6e72..a6fdbd1 100644 --- a/src/iface-no-mixer.c +++ b/src/iface-no-mixer.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "gtkhelper.h" @@ -6,38 +6,49 @@ #include "stringhelper.h" #include "tooltips.h" #include "widget-boolean.h" -#include "widget-combo.h" +#include "widget-drop-down.h" #include "window-helper.h" #include "window-startup.h" GtkWidget *create_iface_no_mixer_main(struct alsa_card *card) { GArray *elems = card->elems; - GtkWidget *grid = gtk_grid_new(); + GtkWidget *top = gtk_frame_new(NULL); + gtk_widget_add_css_class(top, "window-frame"); + + GtkWidget *content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 15); + gtk_widget_add_css_class(content, "window-content"); + gtk_widget_add_css_class(content, "iface-no-mixer"); + gtk_frame_set_child(GTK_FRAME(top), content); + + GtkWidget *input_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + GtkWidget *output_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + gtk_box_append(GTK_BOX(content), input_box); + gtk_box_append(GTK_BOX(content), output_box); + GtkWidget *label_ic = gtk_label_new("Input Controls"); - GtkWidget *vert_sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL); GtkWidget *label_oc = gtk_label_new("Output Controls"); - gtk_widget_set_margin(grid, 10); - gtk_grid_set_spacing(GTK_GRID(grid), 10); + gtk_widget_add_css_class(label_ic, "controls-label"); + gtk_widget_add_css_class(label_oc, "controls-label"); - gtk_grid_attach(GTK_GRID(grid), label_ic, 0, 0, 1, 1); - gtk_grid_attach(GTK_GRID(grid), vert_sep, 1, 0, 1, 3); - gtk_grid_attach(GTK_GRID(grid), label_oc, 2, 0, 1, 1); + gtk_widget_set_halign(label_ic, GTK_ALIGN_START); + gtk_widget_set_halign(label_oc, GTK_ALIGN_START); - GtkWidget *horiz_input_sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); - gtk_grid_attach(GTK_GRID(grid), horiz_input_sep, 0, 1, 1, 1); + gtk_box_append(GTK_BOX(input_box), label_ic); + gtk_box_append(GTK_BOX(output_box), label_oc); GtkWidget *input_grid = gtk_grid_new(); gtk_grid_set_spacing(GTK_GRID(input_grid), 10); - gtk_grid_attach(GTK_GRID(grid), input_grid, 0, 2, 1, 1); - - GtkWidget *horiz_output_sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL); - gtk_grid_attach(GTK_GRID(grid), horiz_output_sep, 2, 1, 1, 1); + gtk_widget_add_css_class(input_grid, "controls-content"); + gtk_widget_set_vexpand(input_grid, TRUE); + gtk_box_append(GTK_BOX(input_box), input_grid); GtkWidget *output_grid = gtk_grid_new(); gtk_grid_set_spacing(GTK_GRID(output_grid), 10); - gtk_grid_attach(GTK_GRID(grid), output_grid, 2, 2, 1, 1); + gtk_widget_add_css_class(output_grid, "controls-content"); + gtk_widget_set_vexpand(output_grid, TRUE); + gtk_box_append(GTK_BOX(output_box), output_grid); // Solo or 2i2? // Solo Phantom Power is Line 1 only @@ -48,7 +59,7 @@ GtkWidget *create_iface_no_mixer_main(struct alsa_card *card) { for (int i = 0; i < 2; i++) { char s[20]; - snprintf(s, 20, "Analogue %d", i + 1); + snprintf(s, 20, "%d", i + 1); GtkWidget *label = gtk_label_new(s); gtk_grid_attach(GTK_GRID(input_grid), label, i, 0, 1, 1); } @@ -67,23 +78,25 @@ GtkWidget *create_iface_no_mixer_main(struct alsa_card *card) { int line_num = get_num_from_string(elem->name); if (strstr(elem->name, "Level Capture Enum")) { - w = make_boolean_alsa_elem(elem, "Line", "Inst"); + w = make_boolean_alsa_elem(elem, "Inst", NULL); + gtk_widget_add_css_class(w, "inst"); gtk_widget_set_tooltip_text(w, level_descr); gtk_grid_attach(GTK_GRID(input_grid), w, line_num - 1, 1, 1, 1); } else if (strstr(elem->name, "Air Capture Switch")) { - w = make_boolean_alsa_elem(elem, "Air Off", "Air On"); + w = make_boolean_alsa_elem(elem, "Air", NULL); + gtk_widget_add_css_class(w, "air"); gtk_widget_set_tooltip_text(w, air_descr); gtk_grid_attach( GTK_GRID(input_grid), w, line_num - 1, 1 + !is_solo, 1, 1 ); } else if (strstr(elem->name, "Phantom Power Capture Switch")) { - w = make_boolean_alsa_elem(elem, "48V Off", "48V On"); + w = make_boolean_alsa_elem(elem, "48V", NULL); + gtk_widget_add_css_class(w, "phantom"); gtk_widget_set_tooltip_text(w, phantom_descr); gtk_grid_attach(GTK_GRID(input_grid), w, 0, 3, 1 + !is_solo, 1); } else if (strcmp(elem->name, "Direct Monitor Playback Switch") == 0) { - w = make_boolean_alsa_elem( - elem, "Direct Monitor Off", "Direct Monitor On" - ); + w = make_boolean_alsa_elem(elem, "Direct Monitor", NULL); + gtk_widget_add_css_class(w, "direct-monitor"); gtk_widget_set_tooltip_text( w, "Direct Monitor sends the analogue input signals to the " @@ -91,9 +104,8 @@ GtkWidget *create_iface_no_mixer_main(struct alsa_card *card) { ); gtk_grid_attach(GTK_GRID(output_grid), w, 0, 0, 1, 1); } else if (strcmp(elem->name, "Direct Monitor Playback Enum") == 0) { - GtkWidget *l = gtk_label_new("Direct Monitor"); - gtk_grid_attach(GTK_GRID(output_grid), l, 0, 0, 1, 1); - w = make_combo_box_alsa_elem(elem); + w = make_drop_down_alsa_elem(elem, "Direct Monitor"); + gtk_widget_add_css_class(w, "direct-monitor"); gtk_widget_set_tooltip_text( w, "Direct Monitor sends the analogue input signals to the " @@ -101,7 +113,7 @@ GtkWidget *create_iface_no_mixer_main(struct alsa_card *card) { "both inputs to the left and right outputs. Stereo sends " "input 1 to the left, and input 2 to the right output." ); - gtk_grid_attach(GTK_GRID(output_grid), w, 0, 1, 1, 1); + gtk_grid_attach(GTK_GRID(output_grid), w, 0, 0, 1, 1); } } @@ -112,5 +124,5 @@ GtkWidget *create_iface_no_mixer_main(struct alsa_card *card) { GtkWidget *startup = create_startup_controls(card); gtk_window_set_child(GTK_WINDOW(card->window_startup), startup); - return grid; + return top; } diff --git a/src/iface-no-mixer.h b/src/iface-no-mixer.h index a512e89..aad8515 100644 --- a/src/iface-no-mixer.h +++ b/src/iface-no-mixer.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/iface-none.c b/src/iface-none.c index f08eafa..ad996da 100644 --- a/src/iface-none.c +++ b/src/iface-none.c @@ -1,6 +1,7 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later +#include "alsa.h" #include "iface-none.h" #include "gtkhelper.h" #include "menu.h" @@ -11,20 +12,22 @@ GtkWidget *create_window_iface_none(GtkApplication *app) { GtkWidget *picture = gtk_picture_new_for_resource( "/vu/b4/alsa-scarlett-gui/icons/alsa-scarlett-gui-logo.png" ); - GtkWidget *label = gtk_label_new("No Scarlett Gen 2/3 interface found."); + GtkWidget *label = gtk_label_new("No Scarlett/Clarett/Vocaster interface found."); gtk_box_append(GTK_BOX(box), picture); gtk_box_append(GTK_BOX(box), label); GtkWidget *w = gtk_application_window_new(app); gtk_window_set_resizable(GTK_WINDOW(w), FALSE); - gtk_window_set_title(GTK_WINDOW(w), "ALSA Scarlett Gen 2/3 Control Panel"); + gtk_window_set_title(GTK_WINDOW(w), "ALSA Scarlett2 Control Panel"); gtk_window_set_child(GTK_WINDOW(w), box); gtk_application_window_set_show_menubar( GTK_APPLICATION_WINDOW(w), TRUE ); add_window_action_map(GTK_WINDOW(w)); - gtk_widget_show(w); + if (!alsa_has_reopen_callbacks()) { + gtk_widget_set_visible(w, TRUE); + } return w; } diff --git a/src/iface-none.h b/src/iface-none.h index f5a48ee..a1ed5b7 100644 --- a/src/iface-none.h +++ b/src/iface-none.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/iface-unknown.c b/src/iface-unknown.c index 23765ae..c7abda6 100644 --- a/src/iface-unknown.c +++ b/src/iface-unknown.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "gtkhelper.h" @@ -8,19 +8,21 @@ GtkWidget *create_iface_unknown_main(void) { GtkWidget *label = gtk_label_new( "Sorry, I don’t recognise the controls on this card.\n\n" - "These Focusrite Scarlett models should be supported:\n" + "These Focusrite models should be supported:\n" "– Gen 2: 6i6/18i8/18i20\n" - "– Gen 3: Solo/2i2/4i4/8i6/18i8/18i20\n\n" + "– Gen 3: Solo/2i2/4i4/8i6/18i8/18i20\n" + "– Gen 4: Solo/2i2/4i4\n" + "– Clarett USB and Clarett+ 2Pre/4Pre/8Pre\n\n" - "Are you running a recent kernel with Scarlett Gen 2/3 support " + "Are you running a recent kernel with Scarlett2 support " "enabled?\n\n" - "Check dmesg output for “Focusrite Scarlett Gen 2/3 Mixer " - "Driver”:\n\n" + "Check dmesg output for “Focusrite ... Mixer Driver”:\n\n" - "dmesg | grep Scarlett\n\n" + "dmesg | grep -A 5 -B 5 -i focusrite\n\n" - "You may need to create a file /etc/modprobe.d/scarlett.conf\n" + "For kernels before 6.7 you may need to create a file\n" + "/etc/modprobe.d/scarlett.conf\n" "with an “options snd_usb_audio ...” line and reboot." ); gtk_widget_set_margin(label, 30); diff --git a/src/iface-unknown.h b/src/iface-unknown.h index 167a0b0..29fc767 100644 --- a/src/iface-unknown.h +++ b/src/iface-unknown.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/iface-update.c b/src/iface-update.c new file mode 100644 index 0000000..16fb8fe --- /dev/null +++ b/src/iface-update.c @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "alsa.h" +#include "device-update-firmware.h" +#include "gtkhelper.h" +#include "scarlett2-firmware.h" + +GtkWidget *create_iface_update_main(struct alsa_card *card) { + GtkWidget *top = gtk_frame_new(NULL); + gtk_widget_add_css_class(top, "window-frame"); + + GtkWidget *content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 30); + gtk_widget_add_css_class(content, "window-content"); + gtk_widget_add_css_class(content, "top-level-content"); + gtk_widget_add_css_class(content, "big-padding"); + gtk_frame_set_child(GTK_FRAME(top), content); + + // explanation + GtkWidget *w; + + w = gtk_label_new("Firmware Update Required"); + gtk_widget_add_css_class(w, "window-title"); + gtk_box_append(GTK_BOX(content), w); + + if (!card->best_firmware_version) { + w = gtk_label_new(NULL); + gtk_label_set_markup( + GTK_LABEL(w), + "A firmware update is required for this device in order to\n" + "access all of its features. Please obtain the firmware from\n" + "" + "https://github.com/geoffreybennett/scarlett2-firmware,\n" + "and restart this application." + ); + + gtk_box_append(GTK_BOX(content), w); + return top; + } + + w = gtk_label_new( + "A firmware update is required for this device in order to\n" + "access all of its features. This process will take about 15\n" + "seconds. Please do not disconnect the device during the\n" + "update." + ); + gtk_box_append(GTK_BOX(content), w); + + w = gtk_button_new_with_label("Update"); + g_signal_connect( + GTK_BUTTON(w), "clicked", G_CALLBACK(create_update_firmware_window), card + ); + gtk_box_append(GTK_BOX(content), w); + + return top; +} diff --git a/src/iface-update.h b/src/iface-update.h new file mode 100644 index 0000000..5469a51 --- /dev/null +++ b/src/iface-update.h @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "alsa.h" + +GtkWidget *create_iface_update_main(struct alsa_card *card); diff --git a/src/main.c b/src/main.c index c206dd1..0ed62f3 100644 --- a/src/main.c +++ b/src/main.c @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "alsa.h" #include "alsa-sim.h" #include "main.h" #include "menu.h" +#include "scarlett2-firmware.h" #include "window-hardware.h" #include "window-iface.h" @@ -32,12 +33,10 @@ static void load_css(void) { static void startup(GtkApplication *app, gpointer user_data) { gtk_application_set_menubar(app, G_MENU_MODEL(create_app_menu(app))); - alsa_inotify_init(); - alsa_cards = g_array_new(FALSE, TRUE, sizeof(struct alsa_card *)); - load_css(); - alsa_scan_cards(); + scarlett2_enum_firmware(); + alsa_init(); create_no_card_window(); create_hardware_window(app); @@ -63,7 +62,9 @@ static void open_cb( } int main(int argc, char **argv) { - app = gtk_application_new("vu.b4.alsa-scarlett-gui", G_APPLICATION_HANDLES_OPEN); + app = gtk_application_new( + "vu.b4.alsa-scarlett-gui", G_APPLICATION_HANDLES_OPEN + ); g_signal_connect(app, "startup", G_CALLBACK(startup), NULL); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); g_signal_connect(app, "open", G_CALLBACK(open_cb), NULL); diff --git a/src/main.h b/src/main.h index 2b98105..3c72d21 100644 --- a/src/main.h +++ b/src/main.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/menu.c b/src/menu.c index baa38ff..9da89d3 100644 --- a/src/menu.c +++ b/src/menu.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "about.h" @@ -6,20 +6,25 @@ #include "menu.h" #include "window-hardware.h" +// helper for common code of activate_*() functions +static void update_visibility( + GSimpleAction *action, + GtkWidget *widget +) { + GVariant *state = g_action_get_state(G_ACTION(action)); + gboolean new_state = !g_variant_get_boolean(state); + + g_action_change_state(G_ACTION(action), g_variant_new_boolean(new_state)); + gtk_widget_set_visible(widget, new_state); +} + static void activate_hardware( GSimpleAction *action, GVariant *parameter, gpointer data ) { - GVariant *state = g_action_get_state(G_ACTION(action)); - - int new_state = !g_variant_get_boolean(state); - g_action_change_state(G_ACTION(action), g_variant_new_boolean(new_state)); - - if (new_state) - gtk_widget_show(window_hardware); - else - gtk_widget_hide(window_hardware); + (void) data; + update_visibility(action, window_hardware); } static void activate_quit( @@ -37,15 +42,7 @@ static void activate_routing( ) { struct alsa_card *card = data; - GVariant *state = g_action_get_state(G_ACTION(action)); - - int new_state = !g_variant_get_boolean(state); - g_action_change_state(G_ACTION(action), g_variant_new_boolean(new_state)); - - if (new_state) - gtk_widget_show(card->window_routing); - else - gtk_widget_hide(card->window_routing); + update_visibility(action, card->window_routing); } static void activate_mixer( @@ -55,15 +52,7 @@ static void activate_mixer( ) { struct alsa_card *card = data; - GVariant *state = g_action_get_state(G_ACTION(action)); - - int new_state = !g_variant_get_boolean(state); - g_action_change_state(G_ACTION(action), g_variant_new_boolean(new_state)); - - if (new_state) - gtk_widget_show(card->window_mixer); - else - gtk_widget_hide(card->window_mixer); + update_visibility(action, card->window_mixer); } static void activate_levels( @@ -73,15 +62,7 @@ static void activate_levels( ) { struct alsa_card *card = data; - GVariant *state = g_action_get_state(G_ACTION(action)); - - int new_state = !g_variant_get_boolean(state); - g_action_change_state(G_ACTION(action), g_variant_new_boolean(new_state)); - - if (new_state) - gtk_widget_show(card->window_levels); - else - gtk_widget_hide(card->window_levels); + update_visibility(action, card->window_levels); } static void activate_startup( @@ -91,15 +72,7 @@ static void activate_startup( ) { struct alsa_card *card = data; - GVariant *state = g_action_get_state(G_ACTION(action)); - - int new_state = !g_variant_get_boolean(state); - g_action_change_state(G_ACTION(action), g_variant_new_boolean(new_state)); - - if (new_state) - gtk_widget_show(card->window_startup); - else - gtk_widget_hide(card->window_startup); + update_visibility(action, card->window_startup); } static const GActionEntry app_entries[] = { @@ -107,6 +80,66 @@ static const GActionEntry app_entries[] = { {"quit", activate_quit}, }; +struct menu_item { + const char *label; + const char *action_name; + const char *accelerators[2]; +}; + +struct menu_data { + const char *label; + struct menu_item *items; +}; + +static const struct menu_data menus[] = { + { + "_File", + (struct menu_item[]){ + { "_Load Configuration", "win.load", { "O", NULL } }, + { "_Save Configuration", "win.save", { "S", NULL } }, + { "_Interface Simulation", "win.sim", { "I", NULL } }, + { "E_xit", "app.quit", { "Q", NULL } }, + {} + } + }, + { + "_View", + (struct menu_item[]){ + { "_Routing", "win.routing", { "R", NULL } }, + { "_Mixer", "win.mixer", { "M", NULL } }, + { "_Levels", "win.levels", { "L", NULL } }, + { "_Startup", "win.startup", { "T", NULL } }, + {} + } + }, + { + "_Help", + (struct menu_item[]){ + { "_Supported Hardware", "app.hardware", { "H", NULL } }, + { "_About", "win.about", { "slash", NULL } }, + {} + } + }, + {} +}; + +static void populate_submenu( + GtkApplication *app, + GMenu *menu, + const struct menu_data *data +) { + GMenu *submenu = g_menu_new(); + g_menu_append_submenu(menu, data->label, G_MENU_MODEL(submenu)); + + // An empty-initialised menu_item marks the end + for (struct menu_item *item = data->items; item->label; item++) { + g_menu_append(submenu, item->label, item->action_name); + gtk_application_set_accels_for_action( + app, item->action_name, item->accelerators + ); + } +} + GMenu *create_app_menu(GtkApplication *app) { g_action_map_add_action_entries( G_ACTION_MAP(app), app_entries, G_N_ELEMENTS(app_entries), app @@ -114,24 +147,10 @@ GMenu *create_app_menu(GtkApplication *app) { GMenu *menu = g_menu_new(); - GMenu *file_menu = g_menu_new(); - g_menu_append_submenu(menu, "_File", G_MENU_MODEL(file_menu)); - g_menu_append(file_menu, "_Load Configuration", "win.load"); - g_menu_append(file_menu, "_Save Configuration", "win.save"); - g_menu_append(file_menu, "_Interface Simulation", "win.sim"); - g_menu_append(file_menu, "E_xit", "app.quit"); - - GMenu *view_menu = g_menu_new(); - g_menu_append_submenu(menu, "_View", G_MENU_MODEL(view_menu)); - g_menu_append(view_menu, "_Routing", "win.routing"); - g_menu_append(view_menu, "_Mixer", "win.mixer"); - g_menu_append(view_menu, "_Levels", "win.levels"); - g_menu_append(view_menu, "_Startup", "win.startup"); - - GMenu *help_menu = g_menu_new(); - g_menu_append_submenu(menu, "_Help", G_MENU_MODEL(help_menu)); - g_menu_append(help_menu, "_Supported Hardware", "app.hardware"); - g_menu_append(help_menu, "_About", "win.about"); + for (const struct menu_data *menu_data = menus; + menu_data->label; + menu_data++) + populate_submenu(app, menu, menu_data); return menu; } @@ -176,7 +195,10 @@ void add_startup_action_map(struct alsa_card *card) { static const GActionEntry mixer_entries[] = { {"routing", activate_routing, NULL, "false"}, - {"mixer", activate_mixer, NULL, "false"}, + {"mixer", activate_mixer, NULL, "false"} +}; + +static const GActionEntry levels_entries[] = { {"levels", activate_levels, NULL, "false"} }; @@ -187,4 +209,16 @@ void add_mixer_action_map(struct alsa_card *card) { G_N_ELEMENTS(mixer_entries), card ); + + // Hide the levels menu item if there is no "Firmware Version" + // control (working kernel support for level meters was added in the + // same version as the "Firmware Version" control) + if (get_elem_by_name(card->elems, "Firmware Version")) { + g_action_map_add_action_entries( + G_ACTION_MAP(card->window_main), + levels_entries, + G_N_ELEMENTS(levels_entries), + card + ); + } } diff --git a/src/menu.h b/src/menu.h index e0fe451..b9ccff9 100644 --- a/src/menu.h +++ b/src/menu.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/routing-drag-line.c b/src/routing-drag-line.c index c95be02..82da21d 100644 --- a/src/routing-drag-line.c +++ b/src/routing-drag-line.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "routing-drag-line.h" @@ -40,6 +40,48 @@ static void drag_motion( card->drag_x = x; card->drag_y = y; + + // Retrieve the scrolled window and its child + GtkWindow *win = GTK_WINDOW(card->window_routing); + GtkScrolledWindow *sw = GTK_SCROLLED_WINDOW(gtk_window_get_child(win)); + GtkWidget *child = gtk_scrolled_window_get_child(sw); + + // Get horizontal and vertical adjustments for the scrolled window + GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(sw); + GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(sw); + + // Calculate the total scrollable width and height + double w = gtk_adjustment_get_upper(hadj) - + gtk_adjustment_get_page_size(hadj); + double h = gtk_adjustment_get_upper(vadj) - + gtk_adjustment_get_page_size(vadj); + + // Determine the relative size of the scrollable area + double rel_w = gtk_adjustment_get_upper(hadj) - + gtk_widget_get_allocated_width(GTK_WIDGET(sw)) + + gtk_widget_get_allocated_width(child); + double rel_h = gtk_adjustment_get_upper(vadj) - + gtk_widget_get_allocated_height(GTK_WIDGET(sw)) + + gtk_widget_get_allocated_height(child); + + // Add margin + rel_w -= 100; + rel_h -= 100; + x -= 50; + y -= 50; + if (x < 0) x = 0; + if (y < 0) y = 0; + if (x > rel_w) x = rel_w; + if (y > rel_h) y = rel_h; + + // Calculate new scroll positions based on mouse coordinates + double new_hpos = (x / rel_w) * w; + double new_vpos = (y / rel_h) * h; + + // Update the scrolled window's position + gtk_adjustment_set_value(vadj, new_vpos); + gtk_adjustment_set_value(hadj, new_hpos); + gtk_widget_queue_draw(card->drag_line); gtk_widget_queue_draw(card->routing_lines); } diff --git a/src/routing-drag-line.h b/src/routing-drag-line.h index 316cef1..99b4ceb 100644 --- a/src/routing-drag-line.h +++ b/src/routing-drag-line.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/routing-lines.c b/src/routing-lines.c index 1460e6e..c439625 100644 --- a/src/routing-lines.c +++ b/src/routing-lines.c @@ -1,73 +1,55 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "alsa.h" #include "routing-lines.h" -// dotted dash when a destination is going to be removed by a drag +// dotted dash when a sink is going to be removed by a drag static const double dash_dotted[] = { 1, 10 }; // dash when dragging and not connected static const double dash[] = { 4 }; -static void choose_line_colour( - struct routing_src *r_src, - struct routing_dst *r_dst, - double *r, - double *g, - double *b +// is a port category a mixer or DSP port, therefore at the +// top/bottom? +#define IS_MIXER(x) ((x) == PC_MIX || (x) == PC_DSP) + +static void hsl_to_rgb( + double h, double s, double l, + double *r, double *g, double *b ) { - // left channels have odd numbers - // right channels have even numbers - int odd_src = r_src->lr_num & 1; - int odd_dst = r_dst->elem->lr_num & 1; + double c = (1 - fabs(2 * l - 1)) * s; + double hp = h / 60; + double x = c * (1 - fabs(fmod(hp, 2) - 1)); + double m = l - c / 2; - // for colouring, pair channels up - // 0 for odd pairs, 1 for even pairs - int src2 = ((r_src->lr_num - 1) / 2 & 1); - int dst2 = ((r_dst->elem->lr_num - 1) / 2 & 1); + if (hp < 1) { *r = c; *g = x; *b = 0; } + else if (hp < 2) { *r = x; *g = c; *b = 0; } + else if (hp < 3) { *r = 0; *g = c; *b = x; } + else if (hp < 4) { *r = 0; *g = x; *b = c; } + else if (hp < 5) { *r = x; *g = 0; *b = c; } + else { *r = c; *g = 0; *b = x; } - // left -> left, black - if (odd_src && odd_dst) { - *r = 0; - *g = 0; - *b = 0; + *r += m; + *g += m; + *b += m; +} - // right -> right, red - } else if (!odd_src && !odd_dst) { - *r = 1; - *g = 0; - *b = 0; - - // left -> right, dark green - } else if (odd_src) { - *r = 0; - *g = 0.25; - *b = 0; - - // right -> left, dark brown/olive - } else { - *r = 0.25; - *g = 0.25; - *b = 0; - } - - // mix <-> non-mix, add blue - if ((r_src->port_category == PC_MIX) != - (r_dst->port_category == PC_MIX)) { - *b = 0.5; - } - - // even input pairs, lighten red and green components - if (src2) { - *r = (*r + 1) / 2; - *g = (*g + 1) / 2; - } - - // even output pairs, lighten blue component - if (dst2) { - *b = (*b + 1) / 2; - } +static void choose_line_colour( + int i, + int count, + double *r, + double *g, + double *b +) { + if (count % 2) + count++; + hsl_to_rgb( + ((i / (count / 2) * 360 + i * 720) / count) % 360, + 0.75, + 0.5, + r, g, b + ); } // draw a bezier curve given the end and control points @@ -160,16 +142,16 @@ static void arrow( cairo_close_path(cr); } -// draw a nice curved line connecting a source at (x1, y1) and a -// destination at (x2, y2) +// draw a nice curved line connecting a source at (x1, y1) and a sink +// at (x2, y2) static void draw_connection( cairo_t *cr, double x1, double y1, - int src_is_mixer, + int src_port_category, double x2, double y2, - int dst_is_mixer, + int snk_port_category, double r, double g, double b, @@ -177,8 +159,11 @@ static void draw_connection( ) { double x3 = x1, y3 = y1, x4 = x2, y4 = y2; + int src_is_mixer = IS_MIXER(src_port_category); + int snk_is_mixer = IS_MIXER(snk_port_category); + // vertical/horizontal? - if (src_is_mixer == dst_is_mixer) { + if (src_is_mixer == snk_is_mixer) { double f1 = 0.3; double f2 = 1 - f1; @@ -222,7 +207,7 @@ static void draw_connection( // locate the center of a widget in the parent coordinates // used for drawing lines to/from the "socket" widget of routing -// sources and destinations +// sources and sinks static void get_widget_center( GtkWidget *w, GtkWidget *parent, @@ -241,23 +226,22 @@ static void get_src_center( double *y ) { get_widget_center(r_src->widget2, parent, x, y); - if (r_src->port_category == PC_MIX) + if (IS_MIXER(r_src->port_category)) (*y)++; } -static void get_dst_center( - struct routing_dst *r_dst, +static void get_snk_center( + struct routing_snk *r_snk, GtkWidget *parent, double *x, double *y ) { - get_widget_center(r_dst->elem->widget2, parent, x, y); - if (r_dst->port_category == PC_MIX) + get_widget_center(r_snk->socket_widget, parent, x, y); + if (IS_MIXER(r_snk->port_category)) (*y)++; } -// redraw the overlay lines between the routing sources and -// destinations +// redraw the overlay lines between the routing sources and sinks void draw_routing_lines( GtkDrawingArea *drawing_area, cairo_t *cr, @@ -272,22 +256,22 @@ void draw_routing_lines( int dragging = card->drag_type != DRAG_TYPE_NONE; - // go through all the routing destinations - for (int i = 0; i < card->routing_dsts->len; i++) { - struct routing_dst *r_dst = &g_array_index( - card->routing_dsts, struct routing_dst, i + // go through all the routing sinks + for (int i = 0; i < card->routing_snks->len; i++) { + struct routing_snk *r_snk = &g_array_index( + card->routing_snks, struct routing_snk, i ); - // if dragging and a routing destination is being reconnected then - // draw it with dots - int dragging_this = dragging && card->dst_drag == r_dst; + // if dragging and a routing sink is being reconnected then draw + // it with dots + int dragging_this = dragging && card->snk_drag == r_snk; if (dragging_this) cairo_set_dash(cr, dash_dotted, 2, 0); else cairo_set_dash(cr, NULL, 0, 0); - // get the destination and skip if it's "Off" - int r_src_idx = alsa_get_elem_value(r_dst->elem); + // get the sink and skip if it's "Off" + int r_src_idx = alsa_get_elem_value(r_snk->elem); if (!r_src_idx) continue; @@ -296,14 +280,14 @@ void draw_routing_lines( card->routing_srcs, struct routing_src, r_src_idx ); - // locate the source and destination coordinates + // locate the source and sink coordinates double x1, y1, x2, y2; get_src_center(r_src, parent, &x1, &y1); - get_dst_center(r_dst, parent, &x2, &y2); + get_snk_center(r_snk, parent, &x2, &y2); // pick a colour double r, g, b; - choose_line_colour(r_src, r_dst, &r, &g, &b); + choose_line_colour(i, card->routing_snks->len, &r, &g, &b); // make the colour lighter if it's being shown dotted if (dragging_this) { @@ -315,8 +299,8 @@ void draw_routing_lines( // draw the connection draw_connection( cr, - x1, y1, r_src->port_category == PC_MIX, - x2, y2, r_dst->port_category == PC_MIX, + x1, y1, r_src->port_category, + x2, y2, r_snk->port_category, r, g, b, 2 ); } @@ -333,19 +317,19 @@ void draw_drag_line( struct alsa_card *card = user_data; GtkWidget *parent = card->drag_line; - // if not dragging or routing src & dst not specified or drag out of + // if not dragging or routing src & snk not specified or drag out of // bounds then do nothing if (card->drag_type == DRAG_TYPE_NONE || - (!card->src_drag && !card->dst_drag) || + (!card->src_drag && !card->snk_drag) || card->drag_x < 0 || card->drag_y < 0) return; // the drag mouse position is relative to card->routing_grid // translate it to the overlay card->drag_line - // (don't need to do this if both src_drag and dst_drag are set) + // (don't need to do this if both src_drag and snk_drag are set) double drag_x, drag_y; - if (!card->src_drag || !card->dst_drag) + if (!card->src_drag || !card->snk_drag) gtk_widget_translate_coordinates( card->routing_grid, parent, card->drag_x, card->drag_y, @@ -362,31 +346,31 @@ void draw_drag_line( y1 = drag_y; } - // get the line end position; either a routing destination socket - // widget or the drag mouse position + // get the line end position; either a routing sink socket widget or + // the drag mouse position double x2, y2; - if (card->dst_drag) { - get_dst_center(card->dst_drag, parent, &x2, &y2); + if (card->snk_drag) { + get_snk_center(card->snk_drag, parent, &x2, &y2); } else { x2 = drag_x; y2 = drag_y; } - // if routing src & dst both specified then draw a curved line as if + // if routing src & snk both specified then draw a curved line as if // it was connected (except black) - if (card->src_drag && card->dst_drag) { + if (card->src_drag && card->snk_drag) { draw_connection( cr, - x1, y1, card->src_drag->port_category == PC_MIX, - x2, y2, card->dst_drag->port_category == PC_MIX, - 0, 0, 0, 2 + x1, y1, card->src_drag->port_category, + x2, y2, card->snk_drag->port_category, + 1, 1, 1, 2 ); // otherwise draw a straight line } else { cairo_set_dash(cr, dash, 1, 0); - cairo_set_source_rgb(cr, 0, 0, 0); + cairo_set_source_rgb(cr, 1, 1, 1); cairo_set_line_width(cr, 2); cairo_move_to(cr, x1, y1); cairo_line_to(cr, x2, y2); diff --git a/src/routing-lines.h b/src/routing-lines.h index fb0a170..db880dc 100644 --- a/src/routing-lines.h +++ b/src/routing-lines.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/scarlett2-firmware.c b/src/scarlett2-firmware.c new file mode 100644 index 0000000..d46362d --- /dev/null +++ b/src/scarlett2-firmware.c @@ -0,0 +1,289 @@ +// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include +#include +#include + +#include "scarlett2-firmware.h" + +// List of found firmware files +struct found_firmware { + char *fn; + struct scarlett2_firmware_header *firmware; +}; + +GHashTable *best_firmware = NULL; + +static int verify_sha256( + const unsigned char *data, + size_t length, + const unsigned char *expected_hash +) { + unsigned char computed_hash[SHA256_DIGEST_LENGTH]; + SHA256(data, length, computed_hash); + return memcmp(computed_hash, expected_hash, SHA256_DIGEST_LENGTH) == 0; +} + +static struct scarlett2_firmware_file *read_header(FILE *file) { + struct scarlett2_firmware_file *firmware = calloc( + 1, sizeof(struct scarlett2_firmware_file) + ); + if (!firmware) { + perror("Failed to allocate memory for firmware structure"); + goto error; + } + + size_t read_count = fread( + &firmware->header, sizeof(struct scarlett2_firmware_header), 1, file + ); + + if (read_count != 1) { + if (feof(file)) + fprintf(stderr, "Unexpected end of file\n"); + else + perror("Failed to read header"); + goto error; + } + + if (strncmp(firmware->header.magic, MAGIC_STRING, 8) != 0) { + fprintf(stderr, "Invalid magic number\n"); + goto error; + } + + firmware->header.usb_vid = ntohs(firmware->header.usb_vid); + firmware->header.usb_pid = ntohs(firmware->header.usb_pid); + firmware->header.firmware_version = ntohl(firmware->header.firmware_version); + firmware->header.firmware_length = ntohl(firmware->header.firmware_length); + + return firmware; + +error: + free(firmware); + return NULL; +} + +struct scarlett2_firmware_header *scarlett2_read_firmware_header( + const char *fn +) { + FILE *file = fopen(fn, "rb"); + if (!file) { + perror("fopen"); + fprintf(stderr, "Unable to open %s\n", fn); + return NULL; + } + + struct scarlett2_firmware_file *firmware = read_header(file); + if (!firmware) { + fprintf(stderr, "Error reading firmware header from %s\n", fn); + return NULL; + } + + fclose(file); + + return realloc(firmware, sizeof(struct scarlett2_firmware_header)); +} + +struct scarlett2_firmware_file *scarlett2_read_firmware_file(const char *fn) { + FILE *file = fopen(fn, "rb"); + if (!file) { + perror("fopen"); + fprintf(stderr, "Unable to open %s\n", fn); + return NULL; + } + + struct scarlett2_firmware_file *firmware = read_header(file); + if (!firmware) { + fprintf(stderr, "Error reading firmware header from %s\n", fn); + return NULL; + } + + firmware->firmware_data = malloc(firmware->header.firmware_length); + if (!firmware->firmware_data) { + perror("Failed to allocate memory for firmware data"); + goto error; + } + + size_t read_count = fread( + firmware->firmware_data, 1, firmware->header.firmware_length, file + ); + + if (read_count != firmware->header.firmware_length) { + if (feof(file)) + fprintf(stderr, "Unexpected end of file\n"); + else + perror("Failed to read firmware data"); + fprintf(stderr, "Error reading firmware data from %s\n", fn); + goto error; + } + + if (!verify_sha256( + firmware->firmware_data, + firmware->header.firmware_length, + firmware->header.sha256 + )) { + fprintf(stderr, "Corrupt firmware (failed checksum) in %s\n", fn); + goto error; + } + + fclose(file); + return firmware; + +error: + scarlett2_free_firmware_file(firmware); + fclose(file); + return NULL; +} + +void scarlett2_free_firmware_header(struct scarlett2_firmware_header *firmware) { + if (firmware) + free(firmware); +} + +void scarlett2_free_firmware_file(struct scarlett2_firmware_file *firmware) { + if (firmware) { + free(firmware->firmware_data); + free(firmware); + } +} + +static void free_found_firmware(gpointer data) { + struct found_firmware *found = data; + + free(found->fn); + scarlett2_free_firmware_header(found->firmware); + free(found); +} + +static void init_best_firmware(void) { + if (best_firmware) + return; + + best_firmware = g_hash_table_new_full( + g_direct_hash, g_direct_equal, NULL, free_found_firmware + ); +} + +// Add a firmware file to the list of found firmware +// files, if it's better than the one already found +// for the same device. +static void add_found_firmware( + char *fn, + struct scarlett2_firmware_header *firmware +) { + gpointer key = GINT_TO_POINTER(firmware->usb_pid); + struct found_firmware *found = g_hash_table_lookup(best_firmware, key); + + // already have a firmware file for this device? + if (found) { + + // lower version number, ignore + if (firmware->firmware_version <= found->firmware->firmware_version) { + free(fn); + scarlett2_free_firmware_header(firmware); + return; + } + + // higher version number, replace + g_hash_table_remove(best_firmware, key); + } + + found = malloc(sizeof(struct found_firmware)); + if (!found) { + perror("Failed to allocate memory for firmware structure"); + return; + } + + found->fn = fn; + found->firmware = firmware; + + g_hash_table_insert(best_firmware, key, found); +} + +// look for firmware files in the given directory +static void enum_firmware_dir(const char *dir_name) { + DIR *dir = opendir(dir_name); + + if (!dir) { + if (errno == ENOENT) { + fprintf(stderr, "Firmware directory %s does not exist\n", dir_name); + return; + } + fprintf( + stderr, "Error opening directory %s: %s\n", dir_name, strerror(errno) + ); + return; + } + + struct dirent *entry; + + while ((entry = readdir(dir))) { + char *full_fn; + + // check if the file is a .bin file + if (strlen(entry->d_name) < 4 || + strcmp(entry->d_name + strlen(entry->d_name) - 4, ".bin") != 0) + continue; + + // check if the file is a regular file + if (entry->d_type == DT_UNKNOWN) { + struct stat st; + full_fn = g_build_filename(dir_name, entry->d_name, NULL); + if (stat(full_fn, &st) < 0) { + perror("stat"); + g_free(full_fn); + continue; + } + if (!S_ISREG(st.st_mode)) { + g_free(full_fn); + continue; + } + } else if (entry->d_type != DT_REG) { + continue; + } else { + full_fn = g_build_filename(dir_name, entry->d_name, NULL); + } + + struct scarlett2_firmware_header *firmware = + scarlett2_read_firmware_header(full_fn); + + if (!firmware) { + fprintf(stderr, "Error reading firmware file %s\n", full_fn); + g_free(full_fn); + continue; + } + + add_found_firmware(full_fn, firmware); + } + + closedir(dir); +} + +void scarlett2_enum_firmware(void) { + init_best_firmware(); + enum_firmware_dir(SCARLETT2_FIRMWARE_DIR); +} + +uint32_t scarlett2_get_best_firmware_version(uint32_t pid) { + struct found_firmware *found = g_hash_table_lookup( + best_firmware, GINT_TO_POINTER(pid) + ); + if (!found) + return 0; + + return found->firmware->firmware_version; +} + +struct scarlett2_firmware_file *scarlett2_get_best_firmware(uint32_t pid) { + struct found_firmware *found = g_hash_table_lookup( + best_firmware, GINT_TO_POINTER(pid) + ); + if (!found) + return NULL; + + return scarlett2_read_firmware_file(found->fn); +} diff --git a/src/scarlett2-firmware.h b/src/scarlett2-firmware.h new file mode 100644 index 0000000..1ed47ec --- /dev/null +++ b/src/scarlett2-firmware.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +// System-wide firmware directory +#define SCARLETT2_FIRMWARE_DIR "/usr/lib/firmware/scarlett2" + +#define MAGIC_STRING "SCARLETT" + +struct scarlett2_firmware_header { + char magic[8]; // "SCARLETT" + uint16_t usb_vid; // Big-endian + uint16_t usb_pid; // Big-endian + uint32_t firmware_version; // Big-endian + uint32_t firmware_length; // Big-endian + uint8_t sha256[32]; +} __attribute__((packed)); + +struct scarlett2_firmware_file { + struct scarlett2_firmware_header header; + uint8_t *firmware_data; +}; + +struct scarlett2_firmware_header *scarlett2_read_firmware_header( + const char *fn +); + +void scarlett2_free_firmware_header( + struct scarlett2_firmware_header *firmware +); + +struct scarlett2_firmware_file *scarlett2_read_firmware_file( + const char *fn +); + +void scarlett2_free_firmware_file( + struct scarlett2_firmware_file *firmware +); + +void scarlett2_enum_firmware(void); + +uint32_t scarlett2_get_best_firmware_version(uint32_t pid); +struct scarlett2_firmware_file *scarlett2_get_best_firmware(uint32_t pid); diff --git a/src/scarlett2-ioctls.c b/src/scarlett2-ioctls.c new file mode 100644 index 0000000..c5cdd6b --- /dev/null +++ b/src/scarlett2-ioctls.c @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2023 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include "scarlett2.h" + +#include "scarlett2-ioctls.h" + +int scarlett2_open_card(char *alsa_name, snd_hwdep_t **hwdep) { + return snd_hwdep_open(hwdep, alsa_name, SND_HWDEP_OPEN_DUPLEX); +} + +int scarlett2_get_protocol_version(snd_hwdep_t *hwdep) { + int version = 0; + int err = snd_hwdep_ioctl(hwdep, SCARLETT2_IOCTL_PVERSION, &version); + + if (err < 0) + return err; + return version; +} + +int scarlett2_close(snd_hwdep_t *hwdep) { + return snd_hwdep_close(hwdep); +} + +int scarlett2_reboot(snd_hwdep_t *hwdep) { + return snd_hwdep_ioctl(hwdep, SCARLETT2_IOCTL_REBOOT, 0); +} + +static int scarlett2_select_flash_segment(snd_hwdep_t *hwdep, int segment) { + return snd_hwdep_ioctl(hwdep, SCARLETT2_IOCTL_SELECT_FLASH_SEGMENT, &segment); +} + +static int scarlett2_erase_flash_segment(snd_hwdep_t *hwdep) { + return snd_hwdep_ioctl(hwdep, SCARLETT2_IOCTL_ERASE_FLASH_SEGMENT, 0); +} + +int scarlett2_erase_config(snd_hwdep_t *hwdep) { + int err; + + err = scarlett2_select_flash_segment(hwdep, SCARLETT2_SEGMENT_ID_SETTINGS); + if (err < 0) + return err; + return scarlett2_erase_flash_segment(hwdep); +} + +int scarlett2_erase_firmware(snd_hwdep_t *hwdep) { + int err; + + err = scarlett2_select_flash_segment(hwdep, SCARLETT2_SEGMENT_ID_FIRMWARE); + if (err < 0) + return err; + return scarlett2_erase_flash_segment(hwdep); +} + +int scarlett2_get_erase_progress(snd_hwdep_t *hwdep) { + struct scarlett2_flash_segment_erase_progress progress; + + int err = snd_hwdep_ioctl( + hwdep, SCARLETT2_IOCTL_GET_ERASE_PROGRESS, &progress + ); + if (err < 0) + return err; + + // translate progress from [1..num_blocks, 255] to [[0..100), 255]] + if (progress.num_blocks == 0 || + progress.progress == 0 || + progress.progress == 255) + return progress.progress; + + return (progress.progress - 1) * 100 / progress.num_blocks; +} diff --git a/src/scarlett2-ioctls.h b/src/scarlett2-ioctls.h new file mode 100644 index 0000000..0a25721 --- /dev/null +++ b/src/scarlett2-ioctls.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SCARLETT2_IOCTLS_H +#define SCARLETT2_IOCTLS_H + +#include + +int scarlett2_open_card(char *alsa_name, snd_hwdep_t **hwdep); +int scarlett2_get_protocol_version(snd_hwdep_t *hwdep); +int scarlett2_lock(snd_hwdep_t *hwdep); +int scarlett2_unlock(snd_hwdep_t *hwdep); +int scarlett2_close(snd_hwdep_t *hwdep); + +int scarlett2_reboot(snd_hwdep_t *hwdep); +int scarlett2_erase_config(snd_hwdep_t *hwdep); +int scarlett2_erase_firmware(snd_hwdep_t *hwdep); +int scarlett2_get_erase_progress(snd_hwdep_t *hwdep); +int scarlett2_write_firmware( + snd_hwdep_t *hwdep, + off_t offset, + unsigned char *buf, + size_t buf_len +); + +#endif // SCARLETT2_IOCTLS_H diff --git a/src/scarlett2.h b/src/scarlett2.h new file mode 100644 index 0000000..d0ff38f --- /dev/null +++ b/src/scarlett2.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Focusrite Scarlett 2 Protocol Driver for ALSA + * (including Scarlett 2nd Gen, 3rd Gen, Clarett USB, and Clarett+ + * series products) + * + * Copyright (c) 2023 by Geoffrey D. Bennett + */ +#ifndef __UAPI_SOUND_SCARLETT2_H +#define __UAPI_SOUND_SCARLETT2_H + +#include +#include + +#define SCARLETT2_HWDEP_MAJOR 1 +#define SCARLETT2_HWDEP_MINOR 0 +#define SCARLETT2_HWDEP_SUBMINOR 0 + +#define SCARLETT2_HWDEP_VERSION \ + ((SCARLETT2_HWDEP_MAJOR << 16) | \ + (SCARLETT2_HWDEP_MINOR << 8) | \ + SCARLETT2_HWDEP_SUBMINOR) + +#define SCARLETT2_HWDEP_VERSION_MAJOR(v) (((v) >> 16) & 0xFF) +#define SCARLETT2_HWDEP_VERSION_MINOR(v) (((v) >> 8) & 0xFF) +#define SCARLETT2_HWDEP_VERSION_SUBMINOR(v) ((v) & 0xFF) + +/* Get protocol version */ +#define SCARLETT2_IOCTL_PVERSION _IOR('S', 0x60, int) + +/* Reboot */ +#define SCARLETT2_IOCTL_REBOOT _IO('S', 0x61) + +/* Select flash segment */ +#define SCARLETT2_SEGMENT_ID_SETTINGS 0 +#define SCARLETT2_SEGMENT_ID_FIRMWARE 1 +#define SCARLETT2_SEGMENT_ID_COUNT 2 + +#define SCARLETT2_IOCTL_SELECT_FLASH_SEGMENT _IOW('S', 0x62, int) + +/* Erase selected flash segment */ +#define SCARLETT2_IOCTL_ERASE_FLASH_SEGMENT _IO('S', 0x63) + +/* Get selected flash segment erase progress + * 1 through to num_blocks, or 255 for complete + */ +struct scarlett2_flash_segment_erase_progress { + unsigned char progress; + unsigned char num_blocks; +}; +#define SCARLETT2_IOCTL_GET_ERASE_PROGRESS \ + _IOR('S', 0x64, struct scarlett2_flash_segment_erase_progress) + +#endif /* __UAPI_SOUND_SCARLETT2_H */ diff --git a/src/stringhelper.c b/src/stringhelper.c index 6a94dbe..c2da8a8 100644 --- a/src/stringhelper.c +++ b/src/stringhelper.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include diff --git a/src/stringhelper.h b/src/stringhelper.h index 3f5154c..a78d992 100644 --- a/src/stringhelper.h +++ b/src/stringhelper.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/tooltips.c b/src/tooltips.c index 74269cf..f107e5b 100644 --- a/src/tooltips.c +++ b/src/tooltips.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "tooltips.h" diff --git a/src/tooltips.h b/src/tooltips.h index ccb0e16..0a1360f 100644 --- a/src/tooltips.h +++ b/src/tooltips.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/vu.b4.alsa-scarlett-gui.desktop.template b/src/vu.b4.alsa-scarlett-gui.desktop.template index 1e0901a..5c328fa 100644 --- a/src/vu.b4.alsa-scarlett-gui.desktop.template +++ b/src/vu.b4.alsa-scarlett-gui.desktop.template @@ -1,6 +1,6 @@ [Desktop Entry] Type=Application -Name=ALSA Scarlett Gen 2/3 Control Panel +Name=ALSA Scarlett2 Control Panel Icon=vu.b4.alsa-scarlett-gui Exec=PREFIX/bin/alsa-scarlett-gui Categories=GTK;AudioVideo;Audio;Mixer; diff --git a/src/widget-boolean.c b/src/widget-boolean.c index 7fa26dc..1029acf 100644 --- a/src/widget-boolean.c +++ b/src/widget-boolean.c @@ -1,34 +1,45 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "widget-boolean.h" +struct boolean { + struct alsa_elem *elem; + GtkWidget *button; + const char *text[2]; +}; + static void button_clicked(GtkWidget *widget, struct alsa_elem *elem) { int value = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); alsa_set_elem_value(elem, value); } -static void toggle_button_set_text(struct alsa_elem *elem, const char *text) { +static void toggle_button_set_text(GtkWidget *button, const char *text) { if (!text) return; if (*text == '*') { GtkWidget *icon = gtk_image_new_from_icon_name(text + 1); - gtk_button_set_child(GTK_BUTTON(elem->widget), icon); + gtk_button_set_child(GTK_BUTTON(button), icon); } else { - gtk_button_set_label(GTK_BUTTON(elem->widget), text); + gtk_button_set_label(GTK_BUTTON(button), text); } } -static void toggle_button_updated(struct alsa_elem *elem) { +static void toggle_button_updated( + struct alsa_elem *elem, + void *private +) { + struct boolean *data = private; + int is_writable = alsa_get_elem_writable(elem); - gtk_widget_set_sensitive(elem->widget, is_writable); + gtk_widget_set_sensitive(data->button, is_writable); - int value = alsa_get_elem_value(elem); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(elem->widget), value); + int value = !!alsa_get_elem_value(elem); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->button), value); - toggle_button_set_text(elem, elem->bool_text[value]); + toggle_button_set_text(data->button, data->text[value]); } GtkWidget *make_boolean_alsa_elem( @@ -36,23 +47,24 @@ GtkWidget *make_boolean_alsa_elem( const char *disabled_text, const char *enabled_text ) { - GtkWidget *button = gtk_toggle_button_new(); + struct boolean *data = g_malloc(sizeof(struct boolean)); + data->elem = elem; + data->button = gtk_toggle_button_new(); g_signal_connect( - button, "clicked", G_CALLBACK(button_clicked), elem + data->button, "clicked", G_CALLBACK(button_clicked), elem ); - elem->widget = button; - elem->widget_callback = toggle_button_updated; - elem->bool_text[0] = disabled_text; - elem->bool_text[1] = enabled_text; + alsa_elem_add_callback(elem, toggle_button_updated, data); + data->text[0] = disabled_text; + data->text[1] = enabled_text; // find the maximum width and height of both possible labels int max_width = 0, max_height = 0; for (int i = 0; i < 2; i++) { - toggle_button_set_text(elem, elem->bool_text[i]); + toggle_button_set_text(data->button, data->text[i]); GtkRequisition *size = gtk_requisition_new(); - gtk_widget_get_preferred_size(button, size, NULL); + gtk_widget_get_preferred_size(data->button, size, NULL); if (size->width > max_width) max_width = size->width; @@ -62,9 +74,9 @@ GtkWidget *make_boolean_alsa_elem( // set the widget minimum size to the maximum label size so that the // widget doesn't change size when the label changes - gtk_widget_set_size_request(button, max_width, max_height); + gtk_widget_set_size_request(data->button, max_width, max_height); - toggle_button_updated(elem); + toggle_button_updated(elem, data); - return button; + return data->button; } diff --git a/src/widget-boolean.h b/src/widget-boolean.h index 36a413f..a6cd5e9 100644 --- a/src/widget-boolean.h +++ b/src/widget-boolean.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/widget-combo.c b/src/widget-combo.c deleted file mode 100644 index ed3f619..0000000 --- a/src/widget-combo.c +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "widget-combo.h" - -static void combo_box_changed(GtkWidget *widget, struct alsa_elem *elem) { - int value = gtk_combo_box_get_active(GTK_COMBO_BOX(widget)); - - alsa_set_elem_value(elem, value); -} - -static void combo_box_updated(struct alsa_elem *elem) { - int value = alsa_get_elem_value(elem); - gtk_combo_box_set_active(GTK_COMBO_BOX(elem->widget), value); -} - -GtkWidget *make_combo_box_alsa_elem(struct alsa_elem *elem) { - GtkWidget *combo_box = gtk_combo_box_text_new(); - int count = alsa_get_item_count(elem); - - for (int i = 0; i < count; i++) { - const char *text = alsa_get_item_name(elem, i); - gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(combo_box), NULL, text); - } - - g_signal_connect( - combo_box, "changed", G_CALLBACK(combo_box_changed), elem - ); - elem->widget = combo_box; - elem->widget_callback = combo_box_updated; - - combo_box_updated(elem); - - return combo_box; -} diff --git a/src/widget-combo.h b/src/widget-combo.h deleted file mode 100644 index d7dae27..0000000 --- a/src/widget-combo.h +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "alsa.h" - -GtkWidget *make_combo_box_alsa_elem(struct alsa_elem *elem); diff --git a/src/widget-drop-down.c b/src/widget-drop-down.c new file mode 100644 index 0000000..1d83f9b --- /dev/null +++ b/src/widget-drop-down.c @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "gtkhelper.h" +#include "widget-drop-down.h" + +struct drop_down { + struct alsa_elem *elem; + GtkWidget *button; + GtkWidget *popover; + GtkWidget *listview; + GtkSingleSelection *selection; + int fixed_text; +}; + +static void sanitise_class_name(char *s) { + char *dst = s; + + while (*s) { + if (isalnum(*s) || *s == '-') + *dst++ = tolower(*s); + s++; + } + + *dst = '\0'; +} + +static void add_class(GtkWidget *widget, const char *class) { + char *class_name = g_strdup_printf("selected-%s", class); + + sanitise_class_name(class_name); + gtk_widget_add_css_class(widget, class_name); + g_free(class_name); +} + +static void list_item_activated( + GtkListItem *list_item, + guint index, + struct drop_down *data +) { + alsa_set_elem_value(data->elem, index); + + gtk_popover_popdown(GTK_POPOVER(data->popover)); +} + +static void toggle_button_clicked(GtkWidget *widget, struct drop_down *data) { + gtk_popover_popup(GTK_POPOVER(data->popover)); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->button), FALSE); +} + +static void setup_factory( + GtkListItemFactory *factory, + GtkListItem *list_item, + gpointer user_data +) { + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + + GtkWidget *label = gtk_label_new(NULL); + gtk_label_set_xalign(GTK_LABEL(label), 0.0); + gtk_box_append(GTK_BOX(box), label); + + GtkWidget *icon = gtk_image_new_from_icon_name("object-select-symbolic"); + gtk_box_append(GTK_BOX(box), icon); + + gtk_list_item_set_child(list_item, box); +} + +static void update_list_item( + GtkListItem *list_item, + struct drop_down *data +) { + GtkWidget *box = gtk_list_item_get_child(list_item); + GtkWidget *icon = gtk_widget_get_last_child(box); + + int index = gtk_single_selection_get_selected(data->selection); + + if (index == gtk_list_item_get_position(list_item)) + gtk_widget_set_opacity(icon, 1.0); + else + gtk_widget_set_opacity(icon, 0.0); +} + +static void bind_factory( + GtkListItemFactory *factory, + GtkListItem *list_item, + gpointer user_data +) { + struct drop_down *data = user_data; + + GtkWidget *box = gtk_list_item_get_child(list_item); + GtkWidget *label = gtk_widget_get_first_child(box); + + int index = gtk_list_item_get_position(list_item); + const char *text = alsa_get_item_name(data->elem, index); + gtk_label_set_text(GTK_LABEL(label), text); + + update_list_item(list_item, data); +} + +static void drop_down_updated( + struct alsa_elem *elem, + void *private +) { + struct drop_down *data = private; + + int is_writable = alsa_get_elem_writable(elem); + gtk_widget_set_sensitive(data->button, is_writable); + + int value = alsa_get_elem_value(elem); + gtk_single_selection_set_selected(data->selection, value); + + gtk_widget_remove_css_classes_by_prefix(data->button, "selected-"); + add_class(data->button, alsa_get_item_name(elem, value)); + + if (data->fixed_text) + return; + + gtk_button_set_label( + GTK_BUTTON(data->button), + alsa_get_item_name(elem, value) + ); +} + +static void drop_down_destroy(GtkWidget *widget, GtkWidget *popover) { + gtk_widget_unparent(popover); +} + +GtkWidget *make_drop_down_alsa_elem( + struct alsa_elem *elem, + const char *label_text +) { + struct drop_down *data = g_malloc(sizeof(struct drop_down)); + data->elem = elem; + + data->button = gtk_toggle_button_new_with_label(label_text); + gtk_widget_add_css_class(data->button, "drop-down"); + data->fixed_text = !!label_text; + + data->popover = gtk_popover_new(); + gtk_popover_set_has_arrow(GTK_POPOVER(data->popover), FALSE); + gtk_widget_set_parent( + data->popover, + gtk_widget_get_first_child(data->button) + ); + g_signal_connect( + gtk_widget_get_first_child(data->button), + "destroy", G_CALLBACK(drop_down_destroy), data->popover + ); + + GListModel *model = G_LIST_MODEL(gtk_string_list_new(NULL)); + + int count = alsa_get_item_count(elem); + for (int i = 0; i < count; i++) { + const char *text = alsa_get_item_name(elem, i); + + gtk_string_list_append(GTK_STRING_LIST(model), text); + } + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + g_signal_connect( + factory, "setup", G_CALLBACK(setup_factory), data + ); + g_signal_connect( + factory, "bind", G_CALLBACK(bind_factory), data + ); + + data->selection = gtk_single_selection_new(model); + data->listview = gtk_list_view_new( + GTK_SELECTION_MODEL(data->selection), + factory + ); + gtk_list_view_set_single_click_activate(GTK_LIST_VIEW(data->listview), TRUE); + + gtk_popover_set_child(GTK_POPOVER(data->popover), data->listview); + + g_signal_connect( + data->button, "clicked", G_CALLBACK(toggle_button_clicked), data + ); + g_signal_connect( + data->listview, "activate", G_CALLBACK(list_item_activated), data + ); + drop_down_updated(elem, data); + + alsa_elem_add_callback(elem, drop_down_updated, data); + + return data->button; +} diff --git a/src/widget-drop-down.h b/src/widget-drop-down.h new file mode 100644 index 0000000..ea53bb3 --- /dev/null +++ b/src/widget-drop-down.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "alsa.h" + +GtkWidget *make_drop_down_alsa_elem( + struct alsa_elem *elem, + const char *label_text +); diff --git a/src/widget-dual.c b/src/widget-dual.c index 95f1bb4..35a2066 100644 --- a/src/widget-dual.c +++ b/src/widget-dual.c @@ -1,20 +1,31 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "widget-dual.h" -static void dual_button_clicked(GtkWidget *widget, struct alsa_elem *elem) { - int value1 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(elem->widget)); - int value2 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(elem->widget2)); +struct dual_button { + struct alsa_elem *elem; + GtkWidget *button1; + GtkWidget *button2; + const char *text[4]; +}; + +static void dual_button_clicked(GtkWidget *widget, struct dual_button *data) { + int value1 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->button1)); + int value2 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->button2)); int value = value1 ? value2 + 1 : 0; - alsa_set_elem_value(elem, value); + alsa_set_elem_value(data->elem, value); - gtk_widget_set_sensitive(elem->widget2, value1); + gtk_widget_set_sensitive(data->button2, value1); } -static void dual_button_updated(struct alsa_elem *elem) { +static void dual_button_updated( + struct alsa_elem *elem, + void *private +) { + struct dual_button *data = private; // value (from ALSA control) is 0/1/2 // value1 (first button) is 0/1/1 @@ -22,14 +33,14 @@ static void dual_button_updated(struct alsa_elem *elem) { int value = alsa_get_elem_value(elem); int value1 = !!value; - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(elem->widget), value1); - gtk_button_set_label(GTK_BUTTON(elem->widget), elem->bool_text[value1]); - gtk_widget_set_sensitive(elem->widget2, value1); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->button1), value1); + gtk_button_set_label(GTK_BUTTON(data->button1), data->text[value1]); + gtk_widget_set_sensitive(data->button2, value1); if (value1) { int value2 = value - 1; - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(elem->widget2), value2); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->button2), value2); gtk_button_set_label( - GTK_BUTTON(elem->widget2), elem->bool_text[value2 + 2] + GTK_BUTTON(data->button2), data->text[value2 + 2] ); } } @@ -38,31 +49,40 @@ static void dual_button_updated(struct alsa_elem *elem) { // buttons: // first button disables/enables the feature // second button switches between the two enabled states -void make_dual_boolean_alsa_elems( +GtkWidget *make_dual_boolean_alsa_elems( struct alsa_elem *elem, + const char *label_text, const char *disabled_text_1, const char *enabled_text_1, const char *disabled_text_2, const char *enabled_text_2 ) { - GtkWidget *button1 = gtk_toggle_button_new(); - GtkWidget *button2 = gtk_toggle_button_new(); + struct dual_button *data = g_malloc(sizeof(struct dual_button)); + data->elem = elem; + data->button1 = gtk_toggle_button_new(); + data->button2 = gtk_toggle_button_new(); g_signal_connect( - button1, "clicked", G_CALLBACK(dual_button_clicked), elem + data->button1, "clicked", G_CALLBACK(dual_button_clicked), data ); g_signal_connect( - button2, "clicked", G_CALLBACK(dual_button_clicked), elem + data->button2, "clicked", G_CALLBACK(dual_button_clicked), data ); - elem->widget = button1; - elem->widget2 = button2; - elem->widget_callback = dual_button_updated; - elem->bool_text[0] = disabled_text_1; - elem->bool_text[1] = enabled_text_1; - elem->bool_text[2] = disabled_text_2; - elem->bool_text[3] = enabled_text_2; + alsa_elem_add_callback(elem, dual_button_updated, data); + data->text[0] = disabled_text_1; + data->text[1] = enabled_text_1; + data->text[2] = disabled_text_2; + data->text[3] = enabled_text_2; - gtk_button_set_label(GTK_BUTTON(elem->widget2), disabled_text_2); + gtk_button_set_label(GTK_BUTTON(data->button2), disabled_text_2); - dual_button_updated(elem); + dual_button_updated(elem, data); + + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + GtkWidget *label = gtk_label_new(label_text); + gtk_box_append(GTK_BOX(box), label); + gtk_box_append(GTK_BOX(box), GTK_WIDGET(data->button1)); + gtk_box_append(GTK_BOX(box), GTK_WIDGET(data->button2)); + + return box; } diff --git a/src/widget-dual.h b/src/widget-dual.h index 2add820..0850bb9 100644 --- a/src/widget-dual.h +++ b/src/widget-dual.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -9,8 +9,9 @@ // buttons: // first button disables/enables the feature // second button switches between the two features states -void make_dual_boolean_alsa_elems( +GtkWidget *make_dual_boolean_alsa_elems( struct alsa_elem *alsa_elem, + const char *label_text, const char *disabled_text_1, const char *enabled_text_1, const char *disabled_text_2, diff --git a/src/widget-gain.c b/src/widget-gain.c index 1e76ace..4a0d280 100644 --- a/src/widget-gain.c +++ b/src/widget-gain.c @@ -1,56 +1,217 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "gtkdial.h" +#include "stringhelper.h" #include "widget-gain.h" -// gain controls -80dB - +6dB, 0.5dB steps -#define DIAL_MIN_VALUE 0 -#define DIAL_MAX_VALUE 172 -#define DIAL_ZERO_DB_VALUE 160 +struct gain { + struct alsa_elem *elem; + struct alsa_elem *direct_monitor_elem; + struct alsa_elem *monitor_mix_elem[2]; + GtkWidget *vbox; + GtkWidget *dial; + GtkWidget *label; + int zero_is_off; + float scale; +}; -static void gain_changed(GtkWidget *widget, struct alsa_elem *elem) { - int value = gtk_dial_get_value(GTK_DIAL(widget)); +static void gain_changed(GtkWidget *widget, struct gain *data) { + int value = gtk_dial_get_value(GTK_DIAL(data->dial)); + alsa_set_elem_value(data->elem, value); - alsa_set_elem_value(elem, value); + // check if there is a corresponding Direct Monitor Mix control to + // update as well + + // Direct Monitor control? + if (!data->direct_monitor_elem) + return; + + // Direct Monitor enabled? + int direct_monitor = alsa_get_elem_value(data->direct_monitor_elem); + + if (!direct_monitor) + return; + + // Get the corresponding Mix control + struct alsa_elem *monitor_mix = data->monitor_mix_elem[direct_monitor - 1]; + if (!monitor_mix) + return; + + // Update it + alsa_set_elem_value(monitor_mix, value); } -static void gain_updated(struct alsa_elem *elem) { - int is_writable = alsa_get_elem_writable(elem); - gtk_widget_set_sensitive(elem->widget, is_writable); +static void gain_updated( + struct alsa_elem *elem, + void *private +) { + struct gain *data = private; - int value = alsa_get_elem_value(elem); - gtk_dial_set_value(GTK_DIAL(elem->widget), value); + int is_writable = alsa_get_elem_writable(elem); + gtk_widget_set_sensitive(data->dial, is_writable); + + int alsa_value = alsa_get_elem_value(elem); + gtk_dial_set_value(GTK_DIAL(data->dial), alsa_value); char s[20]; - snprintf(s, 20, "%.1f", (value / 2.0) - 80); - gtk_label_set_text(GTK_LABEL(elem->widget2), s); + char *p = s; + float value = (float)alsa_value * data->scale + elem->min_dB; + + if (value > elem->max_dB) + value = elem->max_dB; + else if (value < elem->min_dB) + value = elem->min_dB; + + if (data->zero_is_off && alsa_value == 0) { + p += sprintf(p, "−∞"); + } else { + if (value < 0) + p += sprintf(p, "−"); + else if (value > 0) + p += sprintf(p, "+"); + if (data->scale <= 0.5) + p += sprintf(p, "%.1f", fabs(value)); + else + p += sprintf(p, "%.0f", fabs(value)); + } + if (data->scale > 0.5) + p += sprintf(p, "dB"); + + gtk_label_set_text(GTK_LABEL(data->label), s); } -GtkWidget *make_gain_alsa_elem(struct alsa_elem *elem) { - GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_set_hexpand(vbox, TRUE); +// 4th Gen Solo and 2i2 have Mix & Direct Monitor controls which +// interact. If direct monitor is enabled and the Mix A/B controls are +// changed, then the Monitor Mix Playback Volume controls are changed +// too so that the mix settings are restored when direct monitor is +// later enabled again. +static void find_direct_monitor_controls(struct gain *data) { + struct alsa_elem *elem = data->elem; + GArray *elems = elem->card->elems; - GtkWidget *dial = gtk_dial_new_with_range( - DIAL_MIN_VALUE, DIAL_MAX_VALUE, 1 + // Card has no direct monitor control? + struct alsa_elem *direct_monitor_elem = get_elem_by_prefix( + elems, + "Direct Monitor Playback" ); - gtk_dial_set_zero_db(GTK_DIAL(dial), DIAL_ZERO_DB_VALUE); + if (!direct_monitor_elem) + return; - gtk_widget_set_vexpand(dial, TRUE); + // Card has no mixer? + if (strncmp(elem->name, "Mix ", 4) != 0 || + !strstr(elem->name, "Playback Volume")) + return; + + char mix_letter = elem->name[4]; + int input_num = get_num_from_string(elem->name); + + // Find the Monitor Mix control for the 4th Gen Solo + if (strstr(direct_monitor_elem->name, "Switch")) { + char s[80]; + sprintf( + s, + "Monitor Mix %c Input %02d Playback Volume", + mix_letter, input_num + ); + + struct alsa_elem *monitor_mix_elem = get_elem_by_name(elems, s); + if (!monitor_mix_elem) + return; + + data->direct_monitor_elem = direct_monitor_elem; + data->monitor_mix_elem[0] = monitor_mix_elem; + + // Find the Monitor Mix controls for the 4th Gen 2i2 + } else if (strstr(direct_monitor_elem->name, "Enum")) { + for (int i = 0; i <= 1; i++) { + char s[80]; + sprintf( + s, + "Monitor %d Mix %c Input %02d Playback Volume", + i + 1, mix_letter, input_num + ); + + struct alsa_elem *monitor_mix_elem = get_elem_by_name(elems, s); + if (!monitor_mix_elem) + return; + + data->direct_monitor_elem = direct_monitor_elem; + data->monitor_mix_elem[i] = monitor_mix_elem; + } + + } else { + fprintf(stderr, "Couldn't find direct monitor mix control\n"); + } +} + +//GList *make_gain_alsa_elem(struct alsa_elem *elem) { +GtkWidget *make_gain_alsa_elem( + struct alsa_elem *elem, + int zero_is_off, + int widget_taper, + int can_control +) { + struct gain *data = calloc(1, sizeof(struct gain)); + data->elem = elem; + data->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_set_hexpand(data->vbox, TRUE); + gtk_widget_set_valign(data->vbox, GTK_ALIGN_START); + gtk_widget_set_vexpand(data->vbox, TRUE); + + data->scale = (float)(elem->max_dB - elem->min_dB) / + (elem->max_val - elem->min_val); + + data->dial = gtk_dial_new_with_range( + elem->min_val, + elem->max_val, + 1, + 3 / data->scale + ); + + // calculate 0dB value + int zero_db_value = (int)((0 - elem->min_dB) / data->scale + elem->min_val); + + gtk_dial_set_zero_db(GTK_DIAL(data->dial), zero_db_value); + + // convert from widget_taper to gtk_dial_taper + int gtk_dial_taper; + if (widget_taper == WIDGET_GAIN_TAPER_LINEAR) + gtk_dial_taper = GTK_DIAL_TAPER_LINEAR; + else if (widget_taper == WIDGET_GAIN_TAPER_LOG) + gtk_dial_taper = GTK_DIAL_TAPER_LOG; + else + gtk_dial_taper = GTK_DIAL_TAPER_LINEAR; + gtk_dial_set_taper(GTK_DIAL(data->dial), gtk_dial_taper); + + if (widget_taper == WIDGET_GAIN_TAPER_GEN4_VOLUME) + gtk_dial_set_taper_linear_breakpoints( + GTK_DIAL(data->dial), + (const double[]){ 0.488, 0.76 }, + (const double[]){ 0.07, 0.4 }, + 2 + ); + + gtk_dial_set_can_control(GTK_DIAL(data->dial), can_control); + + data->label = gtk_label_new(NULL); + gtk_widget_add_css_class(data->label, "gain"); + gtk_widget_set_vexpand(data->dial, TRUE); + + data->zero_is_off = zero_is_off; + + find_direct_monitor_controls(data); g_signal_connect( - dial, "value-changed", G_CALLBACK(gain_changed), elem + data->dial, "value-changed", G_CALLBACK(gain_changed), data ); - elem->widget = dial; - elem->widget_callback = gain_updated; - GtkWidget *label = gtk_label_new(NULL); - elem->widget2 = label; + alsa_elem_add_callback(elem, gain_updated, data); - gain_updated(elem); + gain_updated(elem, data); - gtk_box_append(GTK_BOX(vbox), dial); - gtk_box_append(GTK_BOX(vbox), label); + gtk_box_append(GTK_BOX(data->vbox), data->dial); + gtk_box_append(GTK_BOX(data->vbox), data->label); - return vbox; + return data->vbox; } diff --git a/src/widget-gain.h b/src/widget-gain.h index 353aa69..65a622a 100644 --- a/src/widget-gain.h +++ b/src/widget-gain.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -7,4 +7,15 @@ #include "alsa.h" -GtkWidget *make_gain_alsa_elem(struct alsa_elem *alsa_elem); +enum { + WIDGET_GAIN_TAPER_LINEAR, + WIDGET_GAIN_TAPER_LOG, + WIDGET_GAIN_TAPER_GEN4_VOLUME +}; + +GtkWidget *make_gain_alsa_elem( + struct alsa_elem *elem, + int zero_is_off, + int taper_type, + int can_control +); diff --git a/src/widget-input-select.c b/src/widget-input-select.c new file mode 100644 index 0000000..45aba25 --- /dev/null +++ b/src/widget-input-select.c @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "stringhelper.h" +#include "widget-input-select.h" + +struct input_select { + struct alsa_elem *elem; + GtkWidget *button; + int line_num; +}; + +static void input_select_clicked( + GtkWidget *widget, + struct input_select *data +) { + int count = alsa_get_item_count(data->elem); + + // select the item that matches the line number that was clicked on + for (int i = 0; i < count; i++) { + const char *text = alsa_get_item_name(data->elem, i); + int a, b; + get_two_num_from_string(text, &a, &b); + + if ((b == -1 && a == data->line_num) || + (a <= data->line_num && b >= data->line_num)) { + alsa_set_elem_value(data->elem, i); + break; + } + } +} + +static void input_select_updated( + struct alsa_elem *elem, + void *private +) { + struct input_select *data = private; + int line_num = data->line_num; + int is_writable = alsa_get_elem_writable(elem); + + int value = alsa_get_elem_value(elem); + const char *text = alsa_get_item_name(elem, value); + + int a, b; + get_two_num_from_string(text, &a, &b); + + // set the button active if it's the selected line number + // (or in the range) + int active = b == -1 + ? a == line_num + : a <= line_num && b >= line_num; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->button), active); + gtk_widget_set_sensitive(data->button, !active && is_writable); +} + +GtkWidget *make_input_select_alsa_elem( + struct alsa_elem *elem, + int line_num +) { + struct input_select *data = malloc(sizeof(struct input_select)); + data->elem = elem; + data->button = gtk_toggle_button_new(); + data->line_num = line_num; + + gtk_widget_add_css_class(data->button, "input-select"); + + char s[20]; + snprintf(s, 20, "%d", line_num); + gtk_button_set_label(GTK_BUTTON(data->button), s); + + g_signal_connect( + data->button, "clicked", G_CALLBACK(input_select_clicked), data + ); + alsa_elem_add_callback(elem, input_select_updated, data); + + input_select_updated(elem, data); + + return data->button; +} diff --git a/src/widget-input-select.h b/src/widget-input-select.h new file mode 100644 index 0000000..833862d --- /dev/null +++ b/src/widget-input-select.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "alsa.h" + +GtkWidget *make_input_select_alsa_elem( + struct alsa_elem *alsa_elem, + int line_num +); diff --git a/src/widget-label.c b/src/widget-label.c new file mode 100644 index 0000000..38eb4ad --- /dev/null +++ b/src/widget-label.c @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "widget-label.h" + +struct label { + struct alsa_elem *elem; + GtkWidget *label; +}; + +static void label_updated(struct alsa_elem *elem, void *private) { + struct label *data = private; + + const char *text = alsa_get_item_name(elem, alsa_get_elem_value(elem)); + + gtk_label_set_text(GTK_LABEL(data->label), text); +} + +GtkWidget *make_label_alsa_elem(struct alsa_elem *elem) { + struct label *data = g_malloc(sizeof(struct label)); + data->label = gtk_label_new(NULL); + + gtk_widget_set_halign(data->label, GTK_ALIGN_CENTER); + gtk_widget_set_valign(data->label, GTK_ALIGN_CENTER); + + alsa_elem_add_callback(elem, label_updated, data); + + label_updated(elem, data); + + return data->label; +} diff --git a/src/widget-label.h b/src/widget-label.h new file mode 100644 index 0000000..49215f1 --- /dev/null +++ b/src/widget-label.h @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "alsa.h" + +GtkWidget *make_label_alsa_elem(struct alsa_elem *elem); diff --git a/src/widget-sample-rate.c b/src/widget-sample-rate.c new file mode 100644 index 0000000..61c3fe8 --- /dev/null +++ b/src/widget-sample-rate.c @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "gtkhelper.h" +#include "widget-boolean.h" + +struct sample_rate { + struct alsa_card *card; + GtkWidget *button; + guint source; + char *path; + int sample_rate; +}; + +static void button_set_text(GtkWidget *button, int value) { + gtk_widget_remove_css_classes_by_prefix(button, "sample-rate-"); + + if (!value) { + gtk_button_set_label(GTK_BUTTON(button), "N/A"); + return; + } + + char *text; + if (value % 1000 == 0) + text = g_strdup_printf("%dkHz", value / 1000); + else + text = g_strdup_printf("%.1fkHz", value / 1000.0); + gtk_button_set_label(GTK_BUTTON(button), text); + g_free(text); + + char *css_class = g_strdup_printf( + "sample-rate-%d", value + ); + gtk_widget_add_css_class(button, css_class); + g_free(css_class); +} + +// Read the sample rate from /proc/asound/cardN/stream0 +// and return it as an integer +// +// Looking for a line containing: +// Momentary freq = 48000 Hz (0x6.0000) +static int get_sample_rate(struct sample_rate *data) { + if (!data->path) + return 0; + + FILE *file = fopen(data->path, "r"); + if (!file) { + perror("fopen"); + return 0; + } + + char *line = NULL; + size_t len = 0; + ssize_t read; + + int sample_rate = 0; + + while ((read = getline(&line, &len, file)) != -1) { + if (strstr(line, "Momentary freq = ")) { + char *start = strstr(line, "Momentary freq = ") + 17; + char *end = strstr(start, " Hz"); + + if (!start || !end) + continue; + + *end = '\0'; + sample_rate = atoi(start); + + break; + } + } + + free(line); + fclose(file); + + return sample_rate; +} + +static gboolean update_sample_rate(struct sample_rate *data) { + int sample_rate = get_sample_rate(data); + + if (sample_rate != data->sample_rate) { + data->sample_rate = sample_rate; + button_set_text(data->button, sample_rate); + } + + return G_SOURCE_CONTINUE; +} + +static void on_destroy(struct sample_rate *data, GObject *widget) { + if (data->source) + g_source_remove(data->source); + g_free(data->path); + g_free(data); +} + +GtkWidget *make_sample_rate_widget( + struct alsa_card *card +) { + struct sample_rate *data = g_malloc0(sizeof(struct sample_rate)); + data->card = card; + data->button = gtk_toggle_button_new(); + data->sample_rate = -1; + + gtk_widget_add_css_class(data->button, "fixed"); + gtk_widget_add_css_class(data->button, "sample-rate"); + + // can only update if it's a real card + if (card->num != SIMULATED_CARD_NUM) { + data->path = g_strdup_printf("/proc/asound/card%d/stream0", card->num); + data->source = + g_timeout_add_seconds(1, (GSourceFunc)update_sample_rate, data); + } + + // initial update (will show "N/A" for simulated card) + update_sample_rate(data); + + // cleanup when the button is destroyed + g_object_weak_ref(G_OBJECT(data->button), (GWeakNotify)on_destroy, data); + + return data->button; +} diff --git a/src/widget-sample-rate.h b/src/widget-sample-rate.h new file mode 100644 index 0000000..cf643c5 --- /dev/null +++ b/src/widget-sample-rate.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "alsa.h" + +GtkWidget *make_sample_rate_widget( + struct alsa_card *alsa_card +); diff --git a/src/widget-volume.c b/src/widget-volume.c deleted file mode 100644 index 6cda54c..0000000 --- a/src/widget-volume.c +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "gtkdial.h" -#include "widget-volume.h" - -// volume controls -127dB - 0dB -#define DIAL_MIN_VALUE 0 -#define DIAL_MAX_VALUE 127 -#define DIAL_ZERO_DB_VALUE 127 - -static void volume_changed(GtkWidget *widget, struct alsa_elem *elem) { - int value = gtk_dial_get_value(GTK_DIAL(widget)); - - alsa_set_elem_value(elem, value); -} - -static void volume_updated(struct alsa_elem *elem) { - int is_writable = alsa_get_elem_writable(elem); - gtk_widget_set_sensitive(elem->widget, is_writable); - - int value = alsa_get_elem_value(elem); - gtk_dial_set_value(GTK_DIAL(elem->widget), value); - - char s[20]; - snprintf(s, 20, "%ddB", value - 127); - gtk_label_set_text(GTK_LABEL(elem->widget2), s); -} - -GtkWidget *make_volume_alsa_elem(struct alsa_elem *elem) { - GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_set_hexpand(vbox, TRUE); - - GtkWidget *dial = gtk_dial_new_with_range( - DIAL_MIN_VALUE, DIAL_MAX_VALUE, 1 - ); - gtk_dial_set_zero_db(GTK_DIAL(dial), DIAL_ZERO_DB_VALUE); - - gtk_widget_set_vexpand(dial, TRUE); - - g_signal_connect( - dial, "value-changed", G_CALLBACK(volume_changed), elem - ); - elem->widget = dial; - elem->widget_callback = volume_updated; - - GtkWidget *label = gtk_label_new(NULL); - elem->widget2 = label; - - volume_updated(elem); - - gtk_box_append(GTK_BOX(vbox), dial); - gtk_box_append(GTK_BOX(vbox), label); - - return vbox; -} diff --git a/src/widget-volume.h b/src/widget-volume.h deleted file mode 100644 index 842ca3d..0000000 --- a/src/widget-volume.h +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "alsa.h" - -GtkWidget *make_volume_alsa_elem(struct alsa_elem *alsa_elem); diff --git a/src/window-hardware.c b/src/window-hardware.c index a08de54..fa76753 100644 --- a/src/window-hardware.c +++ b/src/window-hardware.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "window-hardware.h" @@ -35,6 +35,20 @@ struct hw_info gen_3_big_info[] = { { } }; +struct hw_info clarett_usb_info[] = { + { "Clarett 2Pre USB" }, + { "Clarett 4Pre USB" }, + { "Clarett 8Pre USB" }, + { } +}; + +struct hw_info clarett_plus_info[] = { + { "Clarett+ 2Pre" }, + { "Clarett+ 4Pre" }, + { "Clarett+ 8Pre" }, + { } +}; + struct hw_cat hw_cat[] = { { "2nd Gen", gen_2_info @@ -45,6 +59,12 @@ struct hw_cat hw_cat[] = { { "Big 3rd Gen", gen_3_big_info }, + { "Clarett USB", + clarett_usb_info + }, + { "Clarett+", + clarett_plus_info + }, { } }; diff --git a/src/window-hardware.h b/src/window-hardware.h index e79e08a..69025af 100644 --- a/src/window-hardware.h +++ b/src/window-hardware.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/window-helper.c b/src/window-helper.c index c4e6a28..274bc2d 100644 --- a/src/window-helper.c +++ b/src/window-helper.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "window-helper.h" @@ -12,6 +12,25 @@ gboolean window_startup_close_request(GtkWindow *w, gpointer data) { return true; } +static gboolean on_key_press( + GtkEventControllerKey *controller, + guint keyval, + guint keycode, + GdkModifierType state, + gpointer user_data +) { + GtkWidget *widget = gtk_event_controller_get_widget( + GTK_EVENT_CONTROLLER(controller) + ); + + if (keyval == GDK_KEY_Escape) { + gtk_window_close(GTK_WINDOW(widget)); + return 1; + } + + return 0; +} + GtkWidget *create_subwindow( struct alsa_card *card, const char *name, @@ -24,6 +43,12 @@ GtkWidget *create_subwindow( gtk_window_set_title(GTK_WINDOW(w), title); g_signal_connect(w, "close_request", G_CALLBACK(close_callback), card); + GtkEventController *key_controller = gtk_event_controller_key_new(); + gtk_widget_add_controller(w, key_controller); + g_signal_connect( + key_controller, "key-pressed", G_CALLBACK(on_key_press), NULL + ); + g_free(title); return w; } diff --git a/src/window-helper.h b/src/window-helper.h index 1296139..d858210 100644 --- a/src/window-helper.h +++ b/src/window-helper.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/window-iface.c b/src/window-iface.c index 7dc9e4d..8844a5b 100644 --- a/src/window-iface.c +++ b/src/window-iface.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include @@ -7,6 +7,7 @@ #include "iface-no-mixer.h" #include "iface-none.h" #include "iface-unknown.h" +#include "iface-update.h" #include "main.h" #include "menu.h" #include "window-iface.h" @@ -16,8 +17,6 @@ static GtkWidget *no_cards_window; static int window_count; void create_card_window(struct alsa_card *card) { - struct alsa_elem *msd_elem; - if (no_cards_window) { gtk_window_destroy(GTK_WINDOW(no_cards_window)); no_cards_window = NULL; @@ -27,17 +26,42 @@ void create_card_window(struct alsa_card *card) { int has_startup = true; int has_mixer = true; - // Gen 2 or Gen 3 4i4+ - if (get_elem_by_prefix(card->elems, "Mixer")) { + struct alsa_elem *msd_elem = + get_elem_by_name(card->elems, "MSD Mode Switch"); + int in_msd_mode = msd_elem && alsa_get_elem_value(msd_elem); + + struct alsa_elem *firmware_elem = + get_elem_by_name(card->elems, "Firmware Version"); + struct alsa_elem *min_firmware_elem = + get_elem_by_name(card->elems, "Minimum Firmware Version"); + int firmware_version = 0; + int min_firmware_version = 0; + if (firmware_elem && min_firmware_elem) { + firmware_version = alsa_get_elem_value(firmware_elem); + min_firmware_version = alsa_get_elem_value(min_firmware_elem); + } + + // Firmware update required + // or firmware version available and in MSD mode + // (updating will disable MSD mode) + if (firmware_version < min_firmware_version || + (card->best_firmware_version > firmware_version && + in_msd_mode)) { + card->window_main_contents = create_iface_update_main(card); + has_startup = false; + has_mixer = false; + + // Scarlett Gen 2, Gen 3 4i4+, Gen 4, Clarett, or Vocaster + } else if (get_elem_by_prefix(card->elems, "Mixer")) { card->window_main_contents = create_iface_mixer_main(card); - // Gen 3 Solo or 2i2 + // Scarlett Gen 3 Solo or 2i2 } else if (get_elem_by_prefix(card->elems, "Phantom")) { card->window_main_contents = create_iface_no_mixer_main(card); has_mixer = false; - // Gen 3 in MSD Mode - } else if ((msd_elem = get_elem_by_name(card->elems, "MSD Mode Switch"))) { + // Scarlett Gen 3+ or Vocaster in MSD Mode + } else if (msd_elem) { card->window_main_contents = create_startup_controls(card); has_startup = false; has_mixer = false; @@ -67,7 +91,7 @@ void create_card_window(struct alsa_card *card) { GTK_WINDOW(card->window_main), card->window_main_contents ); - gtk_widget_show(card->window_main); + gtk_widget_set_visible(card->window_main, TRUE); } void create_no_card_window(void) { @@ -86,6 +110,9 @@ void destroy_card_window(struct alsa_card *card) { gtk_window_destroy(GTK_WINDOW(card->window_levels)); if (card->window_startup) gtk_window_destroy(GTK_WINDOW(card->window_startup)); + if (card->window_modal) { + gtk_window_destroy(GTK_WINDOW(card->window_modal)); + } // disable the level meter timer source if (card->meter_gsource_timer) @@ -95,3 +122,8 @@ void destroy_card_window(struct alsa_card *card) { window_count--; create_no_card_window(); } + +void check_modal_window_closed(void) { + if (!window_count) + gtk_widget_set_visible(no_cards_window, TRUE); +} diff --git a/src/window-iface.h b/src/window-iface.h index dee9228..2cb20b2 100644 --- a/src/window-iface.h +++ b/src/window-iface.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -8,3 +8,4 @@ void create_card_window(struct alsa_card *card); void create_no_card_window(void); void destroy_card_window(struct alsa_card *card); +void check_modal_window_closed(void); diff --git a/src/window-levels.c b/src/window-levels.c index 558b3e3..6604a49 100644 --- a/src/window-levels.c +++ b/src/window-levels.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include @@ -9,6 +9,20 @@ #include "widget-gain.h" #include "window-levels.h" +static const int level_breakpoints_out[] = { -80, -18, -12, -6, -3, -1 }; + +// inputs glow all-red when limit is reached +static const int level_breakpoints_in[] = { -80, -18, -12, -6, -3, 0 }; + +static const double level_colours[] = { + 0.00, 1.00, 0.00, // -80 + 0.75, 1.00, 0.00, // -18 + 1.00, 1.00, 0.00, // -12 + 1.00, 0.75, 0.00, // -6 + 1.00, 0.50, 0.00, // -3 + 1.00, 0.00, 0.00 // -1/0 +}; + static int update_levels_controls(void *user_data) { struct alsa_card *card = user_data; @@ -26,15 +40,7 @@ static int update_levels_controls(void *user_data) { GtkWidget *meter = card->meters[meter_num]; double value = 20 * log10(values[meter_num] / 4095.0); - int int_value; - if (value < -80) - int_value = -80; - else if (value > 0) - int_value = 0; - else - int_value = round(value); - - gtk_dial_set_value(GTK_DIAL(meter), int_value); + gtk_dial_set_value(GTK_DIAL(meter), value); meter_num++; } } @@ -72,10 +78,16 @@ static struct alsa_elem *get_level_meter_elem(struct alsa_card *card) { } GtkWidget *create_levels_controls(struct alsa_card *card) { - GtkWidget *levels_top = gtk_grid_new(); - GtkGrid *grid = GTK_GRID(levels_top); + GtkWidget *top = gtk_frame_new(NULL); + gtk_widget_add_css_class(top, "window-frame"); - gtk_widget_set_margin(GTK_WIDGET(grid), 5); + GtkWidget *levels_top = gtk_grid_new(); + gtk_widget_add_css_class(levels_top, "window-content"); + gtk_widget_add_css_class(levels_top, "top-level-content"); + gtk_widget_add_css_class(levels_top, "window-levels"); + gtk_frame_set_child(GTK_FRAME(top), levels_top); + + GtkGrid *grid = GTK_GRID(levels_top); GtkWidget *count_labels[MAX_MUX_IN] = { NULL }; @@ -88,12 +100,16 @@ GtkWidget *create_levels_controls(struct alsa_card *card) { } // go through the port categories - for (int i = 0; i < PC_COUNT; i++) { + for (int i = 0, row = 1; i < PC_COUNT; i++) { + + if (card->routing_out_count[i] == 0) + continue; + GtkWidget *l = gtk_label_new(port_category_names[i]); gtk_widget_set_halign(l, GTK_ALIGN_END); // add the label - gtk_grid_attach(GTK_GRID(grid), l, 0, i + 1, 1, 1); + gtk_grid_attach(GTK_GRID(grid), l, 0, row, 1, 1); // go through the ports in that category for (int j = 0; j < card->routing_out_count[i]; j++) { @@ -103,10 +119,27 @@ GtkWidget *create_levels_controls(struct alsa_card *card) { count_labels[j] = add_count_label(grid, j); // create the meter widget and attach to the grid - GtkWidget *meter = gtk_dial_new_with_range(-80, 0, 1); + GtkWidget *meter = gtk_dial_new_with_range(-80, 0, 0, 0); + gtk_dial_set_taper(GTK_DIAL(meter), GTK_DIAL_TAPER_LINEAR); + gtk_dial_set_can_control(GTK_DIAL(meter), FALSE); + gtk_dial_set_level_meter_colours( + GTK_DIAL(meter), + (i == PC_DSP || i == PC_PCM) + ? level_breakpoints_in + : level_breakpoints_out, + level_colours, + sizeof(level_breakpoints_out) / sizeof(int) + ); + gtk_widget_set_sensitive(meter, FALSE); + + // HW Output off_db is -55db; otherwise -45db + gtk_dial_set_off_db(GTK_DIAL(meter), i == PC_HW ? -55 : -45); + card->meters[meter_num++] = meter; - gtk_grid_attach(GTK_GRID(grid), meter, j + 1, i + 1, 1, 1); + gtk_grid_attach(GTK_GRID(grid), meter, j + 1, row, 1, 1); } + + row++; } int elem_count = card->level_meter_elem->count; @@ -118,5 +151,5 @@ GtkWidget *create_levels_controls(struct alsa_card *card) { card->meter_gsource_timer = g_timeout_add(50, update_levels_controls, card); - return levels_top; + return top; } diff --git a/src/window-levels.h b/src/window-levels.h index 00ae9eb..cc19a2d 100644 --- a/src/window-levels.h +++ b/src/window-levels.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/window-mixer.c b/src/window-mixer.c index 23f25cd..399b2f6 100644 --- a/src/window-mixer.c +++ b/src/window-mixer.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include @@ -8,29 +8,38 @@ #include "widget-gain.h" #include "window-mixer.h" -static struct routing_dst *get_mixer_r_dst( +static struct routing_snk *get_mixer_r_snk( struct alsa_card *card, int input_num ) { - for (int i = 0; i < card->routing_dsts->len; i++) { - struct routing_dst *r_dst = &g_array_index( - card->routing_dsts, struct routing_dst, i + for (int i = 0; i < card->routing_snks->len; i++) { + struct routing_snk *r_snk = &g_array_index( + card->routing_snks, struct routing_snk, i ); - if (r_dst->port_category != PC_MIX) + if (r_snk->port_category != PC_MIX) continue; - if (r_dst->elem->lr_num == input_num) - return r_dst; + if (r_snk->elem->lr_num == input_num) + return r_snk; } return NULL; } GtkWidget *create_mixer_controls(struct alsa_card *card) { - GtkWidget *mixer_top = gtk_grid_new(); - GArray *elems = card->elems; + GtkWidget *top = gtk_frame_new(NULL); + gtk_widget_add_css_class(top, "window-frame"); - gtk_widget_set_margin(mixer_top, 5); + GtkWidget *mixer_top = gtk_grid_new(); + gtk_widget_add_css_class(mixer_top, "window-content"); + gtk_widget_add_css_class(mixer_top, "top-level-content"); + gtk_widget_add_css_class(mixer_top, "window-mixer"); + gtk_frame_set_child(GTK_FRAME(top), mixer_top); + + gtk_widget_set_halign(mixer_top, GTK_ALIGN_CENTER); + gtk_widget_set_valign(mixer_top, GTK_ALIGN_CENTER); gtk_grid_set_column_homogeneous(GTK_GRID(mixer_top), TRUE); + GArray *elems = card->elems; + // create the Mix X labels on the left and right of the grid for (int i = 0; i < card->routing_in_count[PC_MIX]; i++) { char name[10]; @@ -73,24 +82,24 @@ GtkWidget *create_mixer_controls(struct alsa_card *card) { } // create the gain control and attach to the grid - GtkWidget *w = make_gain_alsa_elem(elem); + GtkWidget *w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_LOG, 0); gtk_grid_attach(GTK_GRID(mixer_top), w, input_num + 1, mix_num + 2, 1, 1); - // look up the r_dst entry for the mixer input number - struct routing_dst *r_dst = get_mixer_r_dst(card, input_num + 1); - if (!r_dst) { + // look up the r_snk entry for the mixer input number + struct routing_snk *r_snk = get_mixer_r_snk(card, input_num + 1); + if (!r_snk) { printf("missing mixer input %d\n", input_num); continue; } // lookup the top label for the mixer input - GtkWidget *l_top = r_dst->mixer_label_top; + GtkWidget *l_top = r_snk->mixer_label_top; // if the top label doesn't already exist the bottom doesn't // either; create them both and attach to the grid if (!l_top) { - l_top = r_dst->mixer_label_top = gtk_label_new(""); - GtkWidget *l_bottom = r_dst->mixer_label_bottom = gtk_label_new(""); + l_top = r_snk->mixer_label_top = gtk_label_new(""); + GtkWidget *l_bottom = r_snk->mixer_label_bottom = gtk_label_new(""); gtk_grid_attach( GTK_GRID(mixer_top), l_top, @@ -105,19 +114,19 @@ GtkWidget *create_mixer_controls(struct alsa_card *card) { update_mixer_labels(card); - return mixer_top; + return top; } void update_mixer_labels(struct alsa_card *card) { - for (int i = 0; i < card->routing_dsts->len; i++) { - struct routing_dst *r_dst = &g_array_index( - card->routing_dsts, struct routing_dst, i + for (int i = 0; i < card->routing_snks->len; i++) { + struct routing_snk *r_snk = &g_array_index( + card->routing_snks, struct routing_snk, i ); - if (r_dst->port_category != PC_MIX) + if (r_snk->port_category != PC_MIX) continue; - struct alsa_elem *elem = r_dst->elem; + struct alsa_elem *elem = r_snk->elem; int routing_src_idx = alsa_get_elem_value(elem); @@ -125,9 +134,9 @@ void update_mixer_labels(struct alsa_card *card) { card->routing_srcs, struct routing_src, routing_src_idx ); - if (r_dst->mixer_label_top) { - gtk_label_set_text(GTK_LABEL(r_dst->mixer_label_top), r_src->name); - gtk_label_set_text(GTK_LABEL(r_dst->mixer_label_bottom), r_src->name); + if (r_snk->mixer_label_top) { + gtk_label_set_text(GTK_LABEL(r_snk->mixer_label_top), r_src->name); + gtk_label_set_text(GTK_LABEL(r_snk->mixer_label_bottom), r_src->name); } } } diff --git a/src/window-mixer.h b/src/window-mixer.h index c9360e2..3f56917 100644 --- a/src/window-mixer.h +++ b/src/window-mixer.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/window-modal.c b/src/window-modal.c new file mode 100644 index 0000000..2081650 --- /dev/null +++ b/src/window-modal.c @@ -0,0 +1,214 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "gtkhelper.h" +#include "window-iface.h" +#include "window-modal.h" + +static void modal_no_callback(GtkWidget *w, struct modal_data *modal_data) { + GtkWidget *dialog = modal_data->dialog; + + alsa_unregister_reopen_callback(modal_data->serial); + + gtk_window_destroy(GTK_WINDOW(dialog)); + check_modal_window_closed(); +} + +static void modal_yes_callback(GtkWidget *w, struct modal_data *modal_data) { + // remove the buttons + GtkWidget *child; + while ((child = gtk_widget_get_first_child(modal_data->button_box))) + gtk_box_remove(GTK_BOX(modal_data->button_box), child); + + // add a progress bar + modal_data->progress_bar = gtk_progress_bar_new(); + gtk_box_append(GTK_BOX(modal_data->button_box), modal_data->progress_bar); + + // change the title + gtk_window_set_title( + GTK_WINDOW(modal_data->dialog), modal_data->title_active + ); + + // if the card goes away, don't close this window + modal_data->card->window_modal = NULL; + + modal_data->callback(modal_data); +} + +static void free_modal_data(gpointer user_data) { + struct modal_data *modal_data = user_data; + + g_free(modal_data->serial); + g_free(modal_data); +} + +void create_modal_window( + GtkWidget *w, + struct alsa_card *card, + const char *title, + const char *title_active, + const char *message, + modal_callback callback +) { + if (card->window_modal) { + fprintf(stderr, "Error: Modal window already open\n"); + return; + } + + GtkWidget *dialog = gtk_window_new(); + + struct modal_data *modal_data = g_new0(struct modal_data, 1); + modal_data->card = card; + modal_data->serial = g_strdup(card->serial); + modal_data->title_active = title_active; + modal_data->dialog = dialog; + modal_data->callback = callback; + + gtk_window_set_title(GTK_WINDOW(dialog), title); + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + gtk_widget_add_css_class(dialog, "window-frame"); + + GtkWidget *content_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 50); + gtk_window_set_child(GTK_WINDOW(dialog), content_box); + gtk_widget_add_css_class(content_box, "window-content"); + gtk_widget_add_css_class(content_box, "top-level-content"); + gtk_widget_add_css_class(content_box, "big-padding"); + + modal_data->label = gtk_label_new(message); + gtk_box_append(GTK_BOX(content_box), modal_data->label); + + GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + gtk_widget_set_margin(sep, 0); + gtk_box_append(GTK_BOX(content_box), sep); + + modal_data->button_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 50); + gtk_widget_set_halign(modal_data->button_box, GTK_ALIGN_CENTER); + gtk_box_append(GTK_BOX(content_box), modal_data->button_box); + + g_object_set_data_full( + G_OBJECT(dialog), "modal_data", modal_data, free_modal_data + ); + + GtkWidget *no_button = gtk_button_new_with_label("No"); + g_signal_connect( + no_button, "clicked", G_CALLBACK(modal_no_callback), modal_data + ); + gtk_box_append(GTK_BOX(modal_data->button_box), no_button); + + GtkWidget *yes_button = gtk_button_new_with_label("Yes"); + g_signal_connect( + yes_button, "clicked", G_CALLBACK(modal_yes_callback), modal_data + ); + gtk_box_append(GTK_BOX(modal_data->button_box), yes_button); + + gtk_widget_set_visible(dialog, TRUE); + + card->window_modal = dialog; +} + +gboolean modal_update_progress(gpointer user_data) { + struct progress_data *progress_data = user_data; + struct modal_data *modal_data = progress_data->modal_data; + + // Done? Replace the progress bar with an Ok button. + if (progress_data->progress < 0) { + GtkWidget *child; + while ((child = gtk_widget_get_first_child(modal_data->button_box))) + gtk_box_remove(GTK_BOX(modal_data->button_box), child); + + GtkWidget *ok_button = gtk_button_new_with_label("Ok"); + g_signal_connect( + ok_button, "clicked", G_CALLBACK(modal_no_callback), modal_data + ); + gtk_box_append(GTK_BOX(modal_data->button_box), ok_button); + } else { + gtk_progress_bar_set_fraction( + GTK_PROGRESS_BAR(modal_data->progress_bar), + progress_data->progress / 100.0 + ); + } + + // Update the label text if we have a new message. + if (progress_data->text) + gtk_label_set_text(GTK_LABEL(modal_data->label), progress_data->text); + + g_free(progress_data->text); + g_free(progress_data); + return G_SOURCE_REMOVE; +} + +// make the progress bar move along +// if it gets to the end twice, something probably went wrong +static gboolean update_progress_bar_reboot(gpointer user_data) { + struct progress_data *progress_data = user_data; + struct modal_data *modal_data = progress_data->modal_data; + + if (progress_data->progress >= 200) { + // Done? + gtk_label_set_text( + GTK_LABEL(modal_data->label), + "Reboot failed? Try unplugging/replugging/power-cycling the device." + ); + + GtkWidget *child; + while ((child = gtk_widget_get_first_child(modal_data->button_box))) + gtk_box_remove(GTK_BOX(modal_data->button_box), child); + + GtkWidget *ok_button = gtk_button_new_with_label("Ok"); + g_signal_connect( + ok_button, "clicked", G_CALLBACK(modal_no_callback), modal_data + ); + gtk_box_append(GTK_BOX(modal_data->button_box), ok_button); + + modal_data->timeout_id = 0; + + return G_SOURCE_REMOVE; + } + + progress_data->progress++; + gtk_progress_bar_set_fraction( + GTK_PROGRESS_BAR(modal_data->progress_bar), + (progress_data->progress % 100) / 100.0 + ); + + return G_SOURCE_CONTINUE; +} + +// this is called when the card is seen again so we can close the +// modal window +void modal_reopen_callback(void *user_data) { + struct modal_data *modal_data = user_data; + + // stop the progress bar + if (modal_data->timeout_id) + g_source_remove(modal_data->timeout_id); + + // close the window + gtk_window_destroy(GTK_WINDOW(modal_data->dialog)); +} + +// make a progress bar that moves while the device is rebooting +gboolean modal_start_reboot_progress(gpointer user_data) { + struct modal_data *modal_data = user_data; + + gtk_label_set_text(GTK_LABEL(modal_data->label), "Rebooting..."); + + struct progress_data *progress_data = g_new0(struct progress_data, 1); + progress_data->modal_data = modal_data; + progress_data->progress = 0; + + g_object_set_data_full( + G_OBJECT(modal_data->progress_bar), "progress_data", progress_data, g_free + ); + + modal_data->timeout_id = g_timeout_add( + 55, update_progress_bar_reboot, progress_data + ); + + alsa_register_reopen_callback( + modal_data->card->serial, modal_reopen_callback, modal_data + ); + + return G_SOURCE_REMOVE; +} diff --git a/src/window-modal.h b/src/window-modal.h new file mode 100644 index 0000000..12fde03 --- /dev/null +++ b/src/window-modal.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include "alsa.h" + +// create a modal window with a message and yes/no buttons +// the callback is called with the modal_data when yes is clicked + +struct modal_data; + +typedef void (*modal_callback)(struct modal_data *data); + +struct modal_data { + struct alsa_card *card; + char *serial; + const char *title_active; + GtkWidget *dialog; + GtkWidget *label; + GtkWidget *button_box; + GtkWidget *progress_bar; + guint timeout_id; + modal_callback callback; +}; + +void create_modal_window( + GtkWidget *w, + struct alsa_card *card, + const char *title, + const char *title_active, + const char *message, + modal_callback callback +); + +// update the progress bar in a modal window + +struct progress_data { + struct modal_data *modal_data; + char *text; + int progress; +}; + +gboolean modal_update_progress(gpointer user_data); + +// start a progress bar for a reboot + +gboolean modal_start_reboot_progress(gpointer user_data); diff --git a/src/window-routing.c b/src/window-routing.c index 7878944..b052a50 100644 --- a/src/window-routing.c +++ b/src/window-routing.c @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "gtkhelper.h" @@ -30,6 +30,8 @@ static void get_routing_srcs(struct alsa_card *card) { if (strncmp(name, "Mix", 3) == 0) r->port_category = PC_MIX; + else if (strncmp(name, "DSP", 3) == 0) + r->port_category = PC_DSP; else if (strncmp(name, "PCM", 3) == 0) r->port_category = PC_PCM; else @@ -47,19 +49,19 @@ static void get_routing_srcs(struct alsa_card *card) { assert(card->routing_in_count[PC_MIX] <= MAX_MIX_OUT); } -static void get_routing_dsts(struct alsa_card *card) { +static void get_routing_snks(struct alsa_card *card) { GArray *elems = card->elems; int count = 0; - // count and label routing dsts + // count and label routing snks for (int i = 0; i < elems->len; i++) { struct alsa_elem *elem = &g_array_index(elems, struct alsa_elem, i); if (!elem->card) continue; - if (!is_elem_routing_dst(elem)) + if (!is_elem_routing_snk(elem)) continue; int i = get_num_from_string(elem->name); @@ -70,13 +72,13 @@ static void get_routing_dsts(struct alsa_card *card) { count++; } - // create an array of routing dsts pointing to those elements - card->routing_dsts = g_array_new( - FALSE, TRUE, sizeof(struct routing_dst) + // create an array of routing snks pointing to those elements + card->routing_snks = g_array_new( + FALSE, TRUE, sizeof(struct routing_snk) ); - g_array_set_size(card->routing_dsts, count); + g_array_set_size(card->routing_snks, count); - // count through card->rounting_dsts + // count through card->routing_snks int j = 0; for (int i = 0; i < elems->len; i++) { @@ -85,14 +87,16 @@ static void get_routing_dsts(struct alsa_card *card) { if (!elem->lr_num) continue; - struct routing_dst *r = &g_array_index( - card->routing_dsts, struct routing_dst, j + struct routing_snk *r = &g_array_index( + card->routing_snks, struct routing_snk, j ); r->idx = j; j++; r->elem = elem; if (strncmp(elem->name, "Mixer Input", 11) == 0) { r->port_category = PC_MIX; + } else if (strncmp(elem->name, "DSP Input", 9) == 0) { + r->port_category = PC_DSP; } else if (strncmp(elem->name, "PCM", 3) == 0) { r->port_category = PC_PCM; } else if (strstr(elem->name, "Playback Enum")) { @@ -107,20 +111,14 @@ static void get_routing_dsts(struct alsa_card *card) { assert(j == count); } -static void routing_grid_label(char *s, GtkGrid *g, GtkAlign align) { - GtkWidget *l = gtk_label_new(s); - gtk_widget_set_halign(l, align); - gtk_grid_attach(g, l, 0, 0, 1, 1); -} - -// clear all the routing destinations +// clear all the routing sinks static void routing_preset_clear(struct alsa_card *card) { - for (int i = 0; i < card->routing_dsts->len; i++) { - struct routing_dst *r_dst = &g_array_index( - card->routing_dsts, struct routing_dst, i + for (int i = 0; i < card->routing_snks->len; i++) { + struct routing_snk *r_snk = &g_array_index( + card->routing_snks, struct routing_snk, i ); - alsa_set_elem_value(r_dst->elem, 0); + alsa_set_elem_value(r_snk->elem, 0); } } @@ -128,7 +126,7 @@ static void routing_preset_link( struct alsa_card *card, int src_port_category, int src_mod, - int dst_port_category + int snk_port_category ) { // find the first src port with the selected port category @@ -144,16 +142,16 @@ static void routing_preset_link( break; } - // find the first dst port with the selected port category - int dst_idx; - for (dst_idx = 0; - dst_idx < card->routing_dsts->len; - dst_idx++) { - struct routing_dst *r_dst = &g_array_index( - card->routing_dsts, struct routing_dst, dst_idx + // find the first snk port with the selected port category + int snk_idx; + for (snk_idx = 0; + snk_idx < card->routing_snks->len; + snk_idx++) { + struct routing_snk *r_snk = &g_array_index( + card->routing_snks, struct routing_snk, snk_idx ); - if (r_dst->port_category == dst_port_category) + if (r_snk->port_category == snk_port_category) break; } @@ -161,7 +159,7 @@ static void routing_preset_link( int src_idx = start_src_idx; int src_count = 0; while (src_idx < card->routing_srcs->len && - dst_idx < card->routing_dsts->len) { + snk_idx < card->routing_snks->len) { // stop if no more of the selected src port category struct routing_src *r_src = &g_array_index( @@ -170,20 +168,20 @@ static void routing_preset_link( if (r_src->port_category != src_port_category) break; - // stop if no more of the selected dst port category - struct routing_dst *r_dst = &g_array_index( - card->routing_dsts, struct routing_dst, dst_idx + // stop if no more of the selected snk port category + struct routing_snk *r_snk = &g_array_index( + card->routing_snks, struct routing_snk, snk_idx ); - if (r_dst->port_category != dst_port_category) + if (r_snk->port_category != snk_port_category) break; // do the assignment - alsa_set_elem_value(r_dst->elem, r_src->id); + alsa_set_elem_value(r_snk->elem, r_src->id); // get the next index src_idx++; src_count++; - dst_idx++; + snk_idx++; if (src_count == src_mod) { src_idx = start_src_idx; @@ -232,6 +230,8 @@ static GtkWidget *make_preset_menu_button(struct alsa_card *card) { g_menu_append(menu, "Stereo Out", "routing.preset('stereo_out')"); GtkWidget *button = gtk_menu_button_new(); + gtk_widget_set_halign(button, GTK_ALIGN_CENTER); + gtk_widget_set_valign(button, GTK_ALIGN_CENTER); gtk_menu_button_set_label(GTK_MENU_BUTTON(button), "Presets"); gtk_menu_button_set_menu_model( GTK_MENU_BUTTON(button), @@ -253,93 +253,144 @@ static GtkWidget *make_preset_menu_button(struct alsa_card *card) { return button; } +static GtkWidget *create_routing_group_grid( + struct alsa_card *card, + char *name, + char *descr, + char *tooltip, + GtkOrientation orientation, + GtkAlign align +) { + GtkWidget *grid = gtk_grid_new(); + gtk_widget_set_name(grid, name); + gtk_widget_add_css_class(grid, "controls-content"); + + gtk_grid_set_spacing(GTK_GRID(grid), 2); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) { + gtk_widget_set_halign(grid, GTK_ALIGN_CENTER); + gtk_widget_set_valign(grid, GTK_ALIGN_FILL); + gtk_widget_set_hexpand(grid, TRUE); + } else { + gtk_widget_set_halign(grid, GTK_ALIGN_FILL); + gtk_widget_set_valign(grid, GTK_ALIGN_CENTER); + gtk_widget_set_vexpand(grid, TRUE); + } + + GtkWidget *label = gtk_label_new(descr); + gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1); + if (orientation == GTK_ORIENTATION_HORIZONTAL) { + gtk_widget_set_valign(label, align); + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER); + } else { + gtk_widget_set_halign(label, align); + } + gtk_widget_set_tooltip_text(label, tooltip); + + return grid; +} + static void create_routing_grid(struct alsa_card *card) { - GtkWidget *routing_grid = card->routing_grid = gtk_grid_new(); + GtkGrid *routing_grid = GTK_GRID(card->routing_grid = gtk_grid_new()); + + int has_dsp = !!card->routing_in_count[PC_DSP]; + + gtk_widget_set_halign(card->routing_grid, GTK_ALIGN_CENTER); + gtk_widget_set_valign(card->routing_grid, GTK_ALIGN_CENTER); GtkWidget *preset_menu_button = make_preset_menu_button(card); gtk_grid_attach( - GTK_GRID(routing_grid), preset_menu_button, 0, 0, 1, 1 + routing_grid, preset_menu_button, 0, 0, 1, 1 ); - card->routing_hw_in_grid = gtk_grid_new(); - card->routing_pcm_in_grid = gtk_grid_new(); - card->routing_pcm_out_grid = gtk_grid_new(); - card->routing_hw_out_grid = gtk_grid_new(); - card->routing_mixer_in_grid = gtk_grid_new(); - card->routing_mixer_out_grid = gtk_grid_new(); - gtk_grid_attach( - GTK_GRID(routing_grid), card->routing_hw_in_grid, 0, 1, 1, 1 + card->routing_hw_in_grid = create_routing_group_grid( + card, "routing_hw_in_grid", "Hardware Inputs", + "Hardware Inputs are the physical inputs on the interface", + GTK_ORIENTATION_VERTICAL, GTK_ALIGN_END ); - gtk_grid_attach( - GTK_GRID(routing_grid), card->routing_pcm_in_grid, 0, 2, 1, 1 + card->routing_pcm_in_grid = create_routing_group_grid( + card, "routing_pcm_in_grid", "PCM Outputs", + "PCM Outputs are the digital audio channels sent from the PC to " + "the interface over USB, used for audio playback", + GTK_ORIENTATION_VERTICAL, GTK_ALIGN_END ); - gtk_grid_attach( - GTK_GRID(routing_grid), card->routing_pcm_out_grid, 2, 1, 1, 1 + card->routing_pcm_out_grid = create_routing_group_grid( + card, "routing_pcm_out_grid", "PCM Inputs", + "PCM Inputs are the digital audio channels sent from the interface " + "to the PC over USB, use for audio recording", + GTK_ORIENTATION_VERTICAL, GTK_ALIGN_START ); - gtk_grid_attach( - GTK_GRID(routing_grid), card->routing_hw_out_grid, 2, 2, 1, 1 + card->routing_hw_out_grid = create_routing_group_grid( + card, "routing_hw_out_grid", "Hardware Outputs", + "Hardware Outputs are the physical outputs on the interface", + GTK_ORIENTATION_VERTICAL, GTK_ALIGN_START ); - gtk_grid_attach( - GTK_GRID(routing_grid), card->routing_mixer_in_grid, 1, 0, 1, 1 + if (has_dsp) { + card->routing_dsp_in_grid = create_routing_group_grid( + card, "routing_dsp_in_grid", "DSP\nInputs", + "DSP Inputs are used to send audio to the DSP, which is used for " + "features such as the input level meters, Air mode, and Autogain", + GTK_ORIENTATION_HORIZONTAL, GTK_ALIGN_CENTER + ); + card->routing_dsp_out_grid = create_routing_group_grid( + card, "routing_dsp_out_grid", "DSP\nOutputs", + "DSP Outputs are used to send audio from the DSP after it has " + "done its processing", + GTK_ORIENTATION_HORIZONTAL, GTK_ALIGN_CENTER + ); + } + card->routing_mixer_in_grid = create_routing_group_grid( + card, "routing_mixer_in_grid", "Mixer\nInputs", + "Mixer Inputs are used to mix multiple audio channels together", + GTK_ORIENTATION_HORIZONTAL, GTK_ALIGN_CENTER ); - gtk_grid_attach( - GTK_GRID(routing_grid), card->routing_mixer_out_grid, 1, 3, 1, 1 - ); - gtk_widget_set_margin(routing_grid, 10); - gtk_grid_set_spacing(GTK_GRID(routing_grid), 10); - gtk_grid_set_spacing(GTK_GRID(card->routing_hw_in_grid), 2); - gtk_grid_set_spacing(GTK_GRID(card->routing_pcm_in_grid), 2); - gtk_grid_set_spacing(GTK_GRID(card->routing_pcm_out_grid), 2); - gtk_grid_set_spacing(GTK_GRID(card->routing_hw_out_grid), 2); - gtk_grid_set_spacing(GTK_GRID(card->routing_mixer_in_grid), 2); - gtk_grid_set_spacing(GTK_GRID(card->routing_mixer_out_grid), 10); - gtk_grid_set_row_spacing(GTK_GRID(card->routing_mixer_out_grid), 2); - gtk_grid_set_column_spacing(GTK_GRID(card->routing_mixer_out_grid), 10); - gtk_widget_set_vexpand(card->routing_hw_in_grid, TRUE); - gtk_widget_set_vexpand(card->routing_pcm_in_grid, TRUE); - gtk_widget_set_vexpand(card->routing_pcm_out_grid, TRUE); - gtk_widget_set_vexpand(card->routing_hw_out_grid, TRUE); - gtk_widget_set_hexpand(card->routing_mixer_in_grid, TRUE); - gtk_widget_set_hexpand(card->routing_mixer_out_grid, TRUE); - gtk_widget_set_align( - card->routing_hw_in_grid, GTK_ALIGN_FILL, GTK_ALIGN_CENTER - ); - gtk_widget_set_align( - card->routing_pcm_in_grid, GTK_ALIGN_FILL, GTK_ALIGN_CENTER - ); - gtk_widget_set_align( - card->routing_hw_out_grid, GTK_ALIGN_FILL, GTK_ALIGN_CENTER - ); - gtk_widget_set_align( - card->routing_pcm_out_grid, GTK_ALIGN_FILL, GTK_ALIGN_CENTER - ); - gtk_widget_set_align( - card->routing_mixer_in_grid, GTK_ALIGN_CENTER, GTK_ALIGN_END - ); - gtk_widget_set_align( - card->routing_mixer_out_grid, GTK_ALIGN_CENTER, GTK_ALIGN_START + card->routing_mixer_out_grid = create_routing_group_grid( + card, "routing_mixer_out_grid", + card->has_talkback ? "Mixer Outputs" : "Mixer\nOutputs", + "Mixer Outputs are used to send audio from the mixer", + GTK_ORIENTATION_HORIZONTAL, GTK_ALIGN_CENTER ); - routing_grid_label( - "Hardware Inputs", GTK_GRID(card->routing_hw_in_grid), GTK_ALIGN_END + int left_col_num = 0; + int dsp_col_num = has_dsp ? 1 : 0; + int mix_col_num = dsp_col_num + 1; + int right_col_num = mix_col_num + 1; + + gtk_grid_attach( + routing_grid, card->routing_hw_in_grid, left_col_num, 1, 1, 1 ); - routing_grid_label( - "Hardware Outputs", GTK_GRID(card->routing_hw_out_grid), GTK_ALIGN_START + gtk_grid_attach( + routing_grid, card->routing_pcm_in_grid, left_col_num, 2, 1, 1 ); - routing_grid_label( - "PCM Outputs", GTK_GRID(card->routing_pcm_in_grid), GTK_ALIGN_END + gtk_grid_attach( + routing_grid, card->routing_pcm_out_grid, right_col_num, 1, 1, 1 ); - routing_grid_label( - "PCM Inputs", GTK_GRID(card->routing_pcm_out_grid), GTK_ALIGN_START + gtk_grid_attach( + routing_grid, card->routing_hw_out_grid, right_col_num, 2, 1, 1 ); + if (has_dsp) { + gtk_grid_attach( + routing_grid, card->routing_dsp_in_grid, dsp_col_num, 0, 1, 1 + ); + gtk_grid_attach( + routing_grid, card->routing_dsp_out_grid, dsp_col_num, 3, 1, 1 + ); + } + gtk_grid_attach( + routing_grid, card->routing_mixer_in_grid, mix_col_num, 0, 1, 1 + ); + gtk_grid_attach( + routing_grid, card->routing_mixer_out_grid, mix_col_num, 3, 1, 1 + ); + gtk_grid_set_spacing(routing_grid, 10); GtkWidget *src_label = gtk_label_new("↑\nSources →"); gtk_label_set_justify(GTK_LABEL(src_label), GTK_JUSTIFY_CENTER); - gtk_grid_attach(GTK_GRID(routing_grid), src_label, 0, 3, 1, 1); + gtk_grid_attach(routing_grid, src_label, left_col_num, 3, 1, 1); - GtkWidget *dst_label = gtk_label_new("← Destinations\n↓"); - gtk_label_set_justify(GTK_LABEL(dst_label), GTK_JUSTIFY_CENTER); - gtk_grid_attach(GTK_GRID(routing_grid), dst_label, 2, 0, 1, 1); + GtkWidget *snk_label = gtk_label_new("← Sinks\n↓"); + gtk_label_set_justify(GTK_LABEL(snk_label), GTK_JUSTIFY_CENTER); + gtk_grid_attach(routing_grid, snk_label, right_col_num, 0, 1, 1); } static GtkWidget *make_socket_widget(void) { @@ -347,6 +398,8 @@ static GtkWidget *make_socket_widget(void) { "/vu/b4/alsa-scarlett-gui/icons/socket.svg" ); gtk_widget_set_align(w, GTK_ALIGN_CENTER, GTK_ALIGN_CENTER); + gtk_picture_set_can_shrink(GTK_PICTURE(w), FALSE); + gtk_widget_set_margin(w, 2); return w; } @@ -359,30 +412,30 @@ static gboolean dropped_on_src( gpointer data ) { struct routing_src *src = data; - int dst_id = g_value_get_int(value); + int snk_id = g_value_get_int(value); // don't accept src -> src drops - if (!(dst_id & 0x8000)) + if (!(snk_id & 0x8000)) return FALSE; - // convert the int to a r_dst_idx - int r_dst_idx = dst_id & ~0x8000; + // convert the int to a r_snk_idx + int r_snk_idx = snk_id & ~0x8000; // check the index is in bounds - GArray *r_dsts = src->card->routing_dsts; - if (r_dst_idx < 0 || r_dst_idx >= r_dsts->len) + GArray *r_snks = src->card->routing_snks; + if (r_snk_idx < 0 || r_snk_idx >= r_snks->len) return FALSE; - struct routing_dst *r_dst = &g_array_index( - r_dsts, struct routing_dst, r_dst_idx + struct routing_snk *r_snk = &g_array_index( + r_snks, struct routing_snk, r_snk_idx ); - alsa_set_elem_value(r_dst->elem, src->id); + alsa_set_elem_value(r_snk->elem, src->id); return TRUE; } -// something was dropped on a routing destination -static gboolean dropped_on_dst( +// something was dropped on a routing sink +static gboolean dropped_on_snk( GtkDropTarget *dest, const GValue *value, double x, @@ -392,7 +445,7 @@ static gboolean dropped_on_dst( struct alsa_elem *elem = data; int src_id = g_value_get_int(value); - // don't accept dst -> dst drops + // don't accept snk -> snk drops if (src_id & 0x8000) return FALSE; @@ -409,20 +462,20 @@ static void src_routing_clicked( ) { struct alsa_card *card = r_src->card; - // go through all the routing destinations - for (int i = 0; i < card->routing_dsts->len; i++) { - struct routing_dst *r_dst = &g_array_index( - card->routing_dsts, struct routing_dst, i + // go through all the routing sinks + for (int i = 0; i < card->routing_snks->len; i++) { + struct routing_snk *r_snk = &g_array_index( + card->routing_snks, struct routing_snk, i ); - int r_src_idx = alsa_get_elem_value(r_dst->elem); + int r_src_idx = alsa_get_elem_value(r_snk->elem); if (r_src_idx == r_src->id) - alsa_set_elem_value(r_dst->elem, 0); + alsa_set_elem_value(r_snk->elem, 0); } } -static void dst_routing_clicked( +static void snk_routing_clicked( GtkWidget *widget, int n_press, double x, @@ -444,16 +497,16 @@ static void src_drag_begin( card->src_drag = r_src; } -static void dst_drag_begin( +static void snk_drag_begin( GtkDragSource *source, GdkDrag *drag, gpointer user_data ) { - struct routing_dst *r_dst = user_data; - struct alsa_card *card = r_dst->elem->card; + struct routing_snk *r_snk = user_data; + struct alsa_card *card = r_snk->elem->card; - card->drag_type = DRAG_TYPE_DST; - card->dst_drag = r_dst; + card->drag_type = DRAG_TYPE_SNK; + card->snk_drag = r_snk; } static void src_drag_end( @@ -471,17 +524,17 @@ static void src_drag_end( gtk_widget_queue_draw(card->routing_lines); } -static void dst_drag_end( +static void snk_drag_end( GtkDragSource *source, GdkDrag *drag, gboolean delete_data, gpointer user_data ) { - struct routing_dst *r_dst = user_data; - struct alsa_card *card = r_dst->elem->card; + struct routing_snk *r_snk = user_data; + struct alsa_card *card = r_snk->elem->card; card->drag_type = DRAG_TYPE_NONE; - card->dst_drag = NULL; + card->snk_drag = NULL; gtk_widget_queue_draw(card->drag_line); gtk_widget_queue_draw(card->routing_lines); } @@ -494,16 +547,16 @@ static gboolean src_drop_accept( struct routing_src *r_src = user_data; struct alsa_card *card = r_src->card; - return card->drag_type == DRAG_TYPE_DST; + return card->drag_type == DRAG_TYPE_SNK; } -static gboolean dst_drop_accept( +static gboolean snk_drop_accept( GtkDropTarget *source, GdkDrop *drop, gpointer user_data ) { - struct routing_dst *r_dst = user_data; - struct alsa_card *card = r_dst->elem->card; + struct routing_snk *r_snk = user_data; + struct alsa_card *card = r_snk->elem->card; return card->drag_type == DRAG_TYPE_SRC; } @@ -517,7 +570,7 @@ static GdkDragAction src_drop_enter( struct routing_src *r_src = user_data; struct alsa_card *card = r_src->card; - if (card->drag_type != DRAG_TYPE_DST) + if (card->drag_type != DRAG_TYPE_SNK) return 0; card->src_drag = r_src; @@ -525,19 +578,19 @@ static GdkDragAction src_drop_enter( return GDK_ACTION_COPY; } -static GdkDragAction dst_drop_enter( +static GdkDragAction snk_drop_enter( GtkDropTarget *dest, gdouble x, gdouble y, gpointer user_data ) { - struct routing_dst *r_dst = user_data; - struct alsa_card *card = r_dst->elem->card; + struct routing_snk *r_snk = user_data; + struct alsa_card *card = r_snk->elem->card; if (card->drag_type != DRAG_TYPE_SRC) return 0; - card->dst_drag = r_dst; + card->snk_drag = r_snk; return GDK_ACTION_COPY; } @@ -549,23 +602,23 @@ static void src_drop_leave( struct routing_src *r_src = user_data; struct alsa_card *card = r_src->card; - if (card->drag_type != DRAG_TYPE_DST) + if (card->drag_type != DRAG_TYPE_SNK) return; card->src_drag = NULL; } -static void dst_drop_leave( +static void snk_drop_leave( GtkDropTarget *dest, gpointer user_data ) { - struct routing_dst *r_dst = user_data; - struct alsa_card *card = r_dst->elem->card; + struct routing_snk *r_snk = user_data; + struct alsa_card *card = r_snk->elem->card; if (card->drag_type != DRAG_TYPE_SRC) return; - card->dst_drag = NULL; + card->snk_drag = NULL; } static void setup_src_drag(struct routing_src *r_src) { @@ -601,14 +654,13 @@ static void setup_src_drag(struct routing_src *r_src) { g_signal_connect(dest, "leave", G_CALLBACK(src_drop_leave), r_src); } -static void setup_dst_drag(struct routing_dst *r_dst) { - struct alsa_elem *elem = r_dst->elem; - GtkWidget *box = elem->widget; +static void setup_snk_drag(struct routing_snk *r_snk) { + GtkWidget *box = r_snk->box_widget; // handle drags on the box GtkDragSource *source = gtk_drag_source_new(); - g_signal_connect(source, "drag-begin", G_CALLBACK(dst_drag_begin), r_dst); - g_signal_connect(source, "drag-end", G_CALLBACK(dst_drag_end), r_dst); + g_signal_connect(source, "drag-begin", G_CALLBACK(snk_drag_begin), r_snk); + g_signal_connect(source, "drag-end", G_CALLBACK(snk_drag_end), r_snk); // set the box as a drag source gtk_drag_source_set_actions(source, GDK_ACTION_COPY); @@ -617,7 +669,7 @@ static void setup_dst_drag(struct routing_dst *r_dst) { // set the content // 0x8000 flag indicates alsa_elem numid value GdkContentProvider *content = gdk_content_provider_new_typed( - G_TYPE_INT, 0x8000 | r_dst->idx + G_TYPE_INT, 0x8000 | r_snk->idx ); gtk_drag_source_set_content(source, content); g_object_unref(content); @@ -630,10 +682,10 @@ static void setup_dst_drag(struct routing_dst *r_dst) { // set the box as a drop target GtkDropTarget *dest = gtk_drop_target_new(G_TYPE_INT, GDK_ACTION_COPY); gtk_widget_add_controller(box, GTK_EVENT_CONTROLLER(dest)); - g_signal_connect(dest, "drop", G_CALLBACK(dropped_on_dst), elem); - g_signal_connect(dest, "accept", G_CALLBACK(dst_drop_accept), r_dst); - g_signal_connect(dest, "enter", G_CALLBACK(dst_drop_enter), r_dst); - g_signal_connect(dest, "leave", G_CALLBACK(dst_drop_leave), r_dst); + g_signal_connect(dest, "drop", G_CALLBACK(dropped_on_snk), r_snk->elem); + g_signal_connect(dest, "accept", G_CALLBACK(snk_drop_accept), r_snk); + g_signal_connect(dest, "enter", G_CALLBACK(snk_drop_enter), r_snk); + g_signal_connect(dest, "leave", G_CALLBACK(snk_drop_leave), r_snk); } static void make_src_routing_widget( @@ -653,7 +705,7 @@ static void make_src_routing_widget( if (strlen(name) > 1 || !card->has_talkback) { GtkWidget *label = gtk_label_new(name); gtk_box_append(GTK_BOX(box), label); - gtk_widget_add_class(box, "route-label"); + gtk_widget_add_css_class(box, "route-label"); if (orientation == GTK_ORIENTATION_HORIZONTAL) { gtk_widget_set_halign(label, GTK_ALIGN_END); @@ -698,20 +750,20 @@ static GtkWidget *make_talkback_mix_widget( return make_boolean_alsa_elem(talkback_elem, name, name); } -static void make_dst_routing_widget( - struct routing_dst *r_dst, +static void make_snk_routing_widget( + struct routing_snk *r_snk, char *name, GtkOrientation orientation ) { - struct alsa_elem *elem = r_dst->elem; + struct alsa_elem *elem = r_snk->elem; // create a box, a "socket", and a label - GtkWidget *box = elem->widget = gtk_box_new(orientation, 5); - gtk_widget_add_class(box, "route-label"); + GtkWidget *box = r_snk->box_widget = gtk_box_new(orientation, 5); + gtk_widget_add_css_class(box, "route-label"); GtkWidget *label = gtk_label_new(name); gtk_box_append(GTK_BOX(box), label); - GtkWidget *socket = elem->widget2 = make_socket_widget(); + GtkWidget *socket = r_snk->socket_widget = make_socket_widget(); if (orientation == GTK_ORIENTATION_VERTICAL) { gtk_box_append(GTK_BOX(box), socket); gtk_widget_set_margin_start(box, 5); @@ -727,43 +779,56 @@ static void make_dst_routing_widget( // handle clicks on the box GtkGesture *gesture = gtk_gesture_click_new(); g_signal_connect( - gesture, "released", G_CALLBACK(dst_routing_clicked), elem + gesture, "released", G_CALLBACK(snk_routing_clicked), elem ); gtk_widget_add_controller( GTK_WIDGET(box), GTK_EVENT_CONTROLLER(gesture) ); // handle dragging to or from the box - setup_dst_drag(r_dst); + setup_snk_drag(r_snk); } -static void routing_updated(struct alsa_elem *elem) { +static void routing_updated(struct alsa_elem *elem, void *data) { struct alsa_card *card = elem->card; update_mixer_labels(card); gtk_widget_queue_draw(card->routing_lines); } -static void make_routing_alsa_elem(struct routing_dst *r_dst) { - struct alsa_elem *elem = r_dst->elem; +static void make_routing_alsa_elem(struct routing_snk *r_snk) { + struct alsa_elem *elem = r_snk->elem; struct alsa_card *card = elem->card; - // "Mixer Input X Capture Enum" controls (Mixer Inputs) go along + // "DSP Input X Capture Enum" controls (DSP Inputs) go along // the top, in card->routing_mixer_in_grid - if (r_dst->port_category == PC_MIX) { + if (r_snk->port_category == PC_DSP) { char name[10]; snprintf(name, 10, "%d", elem->lr_num); - make_dst_routing_widget(r_dst, name, GTK_ORIENTATION_VERTICAL); + make_snk_routing_widget(r_snk, name, GTK_ORIENTATION_VERTICAL); gtk_grid_attach( - GTK_GRID(card->routing_mixer_in_grid), elem->widget, - r_dst->port_num + 1, 0, 1, 1 + GTK_GRID(card->routing_dsp_in_grid), r_snk->box_widget, + r_snk->port_num + 1, 0, 1, 1 + ); + + // "Mixer Input X Capture Enum" controls (Mixer Inputs) go along + // the top, in card->routing_mixer_in_grid after the DSP Inputs + } else if (r_snk->port_category == PC_MIX) { + + char name[10]; + + snprintf(name, 10, "%d", elem->lr_num); + make_snk_routing_widget(r_snk, name, GTK_ORIENTATION_VERTICAL); + gtk_grid_attach( + GTK_GRID(card->routing_mixer_in_grid), r_snk->box_widget, + r_snk->port_num + 1, 0, 1, 1 ); // "PCM X Capture Enum" controls (PCM Inputs) go along the right, // in card->routing_pcm_out_grid - } else if (r_dst->port_category == PC_PCM) { + } else if (r_snk->port_category == PC_PCM) { char *name = strdup(elem->name); char *name_end = strchr(name, ' '); @@ -771,17 +836,17 @@ static void make_routing_alsa_elem(struct routing_dst *r_dst) { if (name_end) snprintf(name_end, strlen(name_end) + 1, " %d", elem->lr_num); - make_dst_routing_widget(r_dst, name, GTK_ORIENTATION_HORIZONTAL); + make_snk_routing_widget(r_snk, name, GTK_ORIENTATION_HORIZONTAL); free(name); gtk_grid_attach( - GTK_GRID(card->routing_pcm_out_grid), elem->widget, - 0, r_dst->port_num + 1, 1, 1 + GTK_GRID(card->routing_pcm_out_grid), r_snk->box_widget, + 0, r_snk->port_num + 1, 1, 1 ); // "* Output X Playback Enum" controls go along the right, in // card->routing_hw_out_grid - } else if (r_dst->port_category == PC_HW) { + } else if (r_snk->port_category == PC_HW) { // Convert "Analogue 01 Output Playback Enum" to "Analogue 1" char *name = strdup(elem->name); @@ -791,31 +856,31 @@ static void make_routing_alsa_elem(struct routing_dst *r_dst) { if (name_end) snprintf(name_end, strlen(name_end) + 1, " %d", elem->lr_num); - make_dst_routing_widget(r_dst, name, GTK_ORIENTATION_HORIZONTAL); + make_snk_routing_widget(r_snk, name, GTK_ORIENTATION_HORIZONTAL); free(name); gtk_grid_attach( - GTK_GRID(card->routing_hw_out_grid), elem->widget, - 0, r_dst->port_num + 1, 1, 1 + GTK_GRID(card->routing_hw_out_grid), r_snk->box_widget, + 0, r_snk->port_num + 1, 1, 1 ); } else { - printf("invalid port category %d\n", r_dst->port_category); + printf("invalid port category %d\n", r_snk->port_category); } - elem->widget_callback = routing_updated; + alsa_elem_add_callback(elem, routing_updated, NULL); } static void add_routing_widgets( struct alsa_card *card, GtkWidget *routing_overlay ) { - GArray *r_dsts = card->routing_dsts; + GArray *r_snks = card->routing_snks; - // go through each routing destination and create its control - for (int i = 0; i < r_dsts->len; i++) { - struct routing_dst *r_dst = &g_array_index(r_dsts, struct routing_dst, i); + // go through each routing sink and create its control + for (int i = 0; i < r_snks->len; i++) { + struct routing_snk *r_snk = &g_array_index(r_snks, struct routing_snk, i); - make_routing_alsa_elem(r_dst); + make_routing_alsa_elem(r_snk); } if (!card->routing_out_count[PC_MIX]) { @@ -823,20 +888,26 @@ static void add_routing_widgets( return; } - GtkWidget *l_mixer_in = gtk_label_new("Mixer\nInputs"); - gtk_label_set_justify(GTK_LABEL(l_mixer_in), GTK_JUSTIFY_CENTER); - gtk_grid_attach( - GTK_GRID(card->routing_mixer_in_grid), l_mixer_in, - 0, 0, 1, 1 - ); - // start at 1 to skip the "Off" input for (int i = 1; i < card->routing_srcs->len; i++) { struct routing_src *r_src = &g_array_index( card->routing_srcs, struct routing_src, i ); - if (r_src->port_category == PC_MIX) { + if (r_src->port_category == PC_DSP) { + // r_src->name is "DSP X" + // +4 to skip "DSP " + make_src_routing_widget( + card, r_src, r_src->name + 4, GTK_ORIENTATION_VERTICAL + ); + gtk_grid_attach( + GTK_GRID(card->routing_dsp_out_grid), r_src->widget, + r_src->port_num + 1, 0, 1, 1 + ); + + } else if (r_src->port_category == PC_MIX) { + // r_src->name is "Mix X" + // +4 to skip "Mix " make_src_routing_widget( card, r_src, r_src->name + 4, GTK_ORIENTATION_VERTICAL ); @@ -874,15 +945,6 @@ static void add_routing_widgets( } } - GtkWidget *l_mixer_out = gtk_label_new( - card->has_talkback ? "Mixer Outputs" : "Mixer\nOutputs" - ); - gtk_label_set_justify(GTK_LABEL(l_mixer_out), GTK_JUSTIFY_CENTER); - gtk_grid_attach( - GTK_GRID(card->routing_mixer_out_grid), l_mixer_out, - 0, 0, 1, 1 - ); - if (card->has_talkback) { GtkWidget *l_talkback = gtk_label_new("Talkback"); gtk_widget_set_tooltip_text( @@ -920,11 +982,18 @@ GtkWidget *create_routing_controls(struct alsa_card *card) { } get_routing_srcs(card); - get_routing_dsts(card); + get_routing_snks(card); create_routing_grid(card); + GtkWidget *top = gtk_frame_new(NULL); + gtk_widget_add_css_class(top, "window-frame"); + gtk_widget_add_css_class(top, "window-routing"); + GtkWidget *routing_overlay = gtk_overlay_new(); + gtk_widget_add_css_class(routing_overlay, "window-content"); + gtk_widget_add_css_class(routing_overlay, "window-routing"); + gtk_frame_set_child(GTK_FRAME(top), routing_overlay); gtk_overlay_set_child(GTK_OVERLAY(routing_overlay), card->routing_grid); @@ -932,5 +1001,5 @@ GtkWidget *create_routing_controls(struct alsa_card *card) { add_drop_controller_motion(card, routing_overlay); - return routing_overlay; + return top; } diff --git a/src/window-routing.h b/src/window-routing.h index 95856a6..16dda33 100644 --- a/src/window-routing.h +++ b/src/window-routing.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/window-startup.c b/src/window-startup.c index 69d4ab8..4cd4ea6 100644 --- a/src/window-startup.c +++ b/src/window-startup.c @@ -1,11 +1,17 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later +#include "device-reset-config.h" +#include "device-update-firmware.h" #include "gtkhelper.h" +#include "scarlett2.h" +#include "scarlett2-ioctls.h" #include "widget-boolean.h" #include "window-startup.h" -static GtkWidget *small_label(char *text) { +#define REQUIRED_HWDEP_VERSION_MAJOR 1 + +static GtkWidget *small_label(const char *text) { GtkWidget *w = gtk_label_new(NULL); char *s = g_strdup_printf("%s", text); @@ -17,15 +23,17 @@ static GtkWidget *small_label(char *text) { return w; } -static GtkWidget *big_label(char *text) { - GtkWidget *w = gtk_label_new(text); +static GtkWidget *big_label(const char *text) { + GtkWidget *view = gtk_text_view_new (); + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); - gtk_widget_set_halign(w, GTK_ALIGN_START); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), GTK_WRAP_WORD); + gtk_widget_set_size_request (view, 600, -1); + gtk_widget_set_sensitive (view, FALSE); - gtk_label_set_wrap(GTK_LABEL(w), true); - gtk_label_set_max_width_chars(GTK_LABEL(w), 60); + gtk_text_buffer_set_text (buffer, text, -1); - return w; + return view; } static void add_sep(GtkWidget *grid, int *grid_y) { @@ -33,7 +41,10 @@ static void add_sep(GtkWidget *grid, int *grid_y) { return; GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); - gtk_widget_set_margin(sep, 20); + gtk_widget_set_margin_top(sep, 10); + gtk_widget_set_margin_bottom(sep, 10); + gtk_widget_set_margin_start(sep, 20); + gtk_widget_set_margin_end(sep, 20); gtk_grid_attach(GTK_GRID(grid), sep, 0, (*grid_y)++, 3, 1); } @@ -55,6 +66,7 @@ static void add_standalone_control( gtk_grid_attach(GTK_GRID(grid), w, 0, *grid_y, 1, 1); w = make_boolean_alsa_elem(standalone, "Disabled", "Enabled"); + gtk_widget_set_valign(w, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID(grid), w, 0, *grid_y + 1, 1, 1); w = big_label( @@ -90,6 +102,7 @@ static void add_phantom_persistence_control( gtk_grid_attach(GTK_GRID(grid), w, 0, *grid_y, 1, 1); w = make_boolean_alsa_elem(phantom, "Disabled", "Enabled"); + gtk_widget_set_valign(w, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID(grid), w, 0, *grid_y + 1, 1, 1); w = big_label( @@ -104,7 +117,7 @@ static void add_phantom_persistence_control( *grid_y += 2; } -static void add_msd_control( +static int add_msd_control( GArray *elems, GtkWidget *grid, int *grid_y @@ -114,7 +127,7 @@ static void add_msd_control( ); if (!msd) - return; + return 0; add_sep(grid, grid_y); @@ -124,6 +137,7 @@ static void add_msd_control( gtk_grid_attach(GTK_GRID(grid), w, 0, *grid_y, 1, 1); w = make_boolean_alsa_elem(msd, "Disabled", "Enabled"); + gtk_widget_set_valign(w, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID(grid), w, 0, *grid_y + 1, 1, 1); w = big_label( @@ -138,6 +152,148 @@ static void add_msd_control( gtk_grid_attach(GTK_GRID(grid), w, 1, *grid_y, 1, 2); *grid_y += 2; + + return 1; +} + +static void add_reset_action( + struct alsa_card *card, + GtkWidget *grid, + int *grid_y, + const char *label, + const char *button_label, + const char *description, + GCallback callback +) { + add_sep(grid, grid_y); + + GtkWidget *w; + + w = small_label(label); + gtk_grid_attach(GTK_GRID(grid), w, 0, *grid_y, 1, 1); + + w = gtk_button_new_with_label(button_label); + gtk_grid_attach(GTK_GRID(grid), w, 0, *grid_y + 1, 1, 1); + g_signal_connect(w, "clicked", callback, card); + + w = big_label(description); + gtk_grid_attach(GTK_GRID(grid), w, 1, *grid_y, 1, 2); + + *grid_y += 2; +} + +static void reboot_device(GtkWidget *button, struct alsa_card *card) { + snd_hwdep_t *hwdep; + + int err = scarlett2_open_card(card->device, &hwdep); + if (err < 0) { + fprintf(stderr, "unable to open hwdep interface: %s\n", snd_strerror(err)); + return; + } + + err = scarlett2_reboot(hwdep); + if (err < 0) { + fprintf(stderr, "unable to reboot device: %s\n", snd_strerror(err)); + return; + } + + scarlett2_close(hwdep); +} + +static void add_reset_actions( + struct alsa_card *card, + GtkWidget *grid, + int *grid_y, + int has_msd +) { + // simulated cards don't support hwdep + if (!card->device) + return; + + snd_hwdep_t *hwdep; + + int err = scarlett2_open_card(card->device, &hwdep); + if (err < 0) { + fprintf(stderr, "unable to open hwdep interface: %s\n", snd_strerror(err)); + return; + } + + int ver = scarlett2_get_protocol_version(hwdep); + if (ver < 0) { + fprintf(stderr, "unable to get protocol version: %s\n", snd_strerror(ver)); + return; + } + + if (SCARLETT2_HWDEP_VERSION_MAJOR(ver) != REQUIRED_HWDEP_VERSION_MAJOR) { + fprintf( + stderr, + "Unsupported hwdep protocol version %d.%d.%d on card %s\n", + SCARLETT2_HWDEP_VERSION_MAJOR(ver), + SCARLETT2_HWDEP_VERSION_MINOR(ver), + SCARLETT2_HWDEP_VERSION_SUBMINOR(ver), + card->device + ); + return; + } + + scarlett2_close(hwdep); + + // Add reboot action if there is an MSD control + if (has_msd) { + add_reset_action( + card, + grid, + grid_y, + "Reboot Device", + "Reboot", + "After enabling or disabling MSD mode, the interface must be " + "rebooted for the change to take effect.", + G_CALLBACK(reboot_device) + ); + } + + // Reset Configuration + add_reset_action( + card, + grid, + grid_y, + "Reset Configuration", + "Reset", + "Resetting the configuration will reset the interface to its " + "factory default settings. The firmware will be left unchanged.", + G_CALLBACK(create_reset_config_window) + ); + + // Update Firmware + struct alsa_elem *firmware_elem = + get_elem_by_name(card->elems, "Firmware Version"); + + if (!firmware_elem) + return; + + int firmware_version = alsa_get_elem_value(firmware_elem); + + if (firmware_version >= card->best_firmware_version) + return; + + char *s = g_strdup_printf( + "Updating the firmware will reset the interface to its " + "factory default settings and update the firmware from version " + "%d to %d.", + firmware_version, + card->best_firmware_version + ); + add_reset_action( + card, + grid, + grid_y, + "Update Firmware", + "Update", + s, + G_CALLBACK(create_update_firmware_window) + ); + + g_free(s); } static void add_no_startup_controls_msg(GtkWidget *grid) { @@ -151,18 +307,26 @@ static void add_no_startup_controls_msg(GtkWidget *grid) { GtkWidget *create_startup_controls(struct alsa_card *card) { GArray *elems = card->elems; + GtkWidget *top = gtk_frame_new(NULL); + gtk_widget_add_css_class(top, "window-frame"); + int grid_y = 0; GtkWidget *grid = gtk_grid_new(); - gtk_widget_set_margin(grid, 20); + gtk_widget_add_css_class(grid, "window-content"); + gtk_widget_add_css_class(grid, "top-level-content"); + gtk_widget_add_css_class(grid, "window-startup"); gtk_grid_set_column_spacing(GTK_GRID(grid), 20); + gtk_grid_set_row_spacing(GTK_GRID(grid), 10); + gtk_frame_set_child(GTK_FRAME(top), grid); add_standalone_control(elems, grid, &grid_y); add_phantom_persistence_control(elems, grid, &grid_y); - add_msd_control(elems, grid, &grid_y); + int has_msd = add_msd_control(elems, grid, &grid_y); + add_reset_actions(card, grid, &grid_y, has_msd); if (!grid_y) add_no_startup_controls_msg(grid); - return grid; + return top; } diff --git a/src/window-startup.h b/src/window-startup.h index 6bc82ad..1c79951 100644 --- a/src/window-startup.h +++ b/src/window-startup.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett +// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/vu.b4.alsa-scarlett-gui.yml b/vu.b4.alsa-scarlett-gui.yml index 02a0ef4..11288be 100644 --- a/vu.b4.alsa-scarlett-gui.yml +++ b/vu.b4.alsa-scarlett-gui.yml @@ -3,6 +3,9 @@ runtime: org.gnome.Platform runtime-version: "45" sdk: org.gnome.Sdk command: alsa-scarlett-gui +build-options: + secret-env: + - APP_VERSION finish-args: # X11 + XShm access - --share=ipc