Merge in upstream version 0.4.0 to Debian pkg
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
liberapay: gdb
|
||||
custom: 'https://www.paypal.me/gdbau'
|
||||
54
.github/ISSUE_TEMPLATE/issue.md
vendored
Normal 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`!
|
||||
4
.github/workflows/build-debian-package.yml
vendored
@@ -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
@@ -0,0 +1,88 @@
|
||||
# FAQ for the Scarlett2 Mixer Driver and `alsa-scarlett-gui`
|
||||
|
||||
## What is this?
|
||||
|
||||
The Scarlett2 Protocol Driver (also known as the Scarlett2 Mixer
|
||||
Driver) is a part of the Linux kernel, enhancing the ALSA kernel
|
||||
driver with additional controls for Focusrite Scarlett, Clarett, and
|
||||
Vocaster interfaces.
|
||||
|
||||
To check if your kernel is already up-to-date, and how to upgrade if
|
||||
not, see the [Control Panel Installation Prerequisites — Linux
|
||||
Kernel](https://github.com/geoffreybennett/alsa-scarlett-gui/blob/master/INSTALL.md).
|
||||
|
||||
`alsa-scarlett-gui` is an easy-to-use application to adjust those
|
||||
controls.
|
||||
|
||||
## Do I need the driver for my Focusrite interface?
|
||||
|
||||
In order to get audio working? No. Focusrite USB interfaces are
|
||||
“plug-and-play” — they are USB Audio Class Compliant, meaning they
|
||||
work out-of-the-box with the standard ALSA USB audio driver (to get
|
||||
full functionality on Scarlett 3rd/4th Gen/Vocaster interfaces, first
|
||||
deactivate MSD mode by holding down the 48V button while powering it
|
||||
on).
|
||||
|
||||
## MSD Mode?
|
||||
|
||||
“MSD Mode” is the “Mass Storage Device Mode” that the Scarlett 3rd and
|
||||
4th Gen interfaces ship in.
|
||||
|
||||
If MSD Mode is enabled, you need to disable it and restart your
|
||||
interface to get access to its full functionality.
|
||||
|
||||
When you plug the interface in, there’ll be a tiny read-only virtual
|
||||
disk that has a link to the Focusrite product registration page; until
|
||||
you turn off MSD Mode not all features of the interface will be
|
||||
available.
|
||||
|
||||
You can turn off MSD Mode by holding down the 48V button while
|
||||
powering on the interface, or by clicking the button in
|
||||
`alsa-scarlett-gui` and rebooting it.
|
||||
|
||||
## What is the purpose of the driver if it’s not needed for audio?
|
||||
|
||||
This driver is for users who want more control over their interface.
|
||||
It allows for detailed manipulation of internal audio routing and
|
||||
settings specific to Scarlett, Clarett, and Vocaster devices, beyond
|
||||
the basic audio I/O functionality. Also, being able to monitor the
|
||||
audio levels seen by the interface is really useful.
|
||||
|
||||
## What interfaces are supported?
|
||||
|
||||
- All Scarlett 2nd Gen interfaces with software controls (there are no
|
||||
software controls on the 2nd Gen Solo and 2i2, so the mixer driver
|
||||
is irrelevant).
|
||||
|
||||
- All Scarlett 3rd Gen interfaces.
|
||||
|
||||
- Scarlett 4th Gen Solo, 2i2, and 4i4.
|
||||
|
||||
- All Clarett USB and Clarett+ interfaces.
|
||||
|
||||
- Vocaster One and Vocaster Two.
|
||||
|
||||
## Where are the options to set the sample rate and buffer size?
|
||||
|
||||
It’s important to note that the Scarlett2 driver and
|
||||
`alsa-scarlett-gui` have nothing to do with audio input/output to and
|
||||
from the device. This task is managed by the generic part of the ALSA
|
||||
USB soundcard driver.
|
||||
|
||||
Audio settings like the sample rate and buffer size are chosen by the
|
||||
application which is using the soundcard. In most cases, that is a
|
||||
sound server such as PulseAudio, JACK, or PipeWire.
|
||||
|
||||
Note that not all features are available at higher sample rates; refer
|
||||
to the user manual of your interface for more information.
|
||||
|
||||
## Help?!
|
||||
|
||||
For help with the driver:
|
||||
https://github.com/geoffreybennett/scarlett-gen2/issues
|
||||
|
||||
For help with `alsa-scarlett-gui`:
|
||||
https://github.com/geoffreybennett/alsa-scarlett-gui/issues
|
||||
|
||||
For general Linux audio help:
|
||||
https://linuxmusicians.com
|
||||
158
INSTALL.md
@@ -1,158 +0,0 @@
|
||||
# ALSA Scarlett Gen 2/3 Control Panel Installation
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Linux Kernel with the ALSA Scarlett2 Protocol Driver.
|
||||
|
||||
- Use at least version 5.14 for Scarlett Gen 3 support and bug fixes
|
||||
for the Gen 2 support.
|
||||
- For Clarett+ 8Pre support, you need 6.1.
|
||||
- For the other Clarett USB and Clarett+ models, you need 6.7.
|
||||
- For the level meters to work, you need 6.7.
|
||||
|
||||
If you don't have 6.7, you can get the driver from here and build it
|
||||
for your current kernel:
|
||||
|
||||
https://github.com/geoffreybennett/scarlett-gen2/releases/tag/v6.5.11c1
|
||||
|
||||
## Enabling the Driver
|
||||
|
||||
As of Linux 6.7 the driver is enabled by default and you can skip this
|
||||
section.
|
||||
|
||||
If you're running a kernel before 6.7, the driver needs to be enabled
|
||||
at module load time with the `device_setup=1` option to
|
||||
insmod/modprobe. Create a file /etc/modprobe.d/scarlett.conf
|
||||
containing the appropriate line for your device:
|
||||
|
||||
Scarlett Gen 2:
|
||||
|
||||
- 6i6: `options snd_usb_audio vid=0x1235 pid=0x8203 device_setup=1`
|
||||
- 18i8: `options snd_usb_audio vid=0x1235 pid=0x8204 device_setup=1`
|
||||
- 18i20: `options snd_usb_audio vid=0x1235 pid=0x8201 device_setup=1`
|
||||
|
||||
Scarlett Gen 3:
|
||||
|
||||
- Solo: `options snd_usb_audio vid=0x1235 pid=0x8211 device_setup=1`
|
||||
- 2i2: `options snd_usb_audio vid=0x1235 pid=0x8210 device_setup=1`
|
||||
- 4i4: `options snd_usb_audio vid=0x1235 pid=0x8212 device_setup=1`
|
||||
- 8i6: `options snd_usb_audio vid=0x1235 pid=0x8213 device_setup=1`
|
||||
- 18i8: `options snd_usb_audio vid=0x1235 pid=0x8214 device_setup=1`
|
||||
- 18i20: `options snd_usb_audio vid=0x1235 pid=0x8215 device_setup=1`
|
||||
|
||||
Clarett+:
|
||||
|
||||
- 8Pre: `options snd_usb_audio vid=0x1235 pid=0x820c device_setup=1`
|
||||
|
||||
Or you can use a sledgehammer:
|
||||
```
|
||||
options snd_usb_audio device_setup=1,1,1,1
|
||||
```
|
||||
to pass that option to the first 4 USB audio devices.
|
||||
|
||||
To see if the driver is present and enabled: `dmesg | grep -i -A 5 -B
|
||||
5 focusrite` should display information like:
|
||||
|
||||
```
|
||||
New USB device found, idVendor=1235, idProduct=8215, bcdDevice= 6.0b
|
||||
Product: Scarlett 18i20 USB
|
||||
Focusrite Scarlett Gen 2/3 Mixer Driver enabled pid=0x8215
|
||||
```
|
||||
|
||||
If the driver is disabled you’ll see a message like:
|
||||
|
||||
```
|
||||
Focusrite Scarlett Gen 2/3 Mixer Driver disabled; use options
|
||||
snd_usb_audio vid=0x1235 pid=0x8215 device_setup=1 to enable and
|
||||
report any issues to g@b4.vu",
|
||||
```
|
||||
|
||||
## Building and Running
|
||||
|
||||
On Fedora, the packages `alsa-lib-devel` and `gtk4-devel` need to be
|
||||
installed:
|
||||
|
||||
```
|
||||
sudo dnf -y install alsa-lib-devel gtk4-devel
|
||||
```
|
||||
|
||||
On Ubuntu 22.04:
|
||||
|
||||
```
|
||||
sudo apt -y install git make gcc libgtk-4-dev libasound2-dev
|
||||
```
|
||||
|
||||
To download from github:
|
||||
|
||||
```
|
||||
git clone https://github.com/geoffreybennett/alsa-scarlett-gui
|
||||
cd alsa-scarlett-gui
|
||||
```
|
||||
|
||||
To build:
|
||||
|
||||
```
|
||||
cd src
|
||||
make -j4
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```
|
||||
./alsa-scarlett-gui
|
||||
```
|
||||
|
||||
You can install it into `/usr/local` (binary, desktop file, and icon)
|
||||
with:
|
||||
|
||||
```
|
||||
sudo make install
|
||||
```
|
||||
|
||||
And uninstall with:
|
||||
|
||||
```
|
||||
sudo make uninstall
|
||||
```
|
||||
|
||||
Continue on to reading [USAGE.md](USAGE.md) for usage information and
|
||||
known issues.
|
||||
|
||||
## Flatpak
|
||||
|
||||
With Flatpak, in any distro:
|
||||
|
||||
```
|
||||
flatpak-builder --user --install --force-clean flatpak-build \
|
||||
vu.b4.alsa-scarlett-gui.yml
|
||||
```
|
||||
|
||||
Be sure to use `flatpak-build` as the directory where the flatpak is
|
||||
built or hence you risk bundling the artifacts when comitting!
|
||||
|
||||
If you get messages like these:
|
||||
|
||||
```
|
||||
Failed to init: Unable to find sdk org.gnome.Sdk version 45
|
||||
Failed to init: Unable to find runtime org.gnome.Platform version 45
|
||||
```
|
||||
|
||||
Then install them:
|
||||
|
||||
```
|
||||
flatpak install org.gnome.Sdk
|
||||
flatpak install org.gnome.Platform
|
||||
```
|
||||
|
||||
If you get:
|
||||
|
||||
```
|
||||
Looking for matches…
|
||||
error: No remote refs found for ‘org.gnome.Sdk’
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
```
|
||||
413
INTERFACES.md
@@ -1,413 +0,0 @@
|
||||
# Focusrite Scarlett Interface Features by Model
|
||||
|
||||
The information here was compiled carefully and is believed accurate
|
||||
but there might still be mistakes. Please independently confirm before
|
||||
relying on anything here.
|
||||
|
||||
## 6i6 Gen 2
|
||||
|
||||
- 6× Hardware Inputs
|
||||
- Analogue In 1–2: Mic/Line/Inst In 1–2 (Pad)
|
||||
- Analogue In 3–4: Line In 3–4
|
||||
- S/PDIF 1–2
|
||||
|
||||
- Hardware Input Controls
|
||||
- 2× Input Gain knobs for Analogue In 1–2
|
||||
- Fixed Input Gain for Analogue In 3–4
|
||||
- Phantom Power for Analogue 1–2 linked
|
||||
|
||||
- 6× Hardware Outputs
|
||||
- Analogue 1: Line 1 Out (Monitor L) + Headphone 1 Left
|
||||
- Analogue 2: Line 2 Out (Monitor R) + Headphone 1 Right
|
||||
- Analogue 3: Line 3 Out + Headphone 2 Left
|
||||
- Analogue 4: Line 4 Out + Headphone 2 Right
|
||||
- S/PDIF 1–2
|
||||
|
||||
- Hardware Output Controls
|
||||
- Monitor volume knob controlling Line 1+2
|
||||
- No physical control for Line 3+4 output volume
|
||||
- Headphone 1 volume knob controlling Headphone 1 Volume
|
||||
- Headphone 2 volume knob controlling Headphone 2 Volume
|
||||
- 4× Mute + Line Out Gain controls −127dB to 0dB
|
||||
- 1–2 control Line 1–2 Out and Headphone 1
|
||||
- 3–4 control Line 3–4 Out and Headphone 2
|
||||
|
||||
- Mixer: 18 input (1–18), 10 output (A–J)
|
||||
- 18×10 gain controls −80dB to +6dB
|
||||
- Each input assignable to any of the assignable outputs
|
||||
|
||||
- 6× PCM Inputs (USB device to host)
|
||||
|
||||
- 6× PCM Outputs (USB host to device)
|
||||
|
||||
- Assignable outputs to the hardware outputs, mixer, and PCM Inputs:
|
||||
- Off, Analogue Inputs 1–4, S/PDIF 1–2, Mix A–J, PCM Outputs 1–6
|
||||
|
||||
- Sync Status
|
||||
|
||||
- Clock Source: Internal or S/PDIF
|
||||
|
||||
## 18i8 Gen 2
|
||||
|
||||
- 18× Hardware Inputs
|
||||
- Analogue 1–2: Mic/Line/Inst In 1–2 (Pad)
|
||||
- Analogue 3–4: Mic/Line In 3–4 (Pad)
|
||||
- Analogue 5–8: Line In 5–8
|
||||
- S/PDIF 1–2
|
||||
- ADAT 1–8
|
||||
|
||||
- Hardware Input Controls
|
||||
- 4× Input Gain knobs for Analogue In 1–4
|
||||
- Fixed Input Gain for Analogue In 5–8
|
||||
- Phantom Power for Analogue 1–2 linked
|
||||
- Phantom Power for Analogue 3–4 linked
|
||||
|
||||
- 8× Hardware Outputs
|
||||
- Analogue 1: Line 1 Out (Monitor L)
|
||||
- Analogue 2: Line 2 Out (Monitor R)
|
||||
- Analogue 3: Headphone 1 Left
|
||||
- Analogue 4: Headphone 1 Right
|
||||
- Analogue 5: Headphone 2 Left
|
||||
- Analogue 6: Headphone 2 Right
|
||||
- S/PDIF 1–2
|
||||
|
||||
- Hardware Output Controls
|
||||
- Monitor volume knob controlling Line 1+2
|
||||
- Headphone 1 volume knob controlling Headphone 1 Volume
|
||||
- Headphone 2 volume knob controlling Headphone 2 Volume
|
||||
- 6× Mute + Line Out Gain controls −127dB to 0dB
|
||||
- 1–2 control Line 1–2 Out
|
||||
- 3–4 control Headphone 1
|
||||
- 5–6 control Headphone 2
|
||||
|
||||
- Mixer: 20 input (1–20), 10 output (A–J)
|
||||
- 20×10 gain controls −80dB to +6dB
|
||||
- Each input assignable to any of the assignable outputs
|
||||
|
||||
- 20× PCM Inputs (USB device to host)
|
||||
|
||||
- 8× PCM Outputs (USB host to device)
|
||||
|
||||
- Assignable outputs to the hardware outputs, mixer, and PCM Inputs:
|
||||
- Off, Analogue Inputs 1–8, S/PDIF 1–2, ADAT 1–8, Mix A–J, PCM
|
||||
Outputs 1–8
|
||||
|
||||
- Sync Status
|
||||
|
||||
- Clock Source: Internal, S/PDIF, or ADAT
|
||||
|
||||
## 18i20 Gen 2
|
||||
|
||||
- 18× Hardware Inputs
|
||||
- Analogue 1–2: Mic/Line/Inst In 1–2 (Pad)
|
||||
- Analogue 3–8: Mic/Line In 3–8
|
||||
- S/PDIF 1–2
|
||||
- ADAT 1–8
|
||||
|
||||
- Hardware Input Controls
|
||||
- 8× Input Gain knobs for Analogue In 1–8
|
||||
- Phantom Power for Analogue 1–4 linked
|
||||
- Phantom Power for Analogue 5–8 linked
|
||||
|
||||
- 20× Hardware Outputs
|
||||
- Analogue 1: Line 1 Out (Monitor L)
|
||||
- Analogue 2: Line 2 Out (Monitor R)
|
||||
- Analogue 3: Line 3 Out
|
||||
- Analogue 4: Line 4 Out
|
||||
- Analogue 5: Line 5 Out
|
||||
- Analogue 6: Line 6 Out
|
||||
- Analogue 7: Line 7 Out + Headphone 1 Left
|
||||
- Analogue 8: Line 8 Out + Headphone 1 Right
|
||||
- Analogue 9: Line 9 Out + Headphone 2 Left
|
||||
- Analogue 10: Line 10 Out + Headphone 2 Right
|
||||
- S/PDIF 1–2
|
||||
- ADAT 1–8
|
||||
|
||||
- Hardware Output Controls
|
||||
- For Analogue 1–8 Outputs:
|
||||
- 8× SW/HW Volume Control Switch
|
||||
- 8× SW Line Out Gain controls −127dB to 0dB
|
||||
- 8× SW Mute
|
||||
- Monitor volume knob controlling volume of Analogue 1–8 (selected
|
||||
by SW/HW Volume Control Switches)
|
||||
- Global Mute and Dim controlling Analogue 1–8 (enabled per-channel
|
||||
if SW/HW Volume Control Switch set to HW)
|
||||
- 2× Headphone volume knob controlling Headphone volume (applied in
|
||||
addition to the SW/HW Volume Control)
|
||||
|
||||
- Mixer: 20 input (1–20), 10 output (A–J)
|
||||
- 20×10 gain controls −80dB to +6dB
|
||||
- Each input assignable to any of the assignable outputs
|
||||
|
||||
- 20× PCM Inputs (USB device to host)
|
||||
|
||||
- 18× PCM Outputs (USB host to device)
|
||||
|
||||
- Assignable outputs to the hardware outputs, mixer, and PCM Inputs:
|
||||
- Off, Analogue Inputs 1–8, S/PDIF 1–2, ADAT 1–8, Mix A–J, PCM
|
||||
Outputs 1–8
|
||||
|
||||
- Sync Status
|
||||
|
||||
- Clock Source: Internal, S/PDIF, or ADAT
|
||||
|
||||
## Solo Gen 3
|
||||
|
||||
- 2× Hardware Inputs
|
||||
- Analogue In 1: Mic In (Air, Phantom Power)
|
||||
- Analogue In 2: Line/Inst In
|
||||
|
||||
- Hardware Input Controls
|
||||
- 2× Input Gain knobs for Analogue 1–2
|
||||
- Phantom Power Persistence
|
||||
|
||||
- 2× Hardware Outputs
|
||||
- Analogue Out 1: Line Out Left + Headphone Left
|
||||
- Analogue Out 2: Line Out Right + Headphone Right
|
||||
|
||||
- Hardware Output Controls
|
||||
- Monitor volume knob controls all outputs together
|
||||
|
||||
- 2× PCM Inputs (USB device to host)
|
||||
- Fixed to Analogue Inputs 1–2
|
||||
|
||||
- 2× PCM Outputs (USB host to device)
|
||||
- Fixed to Analogue Outputs 1–2
|
||||
|
||||
- Direct Monitor:
|
||||
- On: mixes Analogue 1+2 Inputs into both Analogue 1+2 Outputs
|
||||
|
||||
## 2i2 Gen 3
|
||||
|
||||
- 2× Hardware Inputs
|
||||
- Analogue In 1–2: Mic/Line/Inst In 1–2 (Air)
|
||||
|
||||
- Hardware Input Controls
|
||||
- 2× Input Gain knobs for Analogue 1–2
|
||||
- Phantom Power for Analogue 1–2 linked
|
||||
- Phantom Power Persistence
|
||||
|
||||
- 2× Hardware Outputs
|
||||
- Analogue Out 1: Line Out Left + Headphone Left
|
||||
- Analogue Out 2: Line Out Right + Headphone Right
|
||||
|
||||
- Hardware Output Controls
|
||||
- Monitor volume knob for Line Out Left and Right
|
||||
- Headphone volume knob for Headphone
|
||||
|
||||
- 2× PCM Inputs (USB device to host)
|
||||
- Fixed to Analogue Inputs 1–2
|
||||
|
||||
- 2× PCM Outputs (USB host to device)
|
||||
- Fixed to Analogue Outputs 1–2
|
||||
|
||||
- Direct Monitor:
|
||||
- Mono: mixes both Analogue 1+2 Inputs into both Analogue 1+2
|
||||
Outputs
|
||||
- Stereo: mixes Analogue 1+2 Inputs into Analogue 1+2 Outputs
|
||||
respectively
|
||||
|
||||
## 4i4 Gen 3
|
||||
|
||||
- 4× Hardware Inputs
|
||||
- Analogue In 1–2: Mic/Line/Inst In 1–2 (Air, Pad)
|
||||
- Analogue In 3–4: Line In 3–4
|
||||
|
||||
- Hardware Input Controls
|
||||
- 2× Input Gain knobs for Analogue In 1–2
|
||||
- Fixed Input Gain for Analogue In 3–4
|
||||
- Phantom Power for Analogue 1–2 linked
|
||||
- Phantom Power Persistence
|
||||
|
||||
- 4× Hardware Outputs
|
||||
- Analogue 1: Line 1 Out (Monitor L)
|
||||
- Analogue 2: Line 2 Out (Monitor R)
|
||||
- Analogue 3: Line 3 Out + Headphone Left
|
||||
- Analogue 4: Line 4 Out + Headphone Right
|
||||
|
||||
- Hardware Output Controls
|
||||
- Monitor volume knob controlling Line 1+2
|
||||
- Headphone volume knob controlling Headphone volume
|
||||
- No physical control for Line 3+4 output volume
|
||||
- 4× Mute + Line Out Gain controls −127dB to 0dB
|
||||
- 1–2 control Line 1–2 Out
|
||||
- 3–4 control Line 3–4 Out and Headphone
|
||||
|
||||
- Mixer: 8 input (1–8), 6 output (A–F)
|
||||
- 8×6 gain controls −80dB to +6dB
|
||||
- Each input assignable to any of the assignable outputs
|
||||
|
||||
- 6× PCM Inputs (USB device to host)
|
||||
|
||||
- 4× PCM Outputs (USB host to device)
|
||||
|
||||
- Assignable outputs to the hardware outputs, mixer, and PCM Inputs:
|
||||
- Off, Analogue Inputs 1–4, Mix A–F, PCM Outputs 1–4
|
||||
|
||||
- Sync Status
|
||||
|
||||
## 8i6 Gen 3
|
||||
|
||||
- 8× Hardware Inputs
|
||||
- Analogue 1–2: Mic/Line/Inst In 1–2 (Air, Pad)
|
||||
- Analogue 3–6: Line In 3–6
|
||||
- S/PDIF 1–2
|
||||
|
||||
- Hardware Input Controls
|
||||
- 2× Input Gain knobs for Analogue In 1–2
|
||||
- Fixed Input Gain for Analogue In 3–6
|
||||
- Phantom Power for Analogue 1–2 linked
|
||||
- Phantom Power Persistence
|
||||
|
||||
- 6× Hardware Outputs
|
||||
- Analogue 1: Line 1 Out (Monitor L) + Headphone 1 Left
|
||||
- Analogue 2: Line 2 Out (Monitor R) + Headphone 1 Right
|
||||
- Analogue 3: Line 3 Out + Headphone 2 Left
|
||||
- Analogue 4: Line 4 Out + Headphone 2 Right
|
||||
- S/PDIF 1–2
|
||||
|
||||
- Hardware Output Controls
|
||||
- Monitor volume knob controlling Line 1+2
|
||||
- No physical control for Line 3+4 output volume
|
||||
- Headphone 1 volume knob controlling Headphone 1 Volume
|
||||
- Headphone 2 volume knob controlling Headphone 2 Volume
|
||||
- 4× Line Out Gain controls −127dB to 0dB
|
||||
- 1–2 control Line 1–2 Out and Headphone 1
|
||||
- 3–4 control Line 3–4 Out and Headphone 2
|
||||
|
||||
- Mixer: 8 input (1–8), 8 output (A–H)
|
||||
- 8×8 gain controls −80dB to +6dB
|
||||
- Each input assignable to any of the assignable outputs
|
||||
|
||||
- 10× PCM Inputs (USB device to host)
|
||||
|
||||
- 6× PCM Outputs (USB host to device)
|
||||
|
||||
- Assignable outputs to the hardware outputs, mixer, and PCM Inputs:
|
||||
- Off, Analogue Inputs 1–6, S/PDIF 1–2, Mix A–H, PCM Outputs 1–6
|
||||
|
||||
- Sync Status
|
||||
|
||||
- Clock Source: Internal or S/PDIF
|
||||
|
||||
## 18i8 Gen 3
|
||||
|
||||
- 18× Hardware Inputs
|
||||
- Analogue 1–2: Mic/Line/Inst In 1–2 (Air, Pad)
|
||||
- Analogue 3–4: Mic/Line In 3–4 (Air, Pad)
|
||||
- Analogue 5–8: Line In 5–8
|
||||
- S/PDIF 1–2
|
||||
- ADAT 1–8
|
||||
|
||||
- Hardware Input Controls
|
||||
- 4× Input Gain knobs for Analogue In 1–4
|
||||
- Fixed Input Gain for Analogue In 5–8
|
||||
- Phantom Power for Analogue 1–2 linked
|
||||
- Phantom Power for Analogue 3–4 linked
|
||||
- Phantom Power Persistence
|
||||
|
||||
- 10× Hardware Outputs
|
||||
- Analogue 1: Line 1 Out (Monitor L)
|
||||
- Analogue 2: Line 2 Out (Monitor R)
|
||||
- Analogue 3: Line 3 Out (Alt Monitor L)
|
||||
- Analogue 4: Line 4 Out (Alt Monitor R)
|
||||
- Analogue 5: Headphone 1 Left
|
||||
- Analogue 6: Headphone 1 Right
|
||||
- Analogue 7: Headphone 2 Left
|
||||
- Analogue 8: Headphone 2 Right
|
||||
- S/PDIF 1–2
|
||||
|
||||
Note: The Headphones outputs are internally Analogue 3–6 and the rear
|
||||
Line 3/4 outputs (Alt Monitor) are internally Analogue 7/8, but the
|
||||
driver hides this from you.
|
||||
|
||||
- Hardware Output Controls
|
||||
- For Analogue 1–8 Outputs:
|
||||
- 8× SW/HW Volume Control Switch
|
||||
- 8× SW Line Out Gain controls −127dB to 0dB
|
||||
- 8× SW Mute
|
||||
- Monitor volume knob controlling volume of Analogue 1–8 (selected
|
||||
by SW/HW Volume Control Switches)
|
||||
- Global mute and dim controlling Analogue 1–8 (enabled per-channel
|
||||
if SW/HW Volume Control Switch set to HW)
|
||||
- 2× Headphone volume knob controlling Headphone volume (applied in
|
||||
addition to the SW/HW Volume Control)
|
||||
|
||||
- Mixer: 18 input (1–18), 10 output (A–J)
|
||||
- 18×10 gain controls −80dB to +6dB
|
||||
- Each input assignable to any of the assignable outputs
|
||||
|
||||
- 18× PCM Inputs (USB device to host)
|
||||
|
||||
- 8× PCM Outputs (USB host to device)
|
||||
|
||||
- Assignable outputs to the hardware outputs, mixer, and PCM Inputs:
|
||||
- Off, Analogue Inputs 1–8, S/PDIF 1–2, ADAT 1–8, Mix A–J, PCM
|
||||
Outputs 1–20
|
||||
|
||||
- Speaker Switching
|
||||
|
||||
- Sync Status
|
||||
|
||||
- Clock Source: Internal, S/PDIF, or ADAT
|
||||
|
||||
## 18i20 Gen 3
|
||||
|
||||
- 19× Hardware Inputs
|
||||
- Analogue 1–2: Mic/Line/Inst In 1–2 (Air, Pad)
|
||||
- Analogue 3–8: Mic/Line In 3–8 (Air, Pad)
|
||||
- Analogue 9: Talkback Mic
|
||||
- S/PDIF 1–2
|
||||
- ADAT 1–8
|
||||
|
||||
- Hardware Input Controls
|
||||
- 8× Input Gain knobs for Analogue In 1–8
|
||||
- Phantom Power for Analogue 1–4 linked
|
||||
- Phantom Power for Analogue 5–8 linked
|
||||
- Phantom Power Persistence
|
||||
|
||||
- 20× Hardware Outputs
|
||||
- Analogue 1: Line 1 Out (Monitor L)
|
||||
- Analogue 2: Line 2 Out (Monitor R)
|
||||
- Analogue 3: Line 3 Out
|
||||
- Analogue 4: Line 4 Out
|
||||
- Analogue 5: Line 5 Out
|
||||
- Analogue 6: Line 6 Out
|
||||
- Analogue 7: Line 7 Out + Headphone 1 Left
|
||||
- Analogue 8: Line 8 Out + Headphone 1 Right
|
||||
- Analogue 9: Line 9 Out + Headphone 2 Left
|
||||
- Analogue 10: Line 10 Out + Headphone 2 Right
|
||||
- S/PDIF 1–2
|
||||
- ADAT 1–8
|
||||
|
||||
- Hardware Output Controls
|
||||
- For Analogue 1–8 Outputs:
|
||||
- 8× SW/HW Volume Control Switch
|
||||
- 8× SW Line Out Gain controls −127dB to 0dB
|
||||
- 8× SW Mute
|
||||
- Monitor volume knob controlling volume of Analogue 1–8 (selected
|
||||
by SW/HW Volume Control Switches)
|
||||
- Global Mute and Dim controlling Analogue 1–8 (enabled per-channel
|
||||
if SW/HW Volume Control Switch set to HW)
|
||||
- 2× Headphone volume knob controlling Headphone volume (applied in
|
||||
addition to the SW/HW Volume Control)
|
||||
|
||||
- Mixer: 25 input (1–25), 12 output (A–L)
|
||||
- 25×12 gain controls −80dB to +6dB
|
||||
- Each input assignable to any of the assignable outputs
|
||||
|
||||
- 20× PCM Inputs (USB device to host)
|
||||
|
||||
- 20× PCM Outputs (USB host to device)
|
||||
|
||||
- Assignable outputs to the hardware outputs, mixer, and PCM Inputs:
|
||||
- Off, Analogue Inputs 1–8, S/PDIF 1–2, ADAT 1–8, Mix A–L, PCM
|
||||
Outputs 1–8
|
||||
|
||||
- Speaker Switching
|
||||
|
||||
- Talkback Mic
|
||||
|
||||
- Sync Status
|
||||
|
||||
- Clock Source: Internal, S/PDIF, or ADAT
|
||||
73
README.md
@@ -1,8 +1,15 @@
|
||||
# ALSA Scarlett Gen 2/3 Control Panel (`alsa-scarlett-gui`)
|
||||
# ALSA Scarlett2 Control Panel (`alsa-scarlett-gui`)
|
||||
|
||||
`alsa-scarlett-gui` is a Gtk4 GUI for the ALSA controls presented by
|
||||
the Linux kernel Focusrite Scarlett Gen 2/3/Clarett USB/Clarett+ Mixer
|
||||
Driver.
|
||||
the Linux kernel Focusrite Scarlett2 USB Protocol Mixer Driver.
|
||||
|
||||
Supported interfaces:
|
||||
- Scarlett 2nd Gen 6i6, 18i8, 18i20
|
||||
- Scarlett 3rd Gen Solo, 2i2, 4i4, 8i6, 18i8, 18i20
|
||||
- Scarlett 4th Gen Solo, 2i2, 4i4
|
||||
- Clarett 2Pre, 4Pre, 8Pre USB
|
||||
- Clarett+ 2Pre, 4Pre, 8Pre
|
||||
- Vocaster One and Vocaster Two
|
||||
|
||||
## About
|
||||
|
||||
@@ -10,71 +17,53 @@ Driver.
|
||||
|
||||
The Focusrite USB audio interfaces are class compliant meaning that
|
||||
they work “out of the box” on Linux as audio and MIDI interfaces
|
||||
(although on Gen 3/4 you need to disable MSD mode first for full
|
||||
functionality). However, except for some of the smallest models, they
|
||||
have a bunch of proprietary functionality that required a kernel
|
||||
(although on Gen 3/4/Vocaster you need to disable MSD mode first for
|
||||
full functionality). However, except for some of the smallest models,
|
||||
they have a bunch of proprietary functionality that required a kernel
|
||||
driver to be written specifically for those devices.
|
||||
|
||||
Linux kernel support (“ALSA Focusrite Scarlett Gen 2/3 Mixer Driver”)
|
||||
for the proprietary functionality was first added in:
|
||||
- Scarlett Gen 2: Linux 5.4 (bugs fixed in Linux 5.14)
|
||||
- Scarlett Gen 3: Linux 5.14
|
||||
- Clarett+ 8Pre: Linux 6.1
|
||||
- Clarett 2Pre/4Pre/8Pre USB, Clarett+ 2Pre/4Pre: Linux 6.7
|
||||
|
||||
Unfortunately, actually using this functionality used to be quite an
|
||||
awful experience. The existing applications like `alsamixer` and
|
||||
`qasmixer` become completely user-hostile with the hundreds of
|
||||
controls presented for the Gen 3 18i20. Even the smallest Gen 3 4i4
|
||||
interface at last count had 84 ALSA controls.
|
||||
|
||||
Announcing the ALSA Scarlett Gen 2/3 (and Clarett USB/Clarett+!)
|
||||
Control Panel!
|
||||
Announcing the ALSA Scarlett2 Control Panel, now supporting Scarlett
|
||||
Gen 2, 3, 4, Clarett, and Vocaster!
|
||||
|
||||

