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
|
- name: Install build dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt -y update
|
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
|
- name: Build from sources
|
||||||
run: |
|
run: |
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
${{ github.workspace }}/deb-workspace/usr/share/doc/${{ env.APP_NAME }}-${{ env.APP_VERSION }}
|
${{ 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/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/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 }}/
|
cp -r *.md img demo ${{ github.workspace }}/deb-workspace/usr/share/doc/${{ env.APP_NAME }}-${{ env.APP_VERSION }}/
|
||||||
|
|
||||||
- name: Build debian package
|
- 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
|
`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
|
the Linux kernel Focusrite Scarlett2 USB Protocol Mixer Driver.
|
||||||
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
|
## About
|
||||||
|
|
||||||
@@ -10,71 +17,53 @@ Driver.
|
|||||||
|
|
||||||
The Focusrite USB audio interfaces are class compliant meaning that
|
The Focusrite USB audio interfaces are class compliant meaning that
|
||||||
they work “out of the box” on Linux as audio and MIDI interfaces
|
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
|
(although on Gen 3/4/Vocaster you need to disable MSD mode first for
|
||||||
functionality). However, except for some of the smallest models, they
|
full functionality). However, except for some of the smallest models,
|
||||||
have a bunch of proprietary functionality that required a kernel
|
they have a bunch of proprietary functionality that required a kernel
|
||||||
driver to be written specifically for those devices.
|
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
|
Unfortunately, actually using this functionality used to be quite an
|
||||||
awful experience. The existing applications like `alsamixer` and
|
awful experience. The existing applications like `alsamixer` and
|
||||||
`qasmixer` become completely user-hostile with the hundreds of
|
`qasmixer` become completely user-hostile with the hundreds of
|
||||||
controls presented for the Gen 3 18i20. Even the smallest Gen 3 4i4
|
controls presented for the Gen 3 18i20. Even the smallest Gen 3 4i4
|
||||||
interface at last count had 84 ALSA controls.
|
interface at last count had 84 ALSA controls.
|
||||||
|
|
||||||
Announcing the ALSA Scarlett Gen 2/3 (and Clarett USB/Clarett+!)
|
Announcing the ALSA Scarlett2 Control Panel, now supporting Scarlett
|
||||||
Control Panel!
|
Gen 2, 3, 4, Clarett, and Vocaster!
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The GUI supports all features presented by the driver (if not, please
|
|
||||||
report a bug).
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Refer to [INSTALL.md](INSTALL.md) for prerequisites, how to build,
|
Refer to [INSTALL.md](docs/INSTALL.md) for prerequisites, how to
|
||||||
install, and run.
|
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
|
## Donations
|
||||||
|
|
||||||
This program is Free Software, developed using my personal resources,
|
This program is Free Software, developed using my personal resources,
|
||||||
over hundreds of hours.
|
over hundreds of hours.
|
||||||
|
|
||||||
If you like this software, please consider a donation to say thank you
|
If you like this software, please consider a donation to say thank
|
||||||
as it was expensive to purchase one of each model for development and
|
you! Any donation is appreciated.
|
||||||
testing! Any donation is appreciated.
|
|
||||||
|
|
||||||
- https://liberapay.com/gdb
|
- https://liberapay.com/gdb
|
||||||
- https://paypal.me/gdbau
|
- 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
|
## 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
|
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
|
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
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# Credit to Tom Tromey and Paul D. Smith:
|
# Credit to Tom Tromey and Paul D. Smith:
|
||||||
# http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/
|
# 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
|
DEPDIR := .deps
|
||||||
DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d
|
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 += -DVERSION=\"$(VERSION)\"
|
||||||
CFLAGS += -Wno-error=deprecated-declarations
|
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 glib-2.0)
|
||||||
LDFLAGS += $(shell $(PKG_CONFIG) --libs gtk4)
|
LDFLAGS += $(shell $(PKG_CONFIG) --libs gtk4)
|
||||||
LDFLAGS += $(shell $(PKG_CONFIG) --libs alsa)
|
LDFLAGS += $(shell $(PKG_CONFIG) --libs alsa)
|
||||||
|
LDFLAGS += -lm -lcrypto
|
||||||
|
|
||||||
COMPILE.c = $(CC) $(DEPFLAGS) $(CFLAGS) -c
|
COMPILE.c = $(CC) $(DEPFLAGS) $(CFLAGS) -c
|
||||||
|
|
||||||
@@ -60,7 +66,7 @@ $(DEPFILES):
|
|||||||
include $(wildcard $(DEPFILES))
|
include $(wildcard $(DEPFILES))
|
||||||
|
|
||||||
$(TARGET): $(OBJS)
|
$(TARGET): $(OBJS)
|
||||||
cc -o $(TARGET) $(OBJS) ${LDFLAGS} -lm
|
cc -o $(TARGET) $(OBJS) ${LDFLAGS}
|
||||||
|
|
||||||
ifeq ($(PREFIX),)
|
ifeq ($(PREFIX),)
|
||||||
PREFIX := /usr/local
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "about.h"
|
#include "about.h"
|
||||||
@@ -11,20 +11,22 @@ void activate_about(
|
|||||||
GtkWindow *w = GTK_WINDOW(data);
|
GtkWindow *w = GTK_WINDOW(data);
|
||||||
|
|
||||||
const char *authors[] = {
|
const char *authors[] = {
|
||||||
"Geoffrey D. Bennett",
|
"Geoffrey D. Bennett <g@b4.vu>",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
gtk_show_about_dialog(
|
gtk_show_about_dialog(
|
||||||
w,
|
w,
|
||||||
"program-name", "ALSA Scarlett Gen 2/3 Control Panel",
|
"program-name", "ALSA Scarlett2 Control Panel",
|
||||||
"version", "Version " VERSION,
|
"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",
|
"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,
|
"license-type", GTK_LICENSE_GPL_3_0,
|
||||||
"logo-icon-name", "alsa-scarlett-gui-logo",
|
"logo-icon-name", "alsa-scarlett-gui-logo",
|
||||||
"title", "About ALSA Scarlett Mixer Interface",
|
"title", "About ALSA Scarlett2 Mixer Interface",
|
||||||
"authors", authors,
|
"authors", authors,
|
||||||
NULL
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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 {
|
.route-label {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-label:hover {
|
.route-label:hover {
|
||||||
background: @theme_selected_bg_color;
|
background: #801010;
|
||||||
outline: 2px solid @theme_selected_bg_color;
|
outline: 2px solid #801010;
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-label:drop(active) {
|
.route-label:drop(active) {
|
||||||
box-shadow: none;
|
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;
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "alsa.h"
|
#include "alsa.h"
|
||||||
@@ -151,6 +151,42 @@ static void alsa_parse_comment_node(
|
|||||||
elem->type = SND_CTL_ELEM_TYPE_INTEGER;
|
elem->type = SND_CTL_ELEM_TYPE_INTEGER;
|
||||||
} else if (strcmp(key, "item") == 0) {
|
} else if (strcmp(key, "item") == 0) {
|
||||||
alsa_parse_enum_items(node, elem);
|
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) {
|
if (!iface) {
|
||||||
printf("missing iface node in control id %d\n", id);
|
printf("missing iface node in control id %d\n", id);
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
if (strcmp(iface, "MIXER") != 0 &&
|
if (strcmp(iface, "CARD") != 0 &&
|
||||||
|
strcmp(iface, "MIXER") != 0 &&
|
||||||
strcmp(iface, "PCM") != 0)
|
strcmp(iface, "PCM") != 0)
|
||||||
goto fail;
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <sys/inotify.h>
|
#include <sys/inotify.h>
|
||||||
|
|
||||||
#include "alsa.h"
|
#include "alsa.h"
|
||||||
|
#include "scarlett2-firmware.h"
|
||||||
#include "stringhelper.h"
|
#include "stringhelper.h"
|
||||||
#include "window-iface.h"
|
#include "window-iface.h"
|
||||||
|
|
||||||
|
#define MAX_TLV_RANGE_SIZE 256
|
||||||
|
|
||||||
// names for the port categories
|
// names for the port categories
|
||||||
const char *port_category_names[PC_COUNT] = {
|
const char *port_category_names[PC_COUNT] = {
|
||||||
"Hardware Outputs",
|
"Hardware Outputs",
|
||||||
"Mixer Inputs",
|
"Mixer Inputs",
|
||||||
|
"DSP Inputs",
|
||||||
"PCM Inputs"
|
"PCM Inputs"
|
||||||
};
|
};
|
||||||
|
|
||||||
// global array of cards
|
// global array of cards
|
||||||
GArray *alsa_cards;
|
static GArray *alsa_cards;
|
||||||
|
|
||||||
// static fd and wd for ALSA inotify
|
// static fd and wd for ALSA inotify
|
||||||
static int inotify_fd, inotify_wd;
|
static int inotify_fd, inotify_wd;
|
||||||
|
|
||||||
|
struct reopen_callback {
|
||||||
|
ReOpenCallback *callback;
|
||||||
|
void *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// hash table for cards being rebooted
|
||||||
|
GHashTable *reopen_callbacks;
|
||||||
|
|
||||||
// forward declaration
|
// forward declaration
|
||||||
static void alsa_elem_change(struct alsa_elem *elem);
|
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 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
|
// PCM xx Capture Enum
|
||||||
// Mixer Input xx Capture Enum
|
// Mixer Input xx Capture Enum
|
||||||
// Analogue Output xx Playback Enum
|
// Analogue Output xx Playback Enum
|
||||||
// S/PDIF Output xx Playback Enum
|
// S/PDIF Output xx Playback Enum
|
||||||
// ADAT Output xx Playback Enum
|
// ADAT Output xx Playback Enum
|
||||||
int is_elem_routing_dst(struct alsa_elem *elem) {
|
int is_elem_routing_snk(struct alsa_elem *elem) {
|
||||||
if (strstr(elem->name, "Capture Enum") &&
|
if (strstr(elem->name, "Capture Enum") && (
|
||||||
!strstr(elem->name, "Level"))
|
strncmp(elem->name, "PCM ", 4) == 0 ||
|
||||||
|
strncmp(elem->name, "Mixer Input ", 12) == 0 ||
|
||||||
|
strncmp(elem->name, "DSP Input ", 10) == 0
|
||||||
|
))
|
||||||
return 1;
|
return 1;
|
||||||
if (strstr(elem->name, "Output") &&
|
if (strstr(elem->name, "Output") &&
|
||||||
strstr(elem->name, "Playback Enum"))
|
strstr(elem->name, "Playback Enum"))
|
||||||
@@ -110,6 +125,20 @@ int is_elem_routing_dst(struct alsa_elem *elem) {
|
|||||||
return 0;
|
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
|
// alsa snd_ctl_elem_*() mediation functions
|
||||||
// for simulated elements, fake the ALSA element
|
// 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"))
|
if (strstr(alsa_elem.name, "Channel Map"))
|
||||||
continue;
|
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)
|
if (card->elems->len <= alsa_elem.numid)
|
||||||
g_array_set_size(card->elems, alsa_elem.numid + 1);
|
g_array_set_size(card->elems, alsa_elem.numid + 1);
|
||||||
g_array_index(card->elems, struct alsa_elem, alsa_elem.numid) = alsa_elem;
|
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) {
|
static void alsa_elem_change(struct alsa_elem *elem) {
|
||||||
if (!elem->widget)
|
if (!elem || !elem->callbacks)
|
||||||
return;
|
return;
|
||||||
if (!elem->widget_callback)
|
|
||||||
return;
|
for (GList *l = elem->callbacks; l; l = l->next) {
|
||||||
elem->widget_callback(elem);
|
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(
|
static gboolean alsa_card_callback(
|
||||||
@@ -449,6 +532,7 @@ static void card_destroy_callback(void *data) {
|
|||||||
|
|
||||||
// TODO: there is more to free
|
// TODO: there is more to free
|
||||||
free(card->device);
|
free(card->device);
|
||||||
|
free(card->serial);
|
||||||
free(card->name);
|
free(card->name);
|
||||||
free(card);
|
free(card);
|
||||||
|
|
||||||
@@ -481,7 +565,174 @@ static void alsa_subscribe(struct alsa_card *card) {
|
|||||||
snd_ctl_poll_descriptors(card->handle, &card->pfd, 1);
|
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_card_info_t *info;
|
||||||
snd_ctl_t *ctl;
|
snd_ctl_t *ctl;
|
||||||
int card_num = -1;
|
int card_num = -1;
|
||||||
@@ -506,7 +757,8 @@ void alsa_scan_cards(void) {
|
|||||||
goto next;
|
goto next;
|
||||||
|
|
||||||
if (strncmp(snd_ctl_card_info_get_name(info), "Scarlett", 8) != 0 &&
|
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;
|
goto next;
|
||||||
|
|
||||||
// is there already an entry for this card in alsa_cards?
|
// 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_get_elem_list(card);
|
||||||
alsa_subscribe(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);
|
create_card_window(card);
|
||||||
alsa_add_card_callback(card);
|
alsa_add_card_callback(card);
|
||||||
@@ -571,7 +838,7 @@ static gboolean inotify_callback(
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void alsa_inotify_init(void) {
|
static void alsa_inotify_init(void) {
|
||||||
GIOChannel *io_channel;
|
GIOChannel *io_channel;
|
||||||
|
|
||||||
inotify_fd = inotify_init();
|
inotify_fd = inotify_init();
|
||||||
@@ -583,3 +850,32 @@ void alsa_inotify_init(void) {
|
|||||||
inotify_callback, NULL, NULL
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -17,9 +17,9 @@ struct alsa_card;
|
|||||||
|
|
||||||
// typedef for callbacks to update widgets when the alsa element
|
// typedef for callbacks to update widgets when the alsa element
|
||||||
// notifies of a change
|
// 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
|
// must match the level meter ordering from the driver
|
||||||
enum {
|
enum {
|
||||||
// Hardware inputs/outputs
|
// Hardware inputs/outputs
|
||||||
@@ -28,22 +28,25 @@ enum {
|
|||||||
// Mixer inputs/outputs
|
// Mixer inputs/outputs
|
||||||
PC_MIX = 1,
|
PC_MIX = 1,
|
||||||
|
|
||||||
|
// DSP inputs/outputs
|
||||||
|
PC_DSP = 2,
|
||||||
|
|
||||||
// PCM inputs/outputs
|
// PCM inputs/outputs
|
||||||
PC_PCM = 2,
|
PC_PCM = 3,
|
||||||
|
|
||||||
// number of port categories
|
// number of port categories
|
||||||
PC_COUNT = 3
|
PC_COUNT = 4
|
||||||
};
|
};
|
||||||
|
|
||||||
// names for the port categories
|
// names for the port categories
|
||||||
extern const char *port_category_names[PC_COUNT];
|
extern const char *port_category_names[PC_COUNT];
|
||||||
|
|
||||||
// is a drag active, and whether dragging from a routing source or a
|
// is a drag active, and whether dragging from a routing source or a
|
||||||
// routing destination
|
// routing sink
|
||||||
enum {
|
enum {
|
||||||
DRAG_TYPE_NONE = 0,
|
DRAG_TYPE_NONE = 0,
|
||||||
DRAG_TYPE_SRC = 1,
|
DRAG_TYPE_SRC = 1,
|
||||||
DRAG_TYPE_DST = 2,
|
DRAG_TYPE_SNK = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
// entry in alsa_card routing_srcs (routing sources) array
|
// entry in alsa_card routing_srcs (routing sources) array
|
||||||
@@ -56,7 +59,7 @@ struct routing_src {
|
|||||||
// the enum id of the alsa item
|
// the enum id of the alsa item
|
||||||
int id;
|
int id;
|
||||||
|
|
||||||
// PC_MIX, PC_PCM, or PC_HW
|
// PC_DSP, PC_MIX, PC_PCM, or PC_HW
|
||||||
int port_category;
|
int port_category;
|
||||||
|
|
||||||
// 0-based count within port_category
|
// 0-based count within port_category
|
||||||
@@ -76,12 +79,12 @@ struct routing_src {
|
|||||||
GtkWidget *widget2;
|
GtkWidget *widget2;
|
||||||
};
|
};
|
||||||
|
|
||||||
// entry in alsa_card routing_dsts (routing destinations) array
|
// entry in alsa_card routing_snks (routing sinks) array for alsa
|
||||||
// for alsa elements that are routing destinations like Analogue
|
// elements that are routing sinks like Analogue Output 01 Playback
|
||||||
// Output 01 Playback Enum
|
// Enum
|
||||||
// port_category is set to PC_MIX, PC_PCM, PC_HW
|
// port_category is set to PC_DSP, PC_MIX, PC_PCM, PC_HW
|
||||||
// port_num is a count (0-based) within that category
|
// port_num is a count (0-based) within that category
|
||||||
struct routing_dst {
|
struct routing_snk {
|
||||||
|
|
||||||
// location within the array
|
// location within the array
|
||||||
int idx;
|
int idx;
|
||||||
@@ -89,17 +92,29 @@ struct routing_dst {
|
|||||||
// pointer back to the element this entry is associated with
|
// pointer back to the element this entry is associated with
|
||||||
struct alsa_elem *elem;
|
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;
|
int port_category;
|
||||||
|
|
||||||
// 0-based count within port_category
|
// 0-based count within port_category
|
||||||
int port_num;
|
int port_num;
|
||||||
|
|
||||||
// the mixer label widgets for this destination
|
// the mixer label widgets for this sink
|
||||||
GtkWidget *mixer_label_top;
|
GtkWidget *mixer_label_top;
|
||||||
GtkWidget *mixer_label_bottom;
|
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
|
// entry in alsa_card elems (ALSA control elements) array
|
||||||
struct alsa_elem {
|
struct alsa_elem {
|
||||||
|
|
||||||
@@ -112,23 +127,18 @@ struct alsa_elem {
|
|||||||
int type;
|
int type;
|
||||||
int count;
|
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
|
// 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;
|
int lr_num;
|
||||||
|
|
||||||
// the primary GTK widget and callback function for this ALSA
|
// the callback functions for this ALSA control element
|
||||||
// control element
|
GList *callbacks;
|
||||||
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];
|
|
||||||
|
|
||||||
// for simulated elements, the current state
|
// for simulated elements, the current state
|
||||||
int writable;
|
int writable;
|
||||||
@@ -142,14 +152,17 @@ struct alsa_elem {
|
|||||||
struct alsa_card {
|
struct alsa_card {
|
||||||
int num;
|
int num;
|
||||||
char *device;
|
char *device;
|
||||||
|
uint32_t pid;
|
||||||
|
char *serial;
|
||||||
char *name;
|
char *name;
|
||||||
|
int best_firmware_version;
|
||||||
snd_ctl_t *handle;
|
snd_ctl_t *handle;
|
||||||
struct pollfd pfd;
|
struct pollfd pfd;
|
||||||
GArray *elems;
|
GArray *elems;
|
||||||
struct alsa_elem *sample_capture_elem;
|
struct alsa_elem *sample_capture_elem;
|
||||||
struct alsa_elem *level_meter_elem;
|
struct alsa_elem *level_meter_elem;
|
||||||
GArray *routing_srcs;
|
GArray *routing_srcs;
|
||||||
GArray *routing_dsts;
|
GArray *routing_snks;
|
||||||
GIOChannel *io_channel;
|
GIOChannel *io_channel;
|
||||||
guint event_source_id;
|
guint event_source_id;
|
||||||
GtkWidget *window_main;
|
GtkWidget *window_main;
|
||||||
@@ -157,6 +170,7 @@ struct alsa_card {
|
|||||||
GtkWidget *window_mixer;
|
GtkWidget *window_mixer;
|
||||||
GtkWidget *window_levels;
|
GtkWidget *window_levels;
|
||||||
GtkWidget *window_startup;
|
GtkWidget *window_startup;
|
||||||
|
GtkWidget *window_modal;
|
||||||
GtkWidget *window_main_contents;
|
GtkWidget *window_main_contents;
|
||||||
GtkWidget *routing_grid;
|
GtkWidget *routing_grid;
|
||||||
GtkWidget *routing_lines;
|
GtkWidget *routing_lines;
|
||||||
@@ -164,6 +178,8 @@ struct alsa_card {
|
|||||||
GtkWidget *routing_hw_out_grid;
|
GtkWidget *routing_hw_out_grid;
|
||||||
GtkWidget *routing_pcm_in_grid;
|
GtkWidget *routing_pcm_in_grid;
|
||||||
GtkWidget *routing_pcm_out_grid;
|
GtkWidget *routing_pcm_out_grid;
|
||||||
|
GtkWidget *routing_dsp_in_grid;
|
||||||
|
GtkWidget *routing_dsp_out_grid;
|
||||||
GtkWidget *routing_mixer_in_grid;
|
GtkWidget *routing_mixer_in_grid;
|
||||||
GtkWidget *routing_mixer_out_grid;
|
GtkWidget *routing_mixer_out_grid;
|
||||||
GtkWidget *meters[MAX_METERS];
|
GtkWidget *meters[MAX_METERS];
|
||||||
@@ -176,13 +192,10 @@ struct alsa_card {
|
|||||||
GtkWidget *drag_line;
|
GtkWidget *drag_line;
|
||||||
int drag_type;
|
int drag_type;
|
||||||
struct routing_src *src_drag;
|
struct routing_src *src_drag;
|
||||||
struct routing_dst *dst_drag;
|
struct routing_snk *snk_drag;
|
||||||
double drag_x, drag_y;
|
double drag_x, drag_y;
|
||||||
};
|
};
|
||||||
|
|
||||||
// global array of cards
|
|
||||||
extern GArray *alsa_cards;
|
|
||||||
|
|
||||||
// utility
|
// utility
|
||||||
void fatal_alsa_error(const char *msg, int err);
|
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_name(GArray *elems, char *name);
|
||||||
struct alsa_elem *get_elem_by_prefix(GArray *elems, char *prefix);
|
struct alsa_elem *get_elem_by_prefix(GArray *elems, char *prefix);
|
||||||
int get_max_elem_by_name(GArray *elems, char *prefix, char *needle);
|
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
|
// alsa snd_ctl_elem_*() functions
|
||||||
int alsa_get_elem_type(struct alsa_elem *elem);
|
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
|
// add to alsa_cards array
|
||||||
struct alsa_card *card_create(int card_num);
|
struct alsa_card *card_create(int card_num);
|
||||||
|
|
||||||
// scan/rescan for cards
|
// init
|
||||||
void alsa_scan_cards(void);
|
void alsa_init(void);
|
||||||
void alsa_inotify_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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
@@ -17,7 +17,7 @@ void show_error(GtkWindow *w, char *s) {
|
|||||||
"%s",
|
"%s",
|
||||||
s
|
s
|
||||||
);
|
);
|
||||||
gtk_widget_show(dialog);
|
gtk_widget_set_visible(dialog, TRUE);
|
||||||
|
|
||||||
g_signal_connect(dialog, "response", G_CALLBACK(gtk_window_destroy), NULL);
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "alsa.h"
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
|||||||
1308
src/gtkdial.c
130
src/gtkdial.h
@@ -1,5 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Stiliyan Varbanov <https://www.fiverr.com/stilvar>
|
// 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
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -14,92 +14,86 @@
|
|||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
#define GTK_TYPE_DIAL (gtk_dial_get_type ())
|
#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(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_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(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_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_DIAL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_DIAL, GtkDialClass))
|
||||||
|
|
||||||
typedef struct _GtkDial GtkDial;
|
typedef struct _GtkDial GtkDial;
|
||||||
typedef struct _GtkDialClass GtkDialClass;
|
typedef struct _GtkDialClass GtkDialClass;
|
||||||
|
|
||||||
struct _GtkDialClass
|
struct _GtkDialClass {
|
||||||
{
|
|
||||||
GtkWidgetClass parent_class;
|
GtkWidgetClass parent_class;
|
||||||
|
|
||||||
void (* value_changed) (GtkDial *dial);
|
void (*value_changed)(GtkDial *dial);
|
||||||
|
|
||||||
/* action signals for keybindings */
|
/* action signals for keybindings */
|
||||||
void (* move_slider) (GtkDial *dial,
|
void (*move_slider)(GtkDial *dial, GtkScrollType scroll);
|
||||||
GtkScrollType scroll);
|
|
||||||
|
|
||||||
gboolean (*change_value) (GtkDial *dial,
|
gboolean (*change_value)(
|
||||||
|
GtkDial *dial,
|
||||||
GtkScrollType scroll,
|
GtkScrollType scroll,
|
||||||
double new_value);
|
double new_value
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef char * (*GtkDialFormatValueFunc) (GtkDial *dial,
|
GType gtk_dial_get_type(void) G_GNUC_CONST;
|
||||||
double value,
|
|
||||||
gpointer user_data);
|
|
||||||
|
|
||||||
GDK_AVAILABLE_IN_ALL
|
GtkWidget *gtk_dial_new(GtkAdjustment *adjustment);
|
||||||
GType gtk_dial_get_type (void) G_GNUC_CONST;
|
|
||||||
GDK_AVAILABLE_IN_ALL
|
GtkWidget *gtk_dial_new_with_range(
|
||||||
GtkWidget * gtk_dial_new (GtkAdjustment *adjustment);
|
double min,
|
||||||
GDK_AVAILABLE_IN_ALL
|
|
||||||
GtkWidget * gtk_dial_new_with_range (double min,
|
|
||||||
double max,
|
double max,
|
||||||
double step);
|
double step,
|
||||||
GDK_AVAILABLE_IN_ALL
|
double page
|
||||||
void gtk_dial_set_has_origin (GtkDial *dial,
|
);
|
||||||
gboolean has_origin);
|
|
||||||
GDK_AVAILABLE_IN_ALL
|
|
||||||
gboolean gtk_dial_get_has_origin (GtkDial *dial);
|
|
||||||
|
|
||||||
GDK_AVAILABLE_IN_ALL
|
void gtk_dial_set_has_origin(GtkDial *dial, gboolean has_origin);
|
||||||
void gtk_dial_set_adjustment (GtkDial *dial,
|
gboolean gtk_dial_get_has_origin(GtkDial *dial);
|
||||||
GtkAdjustment *adj);
|
|
||||||
|
|
||||||
GDK_AVAILABLE_IN_ALL
|
void gtk_dial_set_adjustment(GtkDial *dial, GtkAdjustment *adj);
|
||||||
GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
|
GtkAdjustment *gtk_dial_get_adjustment(GtkDial *dial);
|
||||||
|
|
||||||
GDK_AVAILABLE_IN_ALL
|
double gtk_dial_get_value(GtkDial *dial);
|
||||||
double gtk_dial_get_value (GtkDial *dial);
|
void gtk_dial_set_value(GtkDial *dial, double value);
|
||||||
|
|
||||||
GDK_AVAILABLE_IN_ALL
|
void gtk_dial_set_round_digits(GtkDial *dial, int round_digits);
|
||||||
void gtk_dial_set_value (GtkDial *dial,
|
int gtk_dial_get_round_digits(GtkDial *dial);
|
||||||
double value);
|
|
||||||
GDK_AVAILABLE_IN_ALL
|
void gtk_dial_set_zero_db(GtkDial *dial, double zero_db);
|
||||||
void gtk_dial_set_round_digits (GtkDial *dial,
|
double gtk_dial_get_zero_db(GtkDial *dial);
|
||||||
int round_digits);
|
|
||||||
GDK_AVAILABLE_IN_ALL
|
void gtk_dial_set_off_db(GtkDial *dial, double off_db);
|
||||||
int gtk_dial_get_round_digits (GtkDial *range);
|
double gtk_dial_get_off_db(GtkDial *dial);
|
||||||
GDK_AVAILABLE_IN_ALL
|
|
||||||
void gtk_dial_set_zero_db (GtkDial *dial,
|
// taper functions
|
||||||
double zero_db);
|
enum {
|
||||||
GDK_AVAILABLE_IN_ALL
|
GTK_DIAL_TAPER_LINEAR,
|
||||||
double gtk_dial_get_zero_db (GtkDial *range);
|
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
|
G_END_DECLS
|
||||||
|
|
||||||
#endif
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "gtkhelper.h"
|
#include "gtkhelper.h"
|
||||||
@@ -25,7 +25,15 @@ void gtk_grid_set_spacing(GtkGrid *grid, int spacing) {
|
|||||||
gtk_grid_set_column_spacing(grid, spacing);
|
gtk_grid_set_column_spacing(grid, spacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
void gtk_widget_add_class(GtkWidget *w, const char *class) {
|
void gtk_widget_remove_css_classes_by_prefix(
|
||||||
GtkStyleContext *style_context = gtk_widget_get_style_context(w);
|
GtkWidget *w,
|
||||||
gtk_style_context_add_class(style_context, class);
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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_expand(GtkWidget *w, gboolean expand);
|
||||||
void gtk_widget_set_align(GtkWidget *w, GtkAlign x, GtkAlign y);
|
void gtk_widget_set_align(GtkWidget *w, GtkAlign x, GtkAlign y);
|
||||||
void gtk_grid_set_spacing(GtkGrid *grid, int spacing);
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "gtkhelper.h"
|
#include "gtkhelper.h"
|
||||||
@@ -6,9 +6,12 @@
|
|||||||
#include "stringhelper.h"
|
#include "stringhelper.h"
|
||||||
#include "tooltips.h"
|
#include "tooltips.h"
|
||||||
#include "widget-boolean.h"
|
#include "widget-boolean.h"
|
||||||
#include "widget-combo.h"
|
#include "widget-drop-down.h"
|
||||||
#include "widget-dual.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-helper.h"
|
||||||
#include "window-levels.h"
|
#include "window-levels.h"
|
||||||
#include "window-mixer.h"
|
#include "window-mixer.h"
|
||||||
@@ -36,7 +39,9 @@ static void add_clock_source_control(
|
|||||||
gtk_box_append(GTK_BOX(global_controls), b);
|
gtk_box_append(GTK_BOX(global_controls), b);
|
||||||
|
|
||||||
GtkWidget *l = gtk_label_new("Clock Source");
|
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), l);
|
||||||
gtk_box_append(GTK_BOX(b), w);
|
gtk_box_append(GTK_BOX(b), w);
|
||||||
@@ -75,6 +80,62 @@ static void add_sync_status_control(
|
|||||||
GtkWidget *l = gtk_label_new("Sync Status");
|
GtkWidget *l = gtk_label_new("Sync Status");
|
||||||
gtk_box_append(GTK_BOX(b), l);
|
gtk_box_append(GTK_BOX(b), l);
|
||||||
GtkWidget *w = make_boolean_alsa_elem(sync_status, "Unlocked", "Locked");
|
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);
|
gtk_box_append(GTK_BOX(b), w);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,18 +152,19 @@ static void add_speaker_switching_controls(
|
|||||||
if (!speaker_switching)
|
if (!speaker_switching)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
make_dual_boolean_alsa_elems(speaker_switching, "Off", "On", "Main", "Alt");
|
GtkWidget *w = make_dual_boolean_alsa_elems(
|
||||||
GtkWidget *b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
|
speaker_switching,
|
||||||
|
"Speaker Switching",
|
||||||
|
"Off", "On", "Main", "Alt"
|
||||||
|
);
|
||||||
|
|
||||||
gtk_widget_set_tooltip_text(
|
gtk_widget_set_tooltip_text(
|
||||||
b,
|
w,
|
||||||
"Speaker Switching lets you swap between two pairs of "
|
"Speaker Switching lets you swap between two pairs of "
|
||||||
"monitoring speakers very easily."
|
"monitoring speakers very easily."
|
||||||
);
|
);
|
||||||
GtkWidget *l = gtk_label_new("Speaker Switching");
|
|
||||||
gtk_box_append(GTK_BOX(global_controls), b);
|
gtk_box_append(GTK_BOX(global_controls), w);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_talkback_controls(
|
static void add_talkback_controls(
|
||||||
@@ -118,36 +180,299 @@ static void add_talkback_controls(
|
|||||||
if (!talkback)
|
if (!talkback)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
make_dual_boolean_alsa_elems(talkback, "Disabled", "Enabled", "Off", "On");
|
GtkWidget *w = make_dual_boolean_alsa_elems(
|
||||||
GtkWidget *b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
|
talkback,
|
||||||
|
"Talkback",
|
||||||
|
"Disabled", "Enabled", "Off", "On"
|
||||||
|
);
|
||||||
|
|
||||||
gtk_widget_set_tooltip_text(
|
gtk_widget_set_tooltip_text(
|
||||||
b,
|
w,
|
||||||
"Talkback lets you add another channel (usually the talkback "
|
"Talkback lets you add another channel (usually the talkback "
|
||||||
"mic) to a mix with a button push, usually to talk to "
|
"mic) to a mix with a button push, usually to talk to "
|
||||||
"musicians, and without using an additional mic channel."
|
"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(global_controls), w);
|
||||||
gtk_box_append(GTK_BOX(b), l);
|
|
||||||
gtk_box_append(GTK_BOX(b), talkback->widget);
|
|
||||||
gtk_box_append(GTK_BOX(b), talkback->widget2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static GtkWidget *create_global_box(GtkWidget *grid, int *x, int orient) {
|
static GtkWidget *create_global_box(GtkWidget *grid, int *x, int orient) {
|
||||||
GtkWidget *label = gtk_label_new("Global");
|
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
|
||||||
GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
|
gtk_widget_set_vexpand(box, TRUE);
|
||||||
GtkWidget *controls = gtk_box_new(orient, 15);
|
|
||||||
gtk_widget_set_margin(controls, 10);
|
|
||||||
|
|
||||||
gtk_grid_attach(GTK_GRID(grid), label, *x, 0, 1, 1);
|
GtkWidget *label = gtk_label_new("Global");
|
||||||
gtk_grid_attach(GTK_GRID(grid), sep, *x, 1, 1, 1);
|
gtk_widget_add_css_class(label, "controls-label");
|
||||||
gtk_grid_attach(GTK_GRID(grid), controls, *x, 2, 1, 1);
|
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)++;
|
(*x)++;
|
||||||
|
|
||||||
return controls;
|
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(
|
static void create_input_controls(
|
||||||
struct alsa_card *card,
|
struct alsa_card *card,
|
||||||
GtkWidget *top,
|
GtkWidget *top,
|
||||||
@@ -162,78 +487,95 @@ static void create_input_controls(
|
|||||||
if (!input_count)
|
if (!input_count)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
|
struct alsa_elem *input_select_elem =
|
||||||
gtk_widget_set_halign(sep, GTK_ALIGN_CENTER);
|
get_elem_by_name(elems, "Input Select Capture Enum");
|
||||||
gtk_grid_attach(GTK_GRID(top), sep, (*x)++, 0, 1, 3);
|
|
||||||
|
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
|
||||||
|
|
||||||
GtkWidget *label_ic = gtk_label_new("Analogue Inputs");
|
GtkWidget *label_ic = gtk_label_new("Analogue Inputs");
|
||||||
gtk_grid_attach(GTK_GRID(top), label_ic, *x, 0, 1, 1);
|
gtk_widget_add_css_class(label_ic, "controls-label");
|
||||||
|
gtk_widget_set_halign(label_ic, GTK_ALIGN_START);
|
||||||
GtkWidget *horiz_input_sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
|
gtk_box_append(GTK_BOX(box), label_ic);
|
||||||
gtk_grid_attach(GTK_GRID(top), horiz_input_sep, *x, 1, 1, 1);
|
|
||||||
|
|
||||||
GtkWidget *input_grid = gtk_grid_new();
|
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_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++) {
|
for (int i = 1; i <= input_count; i++) {
|
||||||
|
GtkWidget *label;
|
||||||
|
|
||||||
|
if (input_select_elem) {
|
||||||
|
label = make_input_select_alsa_elem(input_select_elem, i);
|
||||||
|
} else {
|
||||||
char s[20];
|
char s[20];
|
||||||
snprintf(s, 20, "%d", i);
|
snprintf(s, 20, "%d", i);
|
||||||
GtkWidget *label = gtk_label_new(s);
|
label = gtk_label_new(s);
|
||||||
gtk_grid_attach(GTK_GRID(input_grid), label, i, 0, 1, 1);
|
}
|
||||||
|
gtk_grid_attach(GTK_GRID(input_grid), label, i - 1, 0, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
GtkWidget *level_label = NULL;
|
int current_row = 1;
|
||||||
GtkWidget *air_label = NULL;
|
|
||||||
GtkWidget *pad_label = NULL;
|
|
||||||
|
|
||||||
for (int i = 0; i < elems->len; i++) {
|
create_input_select_control(elems, input_grid, ¤t_row);
|
||||||
struct alsa_elem *elem = &g_array_index(elems, struct alsa_elem, i);
|
|
||||||
GtkWidget *w;
|
|
||||||
|
|
||||||
// if no card entry, it's an empty slot
|
create_input_controls_by_type(
|
||||||
if (!elem->card)
|
elems, input_grid, ¤t_row,
|
||||||
continue;
|
"Link Capture Switch", create_input_link_control
|
||||||
|
);
|
||||||
int line_num = get_num_from_string(elem->name);
|
create_input_controls_by_type(
|
||||||
|
elems, input_grid, ¤t_row,
|
||||||
// input controls
|
"Gain Capture Volume", create_input_gain_control
|
||||||
if (strstr(elem->name, "Level Capture Enum")) {
|
);
|
||||||
if (!level_label) {
|
create_input_controls_by_type(
|
||||||
level_label = gtk_label_new("Level");
|
elems, input_grid, ¤t_row,
|
||||||
gtk_grid_attach(GTK_GRID(input_grid), level_label, 0, 1, 1, 1);
|
"Autogain Capture Switch", create_input_autogain_control
|
||||||
}
|
);
|
||||||
w = make_boolean_alsa_elem(elem, "Line", "Inst");
|
create_input_controls_by_type(
|
||||||
gtk_widget_set_tooltip_text(w, level_descr);
|
elems, input_grid, ¤t_row,
|
||||||
gtk_grid_attach(GTK_GRID(input_grid), w, line_num, 1, 1, 1);
|
"Autogain Status Capture Enum", create_input_autogain_status_control
|
||||||
} else if (strstr(elem->name, "Air Capture Switch")) {
|
);
|
||||||
if (!air_label) {
|
create_input_controls_by_type(
|
||||||
air_label = gtk_label_new("Air");
|
elems, input_grid, ¤t_row,
|
||||||
gtk_grid_attach(GTK_GRID(input_grid), air_label, 0, 2, 1, 1);
|
"Safe Capture Switch", create_input_safe_control
|
||||||
}
|
);
|
||||||
w = make_boolean_alsa_elem(elem, "Off", "On");
|
create_input_controls_by_type(
|
||||||
gtk_widget_set_tooltip_text(w, air_descr);
|
elems, input_grid, ¤t_row,
|
||||||
gtk_grid_attach(GTK_GRID(input_grid), w, line_num, 2, 1, 1);
|
"Level Capture Enum", create_input_level_control
|
||||||
} else if (strstr(elem->name, "Pad Capture Switch")) {
|
);
|
||||||
if (!pad_label) {
|
create_input_controls_by_type(
|
||||||
pad_label = gtk_label_new("Pad");
|
elems, input_grid, ¤t_row,
|
||||||
gtk_grid_attach(GTK_GRID(input_grid), pad_label, 0, 3, 1, 1);
|
"Air Capture Switch", create_input_air_switch_control
|
||||||
}
|
);
|
||||||
w = make_boolean_alsa_elem(elem, "Off", "On");
|
create_input_controls_by_type(
|
||||||
gtk_widget_set_tooltip_text(
|
elems, input_grid, ¤t_row,
|
||||||
w,
|
"Air Capture Enum", create_input_air_enum_control
|
||||||
"Enabling Pad engages an attenuator in the channel, giving "
|
);
|
||||||
"you more headroom for very hot signals."
|
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
|
||||||
);
|
);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(*x)++;
|
(*x)++;
|
||||||
}
|
}
|
||||||
@@ -247,24 +589,68 @@ static void create_output_controls(
|
|||||||
) {
|
) {
|
||||||
GArray *elems = card->elems;
|
GArray *elems = card->elems;
|
||||||
|
|
||||||
if (*x) {
|
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
|
||||||
GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
|
|
||||||
gtk_grid_attach(GTK_GRID(top), sep, (*x)++, y, x_span, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
GtkWidget *label_oc = gtk_label_new("Analogue Outputs");
|
GtkWidget *label_oc = gtk_label_new("Analogue Outputs");
|
||||||
gtk_grid_attach(GTK_GRID(top), label_oc, *x, y, x_span, 1);
|
gtk_widget_add_css_class(label_oc, "controls-label");
|
||||||
|
gtk_widget_set_halign(label_oc, GTK_ALIGN_START);
|
||||||
GtkWidget *horiz_output_sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
|
gtk_box_append(GTK_BOX(box), label_oc);
|
||||||
gtk_grid_attach(GTK_GRID(top), horiz_output_sep, *x, y + 1, x_span, 1);
|
|
||||||
|
|
||||||
GtkWidget *output_grid = gtk_grid_new();
|
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_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_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");
|
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 has_hw_vol = !!get_elem_by_name(elems, "Master HW Playback Volume");
|
||||||
int line_1_col = has_hw_vol;
|
int line_1_col = has_hw_vol;
|
||||||
|
|
||||||
@@ -288,7 +674,7 @@ static void create_output_controls(
|
|||||||
// output controls
|
// output controls
|
||||||
if (strncmp(elem->name, "Line", 4) == 0) {
|
if (strncmp(elem->name, "Line", 4) == 0) {
|
||||||
if (strstr(elem->name, "Playback Volume")) {
|
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_attach(
|
||||||
GTK_GRID(output_grid), w, line_num - 1 + line_1_col, 1, 1, 1
|
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(
|
w = make_boolean_alsa_elem(
|
||||||
elem, "*audio-volume-high", "*audio-volume-muted"
|
elem, "*audio-volume-high", "*audio-volume-muted"
|
||||||
);
|
);
|
||||||
|
gtk_widget_add_css_class(w, "mute");
|
||||||
if (has_hw_vol) {
|
if (has_hw_vol) {
|
||||||
gtk_widget_set_tooltip_text(
|
gtk_widget_set_tooltip_text(
|
||||||
w,
|
w,
|
||||||
@@ -309,6 +696,7 @@ static void create_output_controls(
|
|||||||
);
|
);
|
||||||
} else if (strstr(elem->name, "Volume Control Playback Enum")) {
|
} else if (strstr(elem->name, "Volume Control Playback Enum")) {
|
||||||
w = make_boolean_alsa_elem(elem, "SW", "HW");
|
w = make_boolean_alsa_elem(elem, "SW", "HW");
|
||||||
|
gtk_widget_add_css_class(w, "sw-hw");
|
||||||
gtk_widget_set_tooltip_text(
|
gtk_widget_set_tooltip_text(
|
||||||
w,
|
w,
|
||||||
"Set software-controlled (SW) or hardware-controlled (HW) "
|
"Set software-controlled (SW) or hardware-controlled (HW) "
|
||||||
@@ -321,26 +709,47 @@ static void create_output_controls(
|
|||||||
|
|
||||||
// master output controls
|
// master output controls
|
||||||
} else if (strcmp(elem->name, "Master HW Playback Volume") == 0) {
|
} 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(
|
gtk_widget_set_tooltip_text(
|
||||||
l,
|
l,
|
||||||
"This control shows the setting of the physical (hardware) "
|
"This control shows the setting of the headphone volume knob."
|
||||||
"volume knob, which controls the volume of the analogue "
|
|
||||||
"outputs which have been set to “HW”."
|
|
||||||
);
|
);
|
||||||
gtk_grid_attach(GTK_GRID(output_grid), l, 0, 0, 1, 1);
|
gtk_grid_attach(GTK_GRID(output_grid), l, 1, 0, 1, 1);
|
||||||
w = make_volume_alsa_elem(elem);
|
w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_GEN4_VOLUME, 0);
|
||||||
gtk_grid_attach(GTK_GRID(output_grid), w, 0, 1, 1, 1);
|
gtk_grid_attach(GTK_GRID(output_grid), w, 1, 1, 1, 1);
|
||||||
} else if (strcmp(elem->name, "Mute Playback Switch") == 0) {
|
} else if (strcmp(elem->name, "Mute Playback Switch") == 0) {
|
||||||
w = make_boolean_alsa_elem(
|
w = make_boolean_alsa_elem(
|
||||||
elem, "*audio-volume-high", "*audio-volume-muted"
|
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_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) {
|
} else if (strcmp(elem->name, "Dim Playback Switch") == 0) {
|
||||||
w = make_boolean_alsa_elem(
|
w = make_boolean_alsa_elem(
|
||||||
elem, "*audio-volume-medium", "*audio-volume-low"
|
elem, "*audio-volume-medium", "*audio-volume-low"
|
||||||
);
|
);
|
||||||
|
gtk_widget_add_css_class(w, "dim");
|
||||||
gtk_widget_set_tooltip_text(
|
gtk_widget_set_tooltip_text(
|
||||||
w, "Dim (lower volume) of HW controlled outputs"
|
w, "Dim (lower volume) of HW controlled outputs"
|
||||||
);
|
);
|
||||||
@@ -360,37 +769,59 @@ static void create_global_controls(
|
|||||||
? GTK_ORIENTATION_HORIZONTAL
|
? GTK_ORIENTATION_HORIZONTAL
|
||||||
: GTK_ORIENTATION_VERTICAL;
|
: GTK_ORIENTATION_VERTICAL;
|
||||||
GtkWidget *global_controls = create_global_box(top, x, orient);
|
GtkWidget *global_controls = create_global_box(top, x, orient);
|
||||||
GtkWidget *left = global_controls;
|
GtkWidget *column[3];
|
||||||
GtkWidget *right = global_controls;
|
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
column[i] = global_controls;
|
||||||
|
|
||||||
if (card->has_speaker_switching) {
|
if (card->has_speaker_switching) {
|
||||||
left = gtk_box_new(GTK_ORIENTATION_VERTICAL, 15);
|
for (int i = 0; i < 3; i++) {
|
||||||
right = gtk_box_new(GTK_ORIENTATION_VERTICAL, 15);
|
column[i] = gtk_box_new(GTK_ORIENTATION_VERTICAL, 15);
|
||||||
gtk_box_append(GTK_BOX(global_controls), left);
|
gtk_box_append(GTK_BOX(global_controls), column[i]);
|
||||||
gtk_box_append(GTK_BOX(global_controls), right);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
add_clock_source_control(card, left);
|
add_clock_source_control(card, column[0]);
|
||||||
add_sync_status_control(card, right);
|
add_sync_status_control(card, column[1]);
|
||||||
add_speaker_switching_controls(card, left);
|
add_power_status_control(card, column[1]);
|
||||||
add_talkback_controls(card, right);
|
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) {
|
static GtkWidget *create_main_window_controls(struct alsa_card *card) {
|
||||||
int x = 0;
|
int x = 0;
|
||||||
|
|
||||||
GtkWidget *top = gtk_grid_new();
|
GtkWidget *top = gtk_grid_new();
|
||||||
gtk_widget_set_margin(top, 10);
|
gtk_widget_add_css_class(top, "window-content");
|
||||||
gtk_grid_set_spacing(GTK_GRID(top), 10);
|
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_global_controls(card, top, &x);
|
||||||
create_input_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 {
|
} else {
|
||||||
create_output_controls(card, top, &x, 0, 1);
|
create_output_controls(card, top, &x, 0, 1);
|
||||||
}
|
}
|
||||||
@@ -425,13 +856,38 @@ static gboolean window_levels_close_request(GtkWindow *w, gpointer data) {
|
|||||||
return true;
|
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) {
|
GtkWidget *create_iface_mixer_main(struct alsa_card *card) {
|
||||||
card->has_speaker_switching =
|
card->has_speaker_switching =
|
||||||
!!get_elem_by_name(card->elems, "Speaker Switching Playback Enum");
|
!!get_elem_by_name(card->elems, "Speaker Switching Playback Enum");
|
||||||
card->has_talkback =
|
card->has_talkback =
|
||||||
!!get_elem_by_name(card->elems, "Talkback Playback Enum");
|
!!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);
|
GtkWidget *routing_top = create_routing_controls(card);
|
||||||
if (!routing_top)
|
if (!routing_top)
|
||||||
@@ -441,7 +897,7 @@ GtkWidget *create_iface_mixer_main(struct alsa_card *card) {
|
|||||||
card, "Routing", G_CALLBACK(window_routing_close_request)
|
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);
|
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)
|
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);
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "gtkhelper.h"
|
#include "gtkhelper.h"
|
||||||
@@ -6,38 +6,49 @@
|
|||||||
#include "stringhelper.h"
|
#include "stringhelper.h"
|
||||||
#include "tooltips.h"
|
#include "tooltips.h"
|
||||||
#include "widget-boolean.h"
|
#include "widget-boolean.h"
|
||||||
#include "widget-combo.h"
|
#include "widget-drop-down.h"
|
||||||
#include "window-helper.h"
|
#include "window-helper.h"
|
||||||
#include "window-startup.h"
|
#include "window-startup.h"
|
||||||
|
|
||||||
GtkWidget *create_iface_no_mixer_main(struct alsa_card *card) {
|
GtkWidget *create_iface_no_mixer_main(struct alsa_card *card) {
|
||||||
GArray *elems = card->elems;
|
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 *label_ic = gtk_label_new("Input Controls");
|
||||||
GtkWidget *vert_sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
|
|
||||||
GtkWidget *label_oc = gtk_label_new("Output Controls");
|
GtkWidget *label_oc = gtk_label_new("Output Controls");
|
||||||
|
|
||||||
gtk_widget_set_margin(grid, 10);
|
gtk_widget_add_css_class(label_ic, "controls-label");
|
||||||
gtk_grid_set_spacing(GTK_GRID(grid), 10);
|
gtk_widget_add_css_class(label_oc, "controls-label");
|
||||||
|
|
||||||
gtk_grid_attach(GTK_GRID(grid), label_ic, 0, 0, 1, 1);
|
gtk_widget_set_halign(label_ic, GTK_ALIGN_START);
|
||||||
gtk_grid_attach(GTK_GRID(grid), vert_sep, 1, 0, 1, 3);
|
gtk_widget_set_halign(label_oc, GTK_ALIGN_START);
|
||||||
gtk_grid_attach(GTK_GRID(grid), label_oc, 2, 0, 1, 1);
|
|
||||||
|
|
||||||
GtkWidget *horiz_input_sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
|
gtk_box_append(GTK_BOX(input_box), label_ic);
|
||||||
gtk_grid_attach(GTK_GRID(grid), horiz_input_sep, 0, 1, 1, 1);
|
gtk_box_append(GTK_BOX(output_box), label_oc);
|
||||||
|
|
||||||
GtkWidget *input_grid = gtk_grid_new();
|
GtkWidget *input_grid = gtk_grid_new();
|
||||||
gtk_grid_set_spacing(GTK_GRID(input_grid), 10);
|
gtk_grid_set_spacing(GTK_GRID(input_grid), 10);
|
||||||
gtk_grid_attach(GTK_GRID(grid), input_grid, 0, 2, 1, 1);
|
gtk_widget_add_css_class(input_grid, "controls-content");
|
||||||
|
gtk_widget_set_vexpand(input_grid, TRUE);
|
||||||
GtkWidget *horiz_output_sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
|
gtk_box_append(GTK_BOX(input_box), input_grid);
|
||||||
gtk_grid_attach(GTK_GRID(grid), horiz_output_sep, 2, 1, 1, 1);
|
|
||||||
|
|
||||||
GtkWidget *output_grid = gtk_grid_new();
|
GtkWidget *output_grid = gtk_grid_new();
|
||||||
gtk_grid_set_spacing(GTK_GRID(output_grid), 10);
|
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 or 2i2?
|
||||||
// Solo Phantom Power is Line 1 only
|
// 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++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
char s[20];
|
char s[20];
|
||||||
snprintf(s, 20, "Analogue %d", i + 1);
|
snprintf(s, 20, "%d", i + 1);
|
||||||
GtkWidget *label = gtk_label_new(s);
|
GtkWidget *label = gtk_label_new(s);
|
||||||
gtk_grid_attach(GTK_GRID(input_grid), label, i, 0, 1, 1);
|
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);
|
int line_num = get_num_from_string(elem->name);
|
||||||
|
|
||||||
if (strstr(elem->name, "Level Capture Enum")) {
|
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_widget_set_tooltip_text(w, level_descr);
|
||||||
gtk_grid_attach(GTK_GRID(input_grid), w, line_num - 1, 1, 1, 1);
|
gtk_grid_attach(GTK_GRID(input_grid), w, line_num - 1, 1, 1, 1);
|
||||||
} else if (strstr(elem->name, "Air Capture Switch")) {
|
} 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_widget_set_tooltip_text(w, air_descr);
|
||||||
gtk_grid_attach(
|
gtk_grid_attach(
|
||||||
GTK_GRID(input_grid), w, line_num - 1, 1 + !is_solo, 1, 1
|
GTK_GRID(input_grid), w, line_num - 1, 1 + !is_solo, 1, 1
|
||||||
);
|
);
|
||||||
} else if (strstr(elem->name, "Phantom Power Capture Switch")) {
|
} 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_widget_set_tooltip_text(w, phantom_descr);
|
||||||
gtk_grid_attach(GTK_GRID(input_grid), w, 0, 3, 1 + !is_solo, 1);
|
gtk_grid_attach(GTK_GRID(input_grid), w, 0, 3, 1 + !is_solo, 1);
|
||||||
} else if (strcmp(elem->name, "Direct Monitor Playback Switch") == 0) {
|
} else if (strcmp(elem->name, "Direct Monitor Playback Switch") == 0) {
|
||||||
w = make_boolean_alsa_elem(
|
w = make_boolean_alsa_elem(elem, "Direct Monitor", NULL);
|
||||||
elem, "Direct Monitor Off", "Direct Monitor On"
|
gtk_widget_add_css_class(w, "direct-monitor");
|
||||||
);
|
|
||||||
gtk_widget_set_tooltip_text(
|
gtk_widget_set_tooltip_text(
|
||||||
w,
|
w,
|
||||||
"Direct Monitor sends the analogue input signals to the "
|
"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);
|
gtk_grid_attach(GTK_GRID(output_grid), w, 0, 0, 1, 1);
|
||||||
} else if (strcmp(elem->name, "Direct Monitor Playback Enum") == 0) {
|
} else if (strcmp(elem->name, "Direct Monitor Playback Enum") == 0) {
|
||||||
GtkWidget *l = gtk_label_new("Direct Monitor");
|
w = make_drop_down_alsa_elem(elem, "Direct Monitor");
|
||||||
gtk_grid_attach(GTK_GRID(output_grid), l, 0, 0, 1, 1);
|
gtk_widget_add_css_class(w, "direct-monitor");
|
||||||
w = make_combo_box_alsa_elem(elem);
|
|
||||||
gtk_widget_set_tooltip_text(
|
gtk_widget_set_tooltip_text(
|
||||||
w,
|
w,
|
||||||
"Direct Monitor sends the analogue input signals to the "
|
"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 "
|
"both inputs to the left and right outputs. Stereo sends "
|
||||||
"input 1 to the left, and input 2 to the right output."
|
"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);
|
GtkWidget *startup = create_startup_controls(card);
|
||||||
gtk_window_set_child(GTK_WINDOW(card->window_startup), startup);
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "alsa.h"
|
||||||
#include "iface-none.h"
|
#include "iface-none.h"
|
||||||
#include "gtkhelper.h"
|
#include "gtkhelper.h"
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
@@ -11,20 +12,22 @@ GtkWidget *create_window_iface_none(GtkApplication *app) {
|
|||||||
GtkWidget *picture = gtk_picture_new_for_resource(
|
GtkWidget *picture = gtk_picture_new_for_resource(
|
||||||
"/vu/b4/alsa-scarlett-gui/icons/alsa-scarlett-gui-logo.png"
|
"/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), picture);
|
||||||
gtk_box_append(GTK_BOX(box), label);
|
gtk_box_append(GTK_BOX(box), label);
|
||||||
|
|
||||||
GtkWidget *w = gtk_application_window_new(app);
|
GtkWidget *w = gtk_application_window_new(app);
|
||||||
gtk_window_set_resizable(GTK_WINDOW(w), FALSE);
|
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_window_set_child(GTK_WINDOW(w), box);
|
||||||
gtk_application_window_set_show_menubar(
|
gtk_application_window_set_show_menubar(
|
||||||
GTK_APPLICATION_WINDOW(w), TRUE
|
GTK_APPLICATION_WINDOW(w), TRUE
|
||||||
);
|
);
|
||||||
add_window_action_map(GTK_WINDOW(w));
|
add_window_action_map(GTK_WINDOW(w));
|
||||||
gtk_widget_show(w);
|
if (!alsa_has_reopen_callbacks()) {
|
||||||
|
gtk_widget_set_visible(w, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
return w;
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "gtkhelper.h"
|
#include "gtkhelper.h"
|
||||||
@@ -8,19 +8,21 @@ GtkWidget *create_iface_unknown_main(void) {
|
|||||||
GtkWidget *label = gtk_label_new(
|
GtkWidget *label = gtk_label_new(
|
||||||
"Sorry, I don’t recognise the controls on this card.\n\n"
|
"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 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"
|
"enabled?\n\n"
|
||||||
|
|
||||||
"Check dmesg output for “Focusrite Scarlett Gen 2/3 Mixer "
|
"Check dmesg output for “Focusrite ... Mixer Driver”:\n\n"
|
||||||
"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."
|
"with an “options snd_usb_audio ...” line and reboot."
|
||||||
);
|
);
|
||||||
gtk_widget_set_margin(label, 30);
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "alsa.h"
|
#include "alsa.h"
|
||||||
#include "alsa-sim.h"
|
#include "alsa-sim.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
|
#include "scarlett2-firmware.h"
|
||||||
#include "window-hardware.h"
|
#include "window-hardware.h"
|
||||||
#include "window-iface.h"
|
#include "window-iface.h"
|
||||||
|
|
||||||
@@ -32,12 +33,10 @@ static void load_css(void) {
|
|||||||
static void startup(GtkApplication *app, gpointer user_data) {
|
static void startup(GtkApplication *app, gpointer user_data) {
|
||||||
gtk_application_set_menubar(app, G_MENU_MODEL(create_app_menu(app)));
|
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();
|
load_css();
|
||||||
|
|
||||||
alsa_scan_cards();
|
scarlett2_enum_firmware();
|
||||||
|
alsa_init();
|
||||||
|
|
||||||
create_no_card_window();
|
create_no_card_window();
|
||||||
create_hardware_window(app);
|
create_hardware_window(app);
|
||||||
@@ -63,7 +62,9 @@ static void open_cb(
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
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, "startup", G_CALLBACK(startup), NULL);
|
||||||
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
|
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
|
||||||
g_signal_connect(app, "open", G_CALLBACK(open_cb), 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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "about.h"
|
#include "about.h"
|
||||||
@@ -6,20 +6,25 @@
|
|||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
#include "window-hardware.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(
|
static void activate_hardware(
|
||||||
GSimpleAction *action,
|
GSimpleAction *action,
|
||||||
GVariant *parameter,
|
GVariant *parameter,
|
||||||
gpointer data
|
gpointer data
|
||||||
) {
|
) {
|
||||||
GVariant *state = g_action_get_state(G_ACTION(action));
|
(void) data;
|
||||||
|
update_visibility(action, window_hardware);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void activate_quit(
|
static void activate_quit(
|
||||||
@@ -37,15 +42,7 @@ static void activate_routing(
|
|||||||
) {
|
) {
|
||||||
struct alsa_card *card = data;
|
struct alsa_card *card = data;
|
||||||
|
|
||||||
GVariant *state = g_action_get_state(G_ACTION(action));
|
update_visibility(action, card->window_routing);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void activate_mixer(
|
static void activate_mixer(
|
||||||
@@ -55,15 +52,7 @@ static void activate_mixer(
|
|||||||
) {
|
) {
|
||||||
struct alsa_card *card = data;
|
struct alsa_card *card = data;
|
||||||
|
|
||||||
GVariant *state = g_action_get_state(G_ACTION(action));
|
update_visibility(action, card->window_mixer);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void activate_levels(
|
static void activate_levels(
|
||||||
@@ -73,15 +62,7 @@ static void activate_levels(
|
|||||||
) {
|
) {
|
||||||
struct alsa_card *card = data;
|
struct alsa_card *card = data;
|
||||||
|
|
||||||
GVariant *state = g_action_get_state(G_ACTION(action));
|
update_visibility(action, card->window_levels);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void activate_startup(
|
static void activate_startup(
|
||||||
@@ -91,15 +72,7 @@ static void activate_startup(
|
|||||||
) {
|
) {
|
||||||
struct alsa_card *card = data;
|
struct alsa_card *card = data;
|
||||||
|
|
||||||
GVariant *state = g_action_get_state(G_ACTION(action));
|
update_visibility(action, card->window_startup);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const GActionEntry app_entries[] = {
|
static const GActionEntry app_entries[] = {
|
||||||
@@ -107,6 +80,66 @@ static const GActionEntry app_entries[] = {
|
|||||||
{"quit", activate_quit},
|
{"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) {
|
GMenu *create_app_menu(GtkApplication *app) {
|
||||||
g_action_map_add_action_entries(
|
g_action_map_add_action_entries(
|
||||||
G_ACTION_MAP(app), app_entries, G_N_ELEMENTS(app_entries), app
|
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 *menu = g_menu_new();
|
||||||
|
|
||||||
GMenu *file_menu = g_menu_new();
|
for (const struct menu_data *menu_data = menus;
|
||||||
g_menu_append_submenu(menu, "_File", G_MENU_MODEL(file_menu));
|
menu_data->label;
|
||||||
g_menu_append(file_menu, "_Load Configuration", "win.load");
|
menu_data++)
|
||||||
g_menu_append(file_menu, "_Save Configuration", "win.save");
|
populate_submenu(app, menu, menu_data);
|
||||||
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");
|
|
||||||
|
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
@@ -176,7 +195,10 @@ void add_startup_action_map(struct alsa_card *card) {
|
|||||||
|
|
||||||
static const GActionEntry mixer_entries[] = {
|
static const GActionEntry mixer_entries[] = {
|
||||||
{"routing", activate_routing, NULL, "false"},
|
{"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"}
|
{"levels", activate_levels, NULL, "false"}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -187,4 +209,16 @@ void add_mixer_action_map(struct alsa_card *card) {
|
|||||||
G_N_ELEMENTS(mixer_entries),
|
G_N_ELEMENTS(mixer_entries),
|
||||||
card
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "routing-drag-line.h"
|
#include "routing-drag-line.h"
|
||||||
@@ -40,6 +40,48 @@ static void drag_motion(
|
|||||||
|
|
||||||
card->drag_x = x;
|
card->drag_x = x;
|
||||||
card->drag_y = y;
|
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->drag_line);
|
||||||
gtk_widget_queue_draw(card->routing_lines);
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "alsa.h"
|
#include "alsa.h"
|
||||||
#include "routing-lines.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 };
|
static const double dash_dotted[] = { 1, 10 };
|
||||||
|
|
||||||
// dash when dragging and not connected
|
// dash when dragging and not connected
|
||||||
static const double dash[] = { 4 };
|
static const double dash[] = { 4 };
|
||||||
|
|
||||||
|
// 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
|
||||||
|
) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
*r += m;
|
||||||
|
*g += m;
|
||||||
|
*b += m;
|
||||||
|
}
|
||||||
|
|
||||||
static void choose_line_colour(
|
static void choose_line_colour(
|
||||||
struct routing_src *r_src,
|
int i,
|
||||||
struct routing_dst *r_dst,
|
int count,
|
||||||
double *r,
|
double *r,
|
||||||
double *g,
|
double *g,
|
||||||
double *b
|
double *b
|
||||||
) {
|
) {
|
||||||
// left channels have odd numbers
|
if (count % 2)
|
||||||
// right channels have even numbers
|
count++;
|
||||||
int odd_src = r_src->lr_num & 1;
|
hsl_to_rgb(
|
||||||
int odd_dst = r_dst->elem->lr_num & 1;
|
((i / (count / 2) * 360 + i * 720) / count) % 360,
|
||||||
|
0.75,
|
||||||
// for colouring, pair channels up
|
0.5,
|
||||||
// 0 for odd pairs, 1 for even pairs
|
r, g, b
|
||||||
int src2 = ((r_src->lr_num - 1) / 2 & 1);
|
);
|
||||||
int dst2 = ((r_dst->elem->lr_num - 1) / 2 & 1);
|
|
||||||
|
|
||||||
// left -> left, black
|
|
||||||
if (odd_src && odd_dst) {
|
|
||||||
*r = 0;
|
|
||||||
*g = 0;
|
|
||||||
*b = 0;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw a bezier curve given the end and control points
|
// draw a bezier curve given the end and control points
|
||||||
@@ -160,16 +142,16 @@ static void arrow(
|
|||||||
cairo_close_path(cr);
|
cairo_close_path(cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw a nice curved line connecting a source at (x1, y1) and a
|
// draw a nice curved line connecting a source at (x1, y1) and a sink
|
||||||
// destination at (x2, y2)
|
// at (x2, y2)
|
||||||
static void draw_connection(
|
static void draw_connection(
|
||||||
cairo_t *cr,
|
cairo_t *cr,
|
||||||
double x1,
|
double x1,
|
||||||
double y1,
|
double y1,
|
||||||
int src_is_mixer,
|
int src_port_category,
|
||||||
double x2,
|
double x2,
|
||||||
double y2,
|
double y2,
|
||||||
int dst_is_mixer,
|
int snk_port_category,
|
||||||
double r,
|
double r,
|
||||||
double g,
|
double g,
|
||||||
double b,
|
double b,
|
||||||
@@ -177,8 +159,11 @@ static void draw_connection(
|
|||||||
) {
|
) {
|
||||||
double x3 = x1, y3 = y1, x4 = x2, y4 = y2;
|
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?
|
// vertical/horizontal?
|
||||||
if (src_is_mixer == dst_is_mixer) {
|
if (src_is_mixer == snk_is_mixer) {
|
||||||
double f1 = 0.3;
|
double f1 = 0.3;
|
||||||
double f2 = 1 - f1;
|
double f2 = 1 - f1;
|
||||||
|
|
||||||
@@ -222,7 +207,7 @@ static void draw_connection(
|
|||||||
|
|
||||||
// locate the center of a widget in the parent coordinates
|
// locate the center of a widget in the parent coordinates
|
||||||
// used for drawing lines to/from the "socket" widget of routing
|
// used for drawing lines to/from the "socket" widget of routing
|
||||||
// sources and destinations
|
// sources and sinks
|
||||||
static void get_widget_center(
|
static void get_widget_center(
|
||||||
GtkWidget *w,
|
GtkWidget *w,
|
||||||
GtkWidget *parent,
|
GtkWidget *parent,
|
||||||
@@ -241,23 +226,22 @@ static void get_src_center(
|
|||||||
double *y
|
double *y
|
||||||
) {
|
) {
|
||||||
get_widget_center(r_src->widget2, parent, x, 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)++;
|
(*y)++;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void get_dst_center(
|
static void get_snk_center(
|
||||||
struct routing_dst *r_dst,
|
struct routing_snk *r_snk,
|
||||||
GtkWidget *parent,
|
GtkWidget *parent,
|
||||||
double *x,
|
double *x,
|
||||||
double *y
|
double *y
|
||||||
) {
|
) {
|
||||||
get_widget_center(r_dst->elem->widget2, parent, x, y);
|
get_widget_center(r_snk->socket_widget, parent, x, y);
|
||||||
if (r_dst->port_category == PC_MIX)
|
if (IS_MIXER(r_snk->port_category))
|
||||||
(*y)++;
|
(*y)++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// redraw the overlay lines between the routing sources and
|
// redraw the overlay lines between the routing sources and sinks
|
||||||
// destinations
|
|
||||||
void draw_routing_lines(
|
void draw_routing_lines(
|
||||||
GtkDrawingArea *drawing_area,
|
GtkDrawingArea *drawing_area,
|
||||||
cairo_t *cr,
|
cairo_t *cr,
|
||||||
@@ -272,22 +256,22 @@ void draw_routing_lines(
|
|||||||
|
|
||||||
int dragging = card->drag_type != DRAG_TYPE_NONE;
|
int dragging = card->drag_type != DRAG_TYPE_NONE;
|
||||||
|
|
||||||
// go through all the routing destinations
|
// go through all the routing sinks
|
||||||
for (int i = 0; i < card->routing_dsts->len; i++) {
|
for (int i = 0; i < card->routing_snks->len; i++) {
|
||||||
struct routing_dst *r_dst = &g_array_index(
|
struct routing_snk *r_snk = &g_array_index(
|
||||||
card->routing_dsts, struct routing_dst, i
|
card->routing_snks, struct routing_snk, i
|
||||||
);
|
);
|
||||||
|
|
||||||
// if dragging and a routing destination is being reconnected then
|
// if dragging and a routing sink is being reconnected then draw
|
||||||
// draw it with dots
|
// it with dots
|
||||||
int dragging_this = dragging && card->dst_drag == r_dst;
|
int dragging_this = dragging && card->snk_drag == r_snk;
|
||||||
if (dragging_this)
|
if (dragging_this)
|
||||||
cairo_set_dash(cr, dash_dotted, 2, 0);
|
cairo_set_dash(cr, dash_dotted, 2, 0);
|
||||||
else
|
else
|
||||||
cairo_set_dash(cr, NULL, 0, 0);
|
cairo_set_dash(cr, NULL, 0, 0);
|
||||||
|
|
||||||
// get the destination and skip if it's "Off"
|
// get the sink and skip if it's "Off"
|
||||||
int r_src_idx = alsa_get_elem_value(r_dst->elem);
|
int r_src_idx = alsa_get_elem_value(r_snk->elem);
|
||||||
if (!r_src_idx)
|
if (!r_src_idx)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -296,14 +280,14 @@ void draw_routing_lines(
|
|||||||
card->routing_srcs, struct routing_src, r_src_idx
|
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;
|
double x1, y1, x2, y2;
|
||||||
get_src_center(r_src, parent, &x1, &y1);
|
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
|
// pick a colour
|
||||||
double r, g, b;
|
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
|
// make the colour lighter if it's being shown dotted
|
||||||
if (dragging_this) {
|
if (dragging_this) {
|
||||||
@@ -315,8 +299,8 @@ void draw_routing_lines(
|
|||||||
// draw the connection
|
// draw the connection
|
||||||
draw_connection(
|
draw_connection(
|
||||||
cr,
|
cr,
|
||||||
x1, y1, r_src->port_category == PC_MIX,
|
x1, y1, r_src->port_category,
|
||||||
x2, y2, r_dst->port_category == PC_MIX,
|
x2, y2, r_snk->port_category,
|
||||||
r, g, b, 2
|
r, g, b, 2
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -333,19 +317,19 @@ void draw_drag_line(
|
|||||||
struct alsa_card *card = user_data;
|
struct alsa_card *card = user_data;
|
||||||
GtkWidget *parent = card->drag_line;
|
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
|
// bounds then do nothing
|
||||||
if (card->drag_type == DRAG_TYPE_NONE ||
|
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_x < 0 ||
|
||||||
card->drag_y < 0)
|
card->drag_y < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// the drag mouse position is relative to card->routing_grid
|
// the drag mouse position is relative to card->routing_grid
|
||||||
// translate it to the overlay card->drag_line
|
// 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;
|
double drag_x, drag_y;
|
||||||
if (!card->src_drag || !card->dst_drag)
|
if (!card->src_drag || !card->snk_drag)
|
||||||
gtk_widget_translate_coordinates(
|
gtk_widget_translate_coordinates(
|
||||||
card->routing_grid, parent,
|
card->routing_grid, parent,
|
||||||
card->drag_x, card->drag_y,
|
card->drag_x, card->drag_y,
|
||||||
@@ -362,31 +346,31 @@ void draw_drag_line(
|
|||||||
y1 = drag_y;
|
y1 = drag_y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the line end position; either a routing destination socket
|
// get the line end position; either a routing sink socket widget or
|
||||||
// widget or the drag mouse position
|
// the drag mouse position
|
||||||
double x2, y2;
|
double x2, y2;
|
||||||
if (card->dst_drag) {
|
if (card->snk_drag) {
|
||||||
get_dst_center(card->dst_drag, parent, &x2, &y2);
|
get_snk_center(card->snk_drag, parent, &x2, &y2);
|
||||||
} else {
|
} else {
|
||||||
x2 = drag_x;
|
x2 = drag_x;
|
||||||
y2 = drag_y;
|
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)
|
// it was connected (except black)
|
||||||
if (card->src_drag && card->dst_drag) {
|
if (card->src_drag && card->snk_drag) {
|
||||||
draw_connection(
|
draw_connection(
|
||||||
cr,
|
cr,
|
||||||
x1, y1, card->src_drag->port_category == PC_MIX,
|
x1, y1, card->src_drag->port_category,
|
||||||
x2, y2, card->dst_drag->port_category == PC_MIX,
|
x2, y2, card->snk_drag->port_category,
|
||||||
0, 0, 0, 2
|
1, 1, 1, 2
|
||||||
);
|
);
|
||||||
|
|
||||||
// otherwise draw a straight line
|
// otherwise draw a straight line
|
||||||
} else {
|
} else {
|
||||||
cairo_set_dash(cr, dash, 1, 0);
|
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_set_line_width(cr, 2);
|
||||||
cairo_move_to(cr, x1, y1);
|
cairo_move_to(cr, x1, y1);
|
||||||
cairo_line_to(cr, x2, y2);
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <ctype.h>
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "tooltips.h"
|
#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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=ALSA Scarlett Gen 2/3 Control Panel
|
Name=ALSA Scarlett2 Control Panel
|
||||||
Icon=vu.b4.alsa-scarlett-gui
|
Icon=vu.b4.alsa-scarlett-gui
|
||||||
Exec=PREFIX/bin/alsa-scarlett-gui
|
Exec=PREFIX/bin/alsa-scarlett-gui
|
||||||
Categories=GTK;AudioVideo;Audio;Mixer;
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "widget-boolean.h"
|
#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) {
|
static void button_clicked(GtkWidget *widget, struct alsa_elem *elem) {
|
||||||
int value = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
|
int value = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
|
||||||
|
|
||||||
alsa_set_elem_value(elem, value);
|
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)
|
if (!text)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (*text == '*') {
|
if (*text == '*') {
|
||||||
GtkWidget *icon = gtk_image_new_from_icon_name(text + 1);
|
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 {
|
} 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);
|
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);
|
int value = !!alsa_get_elem_value(elem);
|
||||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(elem->widget), value);
|
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(
|
GtkWidget *make_boolean_alsa_elem(
|
||||||
@@ -36,23 +47,24 @@ GtkWidget *make_boolean_alsa_elem(
|
|||||||
const char *disabled_text,
|
const char *disabled_text,
|
||||||
const char *enabled_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(
|
g_signal_connect(
|
||||||
button, "clicked", G_CALLBACK(button_clicked), elem
|
data->button, "clicked", G_CALLBACK(button_clicked), elem
|
||||||
);
|
);
|
||||||
elem->widget = button;
|
alsa_elem_add_callback(elem, toggle_button_updated, data);
|
||||||
elem->widget_callback = toggle_button_updated;
|
data->text[0] = disabled_text;
|
||||||
elem->bool_text[0] = disabled_text;
|
data->text[1] = enabled_text;
|
||||||
elem->bool_text[1] = enabled_text;
|
|
||||||
|
|
||||||
// find the maximum width and height of both possible labels
|
// find the maximum width and height of both possible labels
|
||||||
int max_width = 0, max_height = 0;
|
int max_width = 0, max_height = 0;
|
||||||
for (int i = 0; i < 2; i++) {
|
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();
|
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)
|
if (size->width > max_width)
|
||||||
max_width = size->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
|
// set the widget minimum size to the maximum label size so that the
|
||||||
// widget doesn't change size when the label changes
|
// 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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#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
|
||||||
|
);
|
||||||