This repository has been archived on 2025-09-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
alsa-scarlett-gui/src/alsa-sim.c
Geoffrey D. Bennett 17b4d2f055 Initial revision
2022-03-14 09:24:43 +10:30

401 lines
10 KiB
C

// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
// 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);
}