|
||||
|
||||
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
@@ -1,381 +0,0 @@
|
||||
# ALSA Scarlett Gen 2/3 Control Panel Usage
|
||||
|
||||
Refer to [INSTALL.md](INSTALL.md) for prerequisites, how to build,
|
||||
install, and run.
|
||||
|
||||
For usage instructions, read on...
|
||||
|
||||
## No interface connected
|
||||
|
||||
If no interface is detected (usually because there isn’t one
|
||||
connected!) you’ll see this window:
|
||||
|
||||

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

|
||||
|
||||
## Using on Small Interfaces
|
||||
|
||||
For the small Gen 3 interfaces (Solo and 2i2), there’s just a few
|
||||
buttons to control the Air, Line, Phantom Power, and Direct Monitor
|
||||
settings. Mostly nothing that you can’t access from the front panel
|
||||
anyway.
|
||||
|
||||

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

|
||||
|
||||
The View menu option on the main window lets you open three other
|
||||
windows which contain the other controls:
|
||||
- Routing
|
||||
- Mixer
|
||||
- Startup
|
||||
|
||||
### Global Controls
|
||||
|
||||
Global controls affect the operation of the interface as a whole.
|
||||
|
||||
#### Clock Source
|
||||
|
||||
Clock Source selects where the interface receives its digital clock
|
||||
from. If you aren’t using S/PDIF or ADAT inputs, set this to Internal.
|
||||
|
||||
#### Sync Status
|
||||
|
||||
Sync Status indicates if the interface is locked to a valid digital
|
||||
clock. If you aren’t using S/PDIF or ADAT inputs and the Sync Status
|
||||
is Unlocked, change the Clock Source to Internal.
|
||||
|
||||
#### Speaker Switching (18i8 Gen 3 and 18i20 Gen 3 only)
|
||||
|
||||
Speaker Switching lets you swap between two pairs of monitoring
|
||||
speakers very easily.
|
||||
|
||||
When enabled (Main or Alt):
|
||||
|
||||
- Line Out 1–4 Volume Control Switches are locked to HW
|
||||
- Line Out 3/4 routing is saved
|
||||
- Line Out 3/4 routing is set to the Line Out 1/2 routing
|
||||
|
||||
When set to Main, Line outputs 3 and 4 are muted.
|
||||
|
||||
When set to Alt, Line outputs 1 and 2 are muted.
|
||||
|
||||
When disabled (Off):
|
||||
- Global mute is activated
|
||||
- Line Out 1–4 Volume Control Switches are unlocked
|
||||
- Line Out 3/4 routing is restored to the saved values
|
||||
|
||||
#### Talkback (18i20 Gen 3 only)
|
||||
|
||||
Talkback lets you add another channel (usually the talkback mic) to a
|
||||
mix with a button push, usually to talk to musicians, and without
|
||||
using an additional mic channel.
|
||||
|
||||
The Talkback feature has a few parts:
|
||||
|
||||
- Talkback Microphone connected to Analogue Input 9
|
||||
- Talkback Disable/Enable internal switch
|
||||
- Talkback Off/On physical switch
|
||||
- Talkback Mix (one switch per mix)
|
||||
- Mix Input 25
|
||||
|
||||
To set up the talkback feature, set Mix Input 25 to the talkback
|
||||
source (usually Analogue Input 9), enable the Talkback Mix switches
|
||||
for the mixes you want the talkback input to be heard on, and change
|
||||
the Talkback control from Disabled to Off. Leave the Mix Input 25 gain
|
||||
controls at zero (−127dB), otherwise the talkback inputs will be heard
|
||||
even when talkback is disabled/off.
|
||||
|
||||
Pressing the Talkback switch on the device will then lower the volume
|
||||
of the other inputs on the mixes for which talkback is enabled and
|
||||
unmute Mix Input 25 on those mixes.
|
||||
|
||||
Talkback can also be activated by changing the Talkback control from
|
||||
Off to On.
|
||||
|
||||
The talkback microphone can also be used just the same as any of the
|
||||
other analogue inputs and routed to a physical output, PCM input, or
|
||||
mixer input.
|
||||
|
||||
### Analogue Input Controls
|
||||
|
||||
This is applicable to all interfaces except the Gen 2 18i20 which has
|
||||
hardware-only buttons for these features.
|
||||
|
||||
#### Level
|
||||
|
||||
The Level buttons are used to select between Mic/Line and Instrument
|
||||
level/impedance. When plugging in microphones or line-level equipment
|
||||
to the input, set it to “Line”. The “Inst” setting is for instrument
|
||||
with pickups such as guitars.
|
||||
|
||||
#### Air (Gen 3, Clarett USB, and Clarett+ only)
|
||||
|
||||
Enabling Air will transform your recordings and inspire you while
|
||||
making music.
|
||||
|
||||
#### Pad
|
||||
|
||||
Enabling Pad engages an attenuator in the channel, giving you more
|
||||
headroom for very hot signals.
|
||||
|
||||
#### Phantom Power (48V)
|
||||
|
||||
Gen 2 devices have a hardware button for controlling phantom power.
|
||||
|
||||
Gen 3 devices have hardware and software control of phantom power.
|
||||
Turning the “48V” switch on sends “Phantom Power” to the XLR
|
||||
microphone input. This is required for some microphones (such as
|
||||
condensor microphones), and damaging to some microphones (particularly
|
||||
vintage ribbon microphones).
|
||||
|
||||
On Gen 3 device, phantom power is turned off by default when the
|
||||
interface is turned on. This can be changed in the startup
|
||||
configuration (menu option View → Startup).
|
||||
|
||||
### Analogue Output Controls
|
||||
|
||||
The analogue output controls let you set the output volume (gain) on
|
||||
the analogue line out and headphone outputs. All interfaces support
|
||||
setting the gain and muting individual channels.
|
||||
|
||||
Click and drag up/down to change the volume, or use your mouse scroll
|
||||
wheel. You can also double-click on the volume dial to quickly toggle
|
||||
the volume between the minimum value and 0dB.
|
||||
|
||||
The bigger interfaces: Gen 2 18i20, Gen 3 18i8, and Gen 3 18i20 have a
|
||||
switchable hardware/software volume control. The position of the big
|
||||
volume knob on the front of the interface is indicated by the “HW”
|
||||
dial in the GUI. The analogue outputs can have their volume set either
|
||||
by the knob (“HW” setting of of the HW/SW button) or by the dials on
|
||||
each output (“SW” setting of the HW/SW button).
|
||||
|
||||
When set to HW, the mute/volume status for those channels is
|
||||
controlled by the hardware volume knob and the global dim/mute
|
||||
controls and the software volume dial and mute button for those
|
||||
channels are disabled.
|
||||
|
||||
There are “mute” and “dim” (reduce volume) buttons below the “HW” dial
|
||||
which affect only the outputs with “HW” control enabled. The Gen 3
|
||||
18i8 doesn’t have physical buttons or indicator lights for these
|
||||
controls, but the 18i20 devices do.
|
||||
|
||||
On the other (smaller) interfaces, the big volume knob on the front of
|
||||
the interface controls the volume of the Line 1 and 2 outputs. This is
|
||||
in addition to the software volume control, therefore both must be
|
||||
turned up in order to hear anything. The other (line 3+) analogue
|
||||
outputs are only controlled by the software controls.
|
||||
|
||||
The volume controls for the headphone outputs on each interface
|
||||
operate in addition to any other hardware or software volume controls
|
||||
for those channels. When using headphones, the volumes for those
|
||||
channels would usually be set to 0dB and the actual volume controlled
|
||||
with the physical headphone volume control(s).
|
||||
|
||||
### Routing
|
||||
|
||||
The routing window allows complete control of signal routing between
|
||||
the hardware inputs/outputs, internal mixer, and PCM (USB)
|
||||
inputs/outputs.
|
||||
|
||||

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

