Skip to content

Commit

Permalink
JACK: add autoconnect option for automatic port connections
Browse files Browse the repository at this point in the history
  • Loading branch information
bsdcode authored and karlstav committed Jan 20, 2024
1 parent f25c17a commit b28d867
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 23 deletions.
31 changes: 18 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -447,20 +447,25 @@ of input ports can be controlled via the `channels` option in the input section
file:

channels = 1 # one input port, mono

or

channels = 2 # two input ports, stereo
channels = 2 # two input ports, stereo (default)

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
The option `autoconnect` controls the connection strategy for CAVA's ports to other client's ports:

autoconnect = 0 # don't connect to other ports automatically
autoconnect = 1 # only connect to other ports during startup
autoconnect = 2 # reconnect to new ports regularly (default)

The automatic connection strategies scan the physical terminal input-ports, i.e. the real audio device
which actually outputs the sound, and applies the same connections to CAVA's ports. In this way CAVA
visualizes the played audio of JACK clients by default.

In order to control and manage the connection between CAVA's ports and ports of other client programs,
there are connection management programs for JACK. Some well known connection managers with a graphical
user interface are QjackCtl and Cadence. The JACK package itself often comes with CLI tools. Depending
on the operating system it could be necessary to install them separately, e.g. on FreeBSD:
```sh
$ doas pkg install jack-example-tools
```
Expand All @@ -479,9 +484,9 @@ 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:
JACK clients, `cava` and `moc`, and one internal JACK client `system`. The types and current active
connections between the ports can be listed With the `-p` and `-c` switches for `jack_lsp`. In order
to connect the ports of CAVA and MOC, `jack_connect` is used:
```sh
$ jack_connect cava:L moc:output0
$ jack_connect cava:R moc:output1
Expand Down
2 changes: 2 additions & 0 deletions cava.c
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co
audio.samples_counter = 0;
audio.channels = 2;
audio.IEEE_FLOAT = 0;
audio.autoconnect = 0;

audio.input_buffer_size = BUFFER_SIZE * audio.channels;
audio.cava_buffer_size = audio.input_buffer_size * 8;
Expand Down Expand Up @@ -431,6 +432,7 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co
#ifdef JACK
case INPUT_JACK:
audio.channels = p.channels;
audio.autoconnect = p.autoconnect;
audio.threadparams = 1; // JACK server provides parameters
thr_id = pthread_create(&p_thread, NULL, input_jack, (void *)&audio);
break;
Expand Down
1 change: 1 addition & 0 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,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);
p->autoconnect = iniparser_getint(ini, "input:autoconnect", 2);

enum input_method default_input = INPUT_FIFO;
for (size_t i = 0; i < ARRAY_SIZE(default_methods); i++) {
Expand Down
4 changes: 2 additions & 2 deletions config.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ 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, channels, sleep_timer,
sdl_width, sdl_height, sdl_x, sdl_y, sdl_full_screen, draw_and_quit, zero_test,
bar_height, autosens, overshoot, waves, samplerate, samplebits, channels, autoconnect,
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;
};

Expand Down
12 changes: 6 additions & 6 deletions example_files/config
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,11 @@
; method = jack
; source = default

# The sample rate and format can be configured for some input methods. Currently
# the following methods support such a configuration: 'fifo', 'pipewire' and 'oss'.
# Other methods ignore these settings.
#
# The channels can be configured for some input methods. Currently the following
# methods support such a configuration: 'oss' and 'jack'.
# The options 'sample rate', 'format', 'channels' and 'autoconnect' can be configured for some input methods:
# sample rate: 'fifo', 'pipewire', 'oss'
# format: 'fifo', 'pipewire', 'oss'
# channels: 'oss', 'jack'
# autoconnect: 'jack'
# Other methods ignore these settings.
#
# For 'oss' they are only preferred values, i.e. if the values are not supported
Expand All @@ -122,6 +121,7 @@
; sample_rate = 44100
; sample_bits = 16
; channels = 2
; autoconnect = 2


[output]
Expand Down
3 changes: 2 additions & 1 deletion input/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ struct audio_data {
int terminate; // shared variable used to terminate audio thread
char error_message[1024];
int samples_counter;
int IEEE_FLOAT; // format for 32bit (0=int, 1=float)
int IEEE_FLOAT; // format for 32bit (0=int, 1=float)
int autoconnect; // auto connect to audio source (0=off, 1=once at startup, 2=regularly)
pthread_mutex_t lock;
};

Expand Down
87 changes: 86 additions & 1 deletion input/jack.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ struct jack_data {
jack_nframes_t nframes; // number of samples per port
sample_t *buf; // samples buffer

int shutdown; // JACK shutdown signal (0=keep, 1=shutdown)
int graphorder; // JACK graph ordering signal (0=unchanged, 1=changed)
int shutdown; // JACK shutdown signal (0=keep, 1=shutdown)
};

static bool set_rate(struct jack_data *jack) {
Expand Down Expand Up @@ -119,6 +120,11 @@ static int on_buffer_size(jack_nframes_t nframes, void *arg) {
return 0;
}

static int on_graph_order(void *arg) {
((struct jack_data *)arg)->graphorder = 1;
return 0;
}

static int on_process(jack_nframes_t nframes, void *arg) {
// Interleave samples from separate ports and feed them to CAVA.
struct jack_data *jack = arg;
Expand Down Expand Up @@ -185,6 +191,68 @@ static int on_sample_rate(jack_nframes_t nframes, void *arg) {

static void on_shutdown(void *arg) { ((struct jack_data *)arg)->shutdown = 1; }

static bool auto_connect(struct jack_data *jack) {
// Get all physical terminal input-ports and mirror their connections to CAVA.
static const char type_name_pattern[] = JACK_DEFAULT_AUDIO_TYPE;
static const unsigned long flags = JackPortIsInput | JackPortIsPhysical | JackPortIsTerminal;

unsigned int channels;

const char **ports = NULL;

bool success = false;

if ((jack->shutdown == 1) || (jack->audio->terminate == 1))
return true;

if ((ports = jack_get_ports(jack->client, NULL, type_name_pattern, flags)) == NULL) {
fprintf(stderr,
__FILE__ ": jack_get_ports() failed: No physical terminal input-ports found!\n");
goto cleanup;
}

// If CAVA is configured for mono, then we connect everything to the one mono port. If we have
// more channels, then we limit the number of connection ports to the number of channels.
for (channels = 0; ports[channels] != NULL; ++channels)
;

if ((jack->audio->channels > 1) && (channels > jack->audio->channels))
channels = jack->audio->channels;

// Visit the physical terminal input-ports, get their connections and apply them to CAVA's
// input-ports.
for (unsigned int i = 0; i < channels; ++i) {
const char **connections;
jack_port_t *port;

if ((connections = jack_port_get_all_connections(
jack->client, jack_port_by_name(jack->client, ports[i]))) == NULL)
continue;

if (jack->audio->channels == 1)
port = jack->port[0];
else
port = jack->port[i];

for (int j = 0; connections[j] != NULL; ++j) {
if (jack_port_connected_to(port, connections[j]) == 0)
jack_connect(jack->client, connections[j], jack_port_name(port));
}

jack_free(connections);
}

success = true;

cleanup:
if (!success)
jack->shutdown = 1;

jack_free(ports);

return success;
}

void *input_jack(void *data) {
static const char client_name[] = "cava";
static const jack_options_t options = JackNullOption | JackServerName;
Expand Down Expand Up @@ -244,6 +312,17 @@ void *input_jack(void *data) {
goto cleanup;
}

if (audio->autoconnect > 0) {
if (audio->autoconnect == 1)
jack.graphorder = 1;
else {
if ((err = jack_set_graph_order_callback(jack.client, on_graph_order, &jack)) != 0) {
fprintf(stderr, __FILE__ ": jack_set_graph_order_callback() failed: 0x%x\n", err);
goto cleanup;
}
}
}

if ((err = jack_set_process_callback(jack.client, on_process, &jack)) != 0) {
fprintf(stderr, __FILE__ ": jack_set_process_callback() failed: 0x%x\n", err);
goto cleanup;
Expand All @@ -266,6 +345,12 @@ void *input_jack(void *data) {
while (audio->terminate != 1) {
if (jack.shutdown == 1)
signal_terminate(audio);
else if (jack.graphorder == 1) {
if (!auto_connect(&jack))
goto cleanup;

jack.graphorder = 0;
}

nanosleep(&rqtp, NULL);
}
Expand Down

0 comments on commit b28d867

Please sign in to comment.