//ctl_roar.c: /* * Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2011-2012 * * This file is part of libroar a part of RoarAudio, * a cross-platform sound system for both, home and professional use. * See README for details. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 * as published by the Free Software Foundation. * * libroar is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * NOTE for everyone want's to change something and send patches: * read README and HACKING! There a addition information on * the license of this document you need to read before you send * any patches. * * NOTE for uses of non-GPL (LGPL,...) software using libesd, libartsc * or libpulse*: * The libs libroaresd, libroararts and libroarpulse link this lib * and are therefore GPL. Because of this it may be illigal to use * them with any software that uses libesd, libartsc or libpulse*. */ //#define DEBUG #include "roar.h" #include #ifdef DEBUG static struct roar_vio_calls DEBUG_FH; #endif static void roar_plugin_close(snd_ctl_ext_t * ext) { struct roar_alsa_ctl * self = ext->private_data; roar_disconnect(&(self->roar.con)); free(self); } static int roar_plugin_elem_count(snd_ctl_ext_t *ext) { struct roar_alsa_ctl * self = ext->private_data; int ret = 0; for (; self->streams[ret].id != -1; ret++); return ret; } static int roar_plugin_elem_list(snd_ctl_ext_t *ext, unsigned int offset, snd_ctl_elem_id_t *id) { struct roar_alsa_ctl * self = ext->private_data; snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); snd_ctl_elem_id_set_name(id, self->streams[offset].name); return 0; } // ALSA is strange here, this function makes keys (stream ID) from non-unique stream names... static snd_ctl_ext_key_t roar_plugin_find_elem(snd_ctl_ext_t *ext, const snd_ctl_elem_id_t *id) { struct roar_alsa_ctl * self = ext->private_data; // int numid = snd_ctl_elem_id_get_numid(id); const char * name; int i; // ROAR_DBG("roar_plugin_find_elem(ext=%p, id=%p): numid=%i", ext, id, numid); // return self->streams[numid].id; name = snd_ctl_elem_id_get_name(id); for (i = 0; i < MAX_STREAMS; i++) { if ( !strcmp(self->streams[i].name, name) ) { return self->streams[i].id; } } return SND_CTL_EXT_KEY_NOT_FOUND; } static int roar_plugin_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, int *type, unsigned int *acc, unsigned int *count) { struct roar_alsa_ctl * self = ext->private_data; int i; *type = SND_CTL_ELEM_TYPE_INTEGER; *acc = SND_CTL_EXT_ACCESS_READWRITE; for (i = 0; i < MAX_STREAMS && self->streams[i].id != (int)key; i++) if ( i == MAX_STREAMS ) return -EIO; *count = self->streams[i].channels; return 0; } static int roar_plugin_get_integer_info(snd_ctl_ext_t *ext ATTRIBUTE_UNUSED, snd_ctl_ext_key_t key ATTRIBUTE_UNUSED, long *imin, long *imax, long *istep) { *istep = 0; *imin = 0; *imax = 65535; return 0; } static int roar_plugin_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long *value) { struct roar_alsa_ctl * self = ext->private_data; struct roar_mixer_settings mixer; int channels; int i; for (i = 0; i < MAX_STREAMS && self->streams[i].id != (int)key; i++) if ( i == MAX_STREAMS ) return -EIO; roar_err_clear_all(); if ( roar_get_vol(&(self->roar.con), key, &mixer, &channels) == -1 ) { roar_err_update(); return -errno; } if ( channels != (int)self->streams[i].channels ) return -EIO; for (i = 0; i < channels; i++) value[i] = ((unsigned long int)mixer.mixer[i] * 65535LU) / mixer.scale; return 0; } static int roar_plugin_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long *value) { struct roar_alsa_ctl * self = ext->private_data; struct roar_mixer_settings mixer; int sid; int i; ROAR_DBG("roar_plugin_write_integer(ext=%p, key=%i, value=%p) = ?", ext, (int)key, value); for (sid = 0; sid < MAX_STREAMS && self->streams[sid].id != (int)key; sid++) if ( sid == MAX_STREAMS ) return -EIO; mixer.scale = 65535; mixer.rpg_mul = 1; mixer.rpg_div = 1; for (i = 0; i < (int)self->streams[i].channels; i++) mixer.mixer[i] = value[i]; roar_err_clear_all(); if ( roar_set_vol(&(self->roar.con), key, &mixer, self->streams[sid].channels, ROAR_SET_VOL_ALL) == -1 ) { roar_err_update(); return -errno; } return 0; } static int roar_plugin_read_event(snd_ctl_ext_t *ext ATTRIBUTE_UNUSED, snd_ctl_elem_id_t *id ATTRIBUTE_UNUSED, unsigned int *event_mask ATTRIBUTE_UNUSED) { return -EAGAIN; } static void roar_plugin__handle_stream(struct roar_alsa_ctl * self, int id) { struct roar_mixerstream * cs = NULL; struct roar_stream s; char name[1024]; int i; if ( roar_get_stream(&(self->roar.con), &s, id) == -1 ) return; switch (s.dir) { case ROAR_DIR_OUTPUT: case ROAR_DIR_MIXING: case ROAR_DIR_BRIDGE: if ( roar_stream_get_name(&(self->roar.con), &s, name, sizeof(name)) != 0 ) snprintf(name, sizeof(name), "Stream %i", id); break; default: return; break; } switch (s.dir) { case ROAR_DIR_MIXING: case ROAR_DIR_BRIDGE: if ( strstr(name, "MIDI") != NULL || strstr(name, "Light") != NULL ) return; break; } for (i = 0; cs == NULL && i < (int)(sizeof(self->streams)/sizeof(*self->streams)); i++) if (self->streams[i].id == -1 ) cs = &(self->streams[i]); if ( cs == NULL ) return; cs->id = id; cs->channels = s.info.channels; strncpy(cs->name, name, sizeof(cs->name) - 1); } static int roar_plugin__find_streams(struct roar_alsa_ctl * self) { int id[ROAR_STREAMS_MAX]; int num; int i; for (i = 0; i < (int)(sizeof(self->streams)/sizeof(*self->streams)); i++) self->streams[i].id = -1; roar_err_clear_all(); if ( (num = roar_list_streams(&(self->roar.con), id, ROAR_STREAMS_MAX)) == -1 ) { roar_err_update(); return -errno; } for (i = 0; i < num; i++) roar_plugin__handle_stream(self, i); return 0; } static snd_ctl_ext_callback_t roar_ext_callback = { .close = roar_plugin_close, .elem_count = roar_plugin_elem_count, .elem_list = roar_plugin_elem_list, .find_elem = roar_plugin_find_elem, .get_attribute = roar_plugin_get_attribute, .get_integer_info = roar_plugin_get_integer_info, .read_integer = roar_plugin_read_integer, .write_integer = roar_plugin_write_integer, .read_event = roar_plugin_read_event }; SND_CTL_PLUGIN_DEFINE_FUNC(roar) { struct roar_alsa_ctl * self; snd_config_iterator_t i, next; snd_config_t * n; const char * para; const char * server = NULL; int error; const char * ext_id = "ROAR"; const char * ext_name = "RoarAudio"; (void)root; #ifdef DEBUG roar_vio_open_dstr_simple(&DEBUG_FH, "/tmp/alsamixer-roar.log", O_WRONLY|O_CREAT|O_APPEND); roar_debug_set_stderr_vio(&DEBUG_FH); roar_debug_set_stderr_mode(ROAR_DEBUG_MODE_VIO); #endif snd_config_for_each(i, next, conf) { n = snd_config_iterator_entry(i); if ( snd_config_get_id(n, ¶) < 0 ) continue; if ( !strcmp(para, "type") || !strcmp(para, "comment") || !strcmp(para, "hint") ) continue; if ( !strcmp(para, "server") ) { if (snd_config_get_string(n, &server) < 0) { return -EINVAL; } } else { return -EINVAL; } } if ( (self = malloc(sizeof(struct roar_alsa_ctl))) == NULL ) return -errno; memset(self, 0, sizeof(struct roar_alsa_ctl)); errno = ENOSYS; if ( roar_simple_connect(&(self->roar.con), server, "ALSA Plugin") == -1 ) { free(self); return -errno; } if ( (error = roar_plugin__find_streams(self)) < 0 ) { roar_disconnect(&(self->roar.con)); free(self); return error; } self->ext.version = SND_CTL_EXT_VERSION; self->ext.card_idx = 0; /* FIXME */ strncpy(self->ext.id, ext_id, sizeof(self->ext.id) - 1); strcpy(self->ext.driver, "OSS-Emulation"); strncpy(self->ext.name, ext_name, sizeof(self->ext.name) - 1); strncpy(self->ext.longname, ext_name, sizeof(self->ext.longname) - 1); strncpy(self->ext.mixername, ext_name, sizeof(self->ext.mixername) - 1); self->ext.poll_fd = -1; self->ext.callback = &roar_ext_callback; self->ext.private_data = self; if ( (error = snd_ctl_ext_create(&(self->ext), name, mode)) < 0 ) { roar_disconnect(&(self->roar.con)); free(self); return error; } *handlep = self->ext.handle; return 0; } SND_CTL_PLUGIN_SYMBOL(roar);