diff --git a/src/channel_layout.cpp b/src/channel_layout.cpp index e51e057..31d02eb 100644 --- a/src/channel_layout.cpp +++ b/src/channel_layout.cpp @@ -72,16 +72,6 @@ static struct SoundIoChannelLayout builtin_channel_layouts[] = { SoundIoChannelIdBackCenter, } }, - { - "4.1", - 4, - { - SoundIoChannelIdFrontLeft, - SoundIoChannelIdFrontRight, - SoundIoChannelIdFrontCenter, - SoundIoChannelIdLfe, - } - }, { "Quad", 4, @@ -102,6 +92,17 @@ static struct SoundIoChannelLayout builtin_channel_layouts[] = { SoundIoChannelIdSideRight, } }, + { + "4.1", + 5, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdBackCenter, + SoundIoChannelIdLfe, + } + }, { "5.0", 5, @@ -395,3 +396,17 @@ bool soundio_channel_layout_detect_builtin(struct SoundIoChannelLayout *layout) layout->name = nullptr; return false; } + +const struct SoundIoChannelLayout *soundio_channel_layout_get_default(int channel_count) { + switch (channel_count) { + case 1: return soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono); + case 2: return soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdStereo); + case 3: return soundio_channel_layout_get_builtin(SoundIoChannelLayoutId2Point1); + case 4: return soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdQuad); + case 5: return soundio_channel_layout_get_builtin(SoundIoChannelLayoutId4Point1); + case 6: return soundio_channel_layout_get_builtin(SoundIoChannelLayoutId5Point1); + case 7: return soundio_channel_layout_get_builtin(SoundIoChannelLayoutId6Point1); + case 8: return soundio_channel_layout_get_builtin(SoundIoChannelLayoutId7Point1); + } + return nullptr; +} diff --git a/src/jack.cpp b/src/jack.cpp index bd3e5aa..9da9dfa 100644 --- a/src/jack.cpp +++ b/src/jack.cpp @@ -9,6 +9,7 @@ #include "soundio.hpp" #include "atomics.hpp" #include "os.hpp" +#include "list.hpp" #include #include @@ -26,6 +27,19 @@ struct SoundIoJack { int buffer_size; }; +struct SoundIoJackPort { + const char *name; + int name_len; +}; + +struct SoundIoJackClient { + const char *name; + int name_len; + bool is_physical; + SoundIoDevicePurpose purpose; + SoundIoList ports; +}; + static void flush_events_jack(struct SoundIoPrivate *si) { SoundIo *soundio = &si->pub; SoundIoJack *sij = (SoundIoJack *)si->backend_data; @@ -124,6 +138,59 @@ static int instream_pause_jack(struct SoundIoPrivate *, struct SoundIoInStreamPr soundio_panic("TODO"); } +static void split_str(const char *input_str, int input_str_len, char c, + const char **out_1, int *out_len_1, const char **out_2, int *out_len_2) +{ + *out_1 = input_str; + while (*input_str) { + if (*input_str == c) { + *out_len_1 = input_str - *out_1; + *out_2 = input_str + 1; + *out_len_2 = input_str_len - 1 - *out_len_1; + return; + } + input_str += 1; + } +} + +static bool eql_str(const char *str1, int str1_len, const char *str2, int str2_len) { + if (str1_len != str2_len) + return false; + return memcmp(str1, str2, str1_len) == 0; +} + +static SoundIoJackClient *find_or_create_client(SoundIoList *clients, + SoundIoDevicePurpose purpose, bool is_physical, const char *client_name, int client_name_len) +{ + for (int i = 0; i < clients->length; i += 1) { + SoundIoJackClient *client = &clients->at(i); + if (client->is_physical == is_physical && + client->purpose == purpose && + eql_str(client->name, client->name_len, client_name, client_name_len)) + { + return client; + } + } + int err; + if ((err = clients->add_one())) + return nullptr; + SoundIoJackClient *client = &clients->last(); + client->is_physical = is_physical; + client->purpose = purpose; + client->name = client_name; + client->name_len = client_name_len; + return client; +} + +static char *dupe_str(const char *str, int str_len) { + char *out = allocate_nonzero(str_len + 1); + if (!out) + return nullptr; + memcpy(out, str, str_len); + out[str_len] = 0; + return out; +} + static int refresh_devices(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; SoundIoJack *sij = (SoundIoJack *)si->backend_data; @@ -140,11 +207,52 @@ static int refresh_devices(SoundIoPrivate *si) { return SoundIoErrorNoMem; } + SoundIoList clients; + int err; + const char **port_name_ptr = port_names; while (*port_name_ptr) { - const char *port_name = *port_name_ptr; - jack_port_t *port = jack_port_by_name(sij->client, port_name); - int flags = jack_port_flags(port); + const char *client_and_port_name = *port_name_ptr; + jack_port_t *jport = jack_port_by_name(sij->client, client_and_port_name); + int flags = jack_port_flags(jport); + SoundIoDevicePurpose purpose = (flags & JackPortIsInput) ? + SoundIoDevicePurposeOutput : SoundIoDevicePurposeInput; + bool is_physical = flags & JackPortIsPhysical; + + const char *client_name = nullptr; + const char *port_name = nullptr; + int client_name_len; + int port_name_len; + split_str(client_and_port_name, strlen(client_and_port_name), ':', + &client_name, &client_name_len, &port_name, &port_name_len); + if (!client_name || !port_name) { + // device does not have colon, skip it + continue; + } + SoundIoJackClient *client = find_or_create_client(&clients, purpose, is_physical, + client_name, client_name_len); + if (!client) { + jack_free(port_names); + destroy(devices_info); + return SoundIoErrorNoMem; + } + if ((err = client->ports.add_one())) { + jack_free(port_names); + destroy(devices_info); + return SoundIoErrorNoMem; + } + SoundIoJackPort *port = &client->ports.last(); + port->name = port_name; + port->name_len = port_name_len; + + + port_name_ptr += 1; + } + + for (int i = 0; i < clients.length; i += 1) { + SoundIoJackClient *client = &clients.at(i); + if (client->ports.length <= 0) + continue; SoundIoDevice *device = create(); if (!device) { @@ -152,15 +260,29 @@ static int refresh_devices(SoundIoPrivate *si) { destroy(devices_info); return SoundIoErrorNoMem; } + int description_len = client->name_len + 3 + 2 * client->ports.length; + for (int port_index = 0; port_index < client->ports.length; port_index += 1) { + SoundIoJackPort *port = &client->ports.at(port_index); + description_len += port->name_len; + } + device->ref_count = 1; device->soundio = soundio; device->is_raw = false; - device->name = strdup(port_name); - device->description = strdup(port_name); + device->purpose = client->purpose; + device->name = dupe_str(client->name, client->name_len); + device->description = allocate(description_len); device->layout_count = 1; device->layouts = create(); device->format_count = 1; device->formats = create(); + device->current_format = SoundIoFormatFloat32NE; + device->sample_rate_min = sij->sample_rate; + device->sample_rate_max = sij->sample_rate; + device->sample_rate_current = sij->sample_rate; + device->buffer_duration_min = sij->buffer_size / (double) sij->sample_rate; + device->buffer_duration_max = device->buffer_duration_min; + device->buffer_duration_current = device->buffer_duration_min; if (!device->name || !device->description || !device->layouts || !device->formats) { jack_free(port_names); @@ -169,48 +291,52 @@ static int refresh_devices(SoundIoPrivate *si) { return SoundIoErrorNoMem; } - // TODO figure out how jack does channel layout - device->layouts[0] = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono); - device->current_layout = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono); + memcpy(device->description, client->name, client->name_len); + memcpy(&device->description[client->name_len], ": ", 2); + int index = client->name_len + 2; + for (int port_index = 0; port_index < client->ports.length; port_index += 1) { + SoundIoJackPort *port = &client->ports.at(port_index); + memcpy(&device->description[index], port->name, port->name_len); + index += port->name_len; + if (port_index + 1 < client->ports.length) { + memcpy(&device->description[index], ", ", 2); + index += 2; + } + } - device->formats[0] = SoundIoFormatFloat32NE; - device->current_format = SoundIoFormatFloat32NE; - - device->sample_rate_min = sij->sample_rate; - device->sample_rate_max = sij->sample_rate; - device->sample_rate_current = sij->sample_rate; - - device->buffer_duration_min = sij->buffer_size / (double) sij->sample_rate; - device->buffer_duration_max = device->buffer_duration_min; - device->buffer_duration_current = device->buffer_duration_min; + const struct SoundIoChannelLayout *layout = soundio_channel_layout_get_default(client->ports.length); + if (layout) { + device->current_layout = *layout; + } else { + for (int port_index = 0; port_index < client->ports.length; port_index += 1) + device->current_layout.channels[port_index] = SoundIoChannelIdInvalid; + } + device->layouts[0] = device->current_layout; + device->formats[0] = device->current_format; SoundIoList *device_list; - if (flags & JackPortIsInput) { - device->purpose = SoundIoDevicePurposeOutput; + if (device->purpose == SoundIoDevicePurposeOutput) { device_list = &devices_info->output_devices; - if (devices_info->default_output_index < 0 && (flags & JackPortIsPhysical)) + if (devices_info->default_output_index < 0 && client->is_physical) devices_info->default_output_index = device_list->length; } else { - assert(flags & JackPortIsOutput); - device->purpose = SoundIoDevicePurposeInput; + assert(device->purpose == SoundIoDevicePurposeInput); device_list = &devices_info->input_devices; - if (devices_info->default_input_index < 0 && (flags & JackPortIsPhysical)) + if (devices_info->default_input_index < 0 && client->is_physical) devices_info->default_input_index = device_list->length; } if (device_list->append(device)) { - jack_free(port_names); soundio_device_unref(device); destroy(devices_info); return SoundIoErrorNoMem; } - port_name_ptr += 1; } - jack_free(port_names); + soundio_os_mutex_lock(sij->mutex); soundio_destroy_devices_info(sij->ready_devices_info); sij->ready_devices_info = devices_info; @@ -252,15 +378,19 @@ static int xrun_callback(void *arg) { } static void port_registration_callback(jack_port_id_t port_id, int reg, void *arg) { - //SoundIoPrivate *si = (SoundIoPrivate *)arg; - soundio_panic("TODO port registration callback"); + SoundIoPrivate *si = (SoundIoPrivate *)arg; + SoundIoJack *sij = (SoundIoJack *)si->backend_data; + if (sij->initialized) + refresh_devices(si); } static int port_rename_calllback(jack_port_id_t port_id, const char *old_name, const char *new_name, void *arg) { - //SoundIoPrivate *si = (SoundIoPrivate *)arg; - soundio_panic("TODO port rename callback"); + SoundIoPrivate *si = (SoundIoPrivate *)arg; + SoundIoJack *sij = (SoundIoJack *)si->backend_data; + if (sij->initialized) + refresh_devices(si); return 0; } @@ -277,6 +407,14 @@ static void destroy_jack(SoundIoPrivate *si) { if (sij->client) jack_client_close(sij->client); + if (sij->cond) + soundio_os_cond_destroy(sij->cond); + + if (sij->mutex) + soundio_os_mutex_destroy(sij->mutex); + + soundio_destroy_devices_info(sij->ready_devices_info); + destroy(sij); si->backend_data = nullptr; } @@ -312,6 +450,8 @@ int soundio_jack_init(struct SoundIoPrivate *si) { return SoundIoErrorNoMem; } + // We pass JackNoStartServer due to + // https://github.com/jackaudio/jack2/issues/138 jack_status_t status; sij->client = jack_client_open(soundio->app_name, JackNoStartServer, &status); if (!sij->client) { diff --git a/src/soundio.h b/src/soundio.h index d06119a..6c7b2a7 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -489,6 +489,9 @@ const char *soundio_get_channel_name(enum SoundIoChannelId id); int soundio_channel_layout_builtin_count(void); const struct SoundIoChannelLayout *soundio_channel_layout_get_builtin(int index); +// Get the default builtin channel layout for the given number of channels. +const struct SoundIoChannelLayout *soundio_channel_layout_get_default(int channel_count); + int soundio_channel_layout_find_channel( const struct SoundIoChannelLayout *layout, enum SoundIoChannelId channel);