Compare commits
90 Commits
0.4.1_git2
...
deb
| Author | SHA1 | Date | |
|---|---|---|---|
| 921944e64e | |||
| 9e00217cec | |||
| 04ad890f16 | |||
| a89558d5a7 | |||
| 5b8bdaca4b | |||
| d8b08b46fc | |||
| b33d2ace99 | |||
| 5ddb8b020b | |||
| 3831f4e0c0 | |||
| d8ff8876b4 | |||
| 036b9d1a94 | |||
| 8401d9eada | |||
| be28bf0dc8 | |||
| 0857a02c77 | |||
| e50d13b992 | |||
| 41f1593443 | |||
| 46652862d3 | |||
| 5bd8a5de42 | |||
| 646a62afdb | |||
| 169feb563f | |||
| fb99a3783f | |||
| a3d77ee98a | |||
|
|
e6fbb4f146 | ||
|
|
e4dc805422 | ||
|
|
87ee0ed66b | ||
|
|
adeea461fd | ||
|
|
1f7bafbfc3 | ||
|
|
b8420ba31c | ||
|
|
a5676eeb5a | ||
|
|
9a33b92392 | ||
|
|
97f993db7b | ||
|
|
6f0ab1890d | ||
|
|
c88f7796f4 | ||
|
|
0b5b47ae66 | ||
|
|
b6117a501f | ||
|
|
a34df84dfa | ||
|
|
6677e5c87d | ||
|
|
91fc3bbb03 | ||
|
|
460b03c668 | ||
|
|
8a2e5f5835 | ||
|
|
72fd974da1 | ||
|
|
e6166de04b | ||
|
|
f0213eadb1 | ||
|
|
ae23674f21 | ||
|
|
68e45e58a6 | ||
|
|
f1585a3b8c | ||
|
|
d1c1eb5db2 | ||
|
|
21cdfbbe1a | ||
|
|
7033f9f622 | ||
|
|
5106ed228e | ||
|
|
ab40037064 | ||
|
|
ed4f9cbaa7 | ||
|
|
c7357c0539 | ||
|
|
01c947f434 | ||
|
|
dc21eb52d0 | ||
|
|
c4ab20f9b5 | ||
|
|
b41a47587b | ||
|
|
11dba2b42c | ||
|
|
f1f085abcf | ||
|
|
18841b2a45 | ||
|
|
64d9f8173a | ||
|
|
78e2d9642f | ||
|
|
4a40b00695 | ||
|
|
0f7389dca8 | ||
|
|
640d027502 | ||
|
|
2bc6c86a8d | ||
|
|
67ccd1d684 | ||
|
|
81bc3c77c8 | ||
|
|
e0083f7085 | ||
|
|
5da140df1e | ||
|
|
1b0e072237 | ||
|
|
8178bd298b | ||
|
|
845dd5c98b | ||
|
|
45287711a4 | ||
|
|
b1831c137a | ||
|
|
1cdac65c00 | ||
|
|
c38bbba793 | ||
|
|
da1f011ab4 | ||
|
|
64f0cc36cc | ||
|
|
9034790c06 | ||
|
|
91d7218a47 | ||
|
|
97ced90466 | ||
|
|
fa3e73d52f | ||
|
|
602854d087 | ||
|
|
97ca9ae754 | ||
|
|
6a04e1d1fa | ||
|
|
da4be2993e | ||
|
|
abdb7f40f5 | ||
|
|
0187698826 | ||
| 05ae063b90 |
4
.github/workflows/build-debian-package.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
- name: Build from sources
|
||||
run: |
|
||||
make -C src -j4 PREFIX=/usr
|
||||
make -C src -j$(nproc) PREFIX=/usr
|
||||
|
||||
- name: Prepare package workspace
|
||||
run: |
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
maintainer: geoffreybennett
|
||||
depends: 'libgtk-4-1, libasound2, alsa-utils'
|
||||
version: ${{ env.APP_VERSION }}
|
||||
desc: ${{ env.APP_NAME }} is a Gtk4 GUI for the ALSA controls presented by the Linux kernel Focusrite Scarlett2 Mixer Driver.
|
||||
desc: ${{ env.APP_NAME }} is a Gtk4 GUI for the ALSA controls presented by the Linux kernel Focusrite USB drivers.
|
||||
|
||||
- name: Upload Release Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
|
||||
2
.github/workflows/build-flatpak-package.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
name: "Flatpak"
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: bilelmoussaoui/flatpak-github-actions:gnome-45
|
||||
image: bilelmoussaoui/flatpak-github-actions:gnome-47
|
||||
options: --privileged
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
140
FAQ.md
@@ -1,31 +1,34 @@
|
||||
# FAQ for the Scarlett2 Mixer Driver and `alsa-scarlett-gui`
|
||||
# FAQ for the ALSA Scarlett Control Panel (`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.
|
||||
The ALSA Scarlett Control Panel (`alsa-scarlett-gui`) is an
|
||||
easy-to-use application for adjusting the ALSA controls provided by
|
||||
three Linux kernel drivers for Focusrite USB interfaces:
|
||||
|
||||
1. The Scarlett 1st Gen Mixer Driver (for 1st Gen 6i6, 8i6, 18i6, 18i8, 18i20)
|
||||
2. The Scarlett2 Protocol Driver (for 2nd/3rd Gen interfaces, small 4th Gen, Clarett, and Vocaster)
|
||||
3. The FCP (Focusrite Control Protocol) Driver (for big 4th Gen interfaces: 16i16, 18i16, 18i20)
|
||||
|
||||
To check if your kernel is already up-to-date, and how to upgrade if
|
||||
not, see the [Control Panel Installation Prerequisites — Linux
|
||||
Kernel](docs/INSTALL.md).
|
||||
|
||||
`alsa-scarlett-gui` is an easy-to-use application to adjust those
|
||||
controls.
|
||||
## Do I need these drivers for my Focusrite interface?
|
||||
|
||||
## Do I need the driver for my Focusrite interface?
|
||||
|
||||
In order to get audio working? No. Focusrite USB interfaces are
|
||||
For basic audio functionality? 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).
|
||||
|
||||
However, to access the mixer, routing, and hardware-specific features,
|
||||
you’ll need the appropriate driver for your interface model.
|
||||
|
||||
## MSD Mode?
|
||||
|
||||
“MSD Mode” is the “Mass Storage Device Mode” that the Scarlett 3rd and
|
||||
"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
|
||||
@@ -40,49 +43,112 @@ 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?
|
||||
If you do the recommended/required (depending on the model) firmware
|
||||
update, MSD Mode will automatically be turned off.
|
||||
|
||||
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 is the purpose of these drivers if they’re not needed for basic audio?
|
||||
|
||||
These drivers are for users who want more control over their
|
||||
interface. They allow for detailed manipulation of:
|
||||
|
||||
- Internal audio routing
|
||||
- Hardware-specific settings
|
||||
- Mixer functionality
|
||||
- Level monitoring
|
||||
- Input/output configuration
|
||||
|
||||
These controls go beyond the basic audio I/O functionality provided by
|
||||
the generic ALSA USB audio driver.
|
||||
|
||||
## 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).
|
||||
The ALSA Scarlett Control Panel supports:
|
||||
|
||||
- All Scarlett 3rd Gen interfaces.
|
||||
- **Scarlett 1st Gen**: 6i6, 8i6, 18i6, 18i8, 18i20
|
||||
- **Scarlett 2nd Gen**: 6i6, 18i8, 18i20
|
||||
- **Scarlett 3rd Gen**: Solo, 2i2, 4i4, 8i6, 18i8, 18i20
|
||||
- **Scarlett 4th Gen**: Solo, 2i2, 4i4, 16i16, 18i16, 18i20
|
||||
- **Clarett USB and Clarett+**: 2Pre, 4Pre, 8Pre
|
||||
- **Vocaster**: One, Two
|
||||
|
||||
- Scarlett 4th Gen Solo, 2i2, and 4i4.
|
||||
|
||||
- All Clarett USB and Clarett+ interfaces.
|
||||
|
||||
- Vocaster One and Vocaster Two.
|
||||
Note: The Scarlett 1st and 2nd Gen small interfaces (Solo, 2i2, 2i4)
|
||||
don’t have any software controls. All the controls are available from
|
||||
the front panel, so they don’t require the specialised drivers or this
|
||||
GUI.
|
||||
|
||||
## 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.
|
||||
The ALSA Scarlett Control Panel doesn’t handle audio input/output
|
||||
settings like sample rate and buffer size. These settings are managed
|
||||
by the application using the soundcard, typically a sound server such
|
||||
as PulseAudio, JACK, or PipeWire.
|
||||
|
||||
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.
|
||||
The sample rate shown in the control panel is informative only and
|
||||
displays the current rate being used by applications. If it shows
|
||||
“N/A” then no application is using the interface.
|
||||
|
||||
Note that not all features are available at higher sample rates; refer
|
||||
to the user manual of your interface for more information.
|
||||
|
||||
## Why do my settings keep resetting?
|
||||
|
||||
The settings in the ALSA Scarlett Control Panel are automatically
|
||||
saved in the interface itself (all series except 1st Gen), so they
|
||||
should persist across reboots, power cycles, USB disconnect/reconnect,
|
||||
and even across different computers. This includes all routing,
|
||||
mixing, and other control panel settings.
|
||||
|
||||
If you find that your settings are reverting whenever you plug your
|
||||
interface in or power it back on, the most likely cause is the
|
||||
`alsa-state` and `alsa-restore` systemd services. These services save
|
||||
the state of ALSA controls on system shutdown to
|
||||
`/var/lib/alsa/asound.state` and then restore it each time the device
|
||||
is plugged in, potentially overwriting your interface’s stored
|
||||
settings.
|
||||
|
||||
It can be rather annoying, wondering why your device is unusable or
|
||||
needs to be reconfigured every time you plug it in or turn it on.
|
||||
|
||||
To fix this issue, disable these services:
|
||||
|
||||
```sh
|
||||
sudo systemctl mask alsa-state
|
||||
sudo systemctl mask alsa-restore
|
||||
```
|
||||
|
||||
You can verify if this is the cause of your issues by:
|
||||
|
||||
1. Change some setting that is indicated on the device (the “Inst”
|
||||
setting is a good).
|
||||
2. Disconnect USB and notice the state of the setting on the device
|
||||
has not changed.
|
||||
3. Power cycle the device and notice the state of the setting on the
|
||||
device has not changed.
|
||||
4. Reconnect USB and notice the state of the setting on the device has
|
||||
changed.
|
||||
|
||||
If the setting on the device changes at step 4, then the `alsa-state`
|
||||
and `alsa-restore` services are the likely cause of your issues.
|
||||
|
||||
## Help?!
|
||||
|
||||
For help with the driver:
|
||||
https://github.com/geoffreybennett/scarlett-gen2/issues
|
||||
Have you read the User Guide for your interface? It’s available
|
||||
online: https://downloads.focusrite.com/focusrite and contains a lot
|
||||
of helpful/useful/important information about your device.
|
||||
|
||||
You can skip the “Easy Start” and “Setting up your DAW” sections, but
|
||||
the rest is well worth reading. Even the information about Focusrite
|
||||
Control is useful, although not directly applicable, because it will
|
||||
help you understand more about the possibilities of what you can do
|
||||
with your device.
|
||||
|
||||
For help with the Scarlett2 and FCP kernel drivers:
|
||||
https://github.com/geoffreybennett/linux-fcp/issues
|
||||
|
||||
For help with the FCP user-space side:
|
||||
https://github.com/geoffreybennett/fcp-support/issues
|
||||
|
||||
For help with `alsa-scarlett-gui`:
|
||||
https://github.com/geoffreybennett/alsa-scarlett-gui/issues
|
||||
|
||||
For general Linux audio help:
|
||||
https://linuxmusicians.com
|
||||
For general Linux audio help: https://linuxmusicians.com
|
||||
|
||||
2
Makefile
@@ -10,7 +10,7 @@ default:
|
||||
@echo
|
||||
@echo "If you want to build and install from source, please try:"
|
||||
@echo " cd src"
|
||||
@echo " make -j4"
|
||||
@echo " make -j$(shell nproc)"
|
||||
@echo " sudo make install"
|
||||
@echo
|
||||
@echo "This Makefile knows about packaging:"
|
||||
|
||||
88
README.md
@@ -1,86 +1,8 @@
|
||||
# ALSA Scarlett2 Control Panel (`alsa-scarlett-gui`)
|
||||
# ALSA Scarlett Control Panel (`alsa-scarlett-gui`)
|
||||
|
||||
`alsa-scarlett-gui` is a Gtk4 GUI for the ALSA controls presented by
|
||||
the Linux kernel Focusrite Scarlett2 USB Protocol Mixer Driver.
|
||||
- Upstream project here: https://github.com/geoffreybennett/alsa-scarlett-gui
|
||||
- Debian's packaged version here: https://salsa.debian.org/doge-tech/alsa-scarlett-gui
|
||||
|
||||
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
|
||||
This fork of the repo was started so I could write a Debian package manifest for the program. While I successfully made a working package, I had no intention of uploading it to the Debian archives -- I felt that I didn't have enough experience to bother the Debian maintainers with my nonsense. Someone else, however, [did upload one!](https://salsa.debian.org/doge-tech/alsa-scarlett-gui). That version has since been included with the Debian 13 release.
|
||||
|
||||
## About
|
||||
|
||||
<img src="src/img/alsa-scarlett-gui-logo.png" align="right">
|
||||
|
||||
The Focusrite USB audio interfaces are class compliant meaning that
|
||||
they work “out of the box” on Linux as audio and MIDI interfaces
|
||||
(although on Gen 3/4/Vocaster you need to disable MSD mode first for
|
||||
full functionality). However, except for some of the smallest models,
|
||||
they have a bunch of proprietary functionality that required a kernel
|
||||
driver to be written specifically for those devices.
|
||||
|
||||
Unfortunately, actually using this functionality used to be quite an
|
||||
awful experience. The existing applications like `alsamixer` and
|
||||
`qasmixer` become completely user-hostile with the hundreds of
|
||||
controls presented for the Gen 3 18i20. Even the smallest Gen 3 4i4
|
||||
interface at last count had 84 ALSA controls.
|
||||
|
||||
Announcing the ALSA Scarlett2 Control Panel, now supporting Scarlett
|
||||
Gen 2, 3, 4, Clarett, and Vocaster!
|
||||
|
||||

|
||||
|
||||
## Documentation
|
||||
|
||||
Refer to [INSTALL.md](docs/INSTALL.md) for prerequisites, how to
|
||||
build, install, and run.
|
||||
|
||||
Refer to [USAGE.md](docs/USAGE.md) for general usage information and
|
||||
known issues.
|
||||
|
||||
Information specific to various models:
|
||||
|
||||
- [Scarlett 3rd Gen Solo and 2i2](docs/iface-small.md)
|
||||
|
||||
- [Scarlett 2nd Gen 6i6+, 3rd Gen 4i4+, Clarett USB, and
|
||||
Clarett+](docs/iface-large.md)
|
||||
|
||||
- [Scarlett 4th Gen](docs/iface-4th-gen.md)
|
||||
|
||||
## Donations
|
||||
|
||||
This program is Free Software, developed using my personal resources,
|
||||
over hundreds of hours.
|
||||
|
||||
If you like this software, please consider a donation to say thank
|
||||
you! Any donation is appreciated.
|
||||
|
||||
- https://liberapay.com/gdb
|
||||
- https://paypal.me/gdbau
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2022-2024 Geoffrey D. Bennett
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or (at
|
||||
your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
## Disclaimer Third Parties
|
||||
|
||||
Focusrite, Scarlett, Clarett, and Vocaster are trademarks or
|
||||
registered trademarks of Focusrite Audio Engineering Limited in
|
||||
England, USA, and/or other countries. Use of these trademarks does not
|
||||
imply any affiliation or endorsement of this software.
|
||||
I'm archiving this repo because it is redundant with that package. I want to keep it around as something I made, while also making it clear that this project is dead.
|
||||
|
||||
@@ -1,35 +1,29 @@
|
||||
Summary: ALSA Scarlett Gen 2/3 Control Panel
|
||||
Name: alsa-scarlett-gui
|
||||
Version: VERSION
|
||||
Release: 1%{?dist}
|
||||
License: GPLv3+ LGPLv3+
|
||||
Url: https://github.com/geoffreybennett/alsa-scarlett-gui
|
||||
Source: %{name}-%{version}.tar.gz
|
||||
Summary: ALSA Scarlett Control Panel
|
||||
Name: alsa-scarlett-gui
|
||||
Version: VERSION
|
||||
Release: 1%{?dist}
|
||||
License: GPLv3+ LGPLv3+
|
||||
Url: https://github.com/geoffreybennett/alsa-scarlett-gui
|
||||
Source0: https://github.com/geoffreybennett/alsa-scarlett-gui/archive/refs/tags/%{version}.tar.gz?/%{name}-%{version}.tar.gz
|
||||
BuildRequires: pkgconfig(alsa)
|
||||
BuildRequires: pkgconfig(gtk4)
|
||||
BuildRequires: pkgconfig(openssl)
|
||||
|
||||
%description
|
||||
|
||||
alsa-scarlett-gui is a Gtk4 GUI for the ALSA controls presented by the
|
||||
Linux kernel Focusrite Scarlett2 Mixer Driver.
|
||||
Linux kernel Focusrite USB drivers.
|
||||
|
||||
%prep
|
||||
%setup
|
||||
%setup -q -n %{name}-%{version}/src
|
||||
|
||||
%build
|
||||
make -C src -j4 VERSION=%{version} PREFIX=/usr
|
||||
%make_build VERSION=%{version} PREFIX=%{_prefix}
|
||||
|
||||
%install
|
||||
%make_install -C src PREFIX=/usr
|
||||
DOCDIR=%{buildroot}/usr/share/doc/%{name}-%{version}
|
||||
mkdir -p $DOCDIR/img
|
||||
mkdir $DOCDIR/demo
|
||||
mkdir $DOCDIR/docs
|
||||
cp *.md $DOCDIR
|
||||
cp img/* $DOCDIR/img
|
||||
cp demo/* $DOCDIR/demo
|
||||
cp docs/* $DOCDIR/docs
|
||||
%make_install PREFIX=%{_prefix}
|
||||
|
||||
%files
|
||||
%doc /usr/share/doc/%{name}-%{version}
|
||||
/usr/bin/alsa-scarlett-gui
|
||||
/usr/share/applications/vu.b4.alsa-scarlett-gui.desktop
|
||||
/usr/share/icons/hicolor/256x256/apps/vu.b4.alsa-scarlett-gui.png
|
||||
%doc ../img ../demo ../docs ../*.md
|
||||
%{_bindir}/alsa-scarlett-gui
|
||||
%{_datadir}/applications/vu.b4.alsa-scarlett-gui.desktop
|
||||
%{_iconsdir}/hicolor/256x256/apps/vu.b4.alsa-scarlett-gui.png
|
||||
|
||||
410
debian/changelog
vendored
Normal file
@@ -0,0 +1,410 @@
|
||||
alsa-scarlett-gui (0.5.1-1) unstable; urgency=medium
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Add RTFM advice to FAQ.md
|
||||
* Replace '/" with ’/“/” in *.md
|
||||
* Add information about alsa-state and alsa-restore to FAQ.md
|
||||
|
||||
[ Pro-pra ]
|
||||
* Use template spec with macros
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Improve "settings keep resetting" FAQ entry
|
||||
* Replace 1st Gen Startup Controls info with Startup Configuration
|
||||
* Disable the startup menu option for 1st Gen devices
|
||||
* Move card init from alsa_scan_cards() to new card_init() function
|
||||
* Add driver type detection
|
||||
* Add support for waiting for FCP driver initialisation
|
||||
* Don't attempt to attach unused routing_mixer_in_grid
|
||||
* Replace hwdep check in window-startup.c with driver_type check
|
||||
* Add support for rebooting devices using the FCP socket interface
|
||||
* Update window-hardware with big 4th Gen and Vocaster models
|
||||
* Change alsa_get_elem_int_values() to return longs rather than ints
|
||||
|
||||
[ runiq ]
|
||||
* Add alsactl utility
|
||||
* Remove superfluous files from Flatpak
|
||||
* More Flatpak manifest cleanup
|
||||
|
||||
[ Robert Garrett ]
|
||||
* Change default compression to xz level 9
|
||||
|
||||
-- Robert Garrett <robertgarrett404@gmail.com> Mon, 09 Jun 2025 22:37:54 -0500
|
||||
|
||||
alsa-scarlett-gui (0.5.0-1) unstable; urgency=medium
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Move 4th Gen Solo 48V switch above the Air switch
|
||||
|
||||
[ Giorgio Reale ]
|
||||
* Add 4rd Gen models to window-hardware.c
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Fix widget-boolean.c to free data on button destruction
|
||||
* Add Arch package dependency
|
||||
* Fix Sample Rate button to be insensitive
|
||||
* Override focus and colour CSS button styles
|
||||
* Search $PATH and /usr/sbin for alsactl
|
||||
* Add support for volatile buttons to widget-boolean.c
|
||||
* Allow for boolean controls that are backwards
|
||||
* Update widget-boolean to cache the icon widgets
|
||||
* Switch to embedded SVG icons
|
||||
|
||||
[ Guillaume ]
|
||||
* Add missing GTK and ALSA dependencies on deb package
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Add -fPIE and -pie build flags to fix flatpak build under Fedora
|
||||
* Download and include scarlett2 firmware in flatpak
|
||||
* Make flatpak build faster
|
||||
* Fix up deb and RPM package description & add docs
|
||||
* Move level meter fields out of struct alsa_card
|
||||
* Add peak display to the level meters
|
||||
* Use snprintf() in widget-gain.c when printing floats
|
||||
* Add peak value display to the level meters
|
||||
* Fix link from FAQ.md to INSTALL.md
|
||||
* Add const to get*elem*() char* function arguments
|
||||
* Add 3rd Gen 18i8/18i20 S/PDIF/Digital I/O Mode startup controls
|
||||
|
||||
[ unhappy-ending ]
|
||||
* Update Makefile to use $(CC) rather than cc
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Replace cairo_show_text() in gtkdial.c with Pango
|
||||
* Fix crash in window-level.c on_destroy()
|
||||
* Update flatpak to GNOME 47
|
||||
* Update logo
|
||||
* Add get_elem_by_substr() to alsa.[ch]
|
||||
* Add Scarlett 1st Gen demo state files
|
||||
* Gen 1: Handle different names for clock source and sync status
|
||||
* Gen 1: Mute switches are backwards
|
||||
* Gen 1: Add 1st Gen devices to window-hardware.c
|
||||
* Gen 1: Trigger support based on "Matrix" element presence
|
||||
* Gen 1: Add PC_OFF port category
|
||||
* Gen 1: Ignore control "index" value in saved configurations
|
||||
* Gen 1: Parse and save config count field
|
||||
* Gen 1: Move alsa-sim elem creation into alsa_config_to_new_elem()
|
||||
* Gen 1: Add support for elements with count > 1 in saved config
|
||||
* Gen 1: Add support for 1st Gen stereo elements
|
||||
* Gen 1: Add support for 1st Gen mixer controls
|
||||
* Gen 1: Add support for 1st Gen input controls
|
||||
* Gen 1: Add support for 1st Gen output controls
|
||||
* Wrap long line, fix reopen callback comment in alsa.c
|
||||
* Update constants for new maximum number of mux inputs and meters
|
||||
* Treat locked ALSA elements as read-only
|
||||
* Handle interfaces with fixed mixer inputs
|
||||
* Handle per-channel link buttons
|
||||
* Update routing hover to highlight corresponding source sink
|
||||
* Highlight mixer labels on dial hover
|
||||
* Update gtkdial to support linear-volume controls
|
||||
* Update alsa interface and gain widget to support linear volume
|
||||
* Make perror("fopen") messages distinct
|
||||
* Add support for two-control speaker switching and talkback
|
||||
* Add support for new 4th Gen control names
|
||||
* Simplify update_levels_controls()
|
||||
* Add support for TLVs from the FCP driver
|
||||
* Update alsa.c to handle differing FCP mixer element names
|
||||
* Add support for Level Meter labels
|
||||
* Fix output control column/mute tooltip handling
|
||||
* Move card_destroy_callback() before alsa_card_callback()
|
||||
* Call card_destroy_callback() when an ALSA element is removed
|
||||
* Bump copyright year to 2025
|
||||
* Update docs and such for 1st Gen and big 4th Gen support
|
||||
* Add big 4th Gen demo files
|
||||
* Make make clean do depclean too
|
||||
* Undefine _FORTIFY_SOURCE before defining so GitHub can build the deb
|
||||
* Update flatpak container image from gnome-45 to gnome-47
|
||||
* Remove unused start_x, start_y from gtk_dial_drag_gesture_update()
|
||||
* Add small deadband to dial drag to stop double-click adjustments
|
||||
* Replace -j4 with -j$(nproc)
|
||||
* Update 1st Gen doc to mention Level Meters and Startup Controls
|
||||
* Update startup window no-startup-controls message
|
||||
|
||||
-- Robert Garrett <robertgarrett404@gmail.com> Mon, 09 Jun 2025 22:09:31 -0500
|
||||
|
||||
alsa-scarlett-gui (0.4.0-1) UNRELEASED; urgency=low
|
||||
|
||||
[ Guillaume ]
|
||||
* Fix deb package icon install
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Update Makefile to get version from $APP_VERSION
|
||||
* Global replace "destination" with "sink"
|
||||
|
||||
[ fenugrec ]
|
||||
* Replace deprecated gtk_widget_{show,hide} calls
|
||||
* Reduce code duplication in menu.c
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Add Clarett USB/Clarett+ models to window-hardware.c
|
||||
* Pass APP_VERSION through flatpak-builder
|
||||
* Check Firmware Version before enabling Levels menu item
|
||||
* Remove user-control of level meters
|
||||
* Improve layout for larger interfaces without speaker switching
|
||||
* Fix the socket widget so that it will not shrink
|
||||
* Allow the routing window to be resized and give it scrollbars
|
||||
* Fix typo rounting -> routing
|
||||
* Update drag_motion() to scroll the routing window
|
||||
|
||||
[ sporksnail ]
|
||||
* src/Makefile: Respect CFLAGS from environment
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Update README.md with Gen 4 info
|
||||
* Update software name in INSTALL.md and USAGE.md
|
||||
* Add info to INSTALL.md on how to check the kernel version
|
||||
* Add Gtk4 info to INSTALL.md
|
||||
* Fix typo in INSTALL.md driver disabled message
|
||||
|
||||
[ Jason A. Donenfeld ]
|
||||
* Makefile: prefer distro cflags if specified
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Add initial version of FAQ.md
|
||||
* Add FUNDING.yml
|
||||
* Add issue template
|
||||
* Add Clarett info to INTERFACES.md
|
||||
* Add TOC to INTERFACES.md
|
||||
* Add link from USAGE.md to INTERFACES.md
|
||||
* Allow the mixer window to be resized and give it scrollbars
|
||||
* Update FAQ with more MSD info
|
||||
* Fix long lines
|
||||
* Add OpenSUSE package requirements
|
||||
* Fix typo in INSTALL.md: comitting -> committing
|
||||
* Move FUNDING.yml to the right spot
|
||||
|
||||
[ Jason A. Donenfeld ]
|
||||
* gtkdial: use fabs() for double
|
||||
|
||||
[ Trent ]
|
||||
* Add keyboard accelerators (aka shortcuts, hotkeys) for menu items.
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Reformat keyboard accelerators to match existing code
|
||||
* Remove keyboard accelerator mention from USAGE.md
|
||||
* Split calc_valp() into calc_valp() and calc_valp_log()
|
||||
* Fix dial to grab focus when clicked
|
||||
* Fix dial to display focus indication
|
||||
|
||||
[ Nate Gallaher ]
|
||||
* Clarify pad tooltip to specify attenuation amount
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Remove gtk_widget_add_class()
|
||||
* Fix is_elem_routing_snk() for Gen 4
|
||||
* Remove lots of casts in create_routing_grid()
|
||||
* Make the Level Meter check compatible with alsa-sim.c
|
||||
* Remove repeated group by code in create_routing_grid()
|
||||
* Add orientation parameter to routing_grid_label()
|
||||
* Inline routing_grid_label() in create_routing_group_grid()
|
||||
* Fix Presets button to be centered
|
||||
* Add margin to socket widget
|
||||
* Add support for routing the Gen 4 DSP I/O
|
||||
* Allow for phantom power per-channel
|
||||
* Remove hard-coded values from widget-gain.c and widget-volume.c
|
||||
* Combine gain and volume widgets
|
||||
* Center-align text in combo boxes
|
||||
* Make boolean widget boolify value from alsa_get_elem_value()
|
||||
* Explicitly order main window controls
|
||||
* Add 4th Gen input controls
|
||||
* Remove widgets from struct alsa_elem and add data to callbacks
|
||||
* Add input select widget for 4th Gen 2i2 and 4i4
|
||||
* Add 4th Gen 4i4 headphone volume knob control
|
||||
* Add missing GtkDial scroll_begin() implementation
|
||||
* Add bindings for pgup/pgdn/home/end to GtkDial
|
||||
* Display -inf when volume/gain controls are at zero/off
|
||||
* Don't round level meter values passed to GtkDial
|
||||
* Fix widget-combo to check if the alsa elem is writable
|
||||
* Simplify GtkDial calculations
|
||||
* Bump copyright year
|
||||
* Remove unused GtkDialFormatValueFunc from gtkdial.h
|
||||
* Remove unused guint8 and gsize from gtkdial.c
|
||||
* Remove irrelevant GDK_AVAILABLE_IN_ALL from gtkdial.h
|
||||
* Reformat gtkdial.c to match rest of code
|
||||
* Fix GtkDial:zero_db property comment
|
||||
* Add scale to widget-gain.c struct gain
|
||||
* Fix gtk_dial_new_with_range() to use calculated round_digits
|
||||
* Allow for continuous dials
|
||||
* Add page argument to gtk_dial_new_with_range()
|
||||
* Fix typo range->dial in gtkdial.[ch]
|
||||
* Remove inline from functions in gtkdial.c
|
||||
* Move valp clamp and scale from calc_valp_log() to calc_valp()
|
||||
* Add configurable taper to GtkDial
|
||||
* Add support for piecewise linear interpolation taper to GtkDial
|
||||
* Make GtkDial look better
|
||||
* Make GtkDial dimmer if insensitive
|
||||
* Remove bool_text from struct alsa_elem
|
||||
* Move label into boolean controls and use button state to show status
|
||||
* Remove "Analogue" from small Gen 3 device input labels
|
||||
* Use CSS to set dark colour scheme
|
||||
* Rewrite choose_line_colour() to work with a dark background
|
||||
* Move 4th Gen Solo Air control up to be next to Inst control
|
||||
* Add CSS classes to controls and add more colour
|
||||
* Add widget-drop-down for Air
|
||||
* Switch Clock Source to widget-drop-down
|
||||
* Add 4th Gen Solo/2i2 Direct Monitor controls
|
||||
* Add power status control for 4th Gen 4i4
|
||||
* Update 3rd Gen Solo Direct Monitor control to match 4th Gen
|
||||
* Remove now-unused widget-combo
|
||||
* Add some red and grey
|
||||
* Update about, etc. messages to include Gen 4/Clarett
|
||||
* Add CSS for buttons that get dimmer when checked
|
||||
* Use GtkTextView instead of GtkLabel in startup big_label()
|
||||
* Add draw_slider() to remove some common code from dial_snapshot()
|
||||
* Add GtkDial:can_control property
|
||||
* Add 4th Gen Solo Mix switch
|
||||
* Add off_db (deadband) to GtkDial for quiet signals
|
||||
* Use GtkDial off_db (deadband) for level meters
|
||||
* Remove struct dial_properties; cache values in struct _GtkDial
|
||||
* Allow GtkDial to shrink more and reduce slider thickness
|
||||
* Fix GtkDial to not recreate the cairo patterns on every draw
|
||||
* Add colour to GtkDial level meters
|
||||
* Prefix CSS styles so they don't apply to other windows
|
||||
* Update the gain widget to support updating direct monitor mix controls
|
||||
* Add routing group tooltips
|
||||
* Fix GtkDial to not redraw the dial if the set value doesn't change
|
||||
* Fix GtkDial to redraw on notify::sensitive signal
|
||||
* Add red focus outline to GtkDial
|
||||
* CSS fixes for buttons
|
||||
* Retrieve and store the device serial number
|
||||
* Don't export alsa_cards from alsa.c
|
||||
* Add support for config reset
|
||||
* Retrieve and store the device USB PID
|
||||
* Move -lm into LDFLAGS
|
||||
* Add support for firmware update
|
||||
|
||||
[ Antti-Pekka Meronen ]
|
||||
* Include openssl-devel in Fedora installation instructions
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Update OpenSUSE and Ubuntu package dependencies
|
||||
* Remove fixed/mentioned-elsewhere issues from USAGE.md
|
||||
* Don't set transient on modal window
|
||||
* Make gain widget dB numbers smaller
|
||||
* Add 4th Gen Solo, 2i2, and 4i4 demo files
|
||||
* Don't show empty rows in levels window
|
||||
* If there's an MSD control, add a reboot control too
|
||||
* Reduce GtkDial circle brightness
|
||||
* Make window-helper.c close the window when Esc is pressed
|
||||
* Add missing static to populate_submenu() in menu.c
|
||||
* Add gtk_widget_remove_css_classes_by_prefix() helper
|
||||
* Add display of sample rate
|
||||
* Display "+" before positive dB values in the gain widget
|
||||
* Update gain widget to show no decimal places for scale > 0.5
|
||||
* Add initial support for the Vocaster One and Two
|
||||
* Store the best_firmware_version in struct alsa_card
|
||||
* Prompt for firmware update if in MSD Mode and an update is available
|
||||
* Add firmware info to the INSTALL.md Prerequisites section
|
||||
* Update About dialog
|
||||
* Update documentation for Scarlett 4th Gen and Vocaster
|
||||
|
||||
[ Robert Garrett ]
|
||||
* Add build-depends on libssl-dev
|
||||
|
||||
-- Robert Garrett <robertgarrett404@gmail.com> Mon, 09 Jun 2025 21:57:36 -0500
|
||||
|
||||
alsa-scarlett-gui (0.3-1) UNRELEASED; urgency=low
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Fix format-security warning in error.c
|
||||
|
||||
[ Sebastian Kaminski ]
|
||||
* Fixed typo for 18i20 Gen 3 Mic/Line 3-8
|
||||
|
||||
[ Szabolcs Szőke ]
|
||||
* Make routing sources and destinations the same width
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Clarify comments and variable names in choose_line_colour()
|
||||
|
||||
[ Sebastian Kaminski ]
|
||||
* Prevent the boolean widget from changing size when toggled
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Add Ubuntu install and git download instructions
|
||||
* Add Clarett support
|
||||
* Fix typos in USAGE.md
|
||||
* Fix building with Gtk 4.10
|
||||
* Update README.md and USAGE.md with current driver information
|
||||
* Add link to fundraiser for adding Scarlett Gen 4 support
|
||||
* Fix input counting for Clarett+ series
|
||||
* Update README and USAGE files with new Clarett info
|
||||
* Split USAGE.md into INSTALL.md and USAGE.md
|
||||
* Add Clarett Plus 2Pre and 4Pre demo files
|
||||
* Update Clarett USB and Clarett+ status
|
||||
* Apply correction curve to the dials
|
||||
* Update README with Gen 4 and Vocaster info
|
||||
* Specify full path to alsactl
|
||||
|
||||
[ David Cooper ]
|
||||
* Add keyword to desktop file
|
||||
|
||||
[ Alejandro Domínguez ]
|
||||
* Change icon file name
|
||||
* Do not use deprecated or non-standard keys in desktop file
|
||||
* Enable Flatpak packaging support
|
||||
|
||||
[ Guillaume ]
|
||||
* Fix Error: icon alsa-scarlett-gui not found below...
|
||||
* Remove commented rename-icon property
|
||||
* Using github.com/flatpak/flatpak-github-actions
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Fix iface-none.png image description
|
||||
* Move flatpak instructions to INSTALL.md
|
||||
* Update flatpak to gnome 45
|
||||
* Add org.gnome.Platform and flathub to flatpak instructions
|
||||
* Update INSTALL and README with Linux 6.7 info
|
||||
|
||||
[ sporksnail ]
|
||||
* window-startup.c: fix typo
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Add meter/level display
|
||||
|
||||
[ Guillaume ]
|
||||
* Github action to build debian package on release
|
||||
|
||||
[ Robert Garrett ]
|
||||
* Merge upstream version 0.3 & release package
|
||||
|
||||
-- Robert Garrett <robertgarrett404@gmail.com> Mon, 09 Jun 2025 20:36:56 -0500
|
||||
|
||||
alsa-scarlett-gui (0.2-1) UNRELEASED; urgency=low
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Note lack of keyboard accelerators
|
||||
* Add demo GIF
|
||||
* Fix size of mixer output sockets on Gen 3 18i20 routing
|
||||
* Reduce button padding as intended
|
||||
* Display input labels across the top of the mixer window
|
||||
* Reformat CSS to not be so squishy
|
||||
* Change route-label hover background colour to work with dark theme
|
||||
* Add desktop and icon files and install into the correct place
|
||||
* Add copyright info to the Makefile
|
||||
* Don't hardcode the version number in the about dialog
|
||||
|
||||
[ KottV ]
|
||||
* Fix linking in OBS
|
||||
|
||||
[ Geoffrey D. Bennett ]
|
||||
* Add help target to src/Makefile
|
||||
* Add top-level Makefile and RPM spec file for packaging
|
||||
|
||||
[ Robert Garrett ]
|
||||
* Drop the backported link ordering patch
|
||||
* Update changelog via gbp dch
|
||||
* Create a copyright file
|
||||
* Adjust the copyright file a bit...
|
||||
|
||||
-- Robert Garrett <robertgarrett404@gmail.com> Mon, 09 Jun 2025 20:21:13 -0500
|
||||
|
||||
alsa-scarlett-gui (0.1-1) UNRELEASED; urgency=low
|
||||
|
||||
* Initial revision, starting up a package manifest.
|
||||
* Drop pbuilder settings from gbp.conf
|
||||
* Set upstream version tag in gbp.conf
|
||||
* Name upstream and debian branches properly
|
||||
* Backport a link order fix from upstream
|
||||
|
||||
-- Robert Garrett <robertgarrett404@gmail.com> Mon, 09 Jun 2025 19:37:00 -0500
|
||||
16
debian/control
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
Source: alsa-scarlett-gui
|
||||
Maintainer: Robert Garrett <robertgarrett404@gmail.com>
|
||||
Section: misc
|
||||
Priority: optional
|
||||
Standards-Version: 4.6.2
|
||||
Build-Depends:
|
||||
debhelper-compat (= 13),
|
||||
libasound2-dev,
|
||||
libgtk-4-dev,
|
||||
libssl-dev,
|
||||
|
||||
Package: alsa-scarlett-gui
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
Description: GUI tool for Scarlett audio interfaces.
|
||||
Uses an ALSA backend.
|
||||
34
debian/copyright
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Source: https://github.com/geoffreybennett/alsa-scarlett-gui
|
||||
Upstream-Name: alsa-scarlett-gui
|
||||
Upstream-Contact: Geoffrey D. Bennet <g@b4.vu>
|
||||
License: GPL-3.0-or-later or LGPL-3+
|
||||
|
||||
Files: *
|
||||
Copyright:
|
||||
2022-2025 Geoffrey D. Bennet <g@b4.vu>
|
||||
2022 KottV <kv@kott.no-ip.biz>
|
||||
License: GPL-3+
|
||||
|
||||
Files:
|
||||
debian/*
|
||||
Copyright: 2024-2025 Robert Garrett
|
||||
License: GPL-3+
|
||||
|
||||
License: GPL-3.0+
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
.
|
||||
On Debian systems, the full text of the GNU General Public
|
||||
License Version 3 can be found in the file
|
||||
`/usr/share/common-licenses/GPL-3`
|
||||
8
debian/gbp.conf
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
[DEFAULT]
|
||||
compression = xz
|
||||
compression-level = 9
|
||||
upstream-branch = master
|
||||
debian-branch = deb
|
||||
upstream-tag = %(version)s
|
||||
|
||||
pristine-tar = False
|
||||
5
debian/rules
vendored
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
%:
|
||||
PREFIX=/usr dh $@ --sourcedirectory=src/
|
||||
|
||||
1
debian/source/format
vendored
Normal file
@@ -0,0 +1 @@
|
||||
3.0 (quilt)
|
||||
5449
demo/Scarlett Gen 1 18i20.state
Normal file
3362
demo/Scarlett Gen 1 18i6.state
Normal file
4165
demo/Scarlett Gen 1 18i8.state
Normal file
3277
demo/Scarlett Gen 1 6i6.state
Normal file
2797
demo/Scarlett Gen 1 8i6.state
Normal file
10648
demo/Scarlett Gen 4 16i16.state
Normal file
11560
demo/Scarlett Gen 4 18i16.state
Normal file
14810
demo/Scarlett Gen 4 18i20.state
Normal file
@@ -1,29 +1,46 @@
|
||||
# ALSA Scarlett2 Control Panel Installation
|
||||
# ALSA Scarlett 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.
|
||||
You need to be running a Linux Kernel that contains the appropriate
|
||||
driver for your interface. 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)
|
||||
Check the following table to see which driver your interface uses and
|
||||
the first kernel version that the driver was included in:
|
||||
|
||||
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:
|
||||
| Series | Models | Driver | Kernel Version |
|
||||
|-----------|--------|--------|:----------------------:|
|
||||
| Scarlett 1st Gen | Solo, 2i2, 2i4 | N/A* | Any |
|
||||
| Scarlett 1st Gen | 6i6, 8i6, 18i6, 18i8, 18i20 | Scarlett 1st Gen Mixer Driver | 3.19+ |
|
||||
| Scarlett 2nd Gen | Solo, 2i2, 2i4 | N/A* | Any |
|
||||
| Scarlett 2nd Gen | 6i6, 18i8, 18i20 | Scarlett2 Mixer Driver | 6.7+ |
|
||||
| Scarlett 3rd Gen | Solo, 2i2, 4i4, 8i6, 18i8, 18i20 | Scarlett2 Mixer Driver | 6.7+ |
|
||||
| Scarlett 4th Gen | Solo, 2i2, 4i4 | Scarlett2 Mixer Driver | 6.8+ |
|
||||
| Scarlett 4th Gen | 16i16, 18i16, 18i20 | FCP (Focusrite Control Protocol) Driver | 6.14+ |
|
||||
| Clarett USB and Clarett+ | 2Pre, 4Pre, 8Pre | Scarlett2 Mixer Driver | 6.7+ |
|
||||
| Vocaster | One, Two | Scarlett2 Mixer Driver | 6.10+ |
|
||||
|
||||
https://github.com/geoffreybennett/scarlett-gen2/releases
|
||||
\* The small 1st Gen and 2nd Gen models don’t have any proprietary
|
||||
software controls so they don’t need a driver beyond the standard ALSA
|
||||
USB Audio driver. This means that this application (alsa-scarlett-gui)
|
||||
is not needed, useful, or supported for these models.
|
||||
|
||||
#### Enabling the Driver
|
||||
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 if it’s not too old (the Scarlett2 and FCP drivers
|
||||
are both maintained in the same tree here):
|
||||
https://github.com/geoffreybennett/linux-fcp/releases
|
||||
|
||||
As of Linux 6.7 the driver is enabled by default. Check the driver
|
||||
Kernel 6.7 and later have the Scarlett2 driver enabled by default. The
|
||||
Scarlett 1st Gen driver and the FCP drivers are always enabled.
|
||||
|
||||
#### Enabling the Scarlett2 Driver
|
||||
|
||||
Some kernels before 6.7 have an earlier version of the Scarlett2
|
||||
driver which is disabled by default. If this is you, check the driver
|
||||
status (after plugging your interface in) with this command:
|
||||
|
||||
```
|
||||
@@ -35,12 +52,12 @@ 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
|
||||
Focusrite Scarlett Gen 3 Mixer Driver enabled (pid=0x8215); ...
|
||||
```
|
||||
|
||||
If you don’t see the “Mixer Driver” message or if it shows “disabled”
|
||||
then check the [OLDKERNEL.md](OLDKERNEL.md) instructions.
|
||||
then check the [OLDKERNEL.md](OLDKERNEL.md) instructions (or,
|
||||
preferably, upgrade your distro/kernel!).
|
||||
|
||||
### Gtk4
|
||||
|
||||
@@ -49,16 +66,27 @@ 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.
|
||||
#### Scarlett2 Driver
|
||||
|
||||
As of Linux 6.8, firmware updates of all supported interfaces from the
|
||||
2nd Gen onwards 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 but
|
||||
recommended 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.
|
||||
|
||||
#### FCP Driver
|
||||
|
||||
Firmware updates for the big Scarlett 4th Gen interfaces is currently
|
||||
only possible through the CLI `fcp-tool` utility available in the
|
||||
[fcp-support](https://github.com/geoffreybennett/fcp-support). You
|
||||
need to install this package and update the firmware before
|
||||
alsa-scarlett-gui will work.
|
||||
|
||||
## Building and Running
|
||||
|
||||
On Fedora, these packages need to be installed:
|
||||
@@ -96,7 +124,7 @@ To build:
|
||||
|
||||
```
|
||||
cd src
|
||||
make -j4
|
||||
make -j$(nproc)
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
# ALSA Scarlett2 Usage With Old Kernels
|
||||
|
||||
**This information is mostly for historical purposes. If you’re
|
||||
running a kernel before 6.7, you should upgrade to a newer kernel.**
|
||||
|
||||
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`.
|
||||
kernel version with the Scarlett2 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:
|
||||
the level meters not working, then the first kernel support was added
|
||||
in:
|
||||
|
||||
- **Scarlett Gen 2**: Linux 5.4 (bugs fixed in Linux 5.14)
|
||||
- **Scarlett Gen 3**: Linux 5.14
|
||||
@@ -18,7 +22,7 @@ the level meters not working, then the minimum kernel versions are:
|
||||
|
||||
Install the latest version of the backported driver from here:
|
||||
|
||||
https://github.com/geoffreybennett/scarlett-gen2/releases
|
||||
https://github.com/geoffreybennett/linux-fcp/releases
|
||||
|
||||
then you can ignore the instructions below.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ALSA Scarlett2 Control Panel Usage
|
||||
# ALSA Scarlett Control Panel Usage
|
||||
|
||||
Refer to [INSTALL.md](INSTALL.md) for prerequisites, how to build,
|
||||
install, and run.
|
||||
@@ -59,7 +59,7 @@ restart the interface, and in a moment the main window will appear.
|
||||
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:
|
||||
The options common to most interfaces are:
|
||||
|
||||
- **Reset Configuration**: this will reset the configuration to the
|
||||
factory defaults. This is particularly useful with the 4th Gen and
|
||||
@@ -105,9 +105,13 @@ menu option File → Interface Simulation to load.
|
||||
The controls and menu items which are available vary widely, depending
|
||||
on your specific interface.
|
||||
|
||||
There are three broad categories of interfaces with different
|
||||
There are five broad categories of interfaces with different
|
||||
capabilities; each category of interface is described in a separate
|
||||
ocument:
|
||||
document:
|
||||
|
||||
- [Scarlett 1st Gen 6i6+](iface-1st-gen.md)
|
||||
|
||||
Full routing and mixing capabilities, but some significant caveats.
|
||||
|
||||
- [Scarlett 3rd Gen Solo and 2i2](iface-small.md)
|
||||
|
||||
@@ -119,13 +123,21 @@ ocument:
|
||||
|
||||
Full routing and mixing capabilities.
|
||||
|
||||
- [Scarlett 4th Gen](iface-4th-gen.md)
|
||||
- [Scarlett Small 4th Gen](iface-4th-gen-small.md)
|
||||
|
||||
Full routing and mixing capabilities, remote-controlled input gain,
|
||||
but no output controls.
|
||||
|
||||
- [Scarlett Big 4th Gen](iface-4th-gen-big.md)
|
||||
|
||||
Full routing and mixing capabilities, remote-controlled input gain
|
||||
and output volume controls.
|
||||
|
||||
## Known Bugs/Issues
|
||||
|
||||
- For interfaces using the FCP driver, alsa-scarlett-gui needs to be
|
||||
started after the interface is connected and fcp-server has started.
|
||||
|
||||
- Load/Save uses `alsactl` which will be confused if the ALSA
|
||||
interface name (e.g. `USB`) changes.
|
||||
|
||||
|
||||
164
docs/iface-1st-gen.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# ALSA Scarlett Control Panel
|
||||
|
||||
## Scarlett 1st Gen Interfaces
|
||||
|
||||
This document describes how to use the ALSA Scarlett Control Panel
|
||||
with the Scarlett 1st Gen interfaces:
|
||||
|
||||
- Scarlett 1st Gen 6i6, 8i6, 18i6, 18i8, 18i20
|
||||
|
||||
Note: The 1st Gen Scarlett Solo, 2i2, and 2i4 have all their controls
|
||||
accessible from the front panel of the device, and there are no
|
||||
proprietary software controls, so they do not require this control
|
||||
panel software.
|
||||
|
||||
## Important Driver Limitations
|
||||
|
||||
The 1st Gen Scarlett devices have some important limitations in the
|
||||
ALSA driver implementation that you should be aware of:
|
||||
|
||||
1. **Initial State Detection**: The driver cannot read the current
|
||||
state of hardware controls (this appears to be a limitation of the
|
||||
device firmware). When alsa-scarlett-gui starts, what you see will
|
||||
not reflect the actual state of your device unless the controls
|
||||
have previously been set since startup.
|
||||
|
||||
2. **State Update Issues**: The driver only updates the hardware state
|
||||
when it thinks a setting needs to be changed. If the driver
|
||||
incorrectly believes a control is already in the desired state, it
|
||||
won’t actually update the control.
|
||||
|
||||
3. **Level Meters**: The driver does not support reading the level
|
||||
meters from the hardware.
|
||||
|
||||
4. **Startup Configuration**: The driver is not able to save the
|
||||
current configuration to the non-volatile memory of the device, so
|
||||
you’ll need to reapply the desired configuration each time you
|
||||
restart it (or write your preferred configuration using MixControl
|
||||
on Windows or Mac).
|
||||
|
||||
### Recommended Workaround
|
||||
|
||||
To ensure your settings are properly applied:
|
||||
|
||||
1. Apply a “zero” configuration that sets all controls to values that
|
||||
are *not* what you desire.
|
||||
2. Then apply your desired configuration
|
||||
|
||||
This two-step process helps ensure that the driver actually sends all
|
||||
commands to the hardware. You may want to create a script using
|
||||
`alsactl` for this purpose.
|
||||
|
||||
## 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.
|
||||
|
||||
Note that the View menu option lets you open two other windows which
|
||||
contain additional controls, described in the following sections:
|
||||
- [Routing](#routing)
|
||||
- [Mixer](#mixer)
|
||||
|
||||
The Levels and Startup windows that are available for later-generation
|
||||
interfaces are not available for 1st Gen interfaces due to driver limitations.
|
||||
|
||||
### Global Controls
|
||||
|
||||
Global controls relate to 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 status is
|
||||
Unlocked, change the Clock Source to Internal.
|
||||
|
||||
### Analogue Input Controls
|
||||
|
||||
#### 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.
|
||||
|
||||
#### Pad
|
||||
|
||||
Enabling Pad engages a 10dB attenuator in the channel, giving you more
|
||||
headroom for very hot signals.
|
||||
|
||||
#### Gain
|
||||
|
||||
The Gain switch selects Low or High gain for the input channel.
|
||||
|
||||
### Analogue Output Controls
|
||||
|
||||
The analogue output controls let you set the output volume (gain) on
|
||||
the analogue line outputs.
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
210
docs/iface-4th-gen-big.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# ALSA Scarlett Control Panel
|
||||
|
||||
## Scarlett Big 4th Gen Interfaces
|
||||
|
||||
This document describes how to use the ALSA Scarlett Control Panel
|
||||
with the big Scarlett 4th Gen interfaces:
|
||||
|
||||
- Scarlett 4th Gen 16i16, 18i16, 18i20
|
||||
|
||||
### FCP Driver
|
||||
|
||||
The big 4th Gen interfaces are supported by a new “FCP” (Focusrite
|
||||
Control Protocol) driver introduced in Linux 6.14. If you haven’t
|
||||
installed
|
||||
[fcp-support](https://github.com/geoffreybennett/fcp-support) yet, you
|
||||
need to do that (and update the firmware) before you can use
|
||||
alsa-scarlett-gui.
|
||||
|
||||
## Main Window
|
||||
|
||||
The main window is divided into three sections:
|
||||
- Global Controls
|
||||
- Analogue Input Controls
|
||||
- Analogue Output Controls
|
||||
|
||||
The main window for the 16i16 interface is shown below. The 18i16 and
|
||||
18i20 interfaces are similar, but with more controls.
|
||||
|
||||

|
||||
|
||||
### Global Controls
|
||||
|
||||
#### 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
|
||||
|
||||
Speaker Switching lets you swap between two pairs of monitoring
|
||||
speakers very easily.
|
||||
|
||||
### Analogue Input Controls
|
||||
|
||||
#### Input Select
|
||||
|
||||
The “Input Select” control allows you to choose which channel the
|
||||
hardware 48V, Inst, Air, Auto, and Safe buttons control.
|
||||
|
||||
#### Link
|
||||
|
||||
The “Link” control links the 48V, Inst, Air, Auto, and Safe controls
|
||||
together so that they control a stereo pair of 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.
|
||||
|
||||
#### 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).
|
||||
|
||||
### Analogue Output Controls
|
||||
|
||||
The analogue output controls are a bit sparse. More controls are
|
||||
coming soon.
|
||||
|
||||
#### Volume Knobs
|
||||
|
||||
The volume knobs control the volume of the analogue outputs. The two
|
||||
channels of the stereo pairs are shown separately, but are internally
|
||||
linked together.
|
||||
|
||||
#### Mute and Dim
|
||||
|
||||
The speaker icon buttons are “mute” and “dim” (reduce volume) buttons,
|
||||
corresponding to the front-panel buttons on the interface (although
|
||||
only the 18i20 has a physical dim button).
|
||||
|
||||
## Routing and Mixing
|
||||
|
||||
The routing window allows (almost) complete control of signal routing
|
||||
between the hardware inputs/outputs, internal mixer, and PCM (USB)
|
||||
inputs/outputs.
|
||||
|
||||
The routing and mixing capabilities of the big 4th Gen interfaces are
|
||||
the same in concept as the older interfaces, but the mixer inputs are
|
||||
fixed and not shown in the routing window as there are too many to
|
||||
sensibly display.
|
||||
|
||||
From the main window, open the Routing window with the View → Routing
|
||||
menu option or pressing Ctrl-R:
|
||||
|
||||

|
||||
|
||||
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, connect the sinks to mixer outputs:
|
||||
|
||||
- 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.
|
||||
|
||||
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).
|
||||
|
||||
## Levels
|
||||
|
||||
The meters show the levels seen by the interface at every routing
|
||||
source as well as the analogue outputs. 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).
|
||||
@@ -1,9 +1,9 @@
|
||||
# ALSA Scarlett2 Control Panel
|
||||
# ALSA Scarlett Control Panel
|
||||
|
||||
## Scarlett 4th Gen Interfaces
|
||||
## Scarlett Small 4th Gen Interfaces
|
||||
|
||||
This document describes how to use the ALSA Scarlett2 Control Panel
|
||||
with the Scarlett 4th Gen interfaces:
|
||||
This document describes how to use the ALSA Scarlett Control Panel
|
||||
with the small Scarlett 4th Gen interfaces:
|
||||
|
||||
- Scarlett 4th Gen Solo, 2i2, and 4i4
|
||||
|
||||
@@ -39,7 +39,7 @@ The main window for the Solo and 2i2 interfaces is shown below; the
|
||||
Monitor control, and can show the position of the front panel volume
|
||||
knobs.
|
||||
|
||||

|
||||

|
||||
|
||||
### Global Controls
|
||||
|
||||
@@ -204,10 +204,7 @@ Important Notes:
|
||||
- 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.
|
||||
default settings.
|
||||
|
||||
To adjust the routing:
|
||||
|
||||
@@ -338,7 +335,7 @@ 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.
|
||||
@@ -1,8 +1,8 @@
|
||||
# ALSA Scarlett2 Control Panel
|
||||
# ALSA Scarlett Control Panel
|
||||
|
||||
## Large Scarlett 2nd and 3rd Gen and Clarett Interfaces
|
||||
|
||||
This document describes how to use the ALSA Scarlett2 Control Panel
|
||||
This document describes how to use the ALSA Scarlett Control Panel
|
||||
with the larger Scarlett 2nd Gen, 3rd Gen, and Clarett USB interfaces:
|
||||
|
||||
- Scarlett 2nd Gen 6i6, 18i8, 18i20
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ALSA Scarlett2 Control Panel
|
||||
# ALSA Scarlett Control Panel
|
||||
|
||||
## Small Scarlett 3rd Gen Interfaces
|
||||
|
||||
|
||||
BIN
img/alsa-scarlett-gui.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
img/iface-4th-gen-big.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 76 KiB |
BIN
img/scarlett-1st-gen-6i6-routing.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
img/scarlett-4th-gen-16i16-routing.png
Normal file
|
After Width: | Height: | Size: 180 KiB |
BIN
img/window-levels-4th-gen-big.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 471 KiB After Width: | Height: | Size: 471 KiB |
@@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
# SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Credit to Tom Tromey and Paul D. Smith:
|
||||
@@ -13,7 +13,8 @@ DEPDIR := .deps
|
||||
DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d
|
||||
|
||||
CFLAGS ?= -ggdb -fno-omit-frame-pointer -fPIE -O2
|
||||
CFLAGS += -Wall -Werror -D_FORTIFY_SOURCE=2
|
||||
CFLAGS += -Wall -Werror
|
||||
CFLAGS += -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3
|
||||
CFLAGS += -DVERSION=\"$(VERSION)\"
|
||||
CFLAGS += -Wno-error=deprecated-declarations
|
||||
|
||||
@@ -52,7 +53,7 @@ GLIB_COMPILE_RESOURCES := $(shell $(PKG_CONFIG) --variable=glib_compile_resource
|
||||
|
||||
all: $(TARGET) $(DESKTOP_FILE)
|
||||
|
||||
clean:
|
||||
clean: depclean
|
||||
rm -f $(TARGET) $(DESKTOP_FILE) $(OBJS) $(XML_OBJ)
|
||||
|
||||
depclean:
|
||||
|
||||
19
src/about.c
@@ -1,8 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "about.h"
|
||||
|
||||
static GdkTexture *logo = NULL;
|
||||
|
||||
void activate_about(
|
||||
GSimpleAction *action,
|
||||
GVariant *parameter,
|
||||
@@ -15,18 +17,23 @@ void activate_about(
|
||||
NULL
|
||||
};
|
||||
|
||||
if (!logo)
|
||||
logo = gdk_texture_new_from_resource(
|
||||
"/vu/b4/alsa-scarlett-gui/icons/vu.b4.alsa-scarlett-gui.png"
|
||||
);
|
||||
|
||||
gtk_show_about_dialog(
|
||||
w,
|
||||
"program-name", "ALSA Scarlett2 Control Panel",
|
||||
"program-name", "ALSA Scarlett Control Panel",
|
||||
"version", "Version " VERSION,
|
||||
"comments",
|
||||
"Gtk4 GUI for the ALSA controls presented by the\n"
|
||||
"Linux kernel Focusrite Scarlett2 Mixer Driver",
|
||||
"Linux kernel Focusrite USB drivers",
|
||||
"website", "https://github.com/geoffreybennett/alsa-scarlett-gui",
|
||||
"copyright", "Copyright 2022-2024 Geoffrey D. Bennett",
|
||||
"copyright", "Copyright 2022-2025 Geoffrey D. Bennett",
|
||||
"license-type", GTK_LICENSE_GPL_3_0,
|
||||
"logo-icon-name", "alsa-scarlett-gui-logo",
|
||||
"title", "About ALSA Scarlett2 Mixer Interface",
|
||||
"logo", logo,
|
||||
"title", "About ALSA Scarlett Mixer Interface",
|
||||
"authors", authors,
|
||||
NULL
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/vu/b4/alsa-scarlett-gui/icons">
|
||||
<file alias="alsa-scarlett-gui-logo.png">img/alsa-scarlett-gui-logo.png</file>
|
||||
<file alias="vu.b4.alsa-scarlett-gui.png">img/vu.b4.alsa-scarlett-gui.png</file>
|
||||
<file alias="socket.svg">img/socket.svg</file>
|
||||
<file alias="audio-volume-high.svg">img/audio-volume-high.svg</file>
|
||||
<file alias="audio-volume-low.svg">img/audio-volume-low.svg</file>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.route-label:hover {
|
||||
.route-label-hover {
|
||||
background: #801010;
|
||||
outline: 2px solid #801010;
|
||||
}
|
||||
@@ -65,6 +65,14 @@
|
||||
background: #801010;
|
||||
}
|
||||
|
||||
.mixer-label {
|
||||
}
|
||||
|
||||
.mixer-label-hover {
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 5px #00c000, 0 0 15px #00c000;
|
||||
}
|
||||
|
||||
label.gain {
|
||||
font-size: smaller;
|
||||
}
|
||||
@@ -151,6 +159,27 @@ button.toggle {
|
||||
text-shadow: 0 0 5px #0000ff, 0 0 15px #0000ff;
|
||||
}
|
||||
|
||||
.window-frame button.speaker-switching-enable:checked {
|
||||
text-shadow: 0 0 5px #00c000, 0 0 15px #00c000;
|
||||
}
|
||||
|
||||
.window-frame button.speaker-switching-alt {
|
||||
color: #ffffff;
|
||||
text-shadow: 0 0 5px #00ff00, 0 0 15px #00c000;
|
||||
}
|
||||
|
||||
.window-frame button.speaker-switching-alt:checked {
|
||||
text-shadow: 0 0 5px #ff0000, 0 0 15px #c00000;
|
||||
}
|
||||
|
||||
.window-frame button.talkback-enable:checked {
|
||||
text-shadow: 0 0 5px #00c000, 0 0 15px #00c000;
|
||||
}
|
||||
|
||||
.window-frame button.talk:checked {
|
||||
text-shadow: 0 0 5px #00c000, 0 0 15px #00c000;
|
||||
}
|
||||
|
||||
/* orange */
|
||||
.window-frame .vocaster button.autogain:checked {
|
||||
text-shadow: 0 0 5px #ffc000, 0 0 15px #ffc000;
|
||||
@@ -210,6 +239,10 @@ button.toggle {
|
||||
text-shadow: 0 0 5px #00c000, 0 0 15px #00c000;
|
||||
}
|
||||
|
||||
.window-frame button.gain-switch: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;
|
||||
}
|
||||
|
||||
132
src/alsa-sim.c
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "alsa.h"
|
||||
@@ -99,6 +99,53 @@ static void alsa_parse_enum_items(
|
||||
}
|
||||
}
|
||||
|
||||
static void alsa_parse_int_array(
|
||||
snd_config_t *node,
|
||||
long **int_values
|
||||
) {
|
||||
int count = snd_config_is_array(node);
|
||||
if (count < 0) {
|
||||
printf("error: parse int array value %d\n", count);
|
||||
return;
|
||||
}
|
||||
|
||||
*int_values = calloc(count, sizeof(long));
|
||||
|
||||
int item_num = 0;
|
||||
|
||||
snd_config_iterator_t i, next;
|
||||
snd_config_for_each(i, next, node) {
|
||||
snd_config_t *node = snd_config_iterator_entry(i);
|
||||
|
||||
const char *key;
|
||||
|
||||
int err = snd_config_get_id(node, &key);
|
||||
if (err < 0)
|
||||
fatal_alsa_error("snd_config_get_id error", err);
|
||||
|
||||
int type = snd_config_get_type(node);
|
||||
|
||||
if (type == SND_CONFIG_TYPE_STRING) {
|
||||
const char *string_value;
|
||||
|
||||
err = snd_config_get_string(node, &string_value);
|
||||
if (err < 0)
|
||||
fatal_alsa_error("snd_config_get_string error", err);
|
||||
|
||||
if (strcmp(string_value, "true") == 0)
|
||||
(*int_values)[item_num++] = 1;
|
||||
|
||||
} else if (type == SND_CONFIG_TYPE_INTEGER) {
|
||||
long int_value;
|
||||
err = snd_config_get_integer(node, &int_value);
|
||||
if (err < 0)
|
||||
fatal_alsa_error("snd_config_get_integer error", err);
|
||||
|
||||
(*int_values)[item_num++] = int_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse a comment node and update elem, e.g.:
|
||||
//
|
||||
// comment {
|
||||
@@ -149,6 +196,14 @@ static void alsa_parse_comment_node(
|
||||
elem->type = SND_CTL_ELEM_TYPE_ENUMERATED;
|
||||
else if (strcmp(type, "INTEGER") == 0)
|
||||
elem->type = SND_CTL_ELEM_TYPE_INTEGER;
|
||||
} else if (strcmp(key, "count") == 0) {
|
||||
long count;
|
||||
|
||||
err = snd_config_get_integer(node, &count);
|
||||
if (err < 0)
|
||||
fatal_alsa_error("snd_config_get_integer error", err);
|
||||
|
||||
elem->count = count;
|
||||
} else if (strcmp(key, "item") == 0) {
|
||||
alsa_parse_enum_items(node, elem);
|
||||
} else if (strcmp(key, "range") == 0) {
|
||||
@@ -176,7 +231,7 @@ static void alsa_parse_comment_node(
|
||||
err = snd_config_get_integer(node, &dbmin);
|
||||
if (err < 0)
|
||||
fatal_alsa_error("snd_config_get_integer error", err);
|
||||
elem->min_dB = dbmin / 100;
|
||||
elem->min_cdB = dbmin;
|
||||
} else if (strcmp(key, "dbmax") == 0) {
|
||||
if (type != SND_CONFIG_TYPE_INTEGER) {
|
||||
printf("dbmax type not integer\n");
|
||||
@@ -186,14 +241,14 @@ static void alsa_parse_comment_node(
|
||||
err = snd_config_get_integer(node, &dbmax);
|
||||
if (err < 0)
|
||||
fatal_alsa_error("snd_config_get_integer error", err);
|
||||
elem->max_dB = dbmax / 100;
|
||||
elem->max_cdB = dbmax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int alsa_config_to_new_elem(
|
||||
snd_config_t *config,
|
||||
struct alsa_elem *elem
|
||||
struct alsa_card *card,
|
||||
snd_config_t *config
|
||||
) {
|
||||
const char *s;
|
||||
int id;
|
||||
@@ -202,8 +257,11 @@ static int alsa_config_to_new_elem(
|
||||
int value_type = -1;
|
||||
char *string_value = NULL;
|
||||
long int_value;
|
||||
long *int_values = NULL;
|
||||
int err;
|
||||
|
||||
struct alsa_elem elem = {};
|
||||
|
||||
err = snd_config_get_id(config, &s);
|
||||
if (err < 0)
|
||||
fatal_alsa_error("snd_config_get_id error", err);
|
||||
@@ -260,11 +318,14 @@ static int alsa_config_to_new_elem(
|
||||
fatal_alsa_error("snd_config_get_string error", err);
|
||||
string_value = strdup(s);
|
||||
} else if (type == SND_CONFIG_TYPE_COMPOUND) {
|
||||
elem->count = snd_config_is_array(node);
|
||||
elem.count = snd_config_is_array(node);
|
||||
|
||||
if (strcmp(name, "Level Meter") == 0) {
|
||||
seen_value = 1;
|
||||
value_type = SND_CONFIG_TYPE_INTEGER;
|
||||
int_value = 0;
|
||||
} else if (elem.count == 2 && strncmp(name, "Master", 6) == 0) {
|
||||
alsa_parse_int_array(node, &int_values);
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
@@ -278,7 +339,11 @@ static int alsa_config_to_new_elem(
|
||||
|
||||
// comment node?
|
||||
} else if (strcmp(key, "comment") == 0) {
|
||||
alsa_parse_comment_node(node, elem);
|
||||
alsa_parse_comment_node(node, &elem);
|
||||
|
||||
// this isn't needed
|
||||
} else if (strcmp(key, "index") == 0) {
|
||||
|
||||
} else {
|
||||
printf("skipping unknown node %s for %d\n", key, id);
|
||||
goto fail;
|
||||
@@ -309,21 +374,21 @@ static int alsa_config_to_new_elem(
|
||||
|
||||
// integer in config
|
||||
if (value_type == SND_CONFIG_TYPE_INTEGER) {
|
||||
elem->value = int_value;
|
||||
elem.value = int_value;
|
||||
|
||||
// string in config
|
||||
} else if (value_type == SND_CONFIG_TYPE_STRING) {
|
||||
|
||||
// translate boolean true/false
|
||||
if (elem->type == SND_CTL_ELEM_TYPE_BOOLEAN) {
|
||||
if (elem.type == SND_CTL_ELEM_TYPE_BOOLEAN) {
|
||||
if (strcmp(string_value, "true") == 0)
|
||||
elem->value = 1;
|
||||
elem.value = 1;
|
||||
|
||||
// translate enum string value to integer
|
||||
} else if (elem->type == SND_CTL_ELEM_TYPE_ENUMERATED) {
|
||||
for (int i = 0; i < elem->item_count; i++) {
|
||||
if (strcmp(string_value, elem->item_names[i]) == 0) {
|
||||
elem->value = i;
|
||||
} else if (elem.type == SND_CTL_ELEM_TYPE_ENUMERATED) {
|
||||
for (int i = 0; i < elem.item_count; i++) {
|
||||
if (strcmp(string_value, elem.item_names[i]) == 0) {
|
||||
elem.value = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -334,11 +399,30 @@ static int alsa_config_to_new_elem(
|
||||
}
|
||||
}
|
||||
|
||||
elem->numid = id;
|
||||
elem->name = name;
|
||||
elem.card = card;
|
||||
elem.numid = id;
|
||||
elem.name = name;
|
||||
|
||||
// duplicate the element for each channel except for the Level Meter
|
||||
int count = elem.count;
|
||||
|
||||
if (strcmp(elem.name, "Level Meter") == 0)
|
||||
count = 1;
|
||||
|
||||
// for each channel, create a new element and add it to the card
|
||||
// incrementing the index each time
|
||||
for (int i = 0; i < count; i++, elem.index++) {
|
||||
if (count > 1)
|
||||
elem.value = int_values[i];
|
||||
|
||||
int array_len = card->elems->len;
|
||||
g_array_set_size(card->elems, array_len + 1);
|
||||
g_array_index(card->elems, struct alsa_elem, array_len) = elem;
|
||||
}
|
||||
|
||||
free(iface);
|
||||
free(string_value);
|
||||
free(int_values);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -346,6 +430,7 @@ fail:
|
||||
free(iface);
|
||||
free(name);
|
||||
free(string_value);
|
||||
free(int_values);
|
||||
|
||||
return -1;
|
||||
}
|
||||
@@ -370,18 +455,8 @@ static void alsa_config_to_new_card(
|
||||
if (snd_config_get_type(config) != SND_CONFIG_TYPE_COMPOUND)
|
||||
continue;
|
||||
|
||||
struct alsa_elem elem = {};
|
||||
elem.card = card;
|
||||
|
||||
// create the element
|
||||
int err = alsa_config_to_new_elem(node, &elem);
|
||||
|
||||
if (err)
|
||||
continue;
|
||||
|
||||
if (card->elems->len <= elem.numid)
|
||||
g_array_set_size(card->elems, elem.numid + 1);
|
||||
g_array_index(card->elems, struct alsa_elem, elem.numid) = elem;
|
||||
alsa_config_to_new_elem(card, node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,5 +508,8 @@ void create_sim_from_file(GtkWindow *w, char *fn) {
|
||||
|
||||
snd_config_delete(config);
|
||||
|
||||
alsa_set_lr_nums(card);
|
||||
alsa_get_routing_controls(card);
|
||||
|
||||
create_card_window(card);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
794
src/alsa.c
@@ -1,23 +1,42 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <sys/inotify.h>
|
||||
#include <alsa/sound/uapi/tlv.h>
|
||||
|
||||
#include "alsa.h"
|
||||
#include "scarlett2.h"
|
||||
#include "scarlett2-firmware.h"
|
||||
#include "scarlett2-ioctls.h"
|
||||
#include "stringhelper.h"
|
||||
#include "window-iface.h"
|
||||
|
||||
#define MAX_TLV_RANGE_SIZE 256
|
||||
#define MAJOR_HWDEP_VERSION_SCARLETT2 1
|
||||
#define MAJOR_HWDEP_VERSION_FCP 2
|
||||
|
||||
#define MAX_TLV_RANGE_SIZE 1024
|
||||
|
||||
// TLV type for channel labels
|
||||
#ifndef SNDRV_CTL_TLVT_FCP_CHANNEL_LABELS
|
||||
#define SNDRV_CTL_TLVT_FCP_CHANNEL_LABELS 0x110
|
||||
#endif
|
||||
|
||||
// names for the port categories
|
||||
const char *port_category_names[PC_COUNT] = {
|
||||
NULL,
|
||||
"Hardware Outputs",
|
||||
"Mixer Inputs",
|
||||
"DSP Inputs",
|
||||
"PCM Inputs"
|
||||
};
|
||||
|
||||
// names for the hardware types
|
||||
const char *hw_type_names[HW_TYPE_COUNT] = {
|
||||
"Analogue",
|
||||
"S/PDIF",
|
||||
"ADAT"
|
||||
};
|
||||
|
||||
// global array of cards
|
||||
static GArray *alsa_cards;
|
||||
|
||||
@@ -76,6 +95,21 @@ struct alsa_elem *get_elem_by_prefix(GArray *elems, const char *prefix) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// return the first element with a name containing the given substring
|
||||
struct alsa_elem *get_elem_by_substr(GArray *elems, const char *substr) {
|
||||
for (int i = 0; i < elems->len; i++) {
|
||||
struct alsa_elem *elem = &g_array_index(elems, struct alsa_elem, i);
|
||||
|
||||
if (!elem->card)
|
||||
continue;
|
||||
|
||||
if (strstr(elem->name, substr))
|
||||
return elem;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// find the maximum number in the matching elements
|
||||
// search by element name prefix and substring
|
||||
// e.g. get_max_elem_by_name(elems, "Line", "Pad Capture Switch")
|
||||
@@ -110,25 +144,6 @@ int get_max_elem_by_name(
|
||||
return max;
|
||||
}
|
||||
|
||||
// return true if the element is an routing sink enum, e.g.:
|
||||
// PCM xx Capture Enum
|
||||
// Mixer Input xx Capture Enum
|
||||
// Analogue Output xx Playback Enum
|
||||
// S/PDIF Output xx Playback Enum
|
||||
// ADAT Output xx Playback Enum
|
||||
int is_elem_routing_snk(struct alsa_elem *elem) {
|
||||
if (strstr(elem->name, "Capture Enum") && (
|
||||
strncmp(elem->name, "PCM ", 4) == 0 ||
|
||||
strncmp(elem->name, "Mixer Input ", 12) == 0 ||
|
||||
strncmp(elem->name, "DSP Input ", 10) == 0
|
||||
))
|
||||
return 1;
|
||||
if (strstr(elem->name, "Output") &&
|
||||
strstr(elem->name, "Playback Enum"))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// add a callback to the list of callbacks for this element
|
||||
void alsa_elem_add_callback(
|
||||
struct alsa_elem *elem,
|
||||
@@ -186,11 +201,11 @@ long alsa_get_elem_value(struct alsa_elem *elem) {
|
||||
|
||||
int type = elem->type;
|
||||
if (type == SND_CTL_ELEM_TYPE_BOOLEAN) {
|
||||
return snd_ctl_elem_value_get_boolean(elem_value, 0);
|
||||
return snd_ctl_elem_value_get_boolean(elem_value, elem->index);
|
||||
} else if (type == SND_CTL_ELEM_TYPE_ENUMERATED) {
|
||||
return snd_ctl_elem_value_get_enumerated(elem_value, 0);
|
||||
return snd_ctl_elem_value_get_enumerated(elem_value, elem->index);
|
||||
} else if (type == SND_CTL_ELEM_TYPE_INTEGER) {
|
||||
return snd_ctl_elem_value_get_integer(elem_value, 0);
|
||||
return snd_ctl_elem_value_get_integer(elem_value, elem->index);
|
||||
} else {
|
||||
fprintf(
|
||||
stderr,
|
||||
@@ -205,8 +220,8 @@ long alsa_get_elem_value(struct alsa_elem *elem) {
|
||||
|
||||
// for elements with multiple int values, return all the values
|
||||
// the int array returned needs to be freed by the caller
|
||||
int *alsa_get_elem_int_values(struct alsa_elem *elem) {
|
||||
int *values = calloc(elem->count, sizeof(int));
|
||||
long *alsa_get_elem_int_values(struct alsa_elem *elem) {
|
||||
long *values = calloc(elem->count, sizeof(long));
|
||||
|
||||
if (elem->card->num == SIMULATED_CARD_NUM) {
|
||||
for (int i = 0; i < elem->count; i++)
|
||||
@@ -241,14 +256,15 @@ void alsa_set_elem_value(struct alsa_elem *elem, long value) {
|
||||
|
||||
snd_ctl_elem_value_alloca(&elem_value);
|
||||
snd_ctl_elem_value_set_numid(elem_value, elem->numid);
|
||||
snd_ctl_elem_read(elem->card->handle, elem_value);
|
||||
|
||||
int type = elem->type;
|
||||
if (type == SND_CTL_ELEM_TYPE_BOOLEAN) {
|
||||
snd_ctl_elem_value_set_boolean(elem_value, 0, value);
|
||||
snd_ctl_elem_value_set_boolean(elem_value, elem->index, value);
|
||||
} else if (type == SND_CTL_ELEM_TYPE_ENUMERATED) {
|
||||
snd_ctl_elem_value_set_enumerated(elem_value, 0, value);
|
||||
snd_ctl_elem_value_set_enumerated(elem_value, elem->index, value);
|
||||
} else if (type == SND_CTL_ELEM_TYPE_INTEGER) {
|
||||
snd_ctl_elem_value_set_integer(elem_value, 0, value);
|
||||
snd_ctl_elem_value_set_integer(elem_value, elem->index, value);
|
||||
} else {
|
||||
fprintf(
|
||||
stderr,
|
||||
@@ -274,7 +290,8 @@ int alsa_get_elem_writable(struct alsa_elem *elem) {
|
||||
snd_ctl_elem_info_set_numid(elem_info, elem->numid);
|
||||
snd_ctl_elem_info(elem->card->handle, elem_info);
|
||||
|
||||
return snd_ctl_elem_info_is_writable(elem_info);
|
||||
return snd_ctl_elem_info_is_writable(elem_info) &&
|
||||
!snd_ctl_elem_info_is_locked(elem_info);
|
||||
}
|
||||
|
||||
// return whether the element is volatile (can change without
|
||||
@@ -338,6 +355,163 @@ char *alsa_get_item_name(struct alsa_elem *elem, int i) {
|
||||
// create/destroy alsa cards
|
||||
//
|
||||
|
||||
static void alsa_get_elem_tlv(struct alsa_elem *elem) {
|
||||
struct alsa_card *card = elem->card;
|
||||
|
||||
if (elem->type != SND_CTL_ELEM_TYPE_INTEGER)
|
||||
return;
|
||||
|
||||
snd_ctl_elem_info_t *elem_info;
|
||||
|
||||
snd_ctl_elem_info_alloca(&elem_info);
|
||||
snd_ctl_elem_info_set_numid(elem_info, elem->numid);
|
||||
snd_ctl_elem_info(card->handle, elem_info);
|
||||
|
||||
if (!snd_ctl_elem_info_is_tlv_readable(elem_info))
|
||||
return;
|
||||
|
||||
snd_ctl_elem_id_t *elem_id;
|
||||
unsigned int tlv[MAX_TLV_RANGE_SIZE];
|
||||
unsigned int *dbrec;
|
||||
int ret;
|
||||
long min_cdB, max_cdB;
|
||||
|
||||
snd_ctl_elem_id_alloca(&elem_id);
|
||||
snd_ctl_elem_id_set_numid(elem_id, elem->numid);
|
||||
|
||||
ret = snd_ctl_elem_tlv_read(
|
||||
card->handle, elem_id, tlv, sizeof(tlv)
|
||||
);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "TLV read error: %s\n", snd_strerror(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
// meter map
|
||||
if (tlv[SNDRV_CTL_TLVO_TYPE] == SNDRV_CTL_TLVT_FCP_CHANNEL_LABELS) {
|
||||
int label_data_size = tlv[SNDRV_CTL_TLVO_LEN];
|
||||
char *label_data = (char *)&tlv[SNDRV_CTL_TLVO_LEN + 1];
|
||||
|
||||
// check that there are at least elem->count labels in the data
|
||||
int label_count = 0;
|
||||
for (int i = 0; i < label_data_size; i++) {
|
||||
if (!label_data[i])
|
||||
label_count++;
|
||||
}
|
||||
|
||||
if (label_count < elem->count) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"TLV label count %d < %d\n",
|
||||
label_count,
|
||||
elem->count
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (elem->count < 0 || elem->count > 255) {
|
||||
fprintf(stderr, "TLV label count %d out of range\n", elem->count);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
elem->meter_labels = calloc(elem->count, sizeof(char *));
|
||||
|
||||
char *cur_label = label_data;
|
||||
for (int i = 0; i < elem->count; i++) {
|
||||
elem->meter_labels[i] = strdup(cur_label);
|
||||
if (!elem->meter_labels[i]) {
|
||||
fprintf(stderr, "strdup failed\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
cur_label += strlen(cur_label) + 1;
|
||||
}
|
||||
|
||||
/* firmware version TLV contains socket location */
|
||||
} else if (tlv[SNDRV_CTL_TLVO_TYPE] == 0x53434B54) {
|
||||
card->fcp_socket = strdup((char *)&tlv[SNDRV_CTL_TLVO_LEN + 1]);
|
||||
|
||||
/* dB range */
|
||||
} else {
|
||||
ret = snd_tlv_parse_dB_info(tlv, sizeof(tlv), &dbrec);
|
||||
if (ret <= 0) {
|
||||
fprintf(stderr, "TLV parse error: %s\n", snd_strerror(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
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(
|
||||
dbrec, min_val, max_val, &min_cdB, &max_cdB
|
||||
);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "TLV range error: %s\n", snd_strerror(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
elem->min_val = min_val;
|
||||
elem->max_val = max_val;
|
||||
elem->dB_type = dbrec[SNDRV_CTL_TLVO_TYPE];
|
||||
elem->min_cdB = min_cdB;
|
||||
elem->max_cdB = max_cdB;
|
||||
}
|
||||
}
|
||||
|
||||
static void alsa_get_elem(struct alsa_card *card, int numid) {
|
||||
// allocate a temporary struct alsa_elem (will be copied later if
|
||||
// we want to keep it)
|
||||
struct alsa_elem alsa_elem = {};
|
||||
|
||||
// keep a reference to the card in the element
|
||||
alsa_elem.card = card;
|
||||
|
||||
// get the control's numeric identifier (different to the index
|
||||
// into this array)
|
||||
alsa_elem.numid = numid;
|
||||
|
||||
// get the control's info
|
||||
alsa_elem.type = alsa_get_elem_type(&alsa_elem);
|
||||
alsa_elem.name = alsa_get_elem_name(&alsa_elem);
|
||||
alsa_elem.count = alsa_get_elem_count(&alsa_elem);
|
||||
|
||||
switch (alsa_elem.type) {
|
||||
case SND_CTL_ELEM_TYPE_BOOLEAN:
|
||||
case SND_CTL_ELEM_TYPE_ENUMERATED:
|
||||
case SND_CTL_ELEM_TYPE_INTEGER:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (strstr(alsa_elem.name, "Validity"))
|
||||
return;
|
||||
if (strstr(alsa_elem.name, "Channel Map"))
|
||||
return;
|
||||
|
||||
alsa_get_elem_tlv(&alsa_elem);
|
||||
|
||||
// Scarlett 1st Gen driver puts two volume controls/mutes in the
|
||||
// same element, so split them out to match the other series
|
||||
int count = alsa_elem.count;
|
||||
|
||||
if (strcmp(alsa_elem.name, "Level Meter") == 0)
|
||||
count = 1;
|
||||
|
||||
if (count > 2) {
|
||||
fprintf(stderr, "element %s has count %d\n", alsa_elem.name, count);
|
||||
count = 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++, alsa_elem.lr_num++) {
|
||||
alsa_elem.index = i;
|
||||
|
||||
int array_len = card->elems->len;
|
||||
g_array_set_size(card->elems, array_len + 1);
|
||||
g_array_index(card->elems, struct alsa_elem, array_len) = alsa_elem;
|
||||
}
|
||||
}
|
||||
|
||||
// scan the ALSA ctl element list container and put the useful
|
||||
// elements into the cards->elems array of struct alsa_elem
|
||||
static void alsa_get_elem_list(struct alsa_card *card) {
|
||||
@@ -353,88 +527,8 @@ static void alsa_get_elem_list(struct alsa_card *card) {
|
||||
|
||||
// for each element in the list
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
||||
// allocate a temporary struct alsa_elem (will be copied later if
|
||||
// we want to keep it)
|
||||
struct alsa_elem alsa_elem = {};
|
||||
|
||||
// keep a reference to the card in the element
|
||||
alsa_elem.card = card;
|
||||
|
||||
// get the control's numeric identifier (different to the index
|
||||
// into this array)
|
||||
alsa_elem.numid = snd_ctl_elem_list_get_numid(list, i);
|
||||
|
||||
// get the control's info
|
||||
alsa_elem.type = alsa_get_elem_type(&alsa_elem);
|
||||
alsa_elem.name = alsa_get_elem_name(&alsa_elem);
|
||||
alsa_elem.count = alsa_get_elem_count(&alsa_elem);
|
||||
|
||||
switch (alsa_elem.type) {
|
||||
case SND_CTL_ELEM_TYPE_BOOLEAN:
|
||||
case SND_CTL_ELEM_TYPE_ENUMERATED:
|
||||
case SND_CTL_ELEM_TYPE_INTEGER:
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strstr(alsa_elem.name, "Validity"))
|
||||
continue;
|
||||
if (strstr(alsa_elem.name, "Channel Map"))
|
||||
continue;
|
||||
|
||||
// get TLV info if it's a volume control
|
||||
if (alsa_elem.type == SND_CTL_ELEM_TYPE_INTEGER) {
|
||||
snd_ctl_elem_info_t *elem_info;
|
||||
|
||||
snd_ctl_elem_info_alloca(&elem_info);
|
||||
snd_ctl_elem_info_set_numid(elem_info, alsa_elem.numid);
|
||||
snd_ctl_elem_info(card->handle, elem_info);
|
||||
|
||||
if (snd_ctl_elem_info_is_tlv_readable(elem_info)) {
|
||||
snd_ctl_elem_id_t *elem_id;
|
||||
unsigned int tlv[MAX_TLV_RANGE_SIZE];
|
||||
unsigned int *dbrec;
|
||||
int ret;
|
||||
long min_dB, max_dB;
|
||||
|
||||
snd_ctl_elem_id_alloca(&elem_id);
|
||||
snd_ctl_elem_id_set_numid(elem_id, alsa_elem.numid);
|
||||
|
||||
ret = snd_ctl_elem_tlv_read(
|
||||
card->handle, elem_id, tlv, sizeof(tlv)
|
||||
);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "TLV read error %d\n", ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = snd_tlv_parse_dB_info(tlv, sizeof(tlv), &dbrec);
|
||||
if (ret <= 0) {
|
||||
fprintf(stderr, "TLV parse error %d\n", ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
int min_val = snd_ctl_elem_info_get_min(elem_info);
|
||||
int max_val = snd_ctl_elem_info_get_max(elem_info);
|
||||
|
||||
ret = snd_tlv_get_dB_range(tlv, min_val, max_val, &min_dB, &max_dB);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "TLV range error %d\n", ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
alsa_elem.min_val = min_val;
|
||||
alsa_elem.max_val = max_val;
|
||||
alsa_elem.min_dB = min_dB / 100;
|
||||
alsa_elem.max_dB = max_dB / 100;
|
||||
}
|
||||
}
|
||||
|
||||
if (card->elems->len <= alsa_elem.numid)
|
||||
g_array_set_size(card->elems, alsa_elem.numid + 1);
|
||||
g_array_index(card->elems, struct alsa_elem, alsa_elem.numid) = alsa_elem;
|
||||
int numid = snd_ctl_elem_list_get_numid(list, i);
|
||||
alsa_get_elem(card, numid);
|
||||
}
|
||||
|
||||
// free the ALSA list
|
||||
@@ -442,6 +536,227 @@ static void alsa_get_elem_list(struct alsa_card *card) {
|
||||
snd_ctl_elem_list_free(list);
|
||||
}
|
||||
|
||||
static void alsa_set_elem_lr_num(struct alsa_elem *elem) {
|
||||
const char *name = elem->name;
|
||||
char side;
|
||||
|
||||
if (strncmp(name, "Master Playback", 15) == 0 ||
|
||||
strncmp(name, "Master HW Playback", 18) == 0)
|
||||
elem->lr_num = 0;
|
||||
|
||||
else if (strncmp(name, "Master", 6) == 0)
|
||||
if (sscanf(name, "Master %d%c", &elem->lr_num, &side) != 2)
|
||||
printf("can't parse Master '%s'\n", name);
|
||||
else
|
||||
elem->lr_num = elem->lr_num * 2
|
||||
- (side == 'L' || side == ' ')
|
||||
+ elem->index;
|
||||
|
||||
else
|
||||
elem->lr_num = get_num_from_string(name);
|
||||
}
|
||||
|
||||
void alsa_set_lr_nums(struct alsa_card *card) {
|
||||
for (int i = 0; i < card->elems->len; i++) {
|
||||
struct alsa_elem *elem = &g_array_index(card->elems, struct alsa_elem, i);
|
||||
|
||||
alsa_set_elem_lr_num(elem);
|
||||
}
|
||||
}
|
||||
|
||||
static void get_routing_srcs(struct alsa_card *card) {
|
||||
struct alsa_elem *elem = card->sample_capture_elem;
|
||||
|
||||
int count = alsa_get_item_count(elem);
|
||||
card->routing_srcs = g_array_new(
|
||||
FALSE, TRUE, sizeof(struct routing_src)
|
||||
);
|
||||
g_array_set_size(card->routing_srcs, count);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
char *name = alsa_get_item_name(elem, i);
|
||||
|
||||
struct routing_src *r = &g_array_index(
|
||||
card->routing_srcs, struct routing_src, i
|
||||
);
|
||||
r->card = card;
|
||||
r->id = i;
|
||||
|
||||
if (strcmp(name, "Off") == 0)
|
||||
r->port_category = PC_OFF;
|
||||
else if (strncmp(name, "Mix", 3) == 0)
|
||||
r->port_category = PC_MIX;
|
||||
else if (strncmp(name, "DSP", 3) == 0)
|
||||
r->port_category = PC_DSP;
|
||||
else if (strncmp(name, "PCM", 3) == 0)
|
||||
r->port_category = PC_PCM;
|
||||
else {
|
||||
r->port_category = PC_HW;
|
||||
|
||||
if (strncmp(name, "Analog", 6) == 0)
|
||||
r->hw_type = HW_TYPE_ANALOGUE;
|
||||
else if (strncmp(name, "S/PDIF", 6) == 0)
|
||||
r->hw_type = HW_TYPE_SPDIF;
|
||||
else if (strncmp(name, "SPDIF", 5) == 0)
|
||||
r->hw_type = HW_TYPE_SPDIF;
|
||||
else if (strncmp(name, "ADAT", 4) == 0)
|
||||
r->hw_type = HW_TYPE_ADAT;
|
||||
}
|
||||
|
||||
r->name = name;
|
||||
r->lr_num =
|
||||
r->port_category == PC_MIX
|
||||
? name[4] - 'A' + 1
|
||||
: get_num_from_string(name);
|
||||
|
||||
r->port_num = card->routing_in_count[r->port_category]++;
|
||||
}
|
||||
|
||||
assert(card->routing_in_count[PC_MIX] <= MAX_MIX_OUT);
|
||||
}
|
||||
|
||||
// return true if the element is an routing sink enum, e.g.:
|
||||
// PCM xx Capture Enum
|
||||
// Mixer Input xx Capture Enum
|
||||
// Analogue Output xx Playback Enum
|
||||
// S/PDIF Output xx Playback Enum
|
||||
// ADAT Output xx Playback Enum
|
||||
//
|
||||
// or new style:
|
||||
// PCM xx Capture Enum
|
||||
// Mixer xx Capture Enum
|
||||
// Analogue xx Playback Enum
|
||||
// S/PDIF xx Playback Enum
|
||||
// ADAT xx Playback Enum
|
||||
|
||||
static int is_elem_routing_snk(struct alsa_elem *elem) {
|
||||
if (strstr(elem->name, "Capture Route") ||
|
||||
strstr(elem->name, "Input Playback Route") ||
|
||||
strstr(elem->name, "Source Playback Enu"))
|
||||
return 1;
|
||||
|
||||
if (strstr(elem->name, "Capture Enum") && (
|
||||
strncmp(elem->name, "PCM ", 4) == 0 ||
|
||||
strncmp(elem->name, "Mixer ", 6) == 0 ||
|
||||
strncmp(elem->name, "DSP ", 4) == 0
|
||||
))
|
||||
return 1;
|
||||
|
||||
if (strstr(elem->name, "Playback Enum") && (
|
||||
strncmp(elem->name, "Analogue ", 9) == 0 ||
|
||||
strncmp(elem->name, "S/PDIF ", 7) == 0 ||
|
||||
strncmp(elem->name, "ADAT ", 5) == 0
|
||||
))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void get_routing_snks(struct alsa_card *card) {
|
||||
GArray *elems = card->elems;
|
||||
|
||||
int count = 0;
|
||||
|
||||
// count and label routing snks
|
||||
for (int i = 0; i < elems->len; i++) {
|
||||
struct alsa_elem *elem = &g_array_index(elems, struct alsa_elem, i);
|
||||
|
||||
if (!elem->card)
|
||||
continue;
|
||||
|
||||
if (!is_elem_routing_snk(elem))
|
||||
continue;
|
||||
|
||||
elem->is_routing_snk = 1;
|
||||
|
||||
if (strncmp(elem->name, "Mixer", 5) == 0 ||
|
||||
strncmp(elem->name, "Matrix", 6) == 0) {
|
||||
elem->port_category = PC_MIX;
|
||||
|
||||
if (!alsa_get_elem_writable(elem))
|
||||
card->has_fixed_mixer_inputs = 1;
|
||||
|
||||
} else if (strncmp(elem->name, "DSP", 3) == 0) {
|
||||
elem->port_category = PC_DSP;
|
||||
} else if (strncmp(elem->name, "PCM", 3) == 0 ||
|
||||
strncmp(elem->name, "Input Source", 12) == 0) {
|
||||
elem->port_category = PC_PCM;
|
||||
} else if (strstr(elem->name, "Playback Enu")) {
|
||||
elem->port_category = PC_HW;
|
||||
|
||||
if (strncmp(elem->name, "Analog", 6) == 0)
|
||||
elem->hw_type = HW_TYPE_ANALOGUE;
|
||||
else if (strncmp(elem->name, "S/PDIF", 6) == 0 ||
|
||||
strstr(elem->name, "SPDIF"))
|
||||
elem->hw_type = HW_TYPE_SPDIF;
|
||||
else if (strstr(elem->name, "ADAT"))
|
||||
elem->hw_type = HW_TYPE_ADAT;
|
||||
} else {
|
||||
printf("unknown mixer routing elem %s\n", elem->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (elem->lr_num <= 0) {
|
||||
fprintf(stderr, "routing sink %s had no number\n", elem->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
// create an array of routing snks pointing to those elements
|
||||
card->routing_snks = g_array_new(
|
||||
FALSE, TRUE, sizeof(struct routing_snk)
|
||||
);
|
||||
g_array_set_size(card->routing_snks, count);
|
||||
|
||||
// count through card->routing_snks
|
||||
int j = 0;
|
||||
|
||||
for (int i = 0; i < elems->len; i++) {
|
||||
struct alsa_elem *elem = &g_array_index(elems, struct alsa_elem, i);
|
||||
|
||||
if (!elem->is_routing_snk)
|
||||
continue;
|
||||
|
||||
struct routing_snk *r = &g_array_index(
|
||||
card->routing_snks, struct routing_snk, j
|
||||
);
|
||||
r->idx = j;
|
||||
j++;
|
||||
r->elem = elem;
|
||||
elem->port_num = card->routing_out_count[elem->port_category]++;
|
||||
}
|
||||
|
||||
assert(j == count);
|
||||
}
|
||||
|
||||
void alsa_get_routing_controls(struct alsa_card *card) {
|
||||
|
||||
// check that we can find a routing control
|
||||
card->sample_capture_elem =
|
||||
get_elem_by_name(card->elems, "PCM 01 Capture Enum");
|
||||
if (!card->sample_capture_elem)
|
||||
card->sample_capture_elem =
|
||||
get_elem_by_name(card->elems, "PCM 1 Capture Enum");
|
||||
if (!card->sample_capture_elem)
|
||||
card->sample_capture_elem =
|
||||
get_elem_by_name(card->elems, "Input Source 01 Capture Route");
|
||||
|
||||
if (!card->sample_capture_elem) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"can't find routing control PCM 01 Capture Enum or "
|
||||
"Input Source 01 Capture Route\n"
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
get_routing_srcs(card);
|
||||
get_routing_snks(card);
|
||||
}
|
||||
|
||||
static void alsa_elem_change(struct alsa_elem *elem) {
|
||||
if (!elem || !elem->callbacks)
|
||||
return;
|
||||
@@ -456,6 +771,98 @@ static void alsa_elem_change(struct alsa_elem *elem) {
|
||||
}
|
||||
}
|
||||
|
||||
static void card_destroy_callback(void *data) {
|
||||
struct alsa_card *card = data;
|
||||
|
||||
// close the windows associated with this card
|
||||
destroy_card_window(card);
|
||||
|
||||
// TODO: there is more to free
|
||||
free(card->device);
|
||||
free(card->serial);
|
||||
free(card->name);
|
||||
free(card);
|
||||
|
||||
// go through the alsa_cards array and clear the entry for this card
|
||||
for (int i = 0; i < alsa_cards->len; i++) {
|
||||
struct alsa_card **card_ptr =
|
||||
&g_array_index(alsa_cards, struct alsa_card *, i);
|
||||
if (*card_ptr == card)
|
||||
*card_ptr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Complete card initialisation after the driver is ready
|
||||
static void complete_card_init(struct alsa_card *card) {
|
||||
|
||||
// Get full element list and create main window
|
||||
alsa_get_elem_list(card);
|
||||
alsa_set_lr_nums(card);
|
||||
alsa_get_routing_controls(card);
|
||||
card->best_firmware_version = scarlett2_get_best_firmware_version(card->pid);
|
||||
|
||||
if (card->serial) {
|
||||
// Call the reopen 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);
|
||||
}
|
||||
|
||||
// Check if the Firmware Version control has a TLV and is locked,
|
||||
// indicating the driver is ready
|
||||
static int check_driver_ready(snd_ctl_elem_info_t *info) {
|
||||
return snd_ctl_elem_info_is_tlv_readable(info) &&
|
||||
snd_ctl_elem_info_is_locked(info);
|
||||
}
|
||||
|
||||
// Check if the FCP driver is initialised
|
||||
static void check_driver_init(
|
||||
struct alsa_card *card, int numid, unsigned int mask
|
||||
) {
|
||||
|
||||
// Ignore controls going away
|
||||
if (mask == SND_CTL_EVENT_MASK_REMOVE)
|
||||
return;
|
||||
|
||||
// Get the control's info
|
||||
snd_ctl_elem_id_t *id;
|
||||
snd_ctl_elem_info_t *info;
|
||||
|
||||
snd_ctl_elem_id_alloca(&id);
|
||||
snd_ctl_elem_info_alloca(&info);
|
||||
|
||||
snd_ctl_elem_id_set_numid(id, numid);
|
||||
snd_ctl_elem_info_set_id(info, id);
|
||||
|
||||
if (snd_ctl_elem_info(card->handle, info) < 0) {
|
||||
fprintf(stderr, "error getting elem info %d\n", numid);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *name = snd_ctl_elem_info_get_name(info);
|
||||
|
||||
// Check if it's the Firmware Version control being updated
|
||||
if (strcmp(name, "Firmware Version"))
|
||||
return;
|
||||
|
||||
// Check if the driver is ready
|
||||
if (!check_driver_ready(info))
|
||||
return;
|
||||
|
||||
// The driver is initialised; update the card's driver type
|
||||
card->driver_type = DRIVER_TYPE_SOCKET;
|
||||
|
||||
// Complete the card initialisation
|
||||
complete_card_init(card);
|
||||
}
|
||||
|
||||
static gboolean alsa_card_callback(
|
||||
GIOChannel *source,
|
||||
GIOCondition condition,
|
||||
@@ -463,16 +870,13 @@ static gboolean alsa_card_callback(
|
||||
) {
|
||||
struct alsa_card *card = data;
|
||||
snd_ctl_event_t *event;
|
||||
unsigned int mask;
|
||||
int err, numid;
|
||||
struct alsa_elem *elem;
|
||||
|
||||
snd_ctl_event_alloca(&event);
|
||||
if (!card->handle) {
|
||||
printf("oops, no card handle??\n");
|
||||
return 0;
|
||||
}
|
||||
err = snd_ctl_read(card->handle, event);
|
||||
int err = snd_ctl_read(card->handle, event);
|
||||
if (err == 0) {
|
||||
printf("alsa_card_callback nothing to read??\n");
|
||||
return 0;
|
||||
@@ -483,18 +887,34 @@ static gboolean alsa_card_callback(
|
||||
printf("card_callback_error %d\n", err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM)
|
||||
return 1;
|
||||
|
||||
numid = snd_ctl_event_elem_get_numid(event);
|
||||
elem = &g_array_index(card->elems, struct alsa_elem, numid);
|
||||
if (elem->numid != numid)
|
||||
int numid = snd_ctl_event_elem_get_numid(event);
|
||||
unsigned int mask = snd_ctl_event_elem_get_mask(event);
|
||||
|
||||
// Check if we're waiting for FCP driver to initialise and check if
|
||||
// it's now ready
|
||||
if (card->driver_type == DRIVER_TYPE_SOCKET_UNINIT) {
|
||||
check_driver_init(card, numid, mask);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (mask == SND_CTL_EVENT_MASK_REMOVE) {
|
||||
card_destroy_callback(card);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(mask & (SND_CTL_EVENT_MASK_VALUE | SND_CTL_EVENT_MASK_INFO)))
|
||||
return 1;
|
||||
|
||||
mask = snd_ctl_event_elem_get_mask(event);
|
||||
for (int i = 0; i < card->elems->len; i++) {
|
||||
struct alsa_elem *elem = &g_array_index(card->elems, struct alsa_elem, i);
|
||||
|
||||
if (mask & (SND_CTL_EVENT_MASK_VALUE | SND_CTL_EVENT_MASK_INFO))
|
||||
alsa_elem_change(elem);
|
||||
if (elem->numid == numid)
|
||||
alsa_elem_change(elem);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -543,27 +963,6 @@ struct alsa_card *card_create(int card_num) {
|
||||
return card;
|
||||
}
|
||||
|
||||
static void card_destroy_callback(void *data) {
|
||||
struct alsa_card *card = data;
|
||||
|
||||
// close the windows associated with this card
|
||||
destroy_card_window(card);
|
||||
|
||||
// TODO: there is more to free
|
||||
free(card->device);
|
||||
free(card->serial);
|
||||
free(card->name);
|
||||
free(card);
|
||||
|
||||
// go through the alsa_cards array and clear the entry for this card
|
||||
for (int i = 0; i < alsa_cards->len; i++) {
|
||||
struct alsa_card **card_ptr =
|
||||
&g_array_index(alsa_cards, struct alsa_card *, i);
|
||||
if (*card_ptr == card)
|
||||
*card_ptr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void alsa_add_card_callback(struct alsa_card *card) {
|
||||
card->io_channel = g_io_channel_unix_new(card->pfd.fd);
|
||||
card->event_source_id = g_io_add_watch_full(
|
||||
@@ -751,6 +1150,94 @@ static void alsa_get_serial_number(struct alsa_card *card) {
|
||||
card->serial = strdup(serial);
|
||||
}
|
||||
|
||||
// return true if the Firmware Version control exists and is writable
|
||||
// and locked (i.e. the FCP server is running)
|
||||
static int check_firmware_version_locked(struct alsa_card *card) {
|
||||
snd_ctl_elem_id_t *id;
|
||||
snd_ctl_elem_info_t *info;
|
||||
|
||||
snd_ctl_elem_id_alloca(&id);
|
||||
snd_ctl_elem_info_alloca(&info);
|
||||
|
||||
// look for the Firmware Version control
|
||||
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD);
|
||||
snd_ctl_elem_id_set_name(id, "Firmware Version");
|
||||
snd_ctl_elem_info_set_id(info, id);
|
||||
|
||||
// no Firmware Version control found
|
||||
int err = snd_ctl_elem_info(card->handle, info);
|
||||
if (err < 0)
|
||||
return 0;
|
||||
|
||||
return check_driver_ready(info);
|
||||
}
|
||||
|
||||
// return the driver type for this card
|
||||
// DRIVER_TYPE_NONE: no driver
|
||||
// DRIVER_TYPE_HWDEP: Scarlett2 driver
|
||||
// DRIVER_TYPE_SOCKET: FCP driver
|
||||
// DRIVER_TYPE_SOCKET_UNINIT: FCP driver, but not initialised
|
||||
static int get_driver_type(struct alsa_card *card) {
|
||||
snd_hwdep_t *hwdep;
|
||||
|
||||
int err = scarlett2_open_card(card->device, &hwdep);
|
||||
|
||||
// no hwdep for this card - driver type none
|
||||
if (err == -ENOENT)
|
||||
return DRIVER_TYPE_NONE;
|
||||
|
||||
// if we get EPERM, it's FCP but no server running
|
||||
if (err == -EPERM)
|
||||
return DRIVER_TYPE_SOCKET_UNINIT;
|
||||
|
||||
// if we get EBUSY, it's FCP
|
||||
if (err == -EBUSY)
|
||||
// fcp-server locks the Firmware Version control when it has
|
||||
// finished starting up
|
||||
return check_firmware_version_locked(card) ?
|
||||
DRIVER_TYPE_SOCKET : DRIVER_TYPE_SOCKET_UNINIT;
|
||||
|
||||
// failed to open hwdep
|
||||
if (err < 0)
|
||||
return DRIVER_TYPE_NONE;
|
||||
|
||||
// we can open hwdep, so now check the protocol version
|
||||
int ver = scarlett2_get_protocol_version(hwdep);
|
||||
scarlett2_close(hwdep);
|
||||
|
||||
// failed to get protocol version
|
||||
if (ver < 0)
|
||||
return DRIVER_TYPE_NONE;
|
||||
|
||||
// hwdep protocol version 1.x.x is Scarlett2 driver
|
||||
if (SCARLETT2_HWDEP_VERSION_MAJOR(ver) == MAJOR_HWDEP_VERSION_SCARLETT2)
|
||||
return DRIVER_TYPE_HWDEP;
|
||||
|
||||
// hwdep protocol version 2.x.x is FCP driver (but not initialised,
|
||||
// because we were able to open the hwdep)
|
||||
if (SCARLETT2_HWDEP_VERSION_MAJOR(ver) == MAJOR_HWDEP_VERSION_FCP)
|
||||
return DRIVER_TYPE_SOCKET_UNINIT;
|
||||
|
||||
return DRIVER_TYPE_NONE;
|
||||
}
|
||||
|
||||
static void card_init(struct alsa_card *card) {
|
||||
alsa_get_usbid(card);
|
||||
alsa_get_serial_number(card);
|
||||
alsa_subscribe(card);
|
||||
alsa_add_card_callback(card);
|
||||
|
||||
card->driver_type = get_driver_type(card);
|
||||
|
||||
// Driver not ready? Create the iface-waiting window
|
||||
if (card->driver_type == DRIVER_TYPE_SOCKET_UNINIT) {
|
||||
create_card_window(card);
|
||||
return;
|
||||
}
|
||||
|
||||
complete_card_init(card);
|
||||
}
|
||||
|
||||
static void alsa_scan_cards(void) {
|
||||
snd_ctl_card_info_t *info;
|
||||
snd_ctl_t *ctl;
|
||||
@@ -794,26 +1281,7 @@ static void alsa_scan_cards(void) {
|
||||
card->name = strdup(snd_ctl_card_info_get_name(info));
|
||||
card->handle = ctl;
|
||||
|
||||
alsa_get_elem_list(card);
|
||||
alsa_subscribe(card);
|
||||
alsa_get_usbid(card);
|
||||
alsa_get_serial_number(card);
|
||||
card->best_firmware_version = scarlett2_get_best_firmware_version(card->pid);
|
||||
|
||||
if (card->serial) {
|
||||
|
||||
// call the callbacks for this card
|
||||
struct reopen_callback *rc = g_hash_table_lookup(
|
||||
reopen_callbacks, card->serial
|
||||
);
|
||||
if (rc)
|
||||
rc->callback(rc->data);
|
||||
|
||||
g_hash_table_remove(reopen_callbacks, card->serial);
|
||||
}
|
||||
|
||||
create_card_window(card);
|
||||
alsa_add_card_callback(card);
|
||||
card_init(card);
|
||||
|
||||
continue;
|
||||
|
||||
|
||||
88
src/alsa.h
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
@@ -22,25 +22,41 @@ typedef void (AlsaElemCallback)(struct alsa_elem *, void *);
|
||||
// port categories for routing_src and routing_snk entries
|
||||
// must match the level meter ordering from the driver
|
||||
enum {
|
||||
// Hardware inputs/outputs
|
||||
PC_HW = 0,
|
||||
|
||||
// Mixer inputs/outputs
|
||||
PC_MIX = 1,
|
||||
|
||||
// DSP inputs/outputs
|
||||
PC_DSP = 2,
|
||||
|
||||
// PCM inputs/outputs
|
||||
PC_PCM = 3,
|
||||
|
||||
// number of port categories
|
||||
PC_COUNT = 4
|
||||
PC_OFF, // Off (the source when a sink is not connected)
|
||||
PC_HW, // Hardware inputs/outputs
|
||||
PC_MIX, // Mixer inputs/outputs
|
||||
PC_DSP, // DSP inputs/outputs
|
||||
PC_PCM, // PCM inputs/outputs
|
||||
PC_COUNT // number of port categories
|
||||
};
|
||||
|
||||
// names for the port categories
|
||||
extern const char *port_category_names[PC_COUNT];
|
||||
|
||||
// hardware types
|
||||
enum {
|
||||
HW_TYPE_ANALOGUE,
|
||||
HW_TYPE_SPDIF,
|
||||
HW_TYPE_ADAT,
|
||||
HW_TYPE_COUNT
|
||||
};
|
||||
|
||||
// driver types
|
||||
// NONE is 1st Gen or Scarlett2 before hwdep support was added
|
||||
// (no erase config or firmware update support)
|
||||
// HWDEP is the Scarlett2 driver after hwdep support was added
|
||||
// SOCKET is the FCP driver
|
||||
enum {
|
||||
DRIVER_TYPE_NONE,
|
||||
DRIVER_TYPE_HWDEP,
|
||||
DRIVER_TYPE_SOCKET,
|
||||
DRIVER_TYPE_SOCKET_UNINIT,
|
||||
DRIVER_TYPE_COUNT
|
||||
};
|
||||
|
||||
// names for the hardware types
|
||||
extern const char *hw_type_names[HW_TYPE_COUNT];
|
||||
|
||||
// is a drag active, and whether dragging from a routing source or a
|
||||
// routing sink
|
||||
enum {
|
||||
@@ -59,7 +75,7 @@ struct routing_src {
|
||||
// the enum id of the alsa item
|
||||
int id;
|
||||
|
||||
// PC_DSP, PC_MIX, PC_PCM, or PC_HW
|
||||
// PC_OFF, PC_DSP, PC_MIX, PC_PCM, or PC_HW
|
||||
int port_category;
|
||||
|
||||
// 0-based count within port_category
|
||||
@@ -68,6 +84,9 @@ struct routing_src {
|
||||
// the alsa item name
|
||||
char *name;
|
||||
|
||||
// for PC_HW, the hardware type
|
||||
int hw_type;
|
||||
|
||||
// the number (or translated letter; A = 1) in the item name
|
||||
int lr_num;
|
||||
|
||||
@@ -82,8 +101,6 @@ struct routing_src {
|
||||
// entry in alsa_card routing_snks (routing sinks) array for alsa
|
||||
// elements that are routing sinks like Analogue Output 01 Playback
|
||||
// Enum
|
||||
// port_category is set to PC_DSP, PC_MIX, PC_PCM, PC_HW
|
||||
// port_num is a count (0-based) within that category
|
||||
struct routing_snk {
|
||||
|
||||
// location within the array
|
||||
@@ -98,12 +115,6 @@ struct routing_snk {
|
||||
// socket widget on the routing page
|
||||
GtkWidget *socket_widget;
|
||||
|
||||
// PC_DSP, PC_MIX, PC_PCM, or PC_HW
|
||||
int port_category;
|
||||
|
||||
// 0-based count within port_category
|
||||
int port_num;
|
||||
|
||||
// the mixer label widgets for this sink
|
||||
GtkWidget *mixer_label_top;
|
||||
GtkWidget *mixer_label_bottom;
|
||||
@@ -126,15 +137,23 @@ struct alsa_elem {
|
||||
const char *name;
|
||||
int type;
|
||||
int count;
|
||||
int index;
|
||||
|
||||
// for gain/volume elements, the dB range and step
|
||||
// for gain/volume elements, the value range, dB type, and dB range
|
||||
int min_val;
|
||||
int max_val;
|
||||
int min_dB;
|
||||
int max_dB;
|
||||
int dB_type;
|
||||
int min_cdB;
|
||||
int max_cdB;
|
||||
|
||||
// for the number (or translated letter; A = 1) in the item name
|
||||
// TODO: move this to struct routing_snk?
|
||||
// level meter labels
|
||||
char **meter_labels;
|
||||
|
||||
// for routing sinks
|
||||
int is_routing_snk;
|
||||
int port_category;
|
||||
int port_num;
|
||||
int hw_type;
|
||||
int lr_num;
|
||||
|
||||
// the callback functions for this ALSA control element
|
||||
@@ -156,6 +175,8 @@ struct alsa_card {
|
||||
uint32_t pid;
|
||||
char *serial;
|
||||
char *name;
|
||||
int driver_type;
|
||||
char *fcp_socket;
|
||||
int best_firmware_version;
|
||||
snd_ctl_t *handle;
|
||||
struct pollfd pfd;
|
||||
@@ -184,6 +205,7 @@ struct alsa_card {
|
||||
GtkWidget *routing_mixer_out_grid;
|
||||
int has_speaker_switching;
|
||||
int has_talkback;
|
||||
int has_fixed_mixer_inputs;
|
||||
int routing_out_count[PC_COUNT];
|
||||
int routing_in_count[PC_COUNT];
|
||||
GMenu *routing_src_menu;
|
||||
@@ -200,12 +222,12 @@ void fatal_alsa_error(const char *msg, int err);
|
||||
// locate elements or get information about them
|
||||
struct alsa_elem *get_elem_by_name(GArray *elems, const char *name);
|
||||
struct alsa_elem *get_elem_by_prefix(GArray *elems, const char *prefix);
|
||||
struct alsa_elem *get_elem_by_substr(GArray *elems, const char *substr);
|
||||
int get_max_elem_by_name(
|
||||
GArray *elems,
|
||||
const char *prefix,
|
||||
const char *needle
|
||||
);
|
||||
int is_elem_routing_snk(struct alsa_elem *elem);
|
||||
|
||||
// add callback to alsa_elem callback list
|
||||
void alsa_elem_add_callback(
|
||||
@@ -218,7 +240,7 @@ void alsa_elem_add_callback(
|
||||
int alsa_get_elem_type(struct alsa_elem *elem);
|
||||
char *alsa_get_elem_name(struct alsa_elem *elem);
|
||||
long alsa_get_elem_value(struct alsa_elem *elem);
|
||||
int *alsa_get_elem_int_values(struct alsa_elem *elem);
|
||||
long *alsa_get_elem_int_values(struct alsa_elem *elem);
|
||||
void alsa_set_elem_value(struct alsa_elem *elem, long value);
|
||||
int alsa_get_elem_writable(struct alsa_elem *elem);
|
||||
int alsa_get_elem_volatile(struct alsa_elem *elem);
|
||||
@@ -229,6 +251,10 @@ char *alsa_get_item_name(struct alsa_elem *elem, int i);
|
||||
// add to alsa_cards array
|
||||
struct alsa_card *card_create(int card_num);
|
||||
|
||||
// parse elements (used by alsa-sim.c)
|
||||
void alsa_set_lr_nums(struct alsa_card *card);
|
||||
void alsa_get_routing_controls(struct alsa_card *card);
|
||||
|
||||
// init
|
||||
void alsa_init(void);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
@@ -7,7 +7,7 @@
|
||||
#define MAX_MIX_OUT 12
|
||||
|
||||
// maximum number of mux inputs
|
||||
#define MAX_MUX_IN 25
|
||||
#define MAX_MUX_IN 42
|
||||
|
||||
// maximum number of meters
|
||||
#define MAX_METERS 65
|
||||
#define MAX_METERS 92
|
||||
|
||||
83
src/db.c
Normal file
@@ -0,0 +1,83 @@
|
||||
// SPDX-FileCopyrightText: 2024-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <math.h>
|
||||
|
||||
static double db_to_linear(double db) {
|
||||
if (db <= SND_CTL_TLV_DB_GAIN_MUTE)
|
||||
return 0.0;
|
||||
return pow(10.0, db / 20.0);
|
||||
}
|
||||
|
||||
static double linear_to_db(double linear) {
|
||||
if (linear <= 0.0)
|
||||
return SND_CTL_TLV_DB_GAIN_MUTE;
|
||||
return 20.0 * log10(linear);
|
||||
}
|
||||
|
||||
int cdb_to_linear_value(
|
||||
int cdb, int min_val, int max_val, int min_cdb, int max_cdb
|
||||
) {
|
||||
if (cdb <= min_cdb)
|
||||
return min_val;
|
||||
if (cdb >= max_cdb)
|
||||
return max_val;
|
||||
|
||||
// Convert centidB to dB
|
||||
double db = (double)cdb / 100.0;
|
||||
double max_db = (double)max_cdb / 100.0;
|
||||
|
||||
// Convert dB relative to max_db to linear scale 0.0-1.0
|
||||
double linear = db_to_linear(db - max_db);
|
||||
|
||||
// Scale to full ALSA range
|
||||
double scaled = linear * (double)max_val;
|
||||
int value = (int)round(scaled);
|
||||
if (value < min_val)
|
||||
return min_val;
|
||||
if (value > max_val)
|
||||
return max_val;
|
||||
return value;
|
||||
}
|
||||
|
||||
int linear_value_to_cdb(
|
||||
int value, int min_val, int max_val, int min_cdb, int max_cdb
|
||||
) {
|
||||
if (value <= min_val)
|
||||
return min_cdb;
|
||||
if (value >= max_val)
|
||||
return max_cdb;
|
||||
|
||||
// Convert to 0.0-1.0 linear scale
|
||||
double linear = (double)value / (double)max_val;
|
||||
double max_db = (double)max_cdb / 100.0;
|
||||
|
||||
// Convert to dB relative to max_db and back to centidB
|
||||
int cdb = (int)round((linear_to_db(linear) + max_db) * 100.0);
|
||||
if (cdb < min_cdb)
|
||||
return min_cdb;
|
||||
if (cdb > max_cdb)
|
||||
return max_cdb;
|
||||
return cdb;
|
||||
}
|
||||
|
||||
double linear_value_to_db(
|
||||
int value, int min_val, int max_val, int min_db, int max_db
|
||||
) {
|
||||
if (value <= min_val)
|
||||
return min_db;
|
||||
if (value >= max_val)
|
||||
return max_db;
|
||||
|
||||
// Convert to 0.0-1.0 linear scale
|
||||
double linear = (double)value / (double)max_val;
|
||||
|
||||
// Convert to dB relative to max_db
|
||||
double db = linear_to_db(linear) + max_db;
|
||||
if (db < min_db)
|
||||
return min_db;
|
||||
if (db > max_db)
|
||||
return max_db;
|
||||
return db;
|
||||
}
|
||||
16
src/db.h
Normal file
@@ -0,0 +1,16 @@
|
||||
// SPDX-FileCopyrightText: 2024-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
int cdb_to_linear_value(
|
||||
int cdb, int min_val, int max_val, int min_cdb, int max_cdb
|
||||
);
|
||||
|
||||
int linear_value_to_cdb(
|
||||
int value, int min_val, int max_val, int min_cdb, int max_cdb
|
||||
);
|
||||
|
||||
double linear_value_to_db(
|
||||
int value, int min_val, int max_val, int min_db, int max_db
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2024-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2024-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2024-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2024-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "error.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
19
src/fcp-shared.c
Normal file
@@ -0,0 +1,19 @@
|
||||
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// Error messages
|
||||
const char *fcp_socket_error_messages[] = {
|
||||
"Success",
|
||||
"Invalid magic",
|
||||
"Invalid command",
|
||||
"Invalid length",
|
||||
"Invalid hash",
|
||||
"Firmware PID does not match USB PID",
|
||||
"Configuration error (check fcp-server log)",
|
||||
"FCP communication error",
|
||||
"Timeout",
|
||||
"Read error",
|
||||
"Write error",
|
||||
"Not running leapfrog firmware",
|
||||
"Invalid state"
|
||||
};
|
||||
80
src/fcp-shared.h
Normal file
@@ -0,0 +1,80 @@
|
||||
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Error codes
|
||||
#define FCP_SOCKET_ERR_INVALID_MAGIC 1
|
||||
#define FCP_SOCKET_ERR_INVALID_COMMAND 2
|
||||
#define FCP_SOCKET_ERR_INVALID_LENGTH 3
|
||||
#define FCP_SOCKET_ERR_INVALID_HASH 4
|
||||
#define FCP_SOCKET_ERR_INVALID_USB_ID 5
|
||||
#define FCP_SOCKET_ERR_CONFIG 6
|
||||
#define FCP_SOCKET_ERR_FCP 7
|
||||
#define FCP_SOCKET_ERR_TIMEOUT 8
|
||||
#define FCP_SOCKET_ERR_READ 9
|
||||
#define FCP_SOCKET_ERR_WRITE 10
|
||||
#define FCP_SOCKET_ERR_NOT_LEAPFROG 11
|
||||
#define FCP_SOCKET_ERR_INVALID_STATE 12
|
||||
#define FCP_SOCKET_ERR_MAX 12
|
||||
|
||||
// Protocol constants
|
||||
#define FCP_SOCKET_PROTOCOL_VERSION 1
|
||||
#define FCP_SOCKET_MAGIC_REQUEST 0x53
|
||||
#define FCP_SOCKET_MAGIC_RESPONSE 0x73
|
||||
|
||||
// Maximum payload length (2MB)
|
||||
#define MAX_PAYLOAD_LENGTH 2 * 1024 * 1024
|
||||
|
||||
// Request types
|
||||
#define FCP_SOCKET_REQUEST_REBOOT 0x0001
|
||||
#define FCP_SOCKET_REQUEST_CONFIG_ERASE 0x0002
|
||||
#define FCP_SOCKET_REQUEST_APP_FIRMWARE_ERASE 0x0003
|
||||
#define FCP_SOCKET_REQUEST_APP_FIRMWARE_UPDATE 0x0004
|
||||
#define FCP_SOCKET_REQUEST_ESP_FIRMWARE_UPDATE 0x0005
|
||||
|
||||
// Response types
|
||||
#define FCP_SOCKET_RESPONSE_VERSION 0x00
|
||||
#define FCP_SOCKET_RESPONSE_SUCCESS 0x01
|
||||
#define FCP_SOCKET_RESPONSE_ERROR 0x02
|
||||
#define FCP_SOCKET_RESPONSE_PROGRESS 0x03
|
||||
|
||||
extern const char *fcp_socket_error_messages[];
|
||||
|
||||
// Message structures
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct fcp_socket_msg_header {
|
||||
uint8_t magic;
|
||||
uint8_t msg_type;
|
||||
uint32_t payload_length;
|
||||
};
|
||||
|
||||
struct firmware_payload {
|
||||
uint32_t size;
|
||||
uint16_t usb_vid;
|
||||
uint16_t usb_pid;
|
||||
uint8_t sha256[32];
|
||||
uint8_t md5[16];
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
struct version_msg {
|
||||
struct fcp_socket_msg_header header;
|
||||
uint8_t version;
|
||||
};
|
||||
|
||||
struct progress_msg {
|
||||
struct fcp_socket_msg_header header;
|
||||
uint8_t percent;
|
||||
};
|
||||
|
||||
struct error_msg {
|
||||
struct fcp_socket_msg_header header;
|
||||
int16_t error_code;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
220
src/fcp-socket.c
Normal file
@@ -0,0 +1,220 @@
|
||||
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/time.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "fcp-shared.h"
|
||||
#include "fcp-socket.h"
|
||||
#include "error.h"
|
||||
|
||||
// Connect to the FCP socket server for the given card
|
||||
int fcp_socket_connect(struct alsa_card *card) {
|
||||
if (!card || !card->fcp_socket) {
|
||||
fprintf(stderr, "FCP socket path is not available");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock_fd < 0) {
|
||||
fprintf(stderr, "Cannot create socket: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_un addr = {
|
||||
.sun_family = AF_UNIX
|
||||
};
|
||||
strncpy(addr.sun_path, card->fcp_socket, sizeof(addr.sun_path) - 1);
|
||||
|
||||
if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||
fprintf(stderr, "Cannot connect to server at %s: %s",
|
||||
addr.sun_path, strerror(errno));
|
||||
close(sock_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return sock_fd;
|
||||
}
|
||||
|
||||
// Send a simple command with no payload to the server
|
||||
int fcp_socket_send_command(int sock_fd, uint8_t command) {
|
||||
struct fcp_socket_msg_header header = {
|
||||
.magic = FCP_SOCKET_MAGIC_REQUEST,
|
||||
.msg_type = command,
|
||||
.payload_length = 0
|
||||
};
|
||||
|
||||
if (write(sock_fd, &header, sizeof(header)) != sizeof(header)) {
|
||||
fprintf(stderr, "Error sending command: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle server responses from a command
|
||||
int fcp_socket_handle_response(int sock_fd, bool show_progress) {
|
||||
struct fcp_socket_msg_header header;
|
||||
ssize_t bytes_read;
|
||||
|
||||
// Read response header
|
||||
bytes_read = read(sock_fd, &header, sizeof(header));
|
||||
if (bytes_read != sizeof(header)) {
|
||||
if (bytes_read == 0) {
|
||||
// Server closed the connection
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "Error reading response header: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Verify the magic value
|
||||
if (header.magic != FCP_SOCKET_MAGIC_RESPONSE) {
|
||||
fprintf(stderr, "Invalid response magic: 0x%02x", header.magic);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Handle different response types
|
||||
switch (header.msg_type) {
|
||||
case FCP_SOCKET_RESPONSE_VERSION: {
|
||||
// Protocol version response
|
||||
uint8_t version;
|
||||
bytes_read = read(sock_fd, &version, sizeof(version));
|
||||
if (bytes_read != sizeof(version)) {
|
||||
fprintf(stderr, "Error reading version: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
// Protocol version mismatch?
|
||||
if (version != FCP_SOCKET_PROTOCOL_VERSION) {
|
||||
fprintf(stderr, "Protocol version mismatch: expected %d, got %d",
|
||||
FCP_SOCKET_PROTOCOL_VERSION, version);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case FCP_SOCKET_RESPONSE_SUCCESS:
|
||||
// Command completed successfully
|
||||
return 0;
|
||||
|
||||
case FCP_SOCKET_RESPONSE_ERROR: {
|
||||
// Error response
|
||||
int16_t error_code;
|
||||
bytes_read = read(sock_fd, &error_code, sizeof(error_code));
|
||||
if (bytes_read != sizeof(error_code)) {
|
||||
fprintf(stderr, "Error reading error code: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (error_code > 0 && error_code <= FCP_SOCKET_ERR_MAX) {
|
||||
fprintf(stderr, "Server error: %s", fcp_socket_error_messages[error_code]);
|
||||
} else {
|
||||
fprintf(stderr, "Unknown server error code: %d", error_code);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
case FCP_SOCKET_RESPONSE_PROGRESS: {
|
||||
// Progress update
|
||||
if (show_progress) {
|
||||
uint8_t percent;
|
||||
bytes_read = read(sock_fd, &percent, sizeof(percent));
|
||||
if (bytes_read != sizeof(percent)) {
|
||||
fprintf(stderr, "Error reading progress: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "\rProgress: %d%%", percent);
|
||||
if (percent == 100)
|
||||
fprintf(stderr, "\n");
|
||||
} else {
|
||||
// Skip the progress byte
|
||||
uint8_t dummy;
|
||||
if (read(sock_fd, &dummy, sizeof(dummy)) < 0) {
|
||||
fprintf(stderr, "Error reading progress: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue reading responses
|
||||
return fcp_socket_handle_response(sock_fd, show_progress);
|
||||
}
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown response type: 0x%02x", header.msg_type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Wait for server to disconnect (used after reboot command)
|
||||
int fcp_socket_wait_for_disconnect(int sock_fd) {
|
||||
fd_set rfds;
|
||||
struct timeval tv, start_time, now;
|
||||
char buf[1];
|
||||
const int TIMEOUT_SECS = 2;
|
||||
|
||||
gettimeofday(&start_time, NULL);
|
||||
|
||||
while (1) {
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(sock_fd, &rfds);
|
||||
|
||||
gettimeofday(&now, NULL);
|
||||
int elapsed = now.tv_sec - start_time.tv_sec;
|
||||
if (elapsed >= TIMEOUT_SECS) {
|
||||
fprintf(stderr, "Timeout waiting for server disconnect\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
tv.tv_sec = TIMEOUT_SECS - elapsed;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
int ret = select(sock_fd + 1, &rfds, NULL, NULL, &tv);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
fprintf(stderr, "Select error: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ret > 0) {
|
||||
// Try to read one byte
|
||||
ssize_t n = read(sock_fd, buf, 1);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
fprintf(stderr, "Read error: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (n == 0) {
|
||||
// EOF received - server has disconnected
|
||||
return 0;
|
||||
}
|
||||
// Ignore any data received, just keep waiting for EOF
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reboot a device using the FCP socket interface
|
||||
int fcp_socket_reboot_device(struct alsa_card *card) {
|
||||
int sock_fd, ret = -1;
|
||||
|
||||
sock_fd = fcp_socket_connect(card);
|
||||
if (sock_fd < 0)
|
||||
return -1;
|
||||
|
||||
// Send reboot command and wait for server to disconnect
|
||||
if (fcp_socket_send_command(sock_fd, FCP_SOCKET_REQUEST_REBOOT) == 0)
|
||||
ret = fcp_socket_wait_for_disconnect(sock_fd);
|
||||
|
||||
close(sock_fd);
|
||||
return ret;
|
||||
}
|
||||
27
src/fcp-socket.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "alsa.h"
|
||||
|
||||
// Connect to the FCP socket server for the given card
|
||||
// Returns socket file descriptor on success, -1 on error
|
||||
int fcp_socket_connect(struct alsa_card *card);
|
||||
|
||||
// Send a simple command with no payload to the server
|
||||
// Returns 0 on success, -1 on error
|
||||
int fcp_socket_send_command(int sock_fd, uint8_t command);
|
||||
|
||||
// Handle server responses from a command
|
||||
// Returns 0 on success, -1 on error
|
||||
int fcp_socket_handle_response(int sock_fd, bool show_progress);
|
||||
|
||||
// Wait for server to disconnect (used after reboot command)
|
||||
// Returns 0 if disconnected, -1 on timeout or error
|
||||
int fcp_socket_wait_for_disconnect(int sock_fd);
|
||||
|
||||
// Reboot a device using the FCP socket interface
|
||||
// Returns 0 on success, -1 on error
|
||||
int fcp_socket_reboot_device(struct alsa_card *card);
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "alsa.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
177
src/gtkdial.c
@@ -1,5 +1,5 @@
|
||||
// SPDX-FileCopyrightText: 2021 Stiliyan Varbanov <https://www.fiverr.com/stilvar>
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
/*
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <math.h>
|
||||
|
||||
#include "gtkdial.h"
|
||||
#include "db.h"
|
||||
|
||||
#define DIAL_MIN_WIDTH 50
|
||||
#define DIAL_MAX_WIDTH 70
|
||||
@@ -90,6 +91,7 @@ enum {
|
||||
PROP_ROUND_DIGITS,
|
||||
PROP_ZERO_DB,
|
||||
PROP_OFF_DB,
|
||||
PROP_IS_LINEAR,
|
||||
PROP_TAPER,
|
||||
PROP_CAN_CONTROL,
|
||||
PROP_PEAK_HOLD,
|
||||
@@ -118,6 +120,7 @@ struct _GtkDial {
|
||||
int round_digits;
|
||||
double zero_db;
|
||||
double off_db;
|
||||
gboolean is_linear;
|
||||
int taper;
|
||||
gboolean can_control;
|
||||
int peak_hold;
|
||||
@@ -154,6 +157,10 @@ struct _GtkDial {
|
||||
cairo_pattern_t *fill_pattern[2][2];
|
||||
cairo_pattern_t *outline_pattern[2];
|
||||
|
||||
// pango resources for displaying the peak value
|
||||
PangoLayout *peak_layout;
|
||||
PangoFontDescription *peak_font_desc;
|
||||
|
||||
// variables derived from the dial value
|
||||
double valp;
|
||||
double angle;
|
||||
@@ -250,6 +257,13 @@ static double calc_taper(GtkDial *dial, double val) {
|
||||
double mn = gtk_adjustment_get_lower(dial->adj);
|
||||
double mx = gtk_adjustment_get_upper(dial->adj);
|
||||
double off_db = gtk_dial_get_off_db(dial);
|
||||
gboolean is_linear = gtk_dial_get_is_linear(dial);
|
||||
|
||||
if (is_linear) {
|
||||
val = linear_value_to_cdb(val, mn, mx, -8000, 1200) / 100.0;
|
||||
mn = -60;
|
||||
mx = 12;
|
||||
}
|
||||
|
||||
// if off_db is set, then values below it are considered as
|
||||
// almost-silence, so we clamp them to 0.01
|
||||
@@ -405,6 +419,21 @@ static int update_dial_properties(GtkDial *dial) {
|
||||
dial->outline_pattern[dim] = pat;
|
||||
}
|
||||
|
||||
// init pango layout for peak value
|
||||
if (dial->peak_layout)
|
||||
g_object_unref(dial->peak_layout);
|
||||
if (dial->peak_font_desc)
|
||||
pango_font_description_free(dial->peak_font_desc);
|
||||
|
||||
PangoContext *context = gtk_widget_create_pango_context(GTK_WIDGET(dial));
|
||||
dial->peak_layout = pango_layout_new(context);
|
||||
dial->peak_font_desc = pango_context_get_font_description(context);
|
||||
int size = pango_font_description_get_size(dial->peak_font_desc) * 0.6;
|
||||
dial->peak_font_desc = pango_font_description_copy(dial->peak_font_desc);
|
||||
pango_font_description_set_size(dial->peak_font_desc, size);
|
||||
pango_layout_set_font_description(dial->peak_layout, dial->peak_font_desc);
|
||||
g_object_unref(context);
|
||||
|
||||
// calculate level meter breakpoint angles
|
||||
if (dial->level_breakpoint_angles)
|
||||
free(dial->level_breakpoint_angles);
|
||||
@@ -532,6 +561,19 @@ static void gtk_dial_class_init(GtkDialClass *klass) {
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT
|
||||
);
|
||||
|
||||
/**
|
||||
* GtkDial:is_linear: (attributes org.gtk.Method.get=gtk_dial_get_is_linear org.gtk.Method.set=gtk_dial_set_is_linear)
|
||||
*
|
||||
* Whether the dial values are linear or dB.
|
||||
*/
|
||||
properties[PROP_IS_LINEAR] = g_param_spec_boolean(
|
||||
"is_linear",
|
||||
"IsLinear",
|
||||
"Whether the dial values are linear or dB",
|
||||
FALSE,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT
|
||||
);
|
||||
|
||||
/**
|
||||
* GtkDial:taper: (attributes org.gtk.Method.get=gtk_dial_get_taper org.gtk.Method.set=gtk_dial_set_taper)
|
||||
*
|
||||
@@ -783,16 +825,20 @@ static void show_peak_value(GtkDial *dial, cairo_t *cr) {
|
||||
p += sprintf(p, "−");
|
||||
snprintf(p, 10, "%.0f", fabs(value));
|
||||
|
||||
cairo_text_extents_t extents;
|
||||
cairo_text_extents(cr, s, &extents);
|
||||
pango_layout_set_text(dial->peak_layout, s, -1);
|
||||
|
||||
int width, height;
|
||||
pango_layout_get_pixel_size(dial->peak_layout, &width, &height);
|
||||
|
||||
cairo_set_source_rgba_dim(cr, 1, 1, 1, 0.5, dial->dim);
|
||||
|
||||
cairo_move_to(
|
||||
cr,
|
||||
dial->cx - extents.width / 2,
|
||||
dial->cy + extents.height / 2
|
||||
dial->cx - width / 2 - 1,
|
||||
dial->cy - height / 2
|
||||
);
|
||||
cairo_show_text(cr, s);
|
||||
|
||||
pango_cairo_show_layout(cr, dial->peak_layout);
|
||||
}
|
||||
|
||||
static void draw_slider(
|
||||
@@ -1014,6 +1060,9 @@ static void gtk_dial_set_property(
|
||||
case PROP_OFF_DB:
|
||||
gtk_dial_set_off_db(dial, g_value_get_double(value));
|
||||
break;
|
||||
case PROP_IS_LINEAR:
|
||||
gtk_dial_set_is_linear(dial, g_value_get_boolean(value));
|
||||
break;
|
||||
case PROP_TAPER:
|
||||
gtk_dial_set_taper(dial, g_value_get_int(value));
|
||||
break;
|
||||
@@ -1050,6 +1099,9 @@ static void gtk_dial_get_property(
|
||||
case PROP_OFF_DB:
|
||||
g_value_set_double(value, dial->off_db);
|
||||
break;
|
||||
case PROP_IS_LINEAR:
|
||||
g_value_set_boolean(value, dial->is_linear);
|
||||
break;
|
||||
case PROP_TAPER:
|
||||
g_value_set_int(value, dial->taper);
|
||||
break;
|
||||
@@ -1101,6 +1153,15 @@ double gtk_dial_get_off_db(GtkDial *dial) {
|
||||
return dial->off_db;
|
||||
}
|
||||
|
||||
void gtk_dial_set_is_linear(GtkDial *dial, gboolean is_linear) {
|
||||
dial->is_linear = is_linear;
|
||||
dial->properties_updated = 1;
|
||||
}
|
||||
|
||||
gboolean gtk_dial_get_is_linear(GtkDial *dial) {
|
||||
return dial->is_linear;
|
||||
}
|
||||
|
||||
void gtk_dial_set_taper(GtkDial *dial, int taper) {
|
||||
dial->taper = taper;
|
||||
dial->properties_updated = 1;
|
||||
@@ -1265,32 +1326,41 @@ static int set_value(GtkDial *dial, double newval) {
|
||||
return old_valp != dial->valp || old_peak != dial->current_peak;
|
||||
}
|
||||
|
||||
static void step_back(GtkDial *dial) {
|
||||
double newval;
|
||||
static double do_step(GtkDial *dial, double step_amount) {
|
||||
double mn = gtk_adjustment_get_lower(dial->adj);
|
||||
double mx = gtk_adjustment_get_upper(dial->adj);
|
||||
double newval = gtk_adjustment_get_value(dial->adj);
|
||||
double step = gtk_adjustment_get_step_increment(dial->adj);
|
||||
|
||||
newval = gtk_adjustment_get_value(dial->adj) - gtk_adjustment_get_step_increment(dial->adj);
|
||||
set_value(dial, newval);
|
||||
if (gtk_dial_get_is_linear(dial)) {
|
||||
double db_val = linear_value_to_cdb(newval, mn, mx, -8000, 1200) / 100.0;
|
||||
db_val = round(db_val / step) * step + step_amount;
|
||||
|
||||
newval = cdb_to_linear_value(db_val * 100.0, mn, mx, -8000, 1200);
|
||||
if (newval == gtk_adjustment_get_value(dial->adj)) {
|
||||
newval = CLAMP(newval + (step_amount > 0 ? 1 : -1), mn, mx);
|
||||
}
|
||||
} else {
|
||||
newval += step_amount;
|
||||
}
|
||||
|
||||
return newval;
|
||||
}
|
||||
|
||||
static void step_back(GtkDial *dial) {
|
||||
set_value(dial, do_step(dial, -gtk_adjustment_get_step_increment(dial->adj)));
|
||||
}
|
||||
|
||||
static void step_forward(GtkDial *dial) {
|
||||
double newval;
|
||||
|
||||
newval = gtk_adjustment_get_value(dial->adj) + gtk_adjustment_get_step_increment(dial->adj);
|
||||
set_value(dial, newval);
|
||||
set_value(dial, do_step(dial, gtk_adjustment_get_step_increment(dial->adj)));
|
||||
}
|
||||
|
||||
static void page_back(GtkDial *dial) {
|
||||
double newval;
|
||||
|
||||
newval = gtk_adjustment_get_value(dial->adj) - gtk_adjustment_get_page_increment(dial->adj);
|
||||
set_value(dial, newval);
|
||||
set_value(dial, do_step(dial, -gtk_adjustment_get_page_increment(dial->adj)));
|
||||
}
|
||||
|
||||
static void page_forward(GtkDial *dial) {
|
||||
double newval;
|
||||
|
||||
newval = gtk_adjustment_get_value(dial->adj) + gtk_adjustment_get_page_increment(dial->adj);
|
||||
set_value(dial, newval);
|
||||
set_value(dial, do_step(dial, gtk_adjustment_get_page_increment(dial->adj)));
|
||||
}
|
||||
|
||||
static void scroll_begin(GtkDial *dial) {
|
||||
@@ -1420,20 +1490,56 @@ static void gtk_dial_drag_gesture_update(
|
||||
double offset_y,
|
||||
GtkDial *dial
|
||||
) {
|
||||
double start_x, start_y;
|
||||
double mn = gtk_adjustment_get_lower(dial->adj);
|
||||
double mx = gtk_adjustment_get_upper(dial->adj);
|
||||
gboolean is_linear = gtk_dial_get_is_linear(dial);
|
||||
|
||||
gtk_gesture_drag_get_start_point(gesture, &start_x, &start_y);
|
||||
double valp;
|
||||
|
||||
double valp = dial->dvalp - DRAG_FACTOR * (offset_y / dial->h);
|
||||
valp = CLAMP(valp, 0.0, 1.0);
|
||||
// add a 1px deadband to prevent double-click with zero mouse
|
||||
// movement from changing the value from the toggled -inf/0dB value
|
||||
// (sometimes we see an offset_y value that rounds to +/- 1 which
|
||||
// causes the value to change after the double-click has set the
|
||||
// value)
|
||||
if (offset_y < -1) {
|
||||
offset_y += 1;
|
||||
} else if (offset_y < 1) {
|
||||
offset_y = 0;
|
||||
} else {
|
||||
offset_y -= 1;
|
||||
}
|
||||
|
||||
double val = calc_val(
|
||||
valp,
|
||||
gtk_adjustment_get_lower(dial->adj),
|
||||
gtk_adjustment_get_upper(dial->adj)
|
||||
);
|
||||
if (is_linear) {
|
||||
double step = gtk_adjustment_get_step_increment(dial->adj);
|
||||
|
||||
// Convert initial value from linear to dB space
|
||||
double db_val = linear_value_to_cdb(
|
||||
calc_val(dial->dvalp, mn, mx),
|
||||
mn, mx,
|
||||
-8000, 1200
|
||||
) / 100.0;
|
||||
|
||||
// Adjust in dB space
|
||||
db_val -= 30.0 * DRAG_FACTOR * (offset_y / dial->h);
|
||||
|
||||
// Round
|
||||
db_val = round(db_val / step) * step;
|
||||
|
||||
// Convert back to linear space and normalise
|
||||
double val = cdb_to_linear_value(
|
||||
db_val * 100.0,
|
||||
mn, mx,
|
||||
-8000, 1200
|
||||
);
|
||||
|
||||
valp = calc_valp(val, mn, mx);
|
||||
} else {
|
||||
valp = dial->dvalp - DRAG_FACTOR * (offset_y / dial->h);
|
||||
valp = CLAMP(valp, 0.0, 1.0);
|
||||
}
|
||||
|
||||
set_value(dial, calc_val(valp, mn, mx));
|
||||
|
||||
set_value(dial, val);
|
||||
gtk_widget_queue_draw(GTK_WIDGET(dial));
|
||||
}
|
||||
|
||||
@@ -1496,7 +1602,7 @@ static gboolean gtk_dial_scroll_controller_scroll(
|
||||
|
||||
double step = -gtk_adjustment_get_step_increment(dial->adj) * delta;
|
||||
|
||||
set_value(dial, gtk_adjustment_get_value(dial->adj) + step);
|
||||
set_value(dial, do_step(dial, step));
|
||||
gtk_widget_queue_draw(GTK_WIDGET(dial));
|
||||
|
||||
return GDK_EVENT_STOP;
|
||||
@@ -1522,6 +1628,11 @@ void gtk_dial_dispose(GObject *o) {
|
||||
if (dial->outline_pattern[dim])
|
||||
cairo_pattern_destroy(dial->outline_pattern[dim]);
|
||||
|
||||
if (dial->peak_layout)
|
||||
g_object_unref(dial->peak_layout);
|
||||
if (dial->peak_font_desc)
|
||||
pango_font_description_free(dial->peak_font_desc);
|
||||
|
||||
g_object_unref(dial->adj);
|
||||
dial->adj = NULL;
|
||||
G_OBJECT_CLASS(gtk_dial_parent_class)->dispose(o);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-FileCopyrightText: 2021 Stiliyan Varbanov <https://www.fiverr.com/stilvar>
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
/*
|
||||
@@ -68,6 +68,9 @@ double gtk_dial_get_zero_db(GtkDial *dial);
|
||||
void gtk_dial_set_off_db(GtkDial *dial, double off_db);
|
||||
double gtk_dial_get_off_db(GtkDial *dial);
|
||||
|
||||
void gtk_dial_set_is_linear(GtkDial *dial, gboolean is_linear);
|
||||
gboolean gtk_dial_get_is_linear(GtkDial *dial);
|
||||
|
||||
// taper functions
|
||||
enum {
|
||||
GTK_DIAL_TAPER_LINEAR,
|
||||
@@ -98,6 +101,13 @@ void gtk_dial_set_peak_hold(GtkDial *dial, int peak_hold);
|
||||
int gtk_dial_get_peak_hold(GtkDial *dial);
|
||||
void gtk_dial_peak_tick(void);
|
||||
|
||||
int cdb_to_linear_value(
|
||||
int db, int min_val, int max_val, int min_db, int max_db
|
||||
);
|
||||
int linear_value_to_cdb(
|
||||
int value, int min_val, int max_val, int min_db, int max_db
|
||||
);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "gtkhelper.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2023-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2023-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// Supported devices
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "gtkhelper.h"
|
||||
@@ -26,8 +26,12 @@ static void add_clock_source_control(
|
||||
|
||||
struct alsa_elem *clock_source = get_elem_by_prefix(elems, "Clock Source");
|
||||
|
||||
if (!clock_source)
|
||||
return;
|
||||
if (!clock_source) {
|
||||
clock_source = get_elem_by_substr(elems, "Sync Clock Source");
|
||||
|
||||
if (!clock_source)
|
||||
return;
|
||||
}
|
||||
|
||||
GtkWidget *b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
|
||||
gtk_widget_set_tooltip_text(
|
||||
@@ -55,8 +59,11 @@ static void add_sync_status_control(
|
||||
|
||||
struct alsa_elem *sync_status = get_elem_by_name(elems, "Sync Status");
|
||||
|
||||
if (!sync_status)
|
||||
return;
|
||||
if (!sync_status) {
|
||||
sync_status = get_elem_by_name(elems, "Sample Clock Sync Status");
|
||||
if (!sync_status)
|
||||
return;
|
||||
}
|
||||
|
||||
GtkWidget *b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
|
||||
if (get_elem_by_prefix(elems, "Clock Source")) {
|
||||
@@ -139,7 +146,7 @@ static void add_sample_rate_control(
|
||||
gtk_box_append(GTK_BOX(b), w);
|
||||
}
|
||||
|
||||
static void add_speaker_switching_controls(
|
||||
static void add_speaker_switching_controls_enum(
|
||||
struct alsa_card *card,
|
||||
GtkWidget *global_controls
|
||||
) {
|
||||
@@ -167,7 +174,43 @@ static void add_speaker_switching_controls(
|
||||
gtk_box_append(GTK_BOX(global_controls), w);
|
||||
}
|
||||
|
||||
static void add_talkback_controls(
|
||||
static void add_speaker_switching_controls_switches(
|
||||
struct alsa_card *card,
|
||||
GtkWidget *global_controls
|
||||
) {
|
||||
GArray *elems = card->elems;
|
||||
|
||||
struct alsa_elem *enable = get_elem_by_name(
|
||||
elems, "Speaker Switching Playback Switch"
|
||||
);
|
||||
struct alsa_elem *alt = get_elem_by_name(
|
||||
elems, "Speaker Switching Alt Playback Switch"
|
||||
);
|
||||
|
||||
if (!enable || !alt)
|
||||
return;
|
||||
|
||||
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
|
||||
gtk_widget_set_tooltip_text(
|
||||
box,
|
||||
"Speaker Switching lets you swap between two pairs of "
|
||||
"monitoring speakers very easily."
|
||||
);
|
||||
|
||||
GtkWidget *l = gtk_label_new("Speaker Switching");
|
||||
GtkWidget *w1 = make_boolean_alsa_elem(enable, "Off", "On");
|
||||
GtkWidget *w2 = make_boolean_alsa_elem(alt, "Main", "Alt");
|
||||
|
||||
gtk_widget_add_css_class(w1, "speaker-switching-enable");
|
||||
gtk_widget_add_css_class(w2, "speaker-switching-alt");
|
||||
|
||||
gtk_box_append(GTK_BOX(box), l);
|
||||
gtk_box_append(GTK_BOX(box), w1);
|
||||
gtk_box_append(GTK_BOX(box), w2);
|
||||
gtk_box_append(GTK_BOX(global_controls), box);
|
||||
}
|
||||
|
||||
static void add_talkback_controls_enum(
|
||||
struct alsa_card *card,
|
||||
GtkWidget *global_controls
|
||||
) {
|
||||
@@ -196,6 +239,43 @@ static void add_talkback_controls(
|
||||
gtk_box_append(GTK_BOX(global_controls), w);
|
||||
}
|
||||
|
||||
static void add_talkback_controls_switches(
|
||||
struct alsa_card *card,
|
||||
GtkWidget *global_controls
|
||||
) {
|
||||
GArray *elems = card->elems;
|
||||
|
||||
struct alsa_elem *enable = get_elem_by_name(
|
||||
elems, "Talkback Enable Playback Switch"
|
||||
);
|
||||
struct alsa_elem *talk = get_elem_by_name(
|
||||
elems, "Talk Playback Switch"
|
||||
);
|
||||
|
||||
if (!enable || !talk)
|
||||
return;
|
||||
|
||||
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
|
||||
gtk_widget_set_tooltip_text(
|
||||
box,
|
||||
"Talkback lets you add another channel (usually the talkback "
|
||||
"mic) to a mix with a button push, usually to talk to "
|
||||
"musicians, and without using an additional mic channel."
|
||||
);
|
||||
|
||||
GtkWidget *l = gtk_label_new("Talkback");
|
||||
GtkWidget *w1 = make_boolean_alsa_elem(enable, "Disabled", "Enabled");
|
||||
GtkWidget *w2 = make_boolean_alsa_elem(talk, "Talk", "Talk");
|
||||
|
||||
gtk_widget_add_css_class(w1, "talkback-enable");
|
||||
gtk_widget_add_css_class(w2, "talk");
|
||||
|
||||
gtk_box_append(GTK_BOX(box), l);
|
||||
gtk_box_append(GTK_BOX(box), w1);
|
||||
gtk_box_append(GTK_BOX(box), w2);
|
||||
gtk_box_append(GTK_BOX(global_controls), box);
|
||||
}
|
||||
|
||||
static GtkWidget *create_global_box(GtkWidget *grid, int *x, int orient) {
|
||||
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
|
||||
gtk_widget_set_vexpand(box, TRUE);
|
||||
@@ -253,8 +333,13 @@ static void create_input_link_control(
|
||||
|
||||
int from, to;
|
||||
get_two_num_from_string(elem->name, &from, &to);
|
||||
|
||||
// skip even numbers
|
||||
if (!(from % 2))
|
||||
return;
|
||||
|
||||
if (to == -1)
|
||||
to = from;
|
||||
to = from + 1;
|
||||
|
||||
gtk_grid_attach(GTK_GRID(grid), w, from - 1, current_row, to - from + 1, 1);
|
||||
}
|
||||
@@ -420,6 +505,25 @@ static void create_input_pad_control(
|
||||
gtk_grid_attach(GTK_GRID(grid), w, column_num, current_row, 1, 1);
|
||||
}
|
||||
|
||||
static void create_input_gain_switch_control(
|
||||
struct alsa_elem *elem,
|
||||
GtkWidget *grid,
|
||||
int current_row,
|
||||
int column_num
|
||||
) {
|
||||
GtkWidget *w = make_boolean_alsa_elem(elem, "Gain", NULL);
|
||||
gtk_widget_add_css_class(w, "gain-switch");
|
||||
gtk_widget_set_hexpand(w, TRUE);
|
||||
gtk_widget_set_tooltip_text(
|
||||
w,
|
||||
"Enabling Gain switches from Low gain input (0dBFS = +16dBu)\n"
|
||||
"to High gain input (0dBFS = −10dBV, approx −6dBu)."
|
||||
);
|
||||
|
||||
// ignore current_row, always put it in the first row
|
||||
gtk_grid_attach(GTK_GRID(grid), w, column_num, 1, 1, 1);
|
||||
}
|
||||
|
||||
static void create_input_phantom_control(
|
||||
struct alsa_elem *elem,
|
||||
GtkWidget *grid,
|
||||
@@ -476,13 +580,11 @@ static void create_input_controls_by_type(
|
||||
static void create_input_controls(
|
||||
struct alsa_card *card,
|
||||
GtkWidget *top,
|
||||
int *x
|
||||
int *x,
|
||||
int input_count
|
||||
) {
|
||||
GArray *elems = card->elems;
|
||||
|
||||
// find how many inputs have switches
|
||||
int input_count = get_max_elem_by_name(elems, "Line", "Capture Switch");
|
||||
|
||||
// Only the 18i20 Gen 2 has no input controls
|
||||
if (!input_count)
|
||||
return;
|
||||
@@ -567,6 +669,10 @@ static void create_input_controls(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Level Capture Enum", create_input_level_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Impedance Switch", create_input_level_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Air Capture Switch", create_input_air_switch_control
|
||||
@@ -591,6 +697,14 @@ static void create_input_controls(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Pad Capture Switch", create_input_pad_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Pad Switch", create_input_pad_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Gain Switch", create_input_gain_switch_control
|
||||
);
|
||||
create_input_controls_by_type(
|
||||
elems, input_grid, ¤t_row,
|
||||
"Phantom Power Capture Switch", create_input_phantom_control
|
||||
@@ -604,7 +718,8 @@ static void create_output_controls(
|
||||
GtkWidget *top,
|
||||
int *x,
|
||||
int y,
|
||||
int x_span
|
||||
int x_span,
|
||||
int output_count
|
||||
) {
|
||||
GArray *elems = card->elems;
|
||||
|
||||
@@ -624,8 +739,6 @@ static void create_output_controls(
|
||||
|
||||
gtk_grid_attach(GTK_GRID(top), box, *x, y, x_span, 1);
|
||||
|
||||
int output_count = get_max_elem_by_name(elems, "Line", "Playback Volume");
|
||||
|
||||
/* 4th Gen Solo/2i2 */
|
||||
if (get_elem_by_prefix(elems, "Direct Monitor Playback")) {
|
||||
struct alsa_elem *elem;
|
||||
@@ -670,8 +783,12 @@ static void create_output_controls(
|
||||
return;
|
||||
}
|
||||
|
||||
int has_hw_vol = !!get_elem_by_name(elems, "Master HW Playback Volume");
|
||||
int line_1_col = has_hw_vol;
|
||||
int has_sw_hw_ctrls =
|
||||
!!get_elem_by_substr(elems, "Volume Control Playback Enum");
|
||||
int line_1_col =
|
||||
has_sw_hw_ctrls ||
|
||||
get_elem_by_name(elems, "Mute Playback Switch") ||
|
||||
get_elem_by_name(elems, "Master Playback Switch");
|
||||
|
||||
for (int i = 0; i < output_count; i++) {
|
||||
char s[20];
|
||||
@@ -688,21 +805,31 @@ static void create_output_controls(
|
||||
if (!elem->card)
|
||||
continue;
|
||||
|
||||
int line_num = get_num_from_string(elem->name);
|
||||
|
||||
// output controls
|
||||
if (strncmp(elem->name, "Line", 4) == 0) {
|
||||
|
||||
// Gen 1 master output control
|
||||
if (strcmp(elem->name, "Master Playback Volume") == 0) {
|
||||
GtkWidget *l = gtk_label_new("Master");
|
||||
gtk_grid_attach(GTK_GRID(output_grid), l, 0, 0, 1, 1);
|
||||
w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_LOG, 0);
|
||||
gtk_widget_set_tooltip_text(w, "Master Volume Control");
|
||||
gtk_grid_attach(GTK_GRID(output_grid), w, 0, 1, 1, 1);
|
||||
|
||||
} else if (strncmp(elem->name, "Line", 4) == 0 ||
|
||||
strncmp(elem->name, "Master", 4) == 0 ||
|
||||
strncmp(elem->name, "Analogue", 8) == 0) {
|
||||
|
||||
if (strstr(elem->name, "Playback Volume")) {
|
||||
w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_LOG, 1);
|
||||
gtk_grid_attach(
|
||||
GTK_GRID(output_grid), w, line_num - 1 + line_1_col, 1, 1, 1
|
||||
GTK_GRID(output_grid), w, elem->lr_num - 1 + line_1_col, 1, 1, 1
|
||||
);
|
||||
} else if (strstr(elem->name, "Mute Playback Switch")) {
|
||||
} else if (strstr(elem->name, "Playback Switch")) {
|
||||
w = make_boolean_alsa_elem(
|
||||
elem, "*audio-volume-high", "*audio-volume-muted"
|
||||
);
|
||||
gtk_widget_add_css_class(w, "mute");
|
||||
if (has_hw_vol) {
|
||||
if (has_sw_hw_ctrls) {
|
||||
gtk_widget_set_tooltip_text(
|
||||
w,
|
||||
"Mute (only available when under software control)"
|
||||
@@ -711,7 +838,7 @@ static void create_output_controls(
|
||||
gtk_widget_set_tooltip_text(w, "Mute");
|
||||
}
|
||||
gtk_grid_attach(
|
||||
GTK_GRID(output_grid), w, line_num - 1 + line_1_col, 2, 1, 1
|
||||
GTK_GRID(output_grid), w, elem->lr_num - 1 + line_1_col, 2, 1, 1
|
||||
);
|
||||
} else if (strstr(elem->name, "Volume Control Playback Enum")) {
|
||||
w = make_boolean_alsa_elem(elem, "SW", "HW");
|
||||
@@ -722,7 +849,7 @@ static void create_output_controls(
|
||||
"volume for this analogue output."
|
||||
);
|
||||
gtk_grid_attach(
|
||||
GTK_GRID(output_grid), w, line_num - 1 + line_1_col, 3, 1, 1
|
||||
GTK_GRID(output_grid), w, elem->lr_num - 1 + line_1_col, 3, 1, 1
|
||||
);
|
||||
}
|
||||
|
||||
@@ -804,8 +931,10 @@ static void create_global_controls(
|
||||
add_sync_status_control(card, column[1]);
|
||||
add_power_status_control(card, column[1]);
|
||||
add_sample_rate_control(card, column[2]);
|
||||
add_speaker_switching_controls(card, column[0]);
|
||||
add_talkback_controls(card, column[1]);
|
||||
add_speaker_switching_controls_enum(card, column[0]);
|
||||
add_speaker_switching_controls_switches(card, column[0]);
|
||||
add_talkback_controls_enum(card, column[1]);
|
||||
add_talkback_controls_switches(card, column[1]);
|
||||
}
|
||||
|
||||
static GtkWidget *create_main_window_controls(struct alsa_card *card) {
|
||||
@@ -831,18 +960,28 @@ static GtkWidget *create_main_window_controls(struct alsa_card *card) {
|
||||
int input_count = get_max_elem_by_name(
|
||||
card->elems, "Line", "Capture Switch"
|
||||
);
|
||||
if (!input_count)
|
||||
input_count =
|
||||
get_max_elem_by_name(card->elems, "Input", "Switch");
|
||||
|
||||
int output_count = get_max_elem_by_name(
|
||||
card->elems, "Line", "Playback Volume"
|
||||
);
|
||||
if (!output_count)
|
||||
output_count =
|
||||
get_max_elem_by_name(card->elems, "Master", "Playback Volume") * 2;
|
||||
if (!output_count)
|
||||
output_count =
|
||||
get_max_elem_by_name(card->elems, "Analogue", "Playback Volume");
|
||||
|
||||
create_global_controls(card, top, &x);
|
||||
create_input_controls(card, top, &x);
|
||||
create_input_controls(card, top, &x, input_count);
|
||||
|
||||
if (input_count + output_count >= 12) {
|
||||
x = 0;
|
||||
create_output_controls(card, top, &x, 1, 2);
|
||||
create_output_controls(card, top, &x, 1, 2, output_count);
|
||||
} else {
|
||||
create_output_controls(card, top, &x, 0, 1);
|
||||
create_output_controls(card, top, &x, 0, 1, output_count);
|
||||
}
|
||||
|
||||
return top;
|
||||
@@ -899,9 +1038,12 @@ static void create_scrollable_window(GtkWidget *window, GtkWidget *controls) {
|
||||
|
||||
GtkWidget *create_iface_mixer_main(struct alsa_card *card) {
|
||||
card->has_speaker_switching =
|
||||
!!get_elem_by_name(card->elems, "Speaker Switching Playback Enum");
|
||||
get_elem_by_name(card->elems, "Speaker Switching Playback Enum") ||
|
||||
get_elem_by_name(card->elems, "Speaker Switching Playback Switch");
|
||||
|
||||
card->has_talkback =
|
||||
!!get_elem_by_name(card->elems, "Talkback Playback Enum");
|
||||
get_elem_by_name(card->elems, "Talkback Playback Enum") ||
|
||||
get_elem_by_name(card->elems, "Talkback Enable Playback Switch");
|
||||
|
||||
GtkWidget *top = gtk_frame_new(NULL);
|
||||
gtk_widget_add_css_class(top, "window-frame");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "gtkhelper.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "alsa.h"
|
||||
@@ -10,7 +10,7 @@ GtkWidget *create_window_iface_none(GtkApplication *app) {
|
||||
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 50);
|
||||
gtk_widget_set_margin(box, 50);
|
||||
GtkWidget *picture = gtk_picture_new_for_resource(
|
||||
"/vu/b4/alsa-scarlett-gui/icons/alsa-scarlett-gui-logo.png"
|
||||
"/vu/b4/alsa-scarlett-gui/icons/vu.b4.alsa-scarlett-gui.png"
|
||||
);
|
||||
GtkWidget *label = gtk_label_new("No Scarlett/Clarett/Vocaster interface found.");
|
||||
|
||||
@@ -19,7 +19,7 @@ GtkWidget *create_window_iface_none(GtkApplication *app) {
|
||||
|
||||
GtkWidget *w = gtk_application_window_new(app);
|
||||
gtk_window_set_resizable(GTK_WINDOW(w), FALSE);
|
||||
gtk_window_set_title(GTK_WINDOW(w), "ALSA Scarlett2 Control Panel");
|
||||
gtk_window_set_title(GTK_WINDOW(w), "ALSA Scarlett Control Panel");
|
||||
gtk_window_set_child(GTK_WINDOW(w), box);
|
||||
gtk_application_window_set_show_menubar(
|
||||
GTK_APPLICATION_WINDOW(w), TRUE
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "gtkhelper.h"
|
||||
@@ -9,21 +9,15 @@ GtkWidget *create_iface_unknown_main(void) {
|
||||
"Sorry, I don’t recognise the controls on this card.\n\n"
|
||||
|
||||
"These Focusrite models should be supported:\n"
|
||||
"– Gen 1: 6i6/8i6/18i6/18i8/18i20\n"
|
||||
"– Gen 2: 6i6/18i8/18i20\n"
|
||||
"– Gen 3: Solo/2i2/4i4/8i6/18i8/18i20\n"
|
||||
"– Gen 4: Solo/2i2/4i4\n"
|
||||
"– Gen 4: Solo/2i2/4i4/16i16/18i16/18i20\n"
|
||||
"– Vocaster One and Two\n"
|
||||
"– Clarett USB and Clarett+ 2Pre/4Pre/8Pre\n\n"
|
||||
|
||||
"Are you running a recent kernel with Scarlett2 support "
|
||||
"enabled?\n\n"
|
||||
|
||||
"Check dmesg output for “Focusrite ... Mixer Driver”:\n\n"
|
||||
|
||||
"dmesg | grep -A 5 -B 5 -i focusrite\n\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."
|
||||
"Please check the prerequisites at:\n"
|
||||
"https://github.com/geoffreybennett/alsa-scarlett-gui/"
|
||||
);
|
||||
gtk_widget_set_margin(label, 30);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2024-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2024-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
117
src/iface-waiting.c
Normal file
@@ -0,0 +1,117 @@
|
||||
// SPDX-FileCopyrightText: 2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "alsa.h"
|
||||
#include "iface-waiting.h"
|
||||
#include "scarlett2-ioctls.h"
|
||||
#include "window-iface.h"
|
||||
|
||||
// Structure to hold timeout-related widgets
|
||||
struct timeout_data {
|
||||
GtkWidget *box;
|
||||
GtkWidget *spinner;
|
||||
GtkWidget *message_label;
|
||||
guint timeout_id;
|
||||
};
|
||||
|
||||
// Timeout callback function
|
||||
static gboolean on_timeout(gpointer user_data) {
|
||||
struct timeout_data *data = (struct timeout_data *)user_data;
|
||||
|
||||
// Remove spinner
|
||||
gtk_box_remove(GTK_BOX(data->box), data->spinner);
|
||||
|
||||
// Update message with clickable link
|
||||
if (data->message_label && GTK_IS_WIDGET(data->message_label))
|
||||
gtk_label_set_markup(
|
||||
GTK_LABEL(data->message_label),
|
||||
"Driver not detected. Please ensure "
|
||||
"<span font='monospace'>fcp-server</span> from "
|
||||
"<a href=\"https://github.com/geoffreybennett/fcp-support\">"
|
||||
"https://github.com/geoffreybennett/fcp-support</a> "
|
||||
"has been installed."
|
||||
);
|
||||
|
||||
// Reset the timeout ID since it won't be called again
|
||||
data->timeout_id = 0;
|
||||
|
||||
// Return FALSE to prevent the timeout from repeating
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Weak reference callback for cleanup
|
||||
static void on_widget_dispose(gpointer data, GObject *where_the_object_was) {
|
||||
struct timeout_data *timeout_data = (struct timeout_data *)data;
|
||||
|
||||
// Cancel the timeout if it's still active
|
||||
if (timeout_data->timeout_id > 0)
|
||||
g_source_remove(timeout_data->timeout_id);
|
||||
|
||||
// Free the data structure
|
||||
g_free(timeout_data);
|
||||
}
|
||||
|
||||
GtkWidget *create_iface_waiting_main(struct alsa_card *card) {
|
||||
struct timeout_data *data;
|
||||
|
||||
// Main vertical box
|
||||
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 20);
|
||||
gtk_widget_set_margin_start(box, 40);
|
||||
gtk_widget_set_margin_end(box, 40);
|
||||
gtk_widget_set_margin_top(box, 40);
|
||||
gtk_widget_set_margin_bottom(box, 40);
|
||||
|
||||
// Heading
|
||||
GtkWidget *label = gtk_label_new(NULL);
|
||||
gtk_label_set_markup(GTK_LABEL(label),
|
||||
"<span weight='bold' size='large'>Waiting for FCP Server</span>");
|
||||
gtk_box_append(GTK_BOX(box), label);
|
||||
|
||||
// Add picture (scaled down properly)
|
||||
GtkWidget *picture_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_widget_set_hexpand(picture_box, TRUE);
|
||||
gtk_widget_set_halign(picture_box, GTK_ALIGN_CENTER);
|
||||
|
||||
GtkWidget *picture = gtk_picture_new_for_resource(
|
||||
"/vu/b4/alsa-scarlett-gui/icons/vu.b4.alsa-scarlett-gui.png"
|
||||
);
|
||||
gtk_picture_set_can_shrink(GTK_PICTURE(picture), TRUE);
|
||||
gtk_widget_set_size_request(picture, 128, 128);
|
||||
|
||||
gtk_box_append(GTK_BOX(picture_box), picture);
|
||||
gtk_box_append(GTK_BOX(box), picture_box);
|
||||
|
||||
// Add spinner
|
||||
GtkWidget *spinner = gtk_spinner_new();
|
||||
gtk_spinner_start(GTK_SPINNER(spinner));
|
||||
gtk_widget_set_size_request(spinner, 48, 48);
|
||||
gtk_box_append(GTK_BOX(box), spinner);
|
||||
|
||||
// Description
|
||||
label = gtk_label_new(
|
||||
"Waiting for the user-space FCP driver to initialise..."
|
||||
);
|
||||
gtk_label_set_wrap(GTK_LABEL(label), TRUE);
|
||||
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
|
||||
gtk_label_set_max_width_chars(GTK_LABEL(label), 1);
|
||||
gtk_widget_set_hexpand(label, TRUE);
|
||||
gtk_widget_set_halign(label, GTK_ALIGN_FILL);
|
||||
|
||||
gtk_box_append(GTK_BOX(box), label);
|
||||
|
||||
// Setup timeout
|
||||
data = g_new(struct timeout_data, 1);
|
||||
data->box = box;
|
||||
data->spinner = spinner;
|
||||
data->message_label = label;
|
||||
|
||||
// Set timeout
|
||||
data->timeout_id = g_timeout_add_seconds(5, on_timeout, data);
|
||||
|
||||
// Ensure data is freed when the box is destroyed
|
||||
g_object_weak_ref(G_OBJECT(box), on_widget_dispose, data);
|
||||
|
||||
return box;
|
||||
}
|
||||
8
src/iface-waiting.h
Normal file
@@ -0,0 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "alsa.h"
|
||||
|
||||
GtkWidget *create_iface_waiting_main(struct alsa_card *card);
|
||||
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 47 KiB |
520
src/img/vu.b4.alsa-scarlett-gui.svg
Normal file
|
After Width: | Height: | Size: 91 KiB |
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "alsa.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "about.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "routing-drag-line.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "alsa.h"
|
||||
@@ -237,7 +237,7 @@ static void get_snk_center(
|
||||
double *y
|
||||
) {
|
||||
get_widget_center(r_snk->socket_widget, parent, x, y);
|
||||
if (IS_MIXER(r_snk->port_category))
|
||||
if (IS_MIXER(r_snk->elem->port_category))
|
||||
(*y)++;
|
||||
}
|
||||
|
||||
@@ -261,6 +261,12 @@ void draw_routing_lines(
|
||||
struct routing_snk *r_snk = &g_array_index(
|
||||
card->routing_snks, struct routing_snk, i
|
||||
);
|
||||
struct alsa_elem *elem = r_snk->elem;
|
||||
|
||||
// don't draw lines to read-only mixer sinks
|
||||
if (elem->port_category == PC_MIX &&
|
||||
card->has_fixed_mixer_inputs)
|
||||
continue;
|
||||
|
||||
// if dragging and a routing sink is being reconnected then draw
|
||||
// it with dots
|
||||
@@ -271,7 +277,7 @@ void draw_routing_lines(
|
||||
cairo_set_dash(cr, NULL, 0, 0);
|
||||
|
||||
// get the sink and skip if it's "Off"
|
||||
int r_src_idx = alsa_get_elem_value(r_snk->elem);
|
||||
int r_src_idx = alsa_get_elem_value(elem);
|
||||
if (!r_src_idx)
|
||||
continue;
|
||||
|
||||
@@ -300,7 +306,7 @@ void draw_routing_lines(
|
||||
draw_connection(
|
||||
cr,
|
||||
x1, y1, r_src->port_category,
|
||||
x2, y2, r_snk->port_category,
|
||||
x2, y2, elem->port_category,
|
||||
r, g, b, 2
|
||||
);
|
||||
}
|
||||
@@ -362,7 +368,7 @@ void draw_drag_line(
|
||||
draw_connection(
|
||||
cr,
|
||||
x1, y1, card->src_drag->port_category,
|
||||
x2, y2, card->snk_drag->port_category,
|
||||
x2, y2, card->snk_drag->elem->port_category,
|
||||
1, 1, 1, 2
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2023-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <glib.h>
|
||||
@@ -72,7 +72,7 @@ struct scarlett2_firmware_header *scarlett2_read_firmware_header(
|
||||
) {
|
||||
FILE *file = fopen(fn, "rb");
|
||||
if (!file) {
|
||||
perror("fopen");
|
||||
perror("fopen firmware header");
|
||||
fprintf(stderr, "Unable to open %s\n", fn);
|
||||
return NULL;
|
||||
}
|
||||
@@ -91,7 +91,7 @@ struct scarlett2_firmware_header *scarlett2_read_firmware_header(
|
||||
struct scarlett2_firmware_file *scarlett2_read_firmware_file(const char *fn) {
|
||||
FILE *file = fopen(fn, "rb");
|
||||
if (!file) {
|
||||
perror("fopen");
|
||||
perror("fopen firmware file");
|
||||
fprintf(stderr, "Unable to open %s\n", fn);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2023-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "tooltips.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=ALSA Scarlett2 Control Panel
|
||||
Name=ALSA Scarlett Control Panel
|
||||
Icon=vu.b4.alsa-scarlett-gui
|
||||
Exec=PREFIX/bin/alsa-scarlett-gui
|
||||
Categories=GTK;AudioVideo;Audio;Mixer;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "gtkhelper.h"
|
||||
@@ -85,6 +85,10 @@ GtkWidget *make_boolean_alsa_elem(
|
||||
data->elem = elem;
|
||||
data->button = gtk_toggle_button_new();
|
||||
|
||||
if (strncmp(elem->name, "Master", 6) == 0 &&
|
||||
strstr(elem->name, "Playback Switch"))
|
||||
data->backwards = 1;
|
||||
|
||||
g_signal_connect(
|
||||
data->button, "clicked", G_CALLBACK(button_clicked), data
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-FileCopyrightText: 2023-2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||