Merge in upstream version 0.4.0 to Debian pkg

This commit is contained in:
2025-06-09 20:59:04 -05:00
129 changed files with 17550 additions and 2576 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
liberapay: gdb
custom: 'https://www.paypal.me/gdbau'

54
.github/ISSUE_TEMPLATE/issue.md vendored Normal file
View File

@@ -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`!

View File

@@ -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

88
FAQ.md Normal file
View File

@@ -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, therell 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 its 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?
Its 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

View File

@@ -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 youll 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
```

View File

@@ -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 12: Mic/Line/Inst In 12 (Pad)
- Analogue In 34: Line In 34
- S/PDIF 12
- Hardware Input Controls
- 2× Input Gain knobs for Analogue In 12
- Fixed Input Gain for Analogue In 34
- Phantom Power for Analogue 12 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 12
- 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
- 12 control Line 12 Out and Headphone 1
- 34 control Line 34 Out and Headphone 2
- Mixer: 18 input (118), 10 output (AJ)
- 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 14, S/PDIF 12, Mix AJ, PCM Outputs 16
- Sync Status
- Clock Source: Internal or S/PDIF
## 18i8 Gen 2
- 18× Hardware Inputs
- Analogue 12: Mic/Line/Inst In 12 (Pad)
- Analogue 34: Mic/Line In 34 (Pad)
- Analogue 58: Line In 58
- S/PDIF 12
- ADAT 18
- Hardware Input Controls
- 4× Input Gain knobs for Analogue In 14
- Fixed Input Gain for Analogue In 58
- Phantom Power for Analogue 12 linked
- Phantom Power for Analogue 34 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 12
- 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
- 12 control Line 12 Out
- 34 control Headphone 1
- 56 control Headphone 2
- Mixer: 20 input (120), 10 output (AJ)
- 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 18, S/PDIF 12, ADAT 18, Mix AJ, PCM
Outputs 18
- Sync Status
- Clock Source: Internal, S/PDIF, or ADAT
## 18i20 Gen 2
- 18× Hardware Inputs
- Analogue 12: Mic/Line/Inst In 12 (Pad)
- Analogue 38: Mic/Line In 38
- S/PDIF 12
- ADAT 18
- Hardware Input Controls
- 8× Input Gain knobs for Analogue In 18
- Phantom Power for Analogue 14 linked
- Phantom Power for Analogue 58 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 12
- ADAT 18
- Hardware Output Controls
- For Analogue 18 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 18 (selected
by SW/HW Volume Control Switches)
- Global Mute and Dim controlling Analogue 18 (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 (120), 10 output (AJ)
- 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 18, S/PDIF 12, ADAT 18, Mix AJ, PCM
Outputs 18
- 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 12
- 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 12
- 2× PCM Outputs (USB host to device)
- Fixed to Analogue Outputs 12
- Direct Monitor:
- On: mixes Analogue 1+2 Inputs into both Analogue 1+2 Outputs
## 2i2 Gen 3
- 2× Hardware Inputs
- Analogue In 12: Mic/Line/Inst In 12 (Air)
- Hardware Input Controls
- 2× Input Gain knobs for Analogue 12
- Phantom Power for Analogue 12 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 12
- 2× PCM Outputs (USB host to device)
- Fixed to Analogue Outputs 12
- 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 12: Mic/Line/Inst In 12 (Air, Pad)
- Analogue In 34: Line In 34
- Hardware Input Controls
- 2× Input Gain knobs for Analogue In 12
- Fixed Input Gain for Analogue In 34
- Phantom Power for Analogue 12 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
- 12 control Line 12 Out
- 34 control Line 34 Out and Headphone
- Mixer: 8 input (18), 6 output (AF)
- 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 14, Mix AF, PCM Outputs 14
- Sync Status
## 8i6 Gen 3
- 8× Hardware Inputs
- Analogue 12: Mic/Line/Inst In 12 (Air, Pad)
- Analogue 36: Line In 36
- S/PDIF 12
- Hardware Input Controls
- 2× Input Gain knobs for Analogue In 12
- Fixed Input Gain for Analogue In 36
- Phantom Power for Analogue 12 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 12
- 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
- 12 control Line 12 Out and Headphone 1
- 34 control Line 34 Out and Headphone 2
- Mixer: 8 input (18), 8 output (AH)
- 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 16, S/PDIF 12, Mix AH, PCM Outputs 16
- Sync Status
- Clock Source: Internal or S/PDIF
## 18i8 Gen 3
- 18× Hardware Inputs
- Analogue 12: Mic/Line/Inst In 12 (Air, Pad)
- Analogue 34: Mic/Line In 34 (Air, Pad)
- Analogue 58: Line In 58
- S/PDIF 12
- ADAT 18
- Hardware Input Controls
- 4× Input Gain knobs for Analogue In 14
- Fixed Input Gain for Analogue In 58
- Phantom Power for Analogue 12 linked
- Phantom Power for Analogue 34 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 12
Note: The Headphones outputs are internally Analogue 36 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 18 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 18 (selected
by SW/HW Volume Control Switches)
- Global mute and dim controlling Analogue 18 (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 (118), 10 output (AJ)
- 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 18, S/PDIF 12, ADAT 18, Mix AJ, PCM
Outputs 120
- Speaker Switching
- Sync Status
- Clock Source: Internal, S/PDIF, or ADAT
## 18i20 Gen 3
- 19× Hardware Inputs
- Analogue 12: Mic/Line/Inst In 12 (Air, Pad)
- Analogue 38: Mic/Line In 38 (Air, Pad)
- Analogue 9: Talkback Mic
- S/PDIF 12
- ADAT 18
- Hardware Input Controls
- 8× Input Gain knobs for Analogue In 18
- Phantom Power for Analogue 14 linked
- Phantom Power for Analogue 58 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 12
- ADAT 18
- Hardware Output Controls
- For Analogue 18 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 18 (selected
by SW/HW Volume Control Switches)
- Global Mute and Dim controlling Analogue 18 (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 (125), 12 output (AL)
- 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 18, S/PDIF 12, ADAT 18, Mix AL, PCM
Outputs 18
- Speaker Switching
- Talkback Mic
- Sync Status
- Clock Source: Internal, S/PDIF, or ADAT

View File

@@ -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

381
USAGE.md
View File

@@ -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 isnt one
connected!) youll 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), theres just a few
buttons to control the Air, Line, Phantom Power, and Direct Monitor
settings. Mostly nothing that you cant 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 arent 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 arent 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 14 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 14 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 doesnt 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. Youll 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 cant 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 <fn>`
- Save: `alsactl store USB -f <fn>`
The saved state files can be used to simulate an interface if you
dont 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 dont 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 doesnt work well. Lower
volumes (e.g. below 30dB) dont need as much fine control as higher
volumes.
- Cant select (focus) the gain/volume controls or use a keyboard to
adjust them.
- Level meters dont 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 theres more than one main window open, closing one of them
doesnt 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.

View File

@@ -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
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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
}
}
}

1861
demo/Vocaster One.state Normal file

File diff suppressed because it is too large Load Diff

3428
demo/Vocaster Two.state Normal file

File diff suppressed because it is too large Load Diff

154
docs/INSTALL.md Normal file
View File

@@ -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, youll need to build an updated
`snd-usb-audio` driver (or wait for 6.10)
If youve got a Vocaster, or if your distribution doesnt 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 youll 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 dont 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
doesnt 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 youve already updated it using the
manufacturers 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
```

73
docs/OLDKERNEL.md Normal file
View File

@@ -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. Its recommended
that you run 6.7 or later, or build the backported driver for your
kernel. If you do, then these instructions arent relevant; continue
with [INSTALL.md](INSTALL.md) for prerequisites, how to build,
install, and run `alsa-scarlett-gui`.
If youve got a Scarlett Gen 2 or 3 or a Clarett+ 8Pre and dont 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 youre 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 youll 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
```

144
docs/USAGE.md Normal file
View File

@@ -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 isnt one
connected!) youll 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 havent updated it
using the manufacturers 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, youll 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, youll
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 youll 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 youve 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 <fn>`
- **Save**: `alsactl store USB -f <fn>`
The saved state files can be used to simulate an interface if you
dont 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 dont 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 theres more than one main window open, closing one of them
doesnt 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).

