// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include "alsa.h" #include "alsa-sim.h" #include "error.h" #include "window-iface.h" // check that *config is a compound node, retrieve the first node // within, check that that node is a compound node, optionally check // its ID, and replace *config with the child static void get_and_check_first_compound( snd_config_t **config, const char *expected_id ) { const char *id, *child_id; int err; err = snd_config_get_id(*config, &id); if (err < 0) fatal_alsa_error("snd_config_get_id error", err); if (snd_config_get_type(*config) != SND_CONFIG_TYPE_COMPOUND) { printf("config node '%s' is not of type compound\n", id); exit(1); } snd_config_iterator_t i = snd_config_iterator_first(*config); if (i == snd_config_iterator_end(*config)) { printf("compound config node '%s' has no children\n", id); exit(1); } snd_config_t *config_child = snd_config_iterator_entry(i); err = snd_config_get_id(config_child, &child_id); if (err < 0) fatal_alsa_error("snd_config_get_id error", err); if (snd_config_get_type(config_child) != SND_CONFIG_TYPE_COMPOUND) { printf("config node %s->%s is not of type compound\n", id, child_id); exit(1); } *config = config_child; if (!expected_id) return; if (!child_id) { printf("config node has no id\n"); exit(1); } if (strcmp(child_id, expected_id) != 0) { printf( "found config node %s->%s instead of %s\n", id, child_id, expected_id ); exit(1); } } static void alsa_parse_enum_items( snd_config_t *items, struct alsa_elem *elem ) { int count = snd_config_is_array(items); if (count < 0) { printf("error: parse enum items array value %d\n", count); return; } elem->item_count = count; elem->item_names = calloc(count, sizeof(char *)); int item_num = 0; snd_config_iterator_t i, next; snd_config_for_each(i, next, items) { 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) { printf("error: enum item %s type %d not string\n", key, type); return; } const char *s; err = snd_config_get_string(node, &s); if (err < 0) fatal_alsa_error("snd_config_get_string error", err); elem->item_names[item_num++] = strdup(s); } } // parse a comment node and update elem, e.g.: // // comment { // access read // type ENUMERATED // count 1 // item.0 Line // item.1 Inst // } static void alsa_parse_comment_node( snd_config_t *comment, struct alsa_elem *elem ) { snd_config_iterator_t i, next; snd_config_for_each(i, next, comment) { 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 (strcmp(key, "access") == 0) { if (type != SND_CONFIG_TYPE_STRING) { printf("access type not string\n"); return; } const char *access; err = snd_config_get_string(node, &access); if (err < 0) fatal_alsa_error("snd_config_get_string error", err); if (strstr(access, "write")) elem->writable = 1; } else if (strcmp(key, "type") == 0) { if (type != SND_CONFIG_TYPE_STRING) { printf("type type not string\n"); return; } const char *type; err = snd_config_get_string(node, &type); if (err < 0) fatal_alsa_error("snd_config_get_string error", err); if (strcmp(type, "BOOLEAN") == 0) elem->type = SND_CTL_ELEM_TYPE_BOOLEAN; else if (strcmp(type, "ENUMERATED") == 0) elem->type = SND_CTL_ELEM_TYPE_ENUMERATED; else if (strcmp(type, "INTEGER") == 0) elem->type = SND_CTL_ELEM_TYPE_INTEGER; } else if (strcmp(key, "item") == 0) { alsa_parse_enum_items(node, elem); } } } static int alsa_config_to_new_elem( snd_config_t *config, struct alsa_elem *elem ) { const char *s; int id; char *iface = NULL, *name = NULL; int seen_value; int value_type = -1; char *string_value = NULL; long int_value; int err; err = snd_config_get_id(config, &s); if (err < 0) fatal_alsa_error("snd_config_get_id error", err); id = atoi(s); // loop through the nodes of the control element snd_config_iterator_t i, next; snd_config_for_each(i, next, config) { snd_config_t *node = snd_config_iterator_entry(i); const char *key; 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); // iface node? if (strcmp(key, "iface") == 0) { if (type != SND_CONFIG_TYPE_STRING) { printf("iface type for %d is %d not string", id, type); goto fail; } err = snd_config_get_string(node, &s); if (err < 0) fatal_alsa_error("snd_config_get_string error", err); iface = strdup(s); // name node? } else if (strcmp(key, "name") == 0) { if (type != SND_CONFIG_TYPE_STRING) { printf("name type for %d is %d not string", id, type); goto fail; } err = snd_config_get_string(node, &s); if (err < 0) fatal_alsa_error("snd_config_get_string error", err); name = strdup(s); // value node? } else if (strcmp(key, "value") == 0) { seen_value = 1; value_type = type; if (type == SND_CONFIG_TYPE_INTEGER) { err = snd_config_get_integer(node, &int_value); if (err < 0) fatal_alsa_error("snd_config_get_integer error", err); } else if (type == SND_CONFIG_TYPE_STRING) { err = snd_config_get_string(node, &s); if (err < 0) 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); if (strcmp(name, "Level Meter") == 0) { seen_value = 1; value_type = SND_CONFIG_TYPE_INTEGER; int_value = 0; } else { goto fail; } } else { printf( "skipping value type for %d; is %d, not int or string\n", id, type ); goto fail; } // comment node? } else if (strcmp(key, "comment") == 0) { alsa_parse_comment_node(node, elem); } else { printf("skipping unknown node %s for %d\n", key, id); goto fail; } } // check iface value; only interested in MIXER and PCM if (!iface) { printf("missing iface node in control id %d\n", id); goto fail; } if (strcmp(iface, "MIXER") != 0 && strcmp(iface, "PCM") != 0) goto fail; // check for presence of name and value if (!name) { printf("missing name node in control id %d\n", id); goto fail; } if (!seen_value) { printf("missing value node in control id %d\n", id); goto fail; } // set the element value // integer in config if (value_type == SND_CONFIG_TYPE_INTEGER) { 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 (strcmp(string_value, "true") == 0) 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; break; } } // string value not boolean/enum } else { goto fail; } } elem->numid = id; elem->name = name; free(iface); free(string_value); return 0; fail: free(iface); free(name); free(string_value); return -1; } static void alsa_config_to_new_card( snd_config_t *top, struct alsa_card *card ) { snd_config_t *config = top; // go down through the compound nodes state.X (usually USB), control get_and_check_first_compound(&config, "state"); get_and_check_first_compound(&config, NULL); get_and_check_first_compound(&config, "control"); // loop through the controls snd_config_iterator_t i, next; snd_config_for_each(i, next, config) { snd_config_t *node = snd_config_iterator_entry(i); // ignore non-compound controls 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; } } // return the basename of fn (no path, no extension) // e.g. "/home/user/file.ext" -> "file" static char *sim_card_name(const char *fn) { // strdup fn and remove path (if any) char *name = strrchr(fn, '/'); if (name) name = strdup(name + 1); else name = strdup(fn); // remove extension char *dot = strrchr(name, '.'); if (dot) *dot = '\0'; return name; } void create_sim_from_file(GtkWindow *w, char *fn) { snd_config_t *config; snd_input_t *in; int err; err = snd_config_top(&config); if (err < 0) fatal_alsa_error("snd_config_top error", err); err = snd_input_stdio_open(&in, fn, "r"); if (err < 0) { char *s = g_strdup_printf("Error opening %s: %s", fn, snd_strerror(err)); show_error(w, s); free(s); return; } err = snd_config_load(config, in); snd_input_close(in); if (err < 0) fatal_alsa_error("snd_config_load error", err); struct alsa_card *card = card_create(SIMULATED_CARD_NUM); card->name = sim_card_name(fn); alsa_config_to_new_card(config, card); snd_config_delete(config); create_card_window(card); }