|
||||
|
||||
#### 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:
|
||||
|
||||

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

|
||||
|
||||
#### Standalone
|
||||
|
||||
When Standalone mode is enabled, the interface will continue to route
|
||||
audio as per the previous routing and mixer settings after it has been
|
||||
disconnected from a computer. By configuring the routing between the
|
||||
hardware and mixer inputs and outputs appropriately, the interface can
|
||||
act as a standalone preamp or mixer.
|
||||
|
||||
Standalone mode is supported on all devices supported by the kernel
|
||||
driver. Even the 4i4 Gen 3 (which is bus-powered) will operate in
|
||||
standalone mode.
|
||||
|
||||
#### Phantom Power Persistence (Gen 3 only)
|
||||
|
||||
When Phantom Power Persistence is enabled, the interface will restore
|
||||
the previous Phantom Power/48V setting when the interface is turned
|
||||
on. For the safety of microphones which can be damaged by phantom
|
||||
power, the interface defaults to having phantom power disabled when it
|
||||
is turned on.
|
||||
|
||||
#### MSD (Mass Storage Device) Mode (Gen 3 only)
|
||||
|
||||
When MSD Mode is enabled (as it is from the factory), the interface
|
||||
has reduced functionality. You’ll want to have this disabled. On the
|
||||
other hand, when MSD Mode is enabled, the interface presents itself as
|
||||
a Mass Storage Device (like a USB stick), containing a link to the
|
||||
Focusrite web site encouraging you to register your product and
|
||||
download the proprietary drivers which can’t be used on Linux.
|
||||
|
||||
By default, once MSD Mode is disabled, the control for it is hidden.
|
||||
If for some reason you want to re-enable MSD Mode, you can set the
|
||||
`device_setup` option to 3 to get the control back.
|
||||
|
||||
## Load/Save Configuration
|
||||
|
||||
The entire state of the interface can be loaded and saved using the
|
||||
File → Load Configuration and File → Save Configuration menu options.
|
||||
|
||||
Internally, this uses `alsactl`:
|
||||
|
||||
- Load: `alsactl restore USB -f <fn>`
|
||||
- Save: `alsactl store USB -f <fn>`
|
||||
|
||||
The saved state files can be used to simulate an interface if you
|
||||
don’t have one attached. The `demo` directory in the distribution
|
||||
contains a sample file for every supported model.
|
||||
|
||||
## Interface Simulation Mode
|
||||
|
||||
The GUI can load an `alsactl` state file saved from a real interface
|
||||
and display a GUI as if the corresponding interface was connected.
|
||||
|
||||
This is useful if you don’t have an interface connected and want to
|
||||
try, develop, or debug the GUI.
|
||||
|
||||
Either specify the `.state` filename on the command line or select the
|
||||
menu option File → Interface Simulation to load.
|
||||
|
||||
## Known Bugs/Issues
|
||||
|
||||
- The linear-dB scale of the volume controls doesn’t work well. Lower
|
||||
volumes (e.g. below −30dB) don’t need as much fine control as higher
|
||||
volumes.
|
||||
|
||||
- Can’t select (focus) the gain/volume controls or use a keyboard to
|
||||
adjust them.
|
||||
|
||||
- Level meters don’t work if you're not running the driver from Linux
|
||||
6.7.
|
||||
|
||||
- Load/Save uses `alsactl` which will be confused if the ALSA
|
||||
interface name (e.g. `USB`) changes.
|
||||
|
||||
- Load/Save is not implemented for simulated interfaces.
|
||||
|
||||
- Lots of “couldn't find weak ref” warnings are emitted when loading a
|
||||
state file for simulation.
|
||||
|
||||
- The read-only status of controls in interface simulation mode does
|
||||
not change when the HW/SW button is clicked.
|
||||
|
||||
- When there’s more than one main window open, closing one of them
|
||||
doesn’t free and close everything related to that card.
|
||||
|
||||
- There is no facility to group channels into stereo pairs (needs
|
||||
kernel support to save this information in the interface).
|
||||
|
||||
- There is no facility to give channels custom names (needs kernel
|
||||
support to save this information in the interface).
|
||||
|
||||
- No keyboard accelerators (e.g. Ctrl-Q to quit) have been
|
||||
implemented.
|
||||
997
demo/Scarlett Gen 4 2i2.state
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
3768
demo/Scarlett Gen 4 4i4.state
Normal file
885
demo/Scarlett Gen 4 Solo.state
Normal 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
3428
demo/Vocaster Two.state
Normal file
154
docs/INSTALL.md
Normal 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, you’ll need to build an updated
|
||||
`snd-usb-audio` driver (or wait for 6.10)
|
||||
|
||||
If you’ve got a Vocaster, or if your distribution doesn’t include a
|
||||
recent-enough kernel for your interface, you can get the latest driver
|
||||
from here and build it for your current kernel:
|
||||
|
||||
https://github.com/geoffreybennett/scarlett-gen2/releases
|
||||
|
||||
#### Enabling the Driver
|
||||
|
||||
As of Linux 6.7 the driver is enabled by default. Check the driver
|
||||
status (after plugging your interface in) with this command:
|
||||
|
||||
```
|
||||
dmesg | grep -i -A 5 -B 5 focusrite
|
||||
```
|
||||
|
||||
If all is good you’ll see messages like this:
|
||||
|
||||
```
|
||||
New USB device found, idVendor=1235, idProduct=8215, bcdDevice= 6.0b
|
||||
Product: Scarlett 18i20 USB
|
||||
Focusrite Scarlett Gen 3 Mixer Driver enabled (pid=0x8215); report
|
||||
any issues to https://github.com/geoffreybennett/scarlett-gen2/issues
|
||||
```
|
||||
|
||||
If you don’t see the “Mixer Driver” message or if it shows “disabled”
|
||||
then check the [OLDKERNEL.md](OLDKERNEL.md) instructions.
|
||||
|
||||
### Gtk4
|
||||
|
||||
You need a Linux distribution with Gtk4 development libraries. If it
|
||||
doesn’t have them natively, try the Flatpak instructions below.
|
||||
|
||||
### Firmware
|
||||
|
||||
As of Linux 6.8, firmware updates of all the supported interfaces can
|
||||
be done through Linux. This is mandatory for Scarlett 4th Gen and
|
||||
Vocaster interfaces (unless you’ve already updated it using the
|
||||
manufacturer’s software), and optional for Scarlett 2nd and 3rd Gen,
|
||||
Clarett USB, and Clarett+ interfaces.
|
||||
|
||||
Download the firmware from
|
||||
https://github.com/geoffreybennett/scarlett2-firmware and place it in
|
||||
`/usr/lib/firmware/scarlett2` or use the RPM/deb package.
|
||||
|
||||
## Building and Running
|
||||
|
||||
On Fedora, these packages need to be installed:
|
||||
|
||||
```
|
||||
sudo dnf -y install alsa-lib-devel gtk4-devel openssl-devel
|
||||
```
|
||||
|
||||
On OpenSUSE:
|
||||
|
||||
```
|
||||
sudo zypper in git alsa-devel gtk4-devel libopenssl-devel
|
||||
```
|
||||
|
||||
On Ubuntu:
|
||||
|
||||
```
|
||||
sudo apt -y install git make gcc libgtk-4-dev libasound2-dev libssl-dev
|
||||
```
|
||||
|
||||
To download from github:
|
||||
|
||||
```
|
||||
git clone https://github.com/geoffreybennett/alsa-scarlett-gui
|
||||
cd alsa-scarlett-gui
|
||||
```
|
||||
|
||||
To build:
|
||||
|
||||
```
|
||||
cd src
|
||||
make -j4
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```
|
||||
./alsa-scarlett-gui
|
||||
```
|
||||
|
||||
You can install it into `/usr/local` (binary, desktop file, and icon)
|
||||
with:
|
||||
|
||||
```
|
||||
sudo make install
|
||||
```
|
||||
|
||||
And uninstall with:
|
||||
|
||||
```
|
||||
sudo make uninstall
|
||||
```
|
||||
|
||||
Continue on to reading [USAGE.md](USAGE.md) for how to use the GUI.
|
||||
|
||||
## Flatpak
|
||||
|
||||
With Flatpak, in any distro:
|
||||
|
||||
```
|
||||
flatpak-builder --user --install --force-clean flatpak-build \
|
||||
vu.b4.alsa-scarlett-gui.yml
|
||||
```
|
||||
|
||||
Be sure to use `flatpak-build` as the directory where the flatpak is
|
||||
built or hence you risk bundling the artifacts when committing!
|
||||
|
||||
If you get messages like these:
|
||||
|
||||
```
|
||||
Failed to init: Unable to find sdk org.gnome.Sdk version 45
|
||||
Failed to init: Unable to find runtime org.gnome.Platform version 45
|
||||
```
|
||||
|
||||
Then install them:
|
||||
|
||||
```
|
||||
flatpak install org.gnome.Sdk
|
||||
flatpak install org.gnome.Platform
|
||||
```
|
||||
|
||||
If you get:
|
||||
|
||||
```
|
||||
Looking for matches…
|
||||
error: No remote refs found for ‘org.gnome.Sdk’
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
```
|
||||
73
docs/OLDKERNEL.md
Normal 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. It’s recommended
|
||||
that you run 6.7 or later, or build the backported driver for your
|
||||
kernel. If you do, then these instructions aren’t relevant; continue
|
||||
with [INSTALL.md](INSTALL.md) for prerequisites, how to build,
|
||||
install, and run `alsa-scarlett-gui`.
|
||||
|
||||
If you’ve got a Scarlett Gen 2 or 3 or a Clarett+ 8Pre and don’t mind
|
||||
the level meters not working, then the minimum kernel versions are:
|
||||
|
||||
- **Scarlett Gen 2**: Linux 5.4 (bugs fixed in Linux 5.14)
|
||||
- **Scarlett Gen 3**: Linux 5.14
|
||||
- **Clarett+ 8Pre**: Linux 6.1
|
||||
|
||||
## Linux Kernel with Backported Driver (recommended)
|
||||
|
||||
Install the latest version of the backported driver from here:
|
||||
|
||||
https://github.com/geoffreybennett/scarlett-gen2/releases
|
||||
|
||||
then you can ignore the instructions below.
|
||||
|
||||
## Linux Kernel before 6.7 without Backported Driver
|
||||
|
||||
If you’re running a kernel before 6.7 without the backported driver,
|
||||
you need to enable it at module load time with the `device_setup=1`
|
||||
option to insmod/modprobe. Create a file
|
||||
`/etc/modprobe.d/scarlett.conf` containing the appropriate line for
|
||||
your device:
|
||||
|
||||
Scarlett Gen 2:
|
||||
|
||||
- **6i6**: `options snd_usb_audio vid=0x1235 pid=0x8203 device_setup=1`
|
||||
- **18i8**: `options snd_usb_audio vid=0x1235 pid=0x8204 device_setup=1`
|
||||
- **18i20**: `options snd_usb_audio vid=0x1235 pid=0x8201 device_setup=1`
|
||||
|
||||
Scarlett Gen 3:
|
||||
|
||||
- **Solo**: `options snd_usb_audio vid=0x1235 pid=0x8211 device_setup=1`
|
||||
- **2i2**: `options snd_usb_audio vid=0x1235 pid=0x8210 device_setup=1`
|
||||
- **4i4**: `options snd_usb_audio vid=0x1235 pid=0x8212 device_setup=1`
|
||||
- **8i6**: `options snd_usb_audio vid=0x1235 pid=0x8213 device_setup=1`
|
||||
- **18i8**: `options snd_usb_audio vid=0x1235 pid=0x8214 device_setup=1`
|
||||
- **18i20**: `options snd_usb_audio vid=0x1235 pid=0x8215 device_setup=1`
|
||||
|
||||
Clarett+:
|
||||
|
||||
- **8Pre**: `options snd_usb_audio vid=0x1235 pid=0x820c device_setup=1`
|
||||
|
||||
Or you can use a sledgehammer:
|
||||
```
|
||||
options snd_usb_audio device_setup=1,1,1,1
|
||||
```
|
||||
to pass that option to the first 4 USB audio devices.
|
||||
|
||||
To see if the driver is present and enabled: `dmesg | grep -i -A 5 -B
|
||||
5 focusrite` should display information like:
|
||||
|
||||
```
|
||||
New USB device found, idVendor=1235, idProduct=8215, bcdDevice= 6.0b
|
||||
Product: Scarlett 18i20 USB
|
||||
Focusrite Scarlett Gen 2/3 Mixer Driver enabled pid=0x8215
|
||||
```
|
||||
|
||||
If the driver is disabled you’ll see a message like:
|
||||
|
||||
```
|
||||
Focusrite Scarlett Gen 2/3 Mixer Driver disabled; use options
|
||||
snd_usb_audio vid=0x1235 pid=0x8215 device_setup=1 to enable and
|
||||
report any issues to g@b4.vu
|
||||
```
|
||||
144
docs/USAGE.md
Normal 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 isn’t one
|
||||
connected!) you’ll see this window:
|
||||
|
||||

|
||||
|
||||
Plug in an interface or select the menu option File → Interface
|
||||
Simulation and load a demo file to make more interesting things
|
||||
happen.
|
||||
|
||||
## First Time Usage
|
||||
|
||||
If your interface is fresh out of the box (or you haven’t updated it
|
||||
using the manufacturer’s software), you may need to update the
|
||||
firmware and/or disable MSD Mode first.
|
||||
|
||||
### Firmware Update Required
|
||||
|
||||
Some interfaces require a firmware update before all their
|
||||
functionality is available. If the firmware is not available on your
|
||||
system, you’ll see this window:
|
||||
|
||||

|
||||
|
||||
In this case, click on the link, download and install the firmware
|
||||
package, then restart `alsa-scarlett-gui`.
|
||||
|
||||
If a firmware update is required and the firmware is available, you’ll
|
||||
see this window:
|
||||
|
||||

|
||||
|
||||
Click “Update”, then “Yes” to update the firmware.
|
||||
|
||||

|
||||
|
||||
The update will take about 15 seconds, and then your interface will
|
||||
restart, showing the main window.
|
||||
|
||||
### MSD (Mass Storage Device/Quick Start/Easy Start) Mode
|
||||
|
||||
If MSD Mode is enabled (as it is from the factory) and a firmware
|
||||
update is not available or required, then you’ll see this window:
|
||||
|
||||

|
||||
|
||||
Click the “Enabled” button to disable MSD Mode, then click “Reboot” to
|
||||
restart the interface, and in a moment the main window will appear.
|
||||
|
||||
## Startup Controls
|
||||
|
||||
The View → Startup menu option opens a window to configure settings
|
||||
that only take effect when the interface is powered on.
|
||||
|
||||
The options common to all interfaces are:
|
||||
|
||||
- **Reset Configuration**: this will reset the configuration to the
|
||||
factory defaults. This is particularly useful with the 4th Gen and
|
||||
Vocaster interfaces if you’ve made a mess of the configuration and
|
||||
want to start again.
|
||||
|
||||
- **Update Firmware**: if a firmware update is found in the
|
||||
`/usr/share/firmware/scarlett2` directory, then an option to update
|
||||
the firmware will be available here.
|
||||
|
||||
## File Menu
|
||||
|
||||
The File menu contains options to load and save the configuration,
|
||||
load a configuration in simulation mode, and to exit the application.
|
||||
|
||||
### Load/Save Configuration
|
||||
|
||||
The entire state of the interface can be loaded and saved using the
|
||||
File → Load Configuration and File → Save Configuration menu options.
|
||||
|
||||
Internally, this uses `alsactl`:
|
||||
|
||||
- **Load**: `alsactl restore USB -f <fn>`
|
||||
- **Save**: `alsactl store USB -f <fn>`
|
||||
|
||||
The saved state files can be used to simulate an interface if you
|
||||
don’t have one attached. The `demo` directory in the distribution
|
||||
contains a sample file for every supported model.
|
||||
|
||||
### Interface Simulation Mode
|
||||
|
||||
The GUI can load an `alsactl` state file saved from a real interface
|
||||
and display a GUI as if the corresponding interface was connected.
|
||||
|
||||
This is useful if you don’t have an interface connected and want to
|
||||
try, develop, or debug the GUI.
|
||||
|
||||
Either specify the `.state` filename on the command line or select the
|
||||
menu option File → Interface Simulation to load.
|
||||
|
||||
## Interface Controls
|
||||
|
||||
The controls and menu items which are available vary widely, depending
|
||||
on your specific interface.
|
||||
|
||||
There are three broad categories of interfaces with different
|
||||
capabilities; each category of interface is described in a separate
|
||||
ocument:
|
||||
|
||||
- [Scarlett 3rd Gen Solo and 2i2](iface-small.md)
|
||||
|
||||
Minimal number of controls, and they mostly accessible through
|
||||
hardware buttons anyway. Not very interesting.
|
||||
|
||||
- [Scarlett 2nd Gen 6i6+, 3rd Gen 4i4+, Clarett USB, and
|
||||
Clarett+](iface-large.md)
|
||||
|
||||
Full routing and mixing capabilities.
|
||||
|
||||
- [Scarlett 4th Gen](iface-4th-gen.md)
|
||||
|
||||
Full routing and mixing capabilities, remote-controlled input gain,
|
||||
but no output controls.
|
||||
|
||||
## Known Bugs/Issues
|
||||
|
||||
- Load/Save uses `alsactl` which will be confused if the ALSA
|
||||
interface name (e.g. `USB`) changes.
|
||||
|
||||
- Load/Save is not implemented for simulated interfaces.
|
||||
|
||||
- The read-only status of controls in interface simulation mode does
|
||||
not change when the HW/SW button is clicked.
|
||||
|
||||
- When there’s more than one main window open, closing one of them
|
||||
doesn’t free and close everything related to that card.
|
||||
|
||||
- There is no facility to group channels into stereo pairs (needs
|
||||
kernel support to save this information in the interface).
|
||||
|
||||
- There is no facility to give channels custom names (needs kernel
|
||||
support to save this information in the interface).
|
||||
349
docs/iface-4th-gen.md
Normal 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 don’t have the output volume and mute
|
||||
controls that the 2nd and 3rd Gen and Clarett interfaces have.
|
||||
|
||||
- The Air mode with Presence+Drive is implemented with a DSP which is
|
||||
separately routable.
|
||||
|
||||
## Main Window
|
||||
|
||||
The main window is divided into three sections:
|
||||
- Global Controls
|
||||
- Analogue Input Controls
|
||||
- Analogue Output Controls
|
||||
|
||||
The main window for the Solo and 2i2 interfaces is shown below; the
|
||||
4i4 interface is similar to the 2i2, but doesn’t have the Direct
|
||||
Monitor control, and can show the position of the front panel volume
|
||||
knobs.
|
||||
|
||||

|
||||
|
||||
### Global Controls
|
||||
|
||||
#### Sync Status
|
||||
|
||||
Sync Status indicates if the interface is locked to a valid digital
|
||||
clock. This should only ever briefly show “Unlocked” when the sample
|
||||
rate is changed as these interfaces can only use their internal clock.
|
||||
|
||||
#### Power
|
||||
|
||||
The 4i4 has a “Power” control that displays the power status. It can
|
||||
be “Fail”, “Bus”, or “External”. “Fail” means that the interface is
|
||||
not receiving sufficient power; please see the Scarlett 4i4 4th Gen
|
||||
User Guide for more information. “Bus” vs. “External” indicates
|
||||
whether the interface is receiving power from the second USB-C port
|
||||
(“External”) or not (“Bus”).
|
||||
|
||||
#### Sample Rate
|
||||
|
||||
Sample Rate is informative only, and displays the current sample rate
|
||||
if the interface is currently in use. In ALSA, the sample rate is set
|
||||
by the application using the interface, which is usually a sound
|
||||
server such as PulseAudio, JACK, or PipeWire.
|
||||
|
||||
### Analogue Input Controls
|
||||
|
||||
The analogue input controls available depend on the interface model:
|
||||
|
||||
- **Instrument, Air, and Phantom Power**: All models
|
||||
- **Mix**: Solo only (described later in the [Solo Mix
|
||||
Control](#solo-mix-control) section)
|
||||
- **Input Select, Link, Gain, Autogain, and Safe**: 2i2 and 4i4
|
||||
|
||||
#### Instrument
|
||||
|
||||
The Inst button(s) are used to select between Mic/Line and Instrument
|
||||
level/impedance. When plugging in microphones or line-level equipment
|
||||
(such as a synthesizer, external preamp, or effects processor) to the
|
||||
input, set it to “Line”. The “Inst” setting is for instruments with
|
||||
pickups such as guitars.
|
||||
|
||||
#### Air
|
||||
|
||||
The Scarlett 3rd Gen introduced Air mode which transformed your
|
||||
recordings and inspired you while making music by boosting the
|
||||
signal’s high-end. The 4th Gen interfaces now call that “Air Presence”
|
||||
and add a new mode “Air Presence+Drive” which boosts mid-range
|
||||
harmonics in your sound.
|
||||
|
||||
#### Phantom Power (48V)
|
||||
|
||||
Turning the “48V” switch on sends “Phantom Power” to the XLR
|
||||
microphone input. This is required for some microphones (such as
|
||||
condensor microphones), and damaging to some microphones (particularly
|
||||
vintage ribbon microphones).
|
||||
|
||||
The 2i2 has a single 48V switch that controls both channels, and the
|
||||
4i4 has an independent 48V switch for each channel.
|
||||
|
||||
#### Input Select
|
||||
|
||||
The 2i2 and 4i4 interfaces have hardware buttons for 48V, Inst, Air,
|
||||
Auto, and Safe. The “Input Select” control allows you to choose which
|
||||
channel those buttons control.
|
||||
|
||||
#### Link
|
||||
|
||||
The “Link” control links the 48V, Inst, Air, Auto, and Safe controls
|
||||
together so that they control both channels simultaneously.
|
||||
|
||||
#### Gain
|
||||
|
||||
The “Gain” controls adjust the input gain for the selected channel.
|
||||
Click and drag up/down on the control to adjust the gain, use your
|
||||
mouse scroll wheel, or click the control to select it and use the
|
||||
arrow keys, Page Up, Page Down, Home, and End keys.
|
||||
|
||||
#### Autogain
|
||||
|
||||
When the “Autogain” control is enabled, the interface will listen to
|
||||
the input signal for ten seconds and automatically adjust the gain to
|
||||
get the best signal level. When autogain is not running, the
|
||||
most-recent autogain exit status is shown below the “Autogain”
|
||||
control.
|
||||
|
||||
#### Safe
|
||||
|
||||
“Safe” mode is a feature that automatically reduces the gain if the
|
||||
signal is too loud. This can be useful to prevent clipping.
|
||||
|
||||
### Analogue Output Controls
|
||||
|
||||
The analogue output controls available depend on the interface model:
|
||||
|
||||
- **Direct Monitor**: Solo and 2i2
|
||||
- **Volume Knobs**: 4i4
|
||||
|
||||
#### Direct Monitor
|
||||
|
||||
Enabling Direct Monitor sends the analogue input signals to the
|
||||
analogue outputs (speakers/headphones) for zero-latency monitoring.
|
||||
|
||||
On the 2i2, you have the choice of Mono or Stereo monitoring when you
|
||||
click the button:
|
||||
|
||||
- **Mono** sends both inputs to the left and right outputs
|
||||
- **Stereo** sends input 1 to the left, and input 2 to the right
|
||||
output.
|
||||
|
||||
As the 4th Gen Solo and 2i2 interfaces have the full routing and
|
||||
mixing capabilities of the larger 2nd and 3rd Gen interfaces, the
|
||||
Direct Monitor levels can be [adjusted in the
|
||||
mixer](#solo-direct-monitor).
|
||||
|
||||
The 4i4 has no Direct Monitor button, but that functionality can be
|
||||
achieved with [appropriate configuration in the routing and mixing
|
||||
windows](#4i4-sample-direct-monitor-configuration).
|
||||
|
||||
#### Volume Knobs
|
||||
|
||||
The 4i4 interface has volume knobs on the front panel, the position of
|
||||
which is shown in the main window.
|
||||
|
||||
## Routing and Mixing
|
||||
|
||||
The routing and mixing capabilities of the 4th Gen interfaces are the
|
||||
same in concept as the 2nd and 3rd Gen interfaces, but there is a DSP
|
||||
which is separately routable, and the default routing uses the mixer
|
||||
extensively.
|
||||
|
||||
From the main window, open the Routing window with the View → Routing
|
||||
menu option or pressing Ctrl-R:
|
||||
|
||||

|
||||
|
||||
To understand the signal flow, note the following:
|
||||
1. The Analogue 1 & 2 Inputs (i.e. the Mic/Line/Inst inputs) are
|
||||
routed to the DSP Inputs.
|
||||
2. The DSP Outputs are routed to the PCM 1 & 2 Inputs (that’s what
|
||||
ALSA sees as the first two inputs from the interface for
|
||||
recording).
|
||||
3. The PCM Outputs (that’s what ALSA sees as the interface outputs for
|
||||
playback) and the DSP Outputs are all connected to the Mixer
|
||||
Inputs.
|
||||
4. The Mixer A & B Outputs are connected to the Hardware Analogue
|
||||
outputs (i.e. your speakers/headphones) so you can hear any mix of
|
||||
the PCM and DSP Outputs (this is how the Direct Monitor function
|
||||
works).
|
||||
5. The Mixer C & D Outputs are connected to the PCM 3 & 4 Inputs (this
|
||||
is referred to as Loopback, for recording audio from your computer,
|
||||
but can be used for another purpose if you want).
|
||||
|
||||
Important Notes:
|
||||
- The “Presets” are generally not useful with the 4th Gen interfaces
|
||||
as they are designed for the 2nd and 3rd Gen interfaces. If you try
|
||||
these out, you’ll probably want to reset back to the factory
|
||||
defaults afterwards.
|
||||
- Besides Air Mode, the DSP is also used for the gain halo level
|
||||
meters and autogain, so if you route something else to the DSP
|
||||
Inputs, those features will work “rather differently”.
|
||||
- The Focusrite Control 2 software can’t control most of this routing,
|
||||
so if you make changes here and then want to use Focusrite Control
|
||||
2, you’ll probably need to reset the routing back to the factory
|
||||
default settings. There’s currently no way to reset to factory
|
||||
default settings from the Focusrite Control 2 software; you’ll need
|
||||
to use the [Reset Configuration](USAGE.md#startup-controls) option
|
||||
in this software, or the `scarlett2` utility.
|
||||
|
||||
To adjust the routing:
|
||||
|
||||
- Click and drag from a source to a sink or a sink to a source to
|
||||
connect them. Audio from the source will then be sent to that sink.
|
||||
|
||||
- Click on a source or a sink to clear the links connected to that
|
||||
source/sink.
|
||||
|
||||
Note that a sink can only be connected to one source, but one source
|
||||
can be connected to many sinks.
|
||||
|
||||
To adjust the mixer output levels:
|
||||
|
||||
1) Open the mixer window with the main window View → Mixer menu
|
||||
option, or press Ctrl-M.
|
||||
|
||||
2) Mixer levels can be adjusted with your keyboard or mouse in the
|
||||
same way as the [Gain Controls](#gain).
|
||||
|
||||
### Solo Direct Monitor
|
||||
|
||||
When you enable or disable Direct Monitor on the Solo interface, the
|
||||
interface will update the Mix A and B Outputs so that the DSP 1 & 2
|
||||
Outputs are mixed in (or not) with the PCM 1 & 2 Outputs. Note how the
|
||||
volume of the PCM outputs is also reduced when Direct Monitor is
|
||||
enabled so that you can hear the DSP outputs (i.e. your Analogue
|
||||
inputs) more clearly.
|
||||
|
||||

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

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

|
||||
|
||||
By default, enabling this control will mix the Analogue 1 & 2 Inputs
|
||||
together before they are sent to the PCM 1 & 2 Inputs:
|
||||
|
||||

|
||||
|
||||
This can be useful if you want to treat the PCM 1 & 2 Inputs as a
|
||||
stereo pair, and not have the line/instrument input panned hard left
|
||||
and the microphone input panned hard right.
|
||||
|
||||
The mixer levels for the Mix E & F Outputs can adjusted to suit.
|
||||
|
||||
### 4i4 Routing and Mixing
|
||||
|
||||
Although the 4th Gen 4i4 has no explicit Direct Monitor control, it is
|
||||
far more flexible because it has 6 PCM inputs, 6 PCM outputs, a 10×6
|
||||
mixer, and 6 Analogue Hardware outputs.
|
||||
|
||||
Analogue Outputs 1–4 correspond to the Line Outputs 1–4 on the back of
|
||||
the interface, and Analogue Outputs 5–6 correspond to the Headphone
|
||||
Output on the front of the interface.
|
||||
|
||||
The default routing and mix for the 4i4 is shown below:
|
||||
|
||||

|
||||
|
||||
Note that with the default routing/mix settings:
|
||||
- The Analogue Inputs 1–4 are routed to the PCM Inputs 1–4 (the first
|
||||
two going via the DSP).
|
||||
- PCM Inputs 5–6 are used for Loopback (recording audio from your
|
||||
computer).
|
||||
- All the Hardware Inputs and PCM Outputs are connected to the Mixer
|
||||
Inputs.
|
||||
- PCM Outputs 1–4 are connected to the Analogue Outputs 1–4 (via the
|
||||
mixer).
|
||||
- The Line 1–2 Outputs (Analogue Outputs 1–2) and the Headphones
|
||||
(Analogue Outputs 5–6) share the Mixer Outputs A & B.
|
||||
|
||||
#### 4i4 Sample Direct Monitor Configuration
|
||||
|
||||
A common configuration for the 4i4 is to send the PCM 1 & 2 Outputs
|
||||
mixed with the Analogue Inputs 1 & 2 to the Headphones, while leaving
|
||||
the Analogue Outputs 1–2 as they are. This is an advanced version of
|
||||
the direct monitoring feature that is available on the Solo and 2i2.
|
||||
It can be implemented by:
|
||||
|
||||
1) Route Mixer Outputs E & F to Analogue Outputs 5 & 6.
|
||||
2) Turn up Mix E & F DSP 1 & 2 levels in the mixer (see the mixer
|
||||
example above for [2i2 Direct Monitor](#2i2-direct-monitor)).
|
||||
|
||||
As there are only 6 Mixer Outputs, the PCM 5 & 6 Inputs (Loopback) are
|
||||
now shared with the headphones. If you want to retain the Loopback
|
||||
functionality without having the Analogue Inputs mixed in, you could:
|
||||
- Route the PCM 1 & 2 Outputs directly to the PCM 5 & 6 Inputs, rather
|
||||
than going via the mixer, or
|
||||
- Free up Mixer Outputs A & B for Loopback by routing PCM Outputs 1 &
|
||||
2 directly to Analogue Outputs 1 & 2.
|
||||
|
||||
Besides Direct Monitor, there are many other possibilities for
|
||||
routing/mixing with the 4i4. For example, by using the additional PCM
|
||||
Outputs and Inputs you could set up a mix-minus configuration for a
|
||||
podcast/video call.
|
||||
|
||||
## Levels
|
||||
|
||||
The meters show the levels seen by the interface at every routing
|
||||
sink: Hardware Outputs, Mixer Inputs, DSP Inputs, and PCM Inputs. Open
|
||||
this window by selecting the View → Levels menu option or pressing
|
||||
Ctrl-L.
|
||||
|
||||

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

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

|
||||
|
||||
#### Clock Source (interfaces with S/PDIF or ADAT inputs only)
|
||||
|
||||
Clock Source selects where the interface receives its digital clock
|
||||
from. If you aren’t using S/PDIF or ADAT inputs, set this to Internal.
|
||||
|
||||
#### Sync Status
|
||||
|
||||
Sync Status indicates if the interface is locked to a valid digital
|
||||
clock. If you aren’t using S/PDIF or ADAT inputs and the Sync Status
|
||||
is Unlocked, change the Clock Source to Internal.
|
||||
|
||||
#### Sample Rate
|
||||
|
||||
Sample Rate is informative only, and displays the current sample rate
|
||||
if the interface is currently in use. In ALSA, the sample rate is set
|
||||
by the application using the interface, which is usually a sound
|
||||
server such as PulseAudio, JACK, or PipeWire.
|
||||
|
||||
#### Speaker Switching (Scarlett 3rd Gen 18i8 and 18i20 only)
|
||||
|
||||
Speaker Switching lets you swap between two pairs of monitoring
|
||||
speakers very easily.
|
||||
|
||||
When enabled (Main or Alt):
|
||||
|
||||
- Line Out 1–4 Volume Control Switches are locked to HW
|
||||
- Line Out 3/4 routing is saved
|
||||
- Line Out 3/4 routing is set to the Line Out 1/2 routing
|
||||
|
||||
When set to Main, Line outputs 3 and 4 are muted.
|
||||
|
||||
When set to Alt, Line outputs 1 and 2 are muted.
|
||||
|
||||
When disabled (Off):
|
||||
- Global mute is activated ⭐
|
||||
- Line Out 1–4 Volume Control Switches are unlocked
|
||||
- Line Out 3/4 routing is restored to the saved values
|
||||
|
||||
⭐ You likely won’t expect this to happen. Make sure to unmute the
|
||||
outputs after disabling speaker switching if you want to hear
|
||||
something again.
|
||||
|
||||
#### Talkback (Scarlett 3rd Gen 18i20 only)
|
||||
|
||||
Talkback lets you add another channel (usually the talkback mic) to a
|
||||
mix with a button push, usually to talk to musicians, and without
|
||||
using an additional mic channel.
|
||||
|
||||
The Talkback feature has a few parts:
|
||||
|
||||
- Talkback Microphone connected to Analogue Input 9
|
||||
- Talkback Disable/Enable and Off/On software switches
|
||||
- Talkback Off/On physical switch
|
||||
- Talkback Mix (one switch per mix)
|
||||
- Mix Input 25
|
||||
|
||||
To set up the talkback feature, set Mix Input 25 to the talkback
|
||||
source (usually Analogue Input 9), enable the Talkback Mix switches
|
||||
for the mixes you want the talkback input to be heard on, and change
|
||||
the Talkback control from Disabled to Off. Leave the Mix Input 25 gain
|
||||
controls at zero (−127dB), otherwise the talkback inputs will be heard
|
||||
even when talkback is disabled/off.
|
||||
|
||||
Pressing the Talkback switch on the device will then lower the volume
|
||||
of the other inputs on the mixes for which talkback is enabled and
|
||||
unmute Mix Input 25 on those mixes.
|
||||
|
||||
Talkback can also be activated by changing the Talkback control from
|
||||
Off to On.
|
||||
|
||||
The talkback microphone can also be used just the same as any of the
|
||||
other analogue inputs and routed to a physical output, PCM input, or
|
||||
mixer input.
|
||||
|
||||
### Analogue Input Controls
|
||||
|
||||
This section is applicable to all interfaces except the Scarlett 2nd
|
||||
Gen 18i20 which has hardware-only buttons for these features.
|
||||
|
||||

|
||||
|
||||
#### 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.
|
||||
|
||||

|
||||
|
||||
Click and drag up/down on the volume dial to change the volume, use
|
||||
your arrow keys, Home/End/PgUp/PgDn keys, or use your mouse scroll
|
||||
wheel to adjust. You can also double-click on it to quickly toggle the
|
||||
volume between off and 0dB.
|
||||
|
||||
The biggest interfaces: Scarlett 2nd Gen 18i20, 3rd Gen 18i8, and 3rd
|
||||
Gen 18i20 have a switchable hardware/software volume control. The
|
||||
position of the big volume knob on the front of the interface is
|
||||
indicated by the “HW” dial in the GUI. The analogue outputs can have
|
||||
their volume set either by the knob (“HW” setting of of the HW/SW
|
||||
button) or by the dials on each output (“SW” setting of the HW/SW
|
||||
button).
|
||||
|
||||
When set to HW, the mute/volume status for those channels is
|
||||
controlled by the hardware volume knob and the global dim/mute
|
||||
controls and the software volume dial and mute button for those
|
||||
channels are disabled.
|
||||
|
||||
There are “mute” and “dim” (reduce volume) buttons below the “HW” dial
|
||||
which affect only the outputs with “HW” control enabled. The 3rd Gen
|
||||
18i8 doesn’t have physical buttons or indicator lights for these
|
||||
controls, but the 18i20 devices do.
|
||||
|
||||
On the other (smaller) interfaces, the big volume knob on the front of
|
||||
the interface controls the volume of the Line 1 and 2 outputs. This is
|
||||
in addition to the software volume control, therefore both must be
|
||||
turned up in order to hear anything. The other (line 3+) analogue
|
||||
outputs are only controlled by the software controls.
|
||||
|
||||
The volume controls for the headphone outputs on each interface
|
||||
operate in addition to any other hardware or software volume controls
|
||||
for those channels. When using headphones, the volumes for those
|
||||
channels would usually be set to 0dB and the actual volume controlled
|
||||
with the physical headphone volume control(s).
|
||||
|
||||
## Routing
|
||||
|
||||
The routing window allows complete control of signal routing between
|
||||
the hardware inputs/outputs, internal mixer, and PCM (USB)
|
||||
inputs/outputs.
|
||||
|
||||

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

|
||||
|
||||
### Loopback
|
||||
|
||||
Scarlett 2nd Gen, Clarett USB, and Clarett+ interfaces have as many
|
||||
PCM Inputs as Hardware Inputs. Scarlett 3rd Gen interfaces have two
|
||||
more PCM Inputs which Focusrite Control uses as “Loopback” inputs.
|
||||
|
||||
The “Loopback” feature advertised for Scarlett 3rd Gen devices is
|
||||
actually a limitation of the proprietary Focusrite Control software.
|
||||
All supported devices with a mixer (that’s all but the 2nd and 3rd Gen
|
||||
Solo/2i2 interfaces) support full reassignment of the PCM Inputs, so
|
||||
you can have any PCM Input as a “Loopback” or assigned to any other
|
||||
source.
|
||||
|
||||
### Talkback
|
||||
|
||||
The Scarlett 3rd Gen 18i20 talkback microphone is Analogue Input 9 and
|
||||
can be routed like any other source. If you want to record using it,
|
||||
there is no need for the loopback hack suggested by the manufacturer.
|
||||
Just route it to a PCM Input.
|
||||
|
||||
## Mixer
|
||||
|
||||
If you use the Routing window to connect Sources to Mixer Inputs and
|
||||
Mixer Outputs to Sinks, then you can use the Mixer window to set the
|
||||
amount of each Mixer Input that is sent to each Mixer Output using a
|
||||
matrix of controls:
|
||||
|
||||

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

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

|
||||
|
||||
### 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
@@ -0,0 +1,56 @@
|
||||
# ALSA Scarlett2 Control Panel
|
||||
|
||||
## Small Scarlett 3rd Gen Interfaces
|
||||
|
||||
The Scarlett 3rd Gen Solo and 2i2 interfaces have just a few buttons to control
|
||||
the Air, Line, Phantom Power, and Direct Monitor settings. Mostly
|
||||
nothing that you can’t access from the front panel anyway.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||
BIN
img/demo.gif
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 1.1 MiB |
BIN
img/firmware-missing.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
img/firmware-update-required.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
img/firmware-updating.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
img/iface-4th-gen.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 31 KiB |
BIN
img/main-global.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
img/main-inputs.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
img/main-outputs.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 51 KiB |
BIN
img/scarlett-4th-gen-2i2-monitor.gif
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
img/scarlett-4th-gen-2i2-routing.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
img/scarlett-4th-gen-4i4-routing.png
Normal file
|
After Width: | Height: | Size: 372 KiB |
BIN
img/scarlett-4th-gen-solo-mix-e-f.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
img/scarlett-4th-gen-solo-mix.gif
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
img/scarlett-4th-gen-solo-monitor.gif
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
img/window-levels-3rd-gen.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
img/window-levels-4th-gen.gif
Normal file
|
After Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 63 KiB |
14
src/Makefile
@@ -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
|
||||
|
||||
14
src/about.c
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
322
src/alsa.c
@@ -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);
|
||||
}
|
||||
|
||||
105
src/alsa.h
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
);
|
||||
}
|
||||
9
src/device-reset-config.h
Normal 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);
|
||||
140
src/device-update-firmware.c
Normal 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
|
||||
);
|
||||
}
|
||||
9
src/device-update-firmware.h
Normal 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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
1702
src/gtkdial.c
138
src/gtkdial.h
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
@@ -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, ¤t_row);
|
||||
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Link Capture Switch", create_input_link_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Gain Capture Volume", create_input_gain_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Autogain Capture Switch", create_input_autogain_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Autogain Status Capture Enum", create_input_autogain_status_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Safe Capture Switch", create_input_safe_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Level Capture Enum", create_input_level_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Air Capture Switch", create_input_air_switch_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Air Capture Enum", create_input_air_enum_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"DSP Capture Switch", create_input_dsp_switch_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"DSP Preset Capture Enum", create_input_dsp_preset_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Mute Capture Switch", create_input_mute_switch_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Pad Capture Switch", create_input_pad_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Phantom Power Capture Switch", create_input_phantom_control
|
||||
);
|
||||
|
||||
(*x)++;
|
||||
}
|
||||
|
||||
@@ -247,24 +589,68 @@ static void create_output_controls(
|
||||
) {
|
||||
GArray *elems = card->elems;
|
||||
|
||||
if (*x) {
|
||||
GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
|
||||
gtk_grid_attach(GTK_GRID(top), sep, (*x)++, y, x_span, 3);
|
||||
}
|
||||
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
|
||||
|
||||
GtkWidget *label_oc = gtk_label_new("Analogue Outputs");
|
||||
gtk_grid_attach(GTK_GRID(top), label_oc, *x, y, x_span, 1);
|
||||
|
||||
GtkWidget *horiz_output_sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
|
||||
gtk_grid_attach(GTK_GRID(top), horiz_output_sep, *x, y + 1, x_span, 1);
|
||||
gtk_widget_add_css_class(label_oc, "controls-label");
|
||||
gtk_widget_set_halign(label_oc, GTK_ALIGN_START);
|
||||
gtk_box_append(GTK_BOX(box), label_oc);
|
||||
|
||||
GtkWidget *output_grid = gtk_grid_new();
|
||||
gtk_widget_add_css_class(output_grid, "controls-content");
|
||||
gtk_grid_set_spacing(GTK_GRID(output_grid), 10);
|
||||
gtk_grid_attach(GTK_GRID(top), output_grid, *x, y + 2, x_span, 1);
|
||||
gtk_widget_set_hexpand(output_grid, TRUE);
|
||||
gtk_widget_set_vexpand(output_grid, TRUE);
|
||||
gtk_box_append(GTK_BOX(box), output_grid);
|
||||
|
||||
gtk_grid_attach(GTK_GRID(top), box, *x, y, x_span, 1);
|
||||
|
||||
int output_count = get_max_elem_by_name(elems, "Line", "Playback Volume");
|
||||
|
||||
/* 4th Gen Solo/2i2 */
|
||||
if (get_elem_by_prefix(elems, "Direct Monitor Playback")) {
|
||||
struct alsa_elem *elem;
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
char s[20];
|
||||
snprintf(s, 20, "%d", i + 1);
|
||||
GtkWidget *label = gtk_label_new(s);
|
||||
gtk_grid_attach(GTK_GRID(output_grid), label, i, 0, 1, 1);
|
||||
}
|
||||
|
||||
/* Solo */
|
||||
|
||||
elem = get_elem_by_name(elems, "Direct Monitor Playback Switch");
|
||||
|
||||
if (elem) {
|
||||
GtkWidget *w = make_boolean_alsa_elem(elem, "Direct Monitor", NULL);
|
||||
gtk_widget_add_css_class(w, "direct-monitor");
|
||||
gtk_widget_set_tooltip_text(
|
||||
w,
|
||||
"Direct Monitor sends the analogue input signals to the "
|
||||
"analogue outputs for zero-latency monitoring."
|
||||
);
|
||||
gtk_grid_attach(GTK_GRID(output_grid), w, 0, 1, 2, 1);
|
||||
}
|
||||
|
||||
/* 2i2 */
|
||||
|
||||
elem = get_elem_by_name(elems, "Direct Monitor Playback Enum");
|
||||
|
||||
if (elem) {
|
||||
GtkWidget *w = make_drop_down_alsa_elem(elem, "Direct Monitor");
|
||||
gtk_widget_add_css_class(w, "direct-monitor");
|
||||
gtk_widget_set_tooltip_text(
|
||||
w,
|
||||
"Direct Monitor sends the analogue input signals to the "
|
||||
"analogue outputs for zero-latency monitoring."
|
||||
);
|
||||
gtk_grid_attach(GTK_GRID(output_grid), w, 0, 1, 2, 1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int has_hw_vol = !!get_elem_by_name(elems, "Master HW Playback Volume");
|
||||
int line_1_col = has_hw_vol;
|
||||
|
||||
@@ -288,7 +674,7 @@ static void create_output_controls(
|
||||
// output controls
|
||||
if (strncmp(elem->name, "Line", 4) == 0) {
|
||||
if (strstr(elem->name, "Playback Volume")) {
|
||||
w = make_volume_alsa_elem(elem);
|
||||
w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_LOG, 1);
|
||||
gtk_grid_attach(
|
||||
GTK_GRID(output_grid), w, line_num - 1 + line_1_col, 1, 1, 1
|
||||
);
|
||||
@@ -296,6 +682,7 @@ static void create_output_controls(
|
||||
w = make_boolean_alsa_elem(
|
||||
elem, "*audio-volume-high", "*audio-volume-muted"
|
||||
);
|
||||
gtk_widget_add_css_class(w, "mute");
|
||||
if (has_hw_vol) {
|
||||
gtk_widget_set_tooltip_text(
|
||||
w,
|
||||
@@ -309,6 +696,7 @@ static void create_output_controls(
|
||||
);
|
||||
} else if (strstr(elem->name, "Volume Control Playback Enum")) {
|
||||
w = make_boolean_alsa_elem(elem, "SW", "HW");
|
||||
gtk_widget_add_css_class(w, "sw-hw");
|
||||
gtk_widget_set_tooltip_text(
|
||||
w,
|
||||
"Set software-controlled (SW) or hardware-controlled (HW) "
|
||||
@@ -321,26 +709,47 @@ static void create_output_controls(
|
||||
|
||||
// master output controls
|
||||
} else if (strcmp(elem->name, "Master HW Playback Volume") == 0) {
|
||||
GtkWidget *l = gtk_label_new("HW");
|
||||
int gen4 = !!strstr(card->name, "4th Gen");
|
||||
|
||||
GtkWidget *l = gtk_label_new(gen4 ? "Line 1–2" : "HW");
|
||||
gtk_grid_attach(GTK_GRID(output_grid), l, 0, 0, 1, 1);
|
||||
if (gen4) {
|
||||
w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_GEN4_VOLUME, 0);
|
||||
} else {
|
||||
w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_LOG, 0);
|
||||
}
|
||||
gtk_widget_set_tooltip_text(
|
||||
w,
|
||||
gen4
|
||||
? "This control shows the setting of the master volume "
|
||||
"knob, which controls the volume of the analogue line "
|
||||
"outputs 1 and 2."
|
||||
: "This control shows the setting of the physical "
|
||||
"(hardware) volume knob, which controls the volume of "
|
||||
"the analogue outputs which have been set to “HW”."
|
||||
);
|
||||
gtk_grid_attach(GTK_GRID(output_grid), w, 0, 1, 1, 1);
|
||||
} else if (strcmp(elem->name, "Headphone Playback Volume") == 0) {
|
||||
GtkWidget *l = gtk_label_new("Headphones");
|
||||
gtk_widget_set_tooltip_text(
|
||||
l,
|
||||
"This control shows the setting of the physical (hardware) "
|
||||
"volume knob, which controls the volume of the analogue "
|
||||
"outputs which have been set to “HW”."
|
||||
"This control shows the setting of the headphone volume knob."
|
||||
);
|
||||
gtk_grid_attach(GTK_GRID(output_grid), l, 0, 0, 1, 1);
|
||||
w = make_volume_alsa_elem(elem);
|
||||
gtk_grid_attach(GTK_GRID(output_grid), w, 0, 1, 1, 1);
|
||||
gtk_grid_attach(GTK_GRID(output_grid), l, 1, 0, 1, 1);
|
||||
w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_GEN4_VOLUME, 0);
|
||||
gtk_grid_attach(GTK_GRID(output_grid), w, 1, 1, 1, 1);
|
||||
} else if (strcmp(elem->name, "Mute Playback Switch") == 0) {
|
||||
w = make_boolean_alsa_elem(
|
||||
elem, "*audio-volume-high", "*audio-volume-muted"
|
||||
);
|
||||
gtk_widget_add_css_class(w, "mute");
|
||||
gtk_widget_set_tooltip_text(w, "Mute HW controlled outputs");
|
||||
gtk_grid_attach(GTK_GRID(output_grid), elem->widget, 0, 2, 1, 1);
|
||||
gtk_grid_attach(GTK_GRID(output_grid), w, 0, 2, 1, 1);
|
||||
} else if (strcmp(elem->name, "Dim Playback Switch") == 0) {
|
||||
w = make_boolean_alsa_elem(
|
||||
elem, "*audio-volume-medium", "*audio-volume-low"
|
||||
);
|
||||
gtk_widget_add_css_class(w, "dim");
|
||||
gtk_widget_set_tooltip_text(
|
||||
w, "Dim (lower volume) of HW controlled outputs"
|
||||
);
|
||||
@@ -360,37 +769,59 @@ static void create_global_controls(
|
||||
? GTK_ORIENTATION_HORIZONTAL
|
||||
: GTK_ORIENTATION_VERTICAL;
|
||||
GtkWidget *global_controls = create_global_box(top, x, orient);
|
||||
GtkWidget *left = global_controls;
|
||||
GtkWidget *right = global_controls;
|
||||
GtkWidget *column[3];
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
column[i] = global_controls;
|
||||
|
||||
if (card->has_speaker_switching) {
|
||||
left = gtk_box_new(GTK_ORIENTATION_VERTICAL, 15);
|
||||
right = gtk_box_new(GTK_ORIENTATION_VERTICAL, 15);
|
||||
gtk_box_append(GTK_BOX(global_controls), left);
|
||||
gtk_box_append(GTK_BOX(global_controls), right);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
column[i] = gtk_box_new(GTK_ORIENTATION_VERTICAL, 15);
|
||||
gtk_box_append(GTK_BOX(global_controls), column[i]);
|
||||
}
|
||||
}
|
||||
|
||||
add_clock_source_control(card, left);
|
||||
add_sync_status_control(card, right);
|
||||
add_speaker_switching_controls(card, left);
|
||||
add_talkback_controls(card, right);
|
||||
add_clock_source_control(card, column[0]);
|
||||
add_sync_status_control(card, column[1]);
|
||||
add_power_status_control(card, column[1]);
|
||||
add_sample_rate_control(card, column[2]);
|
||||
add_speaker_switching_controls(card, column[0]);
|
||||
add_talkback_controls(card, column[1]);
|
||||
}
|
||||
|
||||
static GtkWidget *create_main_window_controls(struct alsa_card *card) {
|
||||
int x = 0;
|
||||
|
||||
GtkWidget *top = gtk_grid_new();
|
||||
gtk_widget_set_margin(top, 10);
|
||||
gtk_grid_set_spacing(GTK_GRID(top), 10);
|
||||
gtk_widget_add_css_class(top, "window-content");
|
||||
gtk_widget_add_css_class(top, "iface-mixer");
|
||||
|
||||
if (strstr(card->name, "4th Gen") ||
|
||||
strstr(card->name, "Gen 4")) {
|
||||
gtk_widget_add_css_class(top, "gen4");
|
||||
} else if (strstr(card->name, "Scarlett")) {
|
||||
gtk_widget_add_css_class(top, "scarlett");
|
||||
} else if (strstr(card->name, "Clarett")) {
|
||||
gtk_widget_add_css_class(top, "clarett");
|
||||
} else if (strstr(card->name, "Vocaster")) {
|
||||
gtk_widget_add_css_class(top, "vocaster");
|
||||
}
|
||||
|
||||
gtk_grid_set_spacing(GTK_GRID(top), 15);
|
||||
|
||||
int input_count = get_max_elem_by_name(
|
||||
card->elems, "Line", "Capture Switch"
|
||||
);
|
||||
int output_count = get_max_elem_by_name(
|
||||
card->elems, "Line", "Playback Volume"
|
||||
);
|
||||
|
||||
create_global_controls(card, top, &x);
|
||||
create_input_controls(card, top, &x);
|
||||
if (card->has_speaker_switching) {
|
||||
x = 0;
|
||||
GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
|
||||
gtk_grid_attach(GTK_GRID(top), sep, 0, 3, 3, 1);
|
||||
|
||||
create_output_controls(card, top, &x, 4, 3);
|
||||
if (input_count + output_count >= 12) {
|
||||
x = 0;
|
||||
create_output_controls(card, top, &x, 1, 2);
|
||||
} else {
|
||||
create_output_controls(card, top, &x, 0, 1);
|
||||
}
|
||||
@@ -425,13 +856,38 @@ static gboolean window_levels_close_request(GtkWindow *w, gpointer data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// wrap a scrolled window around the controls
|
||||
static void create_scrollable_window(GtkWidget *window, GtkWidget *controls) {
|
||||
GtkWidget *scrolled_window = gtk_scrolled_window_new();
|
||||
|
||||
gtk_scrolled_window_set_policy(
|
||||
GTK_SCROLLED_WINDOW(scrolled_window),
|
||||
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC
|
||||
);
|
||||
gtk_scrolled_window_set_child(
|
||||
GTK_SCROLLED_WINDOW(scrolled_window), controls
|
||||
);
|
||||
gtk_scrolled_window_set_propagate_natural_height(
|
||||
GTK_SCROLLED_WINDOW(scrolled_window), TRUE
|
||||
);
|
||||
gtk_scrolled_window_set_propagate_natural_width(
|
||||
GTK_SCROLLED_WINDOW(scrolled_window), TRUE
|
||||
);
|
||||
|
||||
gtk_window_set_child(GTK_WINDOW(window), scrolled_window);
|
||||
gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
|
||||
}
|
||||
|
||||
GtkWidget *create_iface_mixer_main(struct alsa_card *card) {
|
||||
card->has_speaker_switching =
|
||||
!!get_elem_by_name(card->elems, "Speaker Switching Playback Enum");
|
||||
card->has_talkback =
|
||||
!!get_elem_by_name(card->elems, "Talkback Playback Enum");
|
||||
|
||||
GtkWidget *top = create_main_window_controls(card);
|
||||
GtkWidget *top = gtk_frame_new(NULL);
|
||||
gtk_widget_add_css_class(top, "window-frame");
|
||||
GtkWidget *contents = create_main_window_controls(card);
|
||||
gtk_frame_set_child(GTK_FRAME(top), contents);
|
||||
|
||||
GtkWidget *routing_top = create_routing_controls(card);
|
||||
if (!routing_top)
|
||||
@@ -441,7 +897,7 @@ GtkWidget *create_iface_mixer_main(struct alsa_card *card) {
|
||||
card, "Routing", G_CALLBACK(window_routing_close_request)
|
||||
);
|
||||
|
||||
gtk_window_set_child(GTK_WINDOW(card->window_routing), routing_top);
|
||||
create_scrollable_window(card->window_routing, routing_top);
|
||||
|
||||
GtkWidget *mixer_top = create_mixer_controls(card);
|
||||
|
||||
@@ -449,7 +905,7 @@ GtkWidget *create_iface_mixer_main(struct alsa_card *card) {
|
||||
card, "Mixer", G_CALLBACK(window_mixer_close_request)
|
||||
);
|
||||
|
||||
gtk_window_set_child(GTK_WINDOW(card->window_mixer), mixer_top);
|
||||
create_scrollable_window(card->window_mixer, mixer_top);
|
||||
|
||||
GtkWidget *levels_top = create_levels_controls(card);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 don’t recognise the controls on this card.\n\n"
|
||||
|
||||
"These Focusrite Scarlett models should be supported:\n"
|
||||
"These Focusrite models should be supported:\n"
|
||||
"– Gen 2: 6i6/18i8/18i20\n"
|
||||
"– Gen 3: Solo/2i2/4i4/8i6/18i8/18i20\n\n"
|
||||
"– Gen 3: Solo/2i2/4i4/8i6/18i8/18i20\n"
|
||||
"– Gen 4: Solo/2i2/4i4\n"
|
||||
"– Clarett USB and Clarett+ 2Pre/4Pre/8Pre\n\n"
|
||||
|
||||
"Are you running a recent kernel with Scarlett Gen 2/3 support "
|
||||
"Are you running a recent kernel with Scarlett2 support "
|
||||
"enabled?\n\n"
|
||||
|
||||
"Check dmesg output for “Focusrite Scarlett Gen 2/3 Mixer "
|
||||
"Driver”:\n\n"
|
||||
"Check dmesg output for “Focusrite ... Mixer Driver”:\n\n"
|
||||
|
||||
"dmesg | grep Scarlett\n\n"
|
||||
"dmesg | grep -A 5 -B 5 -i focusrite\n\n"
|
||||
|
||||
"You may need to create a file /etc/modprobe.d/scarlett.conf\n"
|
||||
"For kernels before 6.7 you may need to create a file\n"
|
||||
"/etc/modprobe.d/scarlett.conf\n"
|
||||
"with an “options snd_usb_audio ...” line and reboot."
|
||||
);
|
||||
gtk_widget_set_margin(label, 30);
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
13
src/main.c
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
164
src/menu.c
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 */
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
);
|
||||