349
docs/iface-4th-gen.md Normal file
View File

@@ -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 dont 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 doesnt 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
signals 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 (thats what
ALSA sees as the first two inputs from the interface for
recording).
3. The PCM Outputs (thats 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, youll 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 cant control most of this routing,
so if you make changes here and then want to use Focusrite Control
2, youll probably need to reset the routing back to the factory
default settings. Theres currently no way to reset to factory
default settings from the Focusrite Control 2 software; youll 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 14 correspond to the Line Outputs 14 on the back of
the interface, and Analogue Outputs 56 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 14 are routed to the PCM Inputs 14 (the first
two going via the DSP).
- PCM Inputs 56 are used for Loopback (recording audio from your
computer).
- All the Hardware Inputs and PCM Outputs are connected to the Mixer
Inputs.
- PCM Outputs 14 are connected to the Analogue Outputs 14 (via the
mixer).
- The Line 12 Outputs (Analogue Outputs 12) and the Headphones
(Analogue Outputs 56) 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 12 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).

321
docs/iface-large.md Normal file
View File

@@ -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 arent 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 arent 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 14 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 14 Volume Control Switches are unlocked
- Line Out 3/4 routing is restored to the saved values
⭐ You likely wont 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 doesnt 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 (thats 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.

56
docs/iface-small.md Normal file
View File

@@ -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 cant 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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
img/firmware-missing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
img/firmware-updating.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
img/iface-4th-gen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 31 KiB

BIN
img/main-global.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
img/main-inputs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
img/main-outputs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -1,14 +1,19 @@
# SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
# SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
# 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

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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 <g@b4.vu>",
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
);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View File

@@ -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;
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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;

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View File

@@ -1,25 +1,37 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <sys/inotify.h>
#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);
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

87
src/device-reset-config.c Normal file
View File

@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <gtk/gtk.h>
#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
);
}

View File

@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <gtk/gtk.h>
#include "alsa.h"
void create_reset_config_window(GtkWidget *w, struct alsa_card *card);

View File

@@ -0,0 +1,140 @@
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <gtk/gtk.h>
#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
);
}

View File

@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <gtk/gtk.h>
#include "alsa.h"
void create_update_firmware_window(GtkWidget *w, struct alsa_card *card);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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);
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "alsa.h"

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <gtk/gtk.h>

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2021 Stiliyan Varbanov <https://www.fiverr.com/stilvar>
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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);
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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);

38
src/hardware.c Normal file
View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <stddef.h>
#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;
}

10
src/hardware.h Normal file
View File

@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett <g@b4.vu>
// 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);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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, &current_row);
create_input_controls_by_type(
elems, input_grid, &current_row,
"Link Capture Switch", create_input_link_control
);
create_input_controls_by_type(
elems, input_grid, &current_row,
"Gain Capture Volume", create_input_gain_control
);
create_input_controls_by_type(
elems, input_grid, &current_row,
"Autogain Capture Switch", create_input_autogain_control
);
create_input_controls_by_type(
elems, input_grid, &current_row,
"Autogain Status Capture Enum", create_input_autogain_status_control
);
create_input_controls_by_type(
elems, input_grid, &current_row,
"Safe Capture Switch", create_input_safe_control
);
create_input_controls_by_type(
elems, input_grid, &current_row,
"Level Capture Enum", create_input_level_control
);
create_input_controls_by_type(
elems, input_grid, &current_row,
"Air Capture Switch", create_input_air_switch_control
);
create_input_controls_by_type(
elems, input_grid, &current_row,
"Air Capture Enum", create_input_air_enum_control
);
create_input_controls_by_type(
elems, input_grid, &current_row,
"DSP Capture Switch", create_input_dsp_switch_control
);
create_input_controls_by_type(
elems, input_grid, &current_row,
"DSP Preset Capture Enum", create_input_dsp_preset_control
);
create_input_controls_by_type(
elems, input_grid, &current_row,
"Mute Capture Switch", create_input_mute_switch_control
);
create_input_controls_by_type(
elems, input_grid, &current_row,
"Pad Capture Switch", create_input_pad_control
);
create_input_controls_by_type(
elems, input_grid, &current_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 12" : "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);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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;
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View File

@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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;
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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 dont 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);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

59
src/iface-update.c Normal file
View File

@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <gtk/gtk.h>
#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"
"<a class=\"linktext\" "
"href=\"https://github.com/geoffreybennett/scarlett2-firmware\">"
"https://github.com/geoffreybennett/scarlett2-firmware</a>,\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;
}

8
src/iface-update.h Normal file
View File

@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "alsa.h"
GtkWidget *create_iface_update_main(struct alsa_card *card);

View File

@@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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", { "<Control>O", NULL } },
{ "_Save Configuration", "win.save", { "<Control>S", NULL } },
{ "_Interface Simulation", "win.sim", { "<Control>I", NULL } },
{ "E_xit", "app.quit", { "<Control>Q", NULL } },
{}
}
},
{
"_View",
(struct menu_item[]){
{ "_Routing", "win.routing", { "<Control>R", NULL } },
{ "_Mixer", "win.mixer", { "<Control>M", NULL } },
{ "_Levels", "win.levels", { "<Control>L", NULL } },
{ "_Startup", "win.startup", { "<Control>T", NULL } },
{}
}
},
{
"_Help",
(struct menu_item[]){
{ "_Supported Hardware", "app.hardware", { "<Control>H", NULL } },
{ "_About", "win.about", { "<Control>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
);
}
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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);
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View File

@@ -1,73 +1,55 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

289
src/scarlett2-firmware.c Normal file
View File

@@ -0,0 +1,289 @@
// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <openssl/sha.h>
#include <sys/stat.h>
#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);
}

46
src/scarlett2-firmware.h Normal file
View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <stdint.h>
// 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);

74
src/scarlett2-ioctls.c Normal file
View File

@@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: 2023 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <stdio.h>
#include <alsa/asoundlib.h>
#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;
}

26
src/scarlett2-ioctls.h Normal file
View File

@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2023 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef SCARLETT2_IOCTLS_H
#define SCARLETT2_IOCTLS_H
#include <alsa/hwdep.h>
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

54
src/scarlett2.h Normal file
View File

@@ -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 <g at b4.vu>
*/
#ifndef __UAPI_SOUND_SCARLETT2_H
#define __UAPI_SOUND_SCARLETT2_H
#include <linux/types.h>
#include <linux/ioctl.h>
#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 */

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <ctype.h>

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "tooltips.h"

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View File

@@ -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;

View File

@@ -1,34 +1,45 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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;
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

View File

@@ -1,35 +0,0 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// 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;
}

View File

@@ -1,10 +0,0 @@
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <gtk/gtk.h>
#include "alsa.h"
GtkWidget *make_combo_box_alsa_elem(struct alsa_elem *elem);

189
src/widget-drop-down.c Normal file
View File

@@ -0,0 +1,189 @@
// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <ctype.h>
#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;
}

13
src/widget-drop-down.h Normal file
View File

@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <gtk/gtk.h>
#include "alsa.h"
GtkWidget *make_drop_down_alsa_elem(
struct alsa_elem *elem,
const char *label_text
);

Some files were not shown because too many files have changed in this diff Show More