Skip to content

Commit

Permalink
JACK support
Browse files Browse the repository at this point in the history
- JACK input backend support
- JACK priority after sndio (and both after pipewire)
- doc: extended documentation in README.md
- fix getPulseDefaultSink compilation warning
  • Loading branch information
bsdcode authored and karlstav committed Jan 18, 2024
1 parent db3084b commit f25c17a
Show file tree
Hide file tree
Showing 13 changed files with 526 additions and 55 deletions.
4 changes: 4 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ if OSS
cava_SOURCES += input/oss.c
endif

if JACK
cava_SOURCES += input/jack.c
endif

if NCURSES
cava_SOURCES += output/terminal_ncurses.c
endif
Expand Down
120 changes: 102 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ by [Karl Stavestrand](mailto:[email protected])
- [MPD](#mpd)
- [sndio](#sndio)
- [OSS](#oss)
- [JACK](#jack)
- [squeezelite](#squeezelite)
- [macOS](#macos-1)
- [Windows](#windows)
Expand Down Expand Up @@ -77,11 +78,12 @@ The development lib of one of these audio frameworks, depending on your distro:
* Pipewire
* Portaudio
* Sndio
* JACK

Optional components:
* SDL2 dev files

Only FFTW and the other build tools are actually required for CAVA to compile, but this will only give you the ability to read from fifo files. To more easly grab audio from your system pulseaudio, alsa, sndio or portaudio dev files are recommended (depending on what audio system you are using). Not sure how to get the pulseaudio dev files for other distros than debian/ubuntu or if they are bundled in pulseaudio.
Only FFTW and the other build tools are actually required for CAVA to compile, but this will only give you the ability to read from fifo files. To more easly grab audio from your system pulseaudio, alsa, sndio, jack or portaudio dev files are recommended (depending on what audio system you are using). Not sure how to get the pulseaudio dev files for other distros than debian/ubuntu or if they are bundled in pulseaudio.


For better a better visual experience ncurses is also recomended.
Expand All @@ -90,11 +92,17 @@ All the requirements can be installed easily in all major distros:

FreeBSD

pkg install autoconf-archive autotools fftw3 iniparser pkgconf psftools sdl2 sndio
pkg install autoconf autoconf-archive automake fftw3 iniparser jackit libglvnd libtool pkgconf psftools sdl2 sndio

Additionally, run these commands on FreeBSD before building:

export CFLAGS="-I/usr/local/include"
export LDFLAGS="-L/usr/local/lib"


Debian/Ubuntu:

sudo apt install build-essential libfftw3-dev libasound2-dev libncursesw5-dev libpulse-dev libtool automake autoconf-archive libiniparser-dev libsdl2-2.0-0 libsdl2-dev libpipewire-0.3-dev pkgconf
sudo apt install build-essential libfftw3-dev libasound2-dev libncursesw5-dev libpulse-dev libtool automake autoconf-archive libiniparser-dev libsdl2-2.0-0 libsdl2-dev libpipewire-0.3-dev libjack-jackd2-dev pkgconf


ArchLinux:
Expand Down Expand Up @@ -340,9 +348,12 @@ $ AUDIODEVICE=snd/0.monitor cava

### OSS

Set

method = oss

The audio system used on FreeBSD is the Open Sound System (OSS).
The following example demonstrates how to setup CAVA for OSS on FreeBSD:

```sh
$ cat /dev/sndstat
Installed devices:
Expand All @@ -351,12 +362,11 @@ pcm1: <Realtek ALC1220 (Front Analog Mic)> (rec)
pcm2: <USB audio> (play/rec)
No devices installed from userspace.
```

The system has three `pcm` sound devices, `pcm0`, `pcm1` and `pcm2`. `pcm0` corresponds to the analog
output jack on the rear, in which external stereo speakers are plugged in, and the analog input jack,
in which one could plug in a microphone. Because it encapsulates both, output and input, it is marked
as `play/rec`. It is also set as the `default` sound device. `pcm1` corresponds to another analog input
jack for a mic on the front side and is marked `rec`. A USB headset which an integrated mic is plugged
jack for a mic on the front side and is marked `rec`. A USB headset with an integrated mic is plugged
in an USB port and the system has created the `pcm2` sound device with `play/rec` capabilities for
it.

Expand All @@ -367,20 +377,17 @@ which acts like a symlink to the `default` audio device, in this example to `/de

Now in order to visualize the mic input in CAVA, the `source` value in the configuration file must
be set to the corresponding audio device, i.e.
```sh
[input]
source = /dev/dsp # or /dev/dsp0 for which /dev/dsp is a symlink in this example
```

source = /dev/dsp # or /dev/dsp0 for which /dev/dsp is a symlink in this example

(which is already the default for CAVA) for the `pcm0` mic on the rear, or
```sh
[input]
source = /dev/dsp1
```

source = /dev/dsp1

for the `pcm1` mic on the front, or
```sh
[input]
source = /dev/dsp2
```

source = /dev/dsp2

for the `pcm2` mic on the USB headset.

OSS can't record the outgoing audio on its own, i.e. the sounds from a music player or a browser which
Expand All @@ -403,6 +410,83 @@ It created a virtual device `dsp` from `/dev/dsp0`. Now the audio is visualized
`source = /dev/dsp` in the configuration file. Virtual OSS can be configured and started as a service
on FreeBSD.

### JACK

Set

method = jack

The JACK Audio Connection Kit (JACK) is a professional sound server API which is available on several
operating systems, e.g. FreeBSD and Linux.

CAVA is a JACK client with the base client name `cava` and adheres to the standard server start and
stop behaviour, i.e. CAVA starts a JACK server if none is already running and the environment variable
`JACK_START_SERVER` is defined, in which case the server also stops when all clients have exited. The
`source` in the CAVA configuration file specifies the name of the JACK server to which CAVA tries to
connect to. The default value is `default`, which is also the default JACK server name. The value can
be empty, in which case it implies `default`. Therefore the following three entries are equivalent:

; source = default
source = default
source =

One exception is the combination of an empty `source` entry and the environment variable `JACK_DEFAULT_SERVER`.
If the environment variable is defined, e.g. `export JACK_DEFAULT_SERVER=foo`, then the following entries
are equivalent:

source = foo
source =

Consult the manpage `jackd(1)` for further information regarding configuration and startup of a JACK
server.

CAVA creates terminal audio-typed (so no MIDI support) input ports. These ports can connect to output
ports of other JACK clients, e.g. connect to the output ports of a music player and CAVA will visualize
the music. Currently CAVA supports up to two input ports, i.e. it supports mono and stereo. The number
of input ports can be controlled via the `channels` option in the input section of the configuration
file:

channels = 1 # one input port, mono

or

channels = 2 # two input ports, stereo

The port's short name is simply `M` for mono, and `L` and `R` for stereo. The full name of the input
port according to the base client name is `cava:M` for mono, and `cava:L` and `cava:R` for stereo.

Currently CAVA doesn't connect its ports to other client's ports automatically, i.e. on program start
CAVA isn't connected to any other program and won't connect during execution on its own. There are
connection management programs in order to control and manage the connection between ports of the different
client programs. Some well known connection managers with a graphical user interface include QjackCtl
and Cadence. The JACK package itself often comes with CLI tools. Depending on the operating system
they might need to get installed separately, e.g. on FreeBSD
```sh
$ doas pkg install jack-example-tools
```
Among the tools are the programs `jack_lsp` and `jack_connect`. These two tools are enough to list
and connect ports on the commandline. The following example demonstrates how to setup connections with
these tools:
```sh
$ jack_lsp
system:capture_1
system:capture_2
system:playback_1
system:playback_2
cava:L
moc:output0
moc:output1
cava:R
```
This listing shows all full port names that are currently available. These correspond to two external
JACK clients (cava and moc) and one internal JACK client (system). There are also `-p` and `-c` switches
for `jack_lsp`, with which the types and current connections between the ports are listed. Connect
the ports of cava and moc:
```sh
$ jack_connect cava:L moc:output0
$ jack_connect cava:R moc:output1
```
Now CAVA visualizes the outgoing audio from MOC.

### squeezelite
[squeezelite](https://en.wikipedia.org/wiki/Squeezelite) is one of several software clients available for the Logitech Media Server. Squeezelite can export its audio data as shared memory, which is what this input module uses.
Expand Down
9 changes: 9 additions & 0 deletions cava.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@

#include "input/alsa.h"
#include "input/fifo.h"
#include "input/jack.h"
#include "input/oss.h"
#include "input/pipewire.h"
#include "input/portaudio.h"
Expand Down Expand Up @@ -422,9 +423,17 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co
case INPUT_OSS:
audio.format = p.samplebits;
audio.rate = p.samplerate;
audio.channels = p.channels;
audio.threadparams = 1; // OSS can adjust parameters
thr_id = pthread_create(&p_thread, NULL, input_oss, (void *)&audio);
break;
#endif
#ifdef JACK
case INPUT_JACK:
audio.channels = p.channels;
audio.threadparams = 1; // JACK server provides parameters
thr_id = pthread_create(&p_thread, NULL, input_jack, (void *)&audio);
break;
#endif
case INPUT_SHMEM:
audio.format = 16;
Expand Down
24 changes: 18 additions & 6 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,21 @@ const char *default_shader_name[NUMBER_OF_SHADERS] = {"northern_lights.frag", "p
double smoothDef[5] = {1, 1, 1, 1, 1};

enum input_method default_methods[] = {
INPUT_FIFO, INPUT_PORTAUDIO, INPUT_ALSA, INPUT_PULSE,
INPUT_PIPEWIRE, INPUT_WINSCAP, INPUT_SNDIO, INPUT_OSS,
INPUT_FIFO, INPUT_PORTAUDIO, INPUT_ALSA, INPUT_PULSE, INPUT_JACK,
INPUT_SNDIO, INPUT_PIPEWIRE, INPUT_WINSCAP, INPUT_OSS,
};

char *outputMethod, *orientation, *channels, *xaxisScale, *monoOption, *fragmentShader,
*vertexShader;

const char *input_method_names[] = {
"fifo", "portaudio", "pipewire", "alsa", "pulse", "sndio", "oss", "shmem", "winscap",
"fifo", "portaudio", "pipewire", "alsa", "pulse", "sndio", "oss", "jack", "shmem", "winscap",
};

const bool has_input_method[] = {
HAS_FIFO, /** Always have at least FIFO and shmem input. */
HAS_PORTAUDIO, HAS_PIPEWIRE, HAS_ALSA, HAS_PULSE, HAS_SNDIO, HAS_OSS, HAS_SHMEM, HAS_WINSCAP,
HAS_PORTAUDIO, HAS_PIPEWIRE, HAS_ALSA, HAS_PULSE, HAS_SNDIO,
HAS_OSS, HAS_JACK, HAS_SHMEM, HAS_WINSCAP,
};

enum input_method input_method_by_name(const char *str) {
Expand Down Expand Up @@ -328,7 +329,7 @@ bool validate_config(struct config_params *p, struct error_s *error) {
if (p->stereo == -1) {
write_errorf(
error,
"output channels %s is not supported, supported channelss are: 'mono' and 'stereo'\n",
"output channels %s is not supported, supported channels are: 'mono' and 'stereo'\n",
channels);
return false;
}
Expand Down Expand Up @@ -389,6 +390,12 @@ bool validate_config(struct config_params *p, struct error_s *error) {
}
p->sens = p->sens / 100;

// validate: channels
if (p->channels <= 1)
p->channels = 1;
else
p->channels = 2;

return validate_colors(p, error);
}

Expand Down Expand Up @@ -546,7 +553,6 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors
monoOption = malloc(sizeof(char) * 32);
p->raw_target = malloc(sizeof(char) * 129);
p->data_format = malloc(sizeof(char) * 32);
channels = malloc(sizeof(char) * 32);
orientation = malloc(sizeof(char) * 32);
vertexShader = malloc(sizeof(char) * PATH_MAX / 2);
fragmentShader = malloc(sizeof(char) * PATH_MAX / 2);
Expand Down Expand Up @@ -677,6 +683,7 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors

p->samplerate = iniparser_getint(ini, "input:sample_rate", 44100);
p->samplebits = iniparser_getint(ini, "input:sample_bits", 16);
p->channels = iniparser_getint(ini, "input:channels", 2);

enum input_method default_input = INPUT_FIFO;
for (size_t i = 0; i < ARRAY_SIZE(default_methods); i++) {
Expand Down Expand Up @@ -716,6 +723,11 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors
case INPUT_OSS:
p->audio_source = strdup(iniparser_getstring(ini, "input:source", "/dev/dsp"));
break;
#endif
#ifdef JACK
case INPUT_JACK:
p->audio_source = strdup(iniparser_getstring(ini, "input:source", "default"));
break;
#endif
case INPUT_SHMEM:
p->audio_source =
Expand Down
13 changes: 10 additions & 3 deletions config.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
#define HAS_OSS false
#endif

#ifdef JACK
#define HAS_JACK true
#else
#define HAS_JACK false
#endif

#ifdef _MSC_VER
#define HAS_WINSCAP true
#define SDL true
Expand All @@ -66,6 +72,7 @@ enum input_method {
INPUT_PULSE,
INPUT_SNDIO,
INPUT_OSS,
INPUT_JACK,
INPUT_SHMEM,
INPUT_WINSCAP,
INPUT_MAX,
Expand Down Expand Up @@ -104,9 +111,9 @@ struct config_params {
enum orientation orientation;
int userEQ_keys, userEQ_enabled, col, bgcol, autobars, stereo, raw_format, ascii_range,
bit_format, gradient, gradient_count, fixedbars, framerate, bar_width, bar_spacing,
bar_height, autosens, overshoot, waves, samplerate, samplebits, sleep_timer, sdl_width,
sdl_height, sdl_x, sdl_y, sdl_full_screen, draw_and_quit, zero_test, non_zero_test, reverse,
sync_updates, continuous_rendering, disable_blanking;
bar_height, autosens, overshoot, waves, samplerate, samplebits, channels, sleep_timer,
sdl_width, sdl_height, sdl_x, sdl_y, sdl_full_screen, draw_and_quit, zero_test,
non_zero_test, reverse, sync_updates, continuous_rendering, disable_blanking;
};

struct error_s {
Expand Down
23 changes: 23 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,29 @@ AS_IF([test "x$enable_input_oss" != "xno"], [

AM_CONDITIONAL([OSS], [test "x$have_oss" = "xyes"])

dnl ######################
dnl checking for jack dev
dnl ######################
AC_ARG_ENABLE([input_jack],
AS_HELP_STRING([--disable-input-jack],
[do not include support for input from jack])
)

AS_IF([test "x$enable_input_jack" != "xno"], [
PKG_CHECK_MODULES(JACK, jack, have_jack=yes, have_jack=no)
if [[ $have_jack = "yes" ]] ; then
LIBS="$LIBS $JACK_LIBS"
CPPFLAGS="$CPPFLAGS -DJACK $JACK_CFLAGS"
fi
if [[ $have_jack = "no" ]] ; then
AC_MSG_NOTICE([WARNING: No jack dev files found building without jack support])
fi],
[have_jack=no]
)

AM_CONDITIONAL([JACK], [test "x$have_jack" = "xyes"])

dnl ######################
dnl checking for math lib
dnl ######################
Expand Down
Loading

0 comments on commit f25c17a

Please sign in to comment.