Current section

Files

Jump to
alsa c_src nif alsa_ctl_nif.c
Raw

c_src/nif/alsa_ctl_nif.c

#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdatomic.h>
#include <assert.h>
#include <erl_nif.h>
#include <alsa/asoundlib.h>
#define ARRAY_LENGTH(x) \
((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
#define clamp(value, min, max) \
((value < min) ? (min) : ((value > max) ? (max) : (value)))
/* Types */
typedef struct alsa_ctl_nif_resource_t {
ErlNifPid owner;
ErlNifMonitor owner_monitor;
snd_ctl_t *handle;
atomic_flag handle_closed;
} alsa_ctl_nif_resource_t;
/* Atoms */
static ERL_NIF_TERM am_ok;
static ERL_NIF_TERM am_error;
static ERL_NIF_TERM am_wait;
static ERL_NIF_TERM am_undefined;
static ERL_NIF_TERM am_other;
static ERL_NIF_TERM am_true;
static ERL_NIF_TERM am_false;
static ERL_NIF_TERM am_closed;
static ERL_NIF_TERM am_enoent;
static ERL_NIF_TERM am_enomem;
static ERL_NIF_TERM am_enotsup;
static ERL_NIF_TERM am_eagain;
static ERL_NIF_TERM am_type;
static ERL_NIF_TERM am_boolean;
static ERL_NIF_TERM am_integer;
static ERL_NIF_TERM am_enumerated;
static ERL_NIF_TERM am_bytes;
static ERL_NIF_TERM am_count;
static ERL_NIF_TERM am_min;
static ERL_NIF_TERM am_max;
static ERL_NIF_TERM am_step;
static ERL_NIF_TERM am_items;
static ERL_NIF_TERM am_card;
static ERL_NIF_TERM am_mixer;
static ERL_NIF_TERM am_pcm;
static ERL_NIF_TERM am_rawmidi;
static ERL_NIF_TERM am_timer;
static ERL_NIF_TERM am_sequencer;
static ERL_NIF_TERM am_numid;
static ERL_NIF_TERM am_interface;
static ERL_NIF_TERM am_name;
static ERL_NIF_TERM am_index;
static ERL_NIF_TERM am_device;
static ERL_NIF_TERM am_subdevice;
/* Helpers */
static ERL_NIF_TERM libasound_error_to_erl(ErlNifEnv *env, int error)
{
switch (-error) {
case ENOENT: return am_enoent;
case ENOMEM: return am_enomem;
default: return enif_make_int(env, -error);
}
}
static bool alsa_ctl_nif_get_bool(ErlNifEnv *env, const ERL_NIF_TERM term, bool *value)
{
if (enif_is_identical(term, am_true)) {
*value = true;
return true;
} else if (enif_is_identical(term, am_false)) {
*value = false;
return true;
}
return false;
}
static bool alsa_ctl_nif_get_elem_iface(ErlNifEnv *env, const ERL_NIF_TERM term, snd_ctl_elem_iface_t *interface)
{
if (enif_is_identical(term, am_card)) {
*interface = SND_CTL_ELEM_IFACE_CARD;
return true;
} else if (enif_is_identical(term, am_mixer)) {
*interface = SND_CTL_ELEM_IFACE_MIXER;
return true;
} else if (enif_is_identical(term, am_pcm)) {
*interface = SND_CTL_ELEM_IFACE_PCM;
return true;
} else if (enif_is_identical(term, am_rawmidi)) {
*interface = SND_CTL_ELEM_IFACE_RAWMIDI;
return true;
} else if (enif_is_identical(term, am_timer)) {
*interface = SND_CTL_ELEM_IFACE_TIMER;
return true;
} else if (enif_is_identical(term, am_sequencer)) {
*interface = SND_CTL_ELEM_IFACE_SEQUENCER;
return true;
} else {
return false;
}
}
static bool alsa_ctl_nif_get_ctl_elem_id(ErlNifEnv *env, const ERL_NIF_TERM term, snd_ctl_elem_id_t *elem_id)
{
ErlNifMapIterator iter;
if (enif_map_iterator_create(env, term, &iter, ERL_NIF_MAP_ITERATOR_FIRST)) {
ERL_NIF_TERM key, value;
snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER);
while (enif_map_iterator_get_pair(env, &iter, &key, &value)) {
if (enif_is_identical(key, am_numid)) {
unsigned int numid;
if (!enif_get_uint(env, value, &numid)) {
return false;
}
snd_ctl_elem_id_set_numid(elem_id, numid);
} else if (enif_is_identical(key, am_interface)) {
snd_ctl_elem_iface_t interface;
if (!alsa_ctl_nif_get_elem_iface(env, value, &interface)) {
return false;
}
snd_ctl_elem_id_set_interface(elem_id, interface);
} else if (enif_is_identical(key, am_name)) {
char name[64];
if (!enif_get_string(env, value, name, ARRAY_LENGTH(name), ERL_NIF_LATIN1)) {
return false;
}
snd_ctl_elem_id_set_name(elem_id, name);
} else if (enif_is_identical(key, am_index)) {
unsigned int index;
if (!enif_get_uint(env, value, &index)) {
return false;
}
snd_ctl_elem_id_set_index(elem_id, index);
} else if (enif_is_identical(key, am_device)) {
unsigned int device;
if (!enif_get_uint(env, value, &device)) {
return false;
}
snd_ctl_elem_id_set_device(elem_id, device);
} else if (enif_is_identical(key, am_subdevice)) {
unsigned int subdevice;
if (!enif_get_uint(env, value, &subdevice)) {
return false;
}
snd_ctl_elem_id_set_subdevice(elem_id, subdevice);
} else {
return false;
}
enif_map_iterator_next(env, &iter);
}
enif_map_iterator_destroy(env, &iter);
return (snd_ctl_elem_id_get_name(elem_id)[0] != 0) || (snd_ctl_elem_id_get_numid(elem_id) != 0);
}
return false;
}
static bool alsa_ctl_nif_get_ctl_elem_value_enumerated(ErlNifEnv *env, const ERL_NIF_TERM term, snd_ctl_t *handle, snd_ctl_elem_info_t *elem_info, unsigned int* elem_value)
{
char value[1024];
if (!enif_get_string(env, term, value, sizeof(value), ERL_NIF_LATIN1)) {
return false;
}
unsigned int items = snd_ctl_elem_info_get_items(elem_info);
unsigned int item;
int ret;
for (item = 0; item < items; item++) {
snd_ctl_elem_info_set_item(elem_info, item);
ret = snd_ctl_elem_info(handle, elem_info);
if (ret >= 0 && strcmp(value, snd_ctl_elem_info_get_item_name(elem_info)) == 0) {
*elem_value = item;
return true;
}
}
return false;
}
static bool alsa_ctl_nif_get_ctl_elem_value(ErlNifEnv *env, const ERL_NIF_TERM term, snd_ctl_t *handle, snd_ctl_elem_info_t *elem_info, snd_ctl_elem_value_t *elem_value)
{
snd_ctl_elem_type_t type = snd_ctl_elem_info_get_type(elem_info);
unsigned int count = snd_ctl_elem_info_get_count(elem_info);
unsigned int i;
if (type == SND_CTL_ELEM_TYPE_BYTES) {
ErlNifBinary bin;
if (!enif_inspect_binary(env, term, &bin) || bin.size != count || count > 512 /* alsa api has a limitation of 512 bytes */) {
return false;
}
for (i = 0; i < count; i++) {
snd_ctl_elem_value_set_byte(elem_value, i, bin.data[i]);
}
return true;
}
int arity;
const ERL_NIF_TERM* values;
if (!enif_get_tuple(env, term, &arity, &values) || arity != count) {
return false;
}
for (i = 0; i < count; i++) {
switch (type) {
case SND_CTL_ELEM_TYPE_BOOLEAN: {
bool value;
if (!alsa_ctl_nif_get_bool(env, values[i], &value)) {
return false;
}
snd_ctl_elem_value_set_boolean(elem_value, i, value ? 1 : 0);
break;
}
case SND_CTL_ELEM_TYPE_INTEGER: {
long value;
if (!enif_get_long(env, values[i], &value)) {
return false;
}
long min = snd_ctl_elem_info_get_min(elem_info);
long max = snd_ctl_elem_info_get_max(elem_info);
snd_ctl_elem_value_set_integer(elem_value, i, clamp(value, min, max));
break;
}
case SND_CTL_ELEM_TYPE_INTEGER64: {
int64_t value;
if (!enif_get_int64(env, values[i], &value)) {
return false;
}
long long min = snd_ctl_elem_info_get_min64(elem_info);
long long max = snd_ctl_elem_info_get_max64(elem_info);
snd_ctl_elem_value_set_integer64(elem_value, i, clamp(value, min, max));
break;
}
case SND_CTL_ELEM_TYPE_ENUMERATED: {
unsigned int value;
if (!alsa_ctl_nif_get_ctl_elem_value_enumerated(env, values[i], handle, elem_info, &value)) {
return false;
}
snd_ctl_elem_value_set_enumerated(elem_value, i, value);
break;
}
default:
return false;
}
}
return true;
}
static ERL_NIF_TERM alsa_ctl_nif_make_elem_iface(snd_ctl_elem_iface_t interface)
{
switch (interface) {
case SND_CTL_ELEM_IFACE_CARD:
return am_card;
case SND_CTL_ELEM_IFACE_MIXER:
return am_mixer;
case SND_CTL_ELEM_IFACE_PCM:
return am_pcm;
case SND_CTL_ELEM_IFACE_RAWMIDI:
return am_rawmidi;
case SND_CTL_ELEM_IFACE_TIMER:
return am_timer;
case SND_CTL_ELEM_IFACE_SEQUENCER:
return am_sequencer;
default:
return am_other;
}
}
static ERL_NIF_TERM alsa_ctl_nif_make_ctl_elem_id(ErlNifEnv *env, const snd_ctl_elem_id_t *elem_id)
{
ERL_NIF_TERM keys[] = {
am_numid,
am_interface,
am_name,
am_index,
am_device,
am_subdevice,
};
ERL_NIF_TERM values[] = {
enif_make_uint(env, snd_ctl_elem_id_get_numid(elem_id)),
alsa_ctl_nif_make_elem_iface(snd_ctl_elem_id_get_interface(elem_id)),
enif_make_string(env, snd_ctl_elem_id_get_name(elem_id), ERL_NIF_LATIN1),
enif_make_uint(env, snd_ctl_elem_id_get_index(elem_id)),
enif_make_uint(env, snd_ctl_elem_id_get_device(elem_id)),
enif_make_uint(env, snd_ctl_elem_id_get_subdevice(elem_id)),
};
ERL_NIF_TERM result;
static_assert(ARRAY_LENGTH(keys) == ARRAY_LENGTH(values), "key/value size mismatch");
enif_make_map_from_arrays(env, keys, values, ARRAY_LENGTH(keys), &result);
return result;
}
static ERL_NIF_TERM alsa_ctl_nif_make_ctl_elem_info(ErlNifEnv *env, snd_ctl_t *handle, snd_ctl_elem_info_t *elem_info)
{
snd_ctl_elem_type_t type = snd_ctl_elem_info_get_type(elem_info);
unsigned int count = snd_ctl_elem_info_get_count(elem_info);
switch (type) {
case SND_CTL_ELEM_TYPE_BOOLEAN: {
ERL_NIF_TERM keys[] = {
am_type,
am_count,
};
ERL_NIF_TERM values[] = {
am_boolean,
enif_make_uint(env, count),
};
ERL_NIF_TERM result;
static_assert(ARRAY_LENGTH(keys) == ARRAY_LENGTH(values), "key/value size mismatch");
enif_make_map_from_arrays(env, keys, values, ARRAY_LENGTH(keys), &result);
return enif_make_tuple2(env, am_ok, result);
}
case SND_CTL_ELEM_TYPE_INTEGER: {
ERL_NIF_TERM keys[] = {
am_type,
am_count,
am_min,
am_max,
am_step,
};
ERL_NIF_TERM values[] = {
am_integer,
enif_make_uint(env, count),
enif_make_int(env, snd_ctl_elem_info_get_min(elem_info)),
enif_make_int(env, snd_ctl_elem_info_get_max(elem_info)),
enif_make_int(env, snd_ctl_elem_info_get_step(elem_info)),
};
ERL_NIF_TERM result;
static_assert(ARRAY_LENGTH(keys) == ARRAY_LENGTH(values), "key/value size mismatch");
enif_make_map_from_arrays(env, keys, values, ARRAY_LENGTH(keys), &result);
return enif_make_tuple2(env, am_ok, result);
}
case SND_CTL_ELEM_TYPE_INTEGER64: {
ERL_NIF_TERM keys[] = {
am_type,
am_count,
am_min,
am_max,
am_step,
};
ERL_NIF_TERM values[] = {
am_integer,
enif_make_uint(env, count),
enif_make_int64(env, snd_ctl_elem_info_get_min64(elem_info)),
enif_make_int64(env, snd_ctl_elem_info_get_max64(elem_info)),
enif_make_int64(env, snd_ctl_elem_info_get_step64(elem_info)),
};
ERL_NIF_TERM result;
static_assert(ARRAY_LENGTH(keys) == ARRAY_LENGTH(values), "key/value size mismatch");
enif_make_map_from_arrays(env, keys, values, ARRAY_LENGTH(keys), &result);
return enif_make_tuple2(env, am_ok, result);
}
case SND_CTL_ELEM_TYPE_ENUMERATED: {
unsigned int items_count = snd_ctl_elem_info_get_items(elem_info);
unsigned int item_index;
ERL_NIF_TERM items = enif_make_list(env, 0);
for (item_index = items_count; item_index > 0; item_index--) {
snd_ctl_elem_info_set_item(elem_info, item_index - 1);
if ((snd_ctl_elem_info(handle, elem_info)) >= 0) {
const char* item_name = snd_ctl_elem_info_get_item_name(elem_info);
items = enif_make_list_cell(env,
enif_make_string(env, item_name, ERL_NIF_LATIN1),
items);
}
}
ERL_NIF_TERM keys[] = {
am_type,
am_count,
am_items,
};
ERL_NIF_TERM values[] = {
am_enumerated,
enif_make_uint(env, count),
items,
};
ERL_NIF_TERM result;
static_assert(ARRAY_LENGTH(keys) == ARRAY_LENGTH(values), "key/value size mismatch");
enif_make_map_from_arrays(env, keys, values, ARRAY_LENGTH(keys), &result);
return enif_make_tuple2(env, am_ok, result);
}
case SND_CTL_ELEM_TYPE_BYTES: {
ERL_NIF_TERM keys[] = {
am_type,
am_count,
};
ERL_NIF_TERM values[] = {
am_bytes,
enif_make_uint(env, count),
};
ERL_NIF_TERM result;
static_assert(ARRAY_LENGTH(keys) == ARRAY_LENGTH(values), "key/value size mismatch");
enif_make_map_from_arrays(env, keys, values, ARRAY_LENGTH(keys), &result);
return enif_make_tuple2(env, am_ok, result);
}
default: {
ERL_NIF_TERM keys[] = {
am_type,
am_count,
};
ERL_NIF_TERM values[] = {
am_other,
enif_make_uint(env, count),
};
ERL_NIF_TERM result;
static_assert(ARRAY_LENGTH(keys) == ARRAY_LENGTH(values), "key/value size mismatch");
enif_make_map_from_arrays(env, keys, values, ARRAY_LENGTH(keys), &result);
return enif_make_tuple2(env, am_ok, result);
}
}
}
static ERL_NIF_TERM alsa_ctl_nif_make_ctl_elem_value(ErlNifEnv *env, snd_ctl_t *handle, snd_ctl_elem_info_t *elem_info, snd_ctl_elem_value_t *elem_value)
{
snd_ctl_elem_type_t type = snd_ctl_elem_info_get_type(elem_info);
unsigned int count = snd_ctl_elem_info_get_count(elem_info);
unsigned int i;
if (type == SND_CTL_ELEM_TYPE_BYTES) {
ERL_NIF_TERM binary;
unsigned char* binary_buffer = enif_make_new_binary(env, count, &binary);
if (binary_buffer != NULL) {
memcpy(binary_buffer, snd_ctl_elem_value_get_bytes(elem_value), count);
return enif_make_tuple2(env, am_ok, binary);
} else {
return enif_make_tuple2(env, am_error, am_enomem);
}
}
ERL_NIF_TERM values[count];
for (i = 0; i < count; i++) {
switch (type) {
case SND_CTL_ELEM_TYPE_BOOLEAN: {
values[i] = snd_ctl_elem_value_get_boolean(elem_value, i)
? am_true
: am_false;
break;
}
case SND_CTL_ELEM_TYPE_INTEGER: {
values[i] = enif_make_long(env, snd_ctl_elem_value_get_integer(elem_value, i));
break;
}
case SND_CTL_ELEM_TYPE_INTEGER64: {
values[i] = enif_make_int64(env, snd_ctl_elem_value_get_integer64(elem_value, i));
break;
}
case SND_CTL_ELEM_TYPE_ENUMERATED: {
unsigned int item = snd_ctl_elem_value_get_enumerated(elem_value, i);
int ret;
snd_ctl_elem_info_set_item(elem_info, item);
ret = snd_ctl_elem_info(handle, elem_info);
if (ret < 0) {
return enif_make_tuple2(env, am_error, libasound_error_to_erl(env, ret));
}
values[i] = enif_make_string(env, snd_ctl_elem_info_get_item_name(elem_info), ERL_NIF_LATIN1);
break;
}
default:
return enif_make_tuple2(env, am_error, am_enotsup);
}
}
return enif_make_tuple2(env, am_ok, enif_make_tuple_from_array(env, values, count));
}
/* Resources */
/* Resource: ctl */
static ErlNifResourceType* alsa_ctl_nif_resource_type;
static alsa_ctl_nif_resource_t* alsa_ctl_nif_resource_new(ErlNifPid owner, snd_ctl_t *handle)
{
alsa_ctl_nif_resource_t *resource = (alsa_ctl_nif_resource_t*) enif_alloc_resource(
alsa_ctl_nif_resource_type,
sizeof(alsa_ctl_nif_resource_t));
resource->owner = owner;
resource->handle = handle;
atomic_flag_clear(&(resource->handle_closed));
// resource->select_fds = kh_init(fd_set);
return resource;
}
// static void alsa_ctl_nif_resource_dtor(ErlNifEnv *env, void *obj)
// {
// alsa_ctl_nif_resource_t *resource = (alsa_ctl_nif_resource_t *) obj;
// kh_destroy(fd_set, resource->select_fds);
// }
// static void alsa_ctl_nif_resource_stop(ErlNifEnv *env, void *obj, int fd, int is_direct_call)
// {
// alsa_ctl_nif_resource_t *resource = (alsa_ctl_nif_resource_t *) obj;
// khint_t iter = kh_get(fd_set, resource->select_fds, fd);
// kh_del(fd_set, resource->select_fds, iter);
// if (kh_size(resource->select_fds) == 0) {
// snd_ctl_close(resource->handle);
// resource->handle = NULL;
// }
// }
static void alsa_ctl_nif_resource_owner_down(ErlNifEnv *env, void *obj, ErlNifPid* pid, ErlNifMonitor* monitor)
{
alsa_ctl_nif_resource_t *resource = (alsa_ctl_nif_resource_t *) obj;
if (!atomic_flag_test_and_set(&resource->handle_closed)) {
snd_ctl_close(resource->handle);
resource->handle = NULL;
}
}
static ErlNifResourceTypeInit alsa_ctl_nif_resource_callbacks = {
// .dtor = alsa_ctl_nif_resource_dtor,
// .stop = alsa_ctl_nif_resource_stop,
.down = alsa_ctl_nif_resource_owner_down,
};
/* API */
static ERL_NIF_TERM alsa_ctl_nif_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
int ret;
char card[1024];
if (!enif_get_string(env, argv[0], card, ARRAY_LENGTH(card), ERL_NIF_LATIN1)) {
return enif_make_badarg(env);
}
snd_ctl_t *handle;
ret = snd_ctl_open(&handle, card, SND_CTL_NONBLOCK);
if (ret != 0) {
return enif_make_tuple2(env, am_error, libasound_error_to_erl(env, ret));
}
ErlNifPid owner;
enif_self(env, &owner);
alsa_ctl_nif_resource_t *resource = alsa_ctl_nif_resource_new(owner, handle);
if (enif_monitor_process(env, resource, &owner, &resource->owner_monitor) != 0) {
enif_release_resource(resource);
snd_ctl_close(handle);
return enif_make_badarg(env);
}
ERL_NIF_TERM result = enif_make_resource(env, resource);
enif_release_resource(resource);
return enif_make_tuple2(env, am_ok, result);
}
static ERL_NIF_TERM alsa_ctl_nif_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
alsa_ctl_nif_resource_t *resource;
if (!enif_get_resource(env, argv[0], alsa_ctl_nif_resource_type, (void**) &resource)) {
return enif_make_badarg(env);
}
if (!atomic_flag_test_and_set(&resource->handle_closed)) {
snd_ctl_close(resource->handle);
resource->handle = NULL;
enif_demonitor_process(env, resource, &resource->owner_monitor);
return am_ok;
} else {
return enif_make_tuple2(env, am_error, am_closed);
}
}
// static ERL_NIF_TERM alsa_ctl_nif_read_event(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
// {
// alsa_ctl_nif_resource_t *resource;
// if (!enif_get_resource(env, argv[0], alsa_ctl_nif_resource_type, (void**) &resource)) {
// return enif_make_badarg(env);
// }
// ERL_NIF_TERM msg = argv[1];
// snd_ctl_event_t *event;
// snd_ctl_event_alloca(&event);
// int event_read = snd_ctl_read(resource->handle, event);
// if (event_read > 0) {
// return enif_make_tuple2(env, am_ok, alsa_ctl_nif_make_event(env, event));
// } else if (event_read == -EAGAIN || event_read == -EWOULDBLOCK || event_read == 0) {
// if (!alsa_ctl_nif_select(env, resource, msg)) {
// return enif_make_tuple2(env, am_error, am_eagain);
// }
// return am_wait;
// } else {
// return enif_make_tuple2(env, am_error, libasound_error_to_erl(env, event_read));
// }
// }
static ERL_NIF_TERM alsa_ctl_nif_elem_list(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int ret;
alsa_ctl_nif_resource_t *resource;
if (!enif_get_resource(env, argv[0], alsa_ctl_nif_resource_type, (void**) &resource)) {
return enif_make_badarg(env);
}
snd_ctl_elem_list_t *elem_list;
snd_ctl_elem_list_alloca(&elem_list);
ret = snd_ctl_elem_list(resource->handle, elem_list);
if (ret < 0) {
return enif_make_tuple2(env, am_error, libasound_error_to_erl(env, ret));
}
unsigned int elem_count = snd_ctl_elem_list_get_count(elem_list);
snd_ctl_elem_list_set_offset(elem_list, 0);
ret = snd_ctl_elem_list_alloc_space(elem_list, elem_count);
if (ret < 0) {
return enif_make_tuple2(env, am_error, libasound_error_to_erl(env, ret));
}
ret = snd_ctl_elem_list(resource->handle, elem_list);
if (ret < 0) {
snd_ctl_elem_list_free_space(elem_list);
return enif_make_tuple2(env, am_error, libasound_error_to_erl(env, ret));
}
unsigned int i;
ERL_NIF_TERM result = enif_make_list(env, 0);
for (i = elem_count; i > 0; i--) {
snd_ctl_elem_id_t *elem_id;
snd_ctl_elem_id_alloca(&elem_id);
snd_ctl_elem_list_get_id(elem_list, i - 1, elem_id);
result = enif_make_list_cell(env,
alsa_ctl_nif_make_ctl_elem_id(env, elem_id),
result);
}
snd_ctl_elem_list_free_space(elem_list);
return enif_make_tuple2(env, am_ok, result);
}
static ERL_NIF_TERM alsa_ctl_nif_elem_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int ret;
alsa_ctl_nif_resource_t *resource;
if (!enif_get_resource(env, argv[0], alsa_ctl_nif_resource_type, (void**) &resource)) {
return enif_make_badarg(env);
}
snd_ctl_elem_id_t *elem_id;
snd_ctl_elem_id_alloca(&elem_id);
if (!alsa_ctl_nif_get_ctl_elem_id(env, argv[1], elem_id)) {
return enif_make_badarg(env);
}
snd_ctl_elem_info_t *elem_info;
snd_ctl_elem_info_alloca(&elem_info);
snd_ctl_elem_info_set_id(elem_info, elem_id);
ret = snd_ctl_elem_info(resource->handle, elem_info);
if (ret != 0) {
return enif_make_tuple2(env, am_error, libasound_error_to_erl(env, ret));
}
return alsa_ctl_nif_make_ctl_elem_info(env, resource->handle, elem_info);
}
static ERL_NIF_TERM alsa_ctl_nif_elem_read(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int ret;
alsa_ctl_nif_resource_t *resource;
if (!enif_get_resource(env, argv[0], alsa_ctl_nif_resource_type, (void**) &resource)) {
return enif_make_badarg(env);
}
snd_ctl_elem_id_t *elem_id;
snd_ctl_elem_id_alloca(&elem_id);
if (!alsa_ctl_nif_get_ctl_elem_id(env, argv[1], elem_id)) {
return enif_make_badarg(env);
}
snd_ctl_elem_info_t *elem_info;
snd_ctl_elem_info_alloca(&elem_info);
snd_ctl_elem_info_set_id(elem_info, elem_id);
ret = snd_ctl_elem_info(resource->handle, elem_info);
if (ret != 0) {
return enif_make_tuple2(env, am_error, libasound_error_to_erl(env, ret));
}
snd_ctl_elem_value_t *elem_value;
snd_ctl_elem_value_alloca(&elem_value);
snd_ctl_elem_value_set_id(elem_value, elem_id);
ret = snd_ctl_elem_read(resource->handle, elem_value);
if (ret != 0) {
return enif_make_tuple2(env, am_error, libasound_error_to_erl(env, ret));
}
return alsa_ctl_nif_make_ctl_elem_value(env, resource->handle, elem_info, elem_value);
}
static ERL_NIF_TERM alsa_ctl_nif_elem_write(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int ret;
alsa_ctl_nif_resource_t *resource;
if (!enif_get_resource(env, argv[0], alsa_ctl_nif_resource_type, (void**) &resource)) {
return enif_make_badarg(env);
}
snd_ctl_elem_id_t *elem_id;
snd_ctl_elem_id_alloca(&elem_id);
if (!alsa_ctl_nif_get_ctl_elem_id(env, argv[1], elem_id)) {
return enif_make_badarg(env);
}
snd_ctl_elem_info_t *elem_info;
snd_ctl_elem_info_alloca(&elem_info);
snd_ctl_elem_info_set_id(elem_info, elem_id);
ret = snd_ctl_elem_info(resource->handle, elem_info);
if (ret != 0) {
return enif_make_tuple2(env, am_error, libasound_error_to_erl(env, ret));
}
snd_ctl_elem_value_t *elem_value;
snd_ctl_elem_value_alloca(&elem_value);
snd_ctl_elem_value_set_id(elem_value, elem_id);
if (!alsa_ctl_nif_get_ctl_elem_value(env, argv[2], resource->handle, elem_info, elem_value)) {
return enif_make_badarg(env);
}
ret = snd_ctl_elem_write(resource->handle, elem_value);
if (ret != 0) {
return enif_make_tuple2(env, am_error, libasound_error_to_erl(env, ret));
}
return am_ok;
}
/* Initialization */
static int alsa_ctl_nif_on_load(ErlNifEnv *env, void** priv_data, ERL_NIF_TERM load_info)
{
// Atoms
am_ok = enif_make_atom(env, "ok");
am_error = enif_make_atom(env, "error");
am_wait = enif_make_atom(env, "wait");
am_undefined = enif_make_atom(env, "undefined");
am_other = enif_make_atom(env, "other");
am_true = enif_make_atom(env, "true");
am_false = enif_make_atom(env, "false");
am_closed = enif_make_atom(env, "closed");
am_enoent = enif_make_atom(env, "enoent");
am_enomem = enif_make_atom(env, "enomem");
am_enotsup = enif_make_atom(env, "enotsup");
am_eagain = enif_make_atom(env, "eagain");
am_type = enif_make_atom(env, "type");
am_boolean = enif_make_atom(env, "boolean");
am_integer = enif_make_atom(env, "integer");
am_enumerated = enif_make_atom(env, "enumerated");
am_bytes = enif_make_atom(env, "bytes");
am_count = enif_make_atom(env, "count");
am_min = enif_make_atom(env, "min");
am_max = enif_make_atom(env, "max");
am_step = enif_make_atom(env, "step");
am_items = enif_make_atom(env, "items");
am_card = enif_make_atom(env, "card");
am_mixer = enif_make_atom(env, "mixer");
am_pcm = enif_make_atom(env, "pcm");
am_rawmidi = enif_make_atom(env, "rawmidi");
am_timer = enif_make_atom(env, "timer");
am_sequencer = enif_make_atom(env, "sequencer");
am_numid = enif_make_atom(env, "numid");
am_interface = enif_make_atom(env, "interface");
am_name = enif_make_atom(env, "name");
am_index = enif_make_atom(env, "index");
am_device = enif_make_atom(env, "device");
am_subdevice = enif_make_atom(env, "subdevice");
// Resources
alsa_ctl_nif_resource_type = enif_open_resource_type_x(env,
"ctl",
&alsa_ctl_nif_resource_callbacks,
ERL_NIF_RT_CREATE,
NULL);
*priv_data = NULL;
return 0;
}
static void alsa_ctl_nif_on_unload(ErlNifEnv *env, void* priv_data)
{
}
static int alsa_ctl_nif_on_upgrade(ErlNifEnv *env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info)
{
if (*old_priv_data != NULL) {
return -1; /* Don't know how to do that */
}
if (*priv_data != NULL) {
return -1; /* Don't know how to do that */
}
if (alsa_ctl_nif_on_load(env, priv_data, load_info)) {
return -1;
}
return 0;
}
static ErlNifFunc nif_funcs[] = {
{"open_nif", 1, alsa_ctl_nif_open, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"close_nif", 1, alsa_ctl_nif_close},
// {"read_event_nif", 2, alsa_ctl_nif_read_event},
{"elem_list_nif", 1, alsa_ctl_nif_elem_list, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"elem_info_nif", 2, alsa_ctl_nif_elem_info, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"elem_read_nif", 2, alsa_ctl_nif_elem_read, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"elem_write_nif", 3, alsa_ctl_nif_elem_write, ERL_NIF_DIRTY_JOB_IO_BOUND},
};
ERL_NIF_INIT(alsa_ctl, nif_funcs, alsa_ctl_nif_on_load, NULL, alsa_ctl_nif_on_upgrade, alsa_ctl_nif_on_unload);