From aa093b46fe2a7192f0e212c147a35e7f8ec64512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Thu, 3 Nov 2005 11:42:02 +0000 Subject: an external to control your alsa-soundcard (simple things like controlling the mixer (if your card has one, but also others like SyncMaster,...) this is a port of Kysela's "amixer" command-line tool svn path=/trunk/externals/iem/amixer/; revision=3826 --- amixer.c | 743 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 743 insertions(+) create mode 100644 amixer.c (limited to 'amixer.c') diff --git a/amixer.c b/amixer.c new file mode 100644 index 0000000..b159664 --- /dev/null +++ b/amixer.c @@ -0,0 +1,743 @@ +/****************************************************** + * + * amixer - ALSA sequencer connection manager + * Copyright (C) 1999-2000 Jaroslav Kysela + * + * + * + * ported from alsa-1.0.9a to pd by: + * copyleft (c) 2005 IOhannes m zmölnig + * + * forum::für::umläute + * + * institute of electronic music and acoustics (iem) + * + * + ****************************************************** + * + * license: GNU General Public License v.2 + * + ******************************************************/ +#include "m_pd.h" + +/* amixer :: allows control of the mixer (controls) for the ALSA soundcard driver */ + +#ifdef HAVE_ALSA +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +#define LEVEL_BASIC (1<<0) +#define LEVEL_INACTIVE (1<<1) +#define LEVEL_ID (1<<2) + +#endif /* ALSA */ + + +static t_class *amixer_class; + +typedef struct _amixer +{ + t_object x_obj; + t_outlet*x_error; + + char card[64]; +} t_amixer; + + +#ifdef HAVE_ALSA + +static const char *control_iface(snd_ctl_elem_id_t *id) +{ + return snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(id)); +} + +static const char *control_type(snd_ctl_elem_info_t *info) +{ + return snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(info)); +} + +static const char *control_access(snd_ctl_elem_info_t *info) +{ + static char result[10]; + char *res = result; + + *res++ = snd_ctl_elem_info_is_readable(info) ? 'r' : '-'; + *res++ = snd_ctl_elem_info_is_writable(info) ? 'w' : '-'; + *res++ = snd_ctl_elem_info_is_inactive(info) ? 'i' : '-'; + *res++ = snd_ctl_elem_info_is_volatile(info) ? 'v' : '-'; + *res++ = snd_ctl_elem_info_is_locked(info) ? 'l' : '-'; + *res++ = '\0'; + return result; +} + + +static int check_range(int val, int min, int max) +{ + if (val < min) + return min; + if (val > max) + return max; + return val; +} + +#if 0 +static int convert_range(int val, int omin, int omax, int nmin, int nmax) +{ + int orange = omax - omin, nrange = nmax - nmin; + + if (orange == 0) + return 0; + return rint((((double)nrange * ((double)val - (double)omin)) + ((double)orange / 2.0)) / ((double)orange + (double)nmin)); +} +#endif + +#if 0 +static int convert_db_range(int val, int omin, int omax, int nmin, int nmax) +{ + int orange = omax - omin, nrange = nmax - nmin; + + if (orange == 0) + return 0; + return rint((((double)nrange * ((double)val - (double)omin)) + ((double)orange / 2.0)) / (double)orange + (double)nmin); +} +#endif + +/* Fuction to convert from volume to percentage. val = volume */ + +static int convert_prange(int val, int min, int max) +{ + int range = max - min; + int tmp; + + if (range == 0) + return 0; + val -= min; + tmp = rint((double)val/(double)range * 100); + return tmp; +} + +/* Function to convert from percentage to volume. val = percentage */ + +static int convert_prange1(int val, int min, int max) +{ + int range = max - min; + int tmp; + + if (range == 0) + return 0; + + tmp = rint((double)range * ((double)val*.01)) + min; + return tmp; +} + +static const char *get_percent(int val, int min, int max) +{ + static char str[32]; + int p; + + p = convert_prange(val, min, max); + sprintf(str, "%i [%i%%]", val, p); + return str; +} + +#if 0 +static const char *get_percent1(int val, int min, int max, int min_dB, int max_dB) +{ + static char str[32]; + int p, db; + + p = convert_prange(val, min, max); + db = convert_db_range(val, min, max, min_dB, max_dB); + sprintf(str, "%i [%i%%] [%i.%02idB]", val, p, db / 100, abs(db % 100)); + return str; +} +#endif + +static long get_integer(char **ptr, long min, long max) +{ + int tmp, tmp1, tmp2; + + if (**ptr == ':') + (*ptr)++; + if (**ptr == '\0' || (!isdigit(**ptr) && **ptr != '-')) + return min; + tmp = strtol(*ptr, ptr, 10); + tmp1 = tmp; + tmp2 = 0; + if (**ptr == '.') { + (*ptr)++; + tmp2 = strtol(*ptr, ptr, 10); + } + if (**ptr == '%') { + tmp1 = convert_prange1(tmp, min, max); + (*ptr)++; + } + tmp1 = check_range(tmp1, min, max); + if (**ptr == ',') + (*ptr)++; + return tmp1; +} + +static long get_integer64(char **ptr, long long min, long long max) +{ + long long tmp, tmp1, tmp2; + + if (**ptr == ':') + (*ptr)++; + if (**ptr == '\0' || (!isdigit(**ptr) && **ptr != '-')) + return min; + tmp = strtol(*ptr, ptr, 10); + tmp1 = tmp; + tmp2 = 0; + if (**ptr == '.') { + (*ptr)++; + tmp2 = strtol(*ptr, ptr, 10); + } + if (**ptr == '%') { + tmp1 = convert_prange1(tmp, min, max); + (*ptr)++; + } + tmp1 = check_range(tmp1, min, max); + if (**ptr == ',') + (*ptr)++; + return tmp1; +} + +static int get_volume_simple(char **ptr, int min, int max, int orig) +{ + int tmp, tmp1, tmp2; + + if (**ptr == ':') + (*ptr)++; + if (**ptr == '\0' || (!isdigit(**ptr) && **ptr != '-')) + return min; + tmp = atoi(*ptr); + if (**ptr == '-') + (*ptr)++; + while (isdigit(**ptr)) + (*ptr)++; + tmp1 = tmp; + tmp2 = 0; + if (**ptr == '.') { + (*ptr)++; + tmp2 = atoi(*ptr); + while (isdigit(**ptr)) + (*ptr)++; + } + if (**ptr == '%') { + tmp1 = convert_prange1(tmp, min, max); + (*ptr)++; + } + if (**ptr == '+') { + tmp1 = orig + tmp1; + (*ptr)++; + } else if (**ptr == '-') { + tmp1 = orig - tmp1; + (*ptr)++; + } + tmp1 = check_range(tmp1, min, max); + if (**ptr == ',') + (*ptr)++; + return tmp1; +} + +static int get_bool_simple(char **ptr, char *str, int invert, int orig) +{ + if (**ptr == ':') + (*ptr)++; + if (!strncasecmp(*ptr, str, strlen(str))) { + orig = 1 ^ (invert ? 1 : 0); + while (**ptr != '\0' && **ptr != ',' && **ptr != ':') + (*ptr)++; + } + if (**ptr == ',' || **ptr == ':') + (*ptr)++; + return orig; +} + +static int parse_control_id(const char *str, snd_ctl_elem_id_t *id) +{ + int c, size; + char *ptr; + + while (*str == ' ' || *str == '\t') + str++; + if (!(*str)) + return -EINVAL; + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); /* default */ + while (*str) { + if (!strncasecmp(str, "numid=", 6)) { + str += 6; + snd_ctl_elem_id_set_numid(id, atoi(str)); + while (isdigit(*str)) + str++; + } else if (!strncasecmp(str, "iface=", 6)) { + str += 6; + if (!strncasecmp(str, "card", 4)) { + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD); + str += 4; + } else if (!strncasecmp(str, "mixer", 5)) { + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + str += 5; + } else if (!strncasecmp(str, "pcm", 3)) { + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM); + str += 3; + } else if (!strncasecmp(str, "rawmidi", 7)) { + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_RAWMIDI); + str += 7; + } else if (!strncasecmp(str, "timer", 5)) { + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_TIMER); + str += 5; + } else if (!strncasecmp(str, "sequencer", 9)) { + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_SEQUENCER); + str += 9; + } else { + return -EINVAL; + } + } else if (!strncasecmp(str, "name=", 5)) { + char buf[64]; + str += 5; + ptr = buf; + size = 0; + if (*str == '\'' || *str == '\"') { + c = *str++; + while (*str && *str != c) { + if (size < (int)sizeof(buf)) { + *ptr++ = *str; + size++; + } + str++; + } + if (*str == c) + str++; + } else { + while (*str && *str != ',') { + if (size < (int)sizeof(buf)) { + *ptr++ = *str; + size++; + } + str++; + } + *ptr = '\0'; + } + snd_ctl_elem_id_set_name(id, buf); + } else if (!strncasecmp(str, "index=", 6)) { + str += 6; + snd_ctl_elem_id_set_index(id, atoi(str)); + while (isdigit(*str)) + str++; + } else if (!strncasecmp(str, "device=", 7)) { + str += 7; + snd_ctl_elem_id_set_device(id, atoi(str)); + while (isdigit(*str)) + str++; + } else if (!strncasecmp(str, "subdevice=", 10)) { + str += 10; + snd_ctl_elem_id_set_subdevice(id, atoi(str)); + while (isdigit(*str)) + str++; + } + if (*str == ',') { + str++; + } else { + if (*str) + return -EINVAL; + } + } + return 0; +} + +static int parse_simple_id(const char *str, snd_mixer_selem_id_t *sid) +{ + int c, size; + char buf[128]; + char *ptr = buf; + + while (*str == ' ' || *str == '\t') + str++; + if (!(*str)) + return -EINVAL; + size = 1; /* for '\0' */ + if (*str != '"' && *str != '\'') { + while (*str && *str != ',') { + if (size < (int)sizeof(buf)) { + *ptr++ = *str; + size++; + } + str++; + } + } else { + c = *str++; + while (*str && *str != c) { + if (size < (int)sizeof(buf)) { + *ptr++ = *str; + size++; + } + str++; + } + if (*str == c) + str++; + } + if (*str == '\0') { + snd_mixer_selem_id_set_index(sid, 0); + *ptr = 0; + goto _set; + } + if (*str != ',') + return -EINVAL; + *ptr = 0; /* terminate the string */ + str++; + if (!isdigit(*str)) + return -EINVAL; + snd_mixer_selem_id_set_index(sid, atoi(str)); + _set: + snd_mixer_selem_id_set_name(sid, buf); + return 0; +} +static void show_control_id(snd_ctl_elem_id_t *id) +{ + unsigned int index, device, subdevice; + printf("numid=%u,iface=%s,name='%s'", + snd_ctl_elem_id_get_numid(id), + control_iface(id), + snd_ctl_elem_id_get_name(id)); + index = snd_ctl_elem_id_get_index(id); + device = snd_ctl_elem_id_get_device(id); + subdevice = snd_ctl_elem_id_get_subdevice(id); + if (index) + printf(",index=%i", index); + if (device) + printf(",device=%i", device); + if (subdevice) + printf(",subdevice=%i", subdevice); +} + +static int show_control(const char* card, const char *space, snd_hctl_elem_t *elem, + int level) +{ + int err; + unsigned int item, idx; + unsigned int count; + snd_ctl_elem_type_t type; + snd_ctl_elem_id_t *id; + snd_ctl_elem_info_t *info; + snd_ctl_elem_value_t *control; + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_info_alloca(&info); + snd_ctl_elem_value_alloca(&control); + if ((err = snd_hctl_elem_info(elem, info)) < 0) { + error("Control %s snd_hctl_elem_info error: %s\n", card, snd_strerror(err)); + return err; + } + if (level & LEVEL_ID) { + snd_hctl_elem_get_id(elem, id); + show_control_id(id); + printf("\n"); + } + count = snd_ctl_elem_info_get_count(info); + type = snd_ctl_elem_info_get_type(info); + printf("%s; type=%s,access=%s,values=%i", space, control_type(info), control_access(info), count); + switch (type) { + case SND_CTL_ELEM_TYPE_INTEGER: + printf(",min=%li,max=%li,step=%li\n", + snd_ctl_elem_info_get_min(info), + snd_ctl_elem_info_get_max(info), + snd_ctl_elem_info_get_step(info)); + break; + case SND_CTL_ELEM_TYPE_INTEGER64: + printf(",min=%Li,max=%Li,step=%Li\n", + snd_ctl_elem_info_get_min64(info), + snd_ctl_elem_info_get_max64(info), + snd_ctl_elem_info_get_step64(info)); + break; + case SND_CTL_ELEM_TYPE_ENUMERATED: + { + unsigned int items = snd_ctl_elem_info_get_items(info); + printf(",items=%u\n", items); + for (item = 0; item < items; item++) { + snd_ctl_elem_info_set_item(info, item); + if ((err = snd_hctl_elem_info(elem, info)) < 0) { + error("Control %s element info error: %s\n", card, snd_strerror(err)); + return err; + } + printf("%s; Item #%u '%s'\n", space, item, snd_ctl_elem_info_get_item_name(info)); + } + break; + } + default: + printf("\n"); + break; + } + if (level & LEVEL_BASIC) { + if ((err = snd_hctl_elem_read(elem, control)) < 0) { + error("Control %s element read error: %s\n", card, snd_strerror(err)); + return err; + } + printf("%s: values=", space); + for (idx = 0; idx < count; idx++) { + if (idx > 0) + printf(","); + switch (type) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + printf("%s", snd_ctl_elem_value_get_boolean(control, idx) ? "on" : "off"); + break; + case SND_CTL_ELEM_TYPE_INTEGER: + printf("%li", snd_ctl_elem_value_get_integer(control, idx)); + break; + case SND_CTL_ELEM_TYPE_INTEGER64: + printf("%Li", snd_ctl_elem_value_get_integer64(control, idx)); + break; + case SND_CTL_ELEM_TYPE_ENUMERATED: + printf("%u", snd_ctl_elem_value_get_enumerated(control, idx)); + break; + case SND_CTL_ELEM_TYPE_BYTES: + printf("0x%02x", snd_ctl_elem_value_get_byte(control, idx)); + break; + default: + printf("?"); + break; + } + } + printf("\n"); + } + return 0; +} + + + +static void amixer_bang(t_amixer *x) +{ + + + +} +static int amixer_control(t_amixer *x, int argc, t_atom *argv, int roflag) +{ + int err; + snd_ctl_t *handle; + snd_ctl_elem_info_t *info; + snd_ctl_elem_id_t *id; + snd_ctl_elem_value_t *control; + char *ptr; + unsigned int idx, count; + long tmp; + snd_ctl_elem_type_t type; + snd_ctl_elem_info_alloca(&info); + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_value_alloca(&control); + + if (argc < 1) { + error("Specify a full control identifier: [[iface=,][name='name',][index=,][device=,][subdevice=]]|[numid=]\n"); + return -EINVAL; + } + if(A_FLOAT==argv->a_type){ + snd_ctl_elem_id_set_numid(id, atom_getint(argv)); + if(0) + { + error("Wrong control identifier: %d\n", atom_getint(argv)); + return -EINVAL; + } + } else { + if (parse_control_id(atom_getsymbol(argv)->s_name, id)) { + error("Wrong control identifier: %s\n", atom_getsymbol(argv)->s_name); + return -EINVAL; + } + } + if ((err = snd_ctl_open(&handle, x->card, 0)) < 0) { + error("Control %s open error: %s\n", x->card, snd_strerror(err)); + return err; + } + snd_ctl_elem_info_set_id(info, id); + if ((err = snd_ctl_elem_info(handle, info)) < 0) { + error("Control %s cinfo error: %s\n", x->card, snd_strerror(err)); + return err; + } + + snd_ctl_elem_info_get_id(info, id); /* FIXME: Remove it when hctl find works ok !!! */ + type = snd_ctl_elem_info_get_type(info); + count = snd_ctl_elem_info_get_count(info); + snd_ctl_elem_value_set_id(control, id); + + if (!roflag) { + t_float atom_float = atom_getfloat(argv+1); + int atom_isfloat = (A_FLOAT==(argv+1)->a_type); + post("now setting"); + ptr = atom_getsymbol(argv+1)->s_name; + for (idx = 0; idx < count && idx < 128 && ptr && *ptr; idx++) { + switch (type) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + tmp = 0; + if(atom_isfloat){ + tmp=(atom_float>0)?1:0; + } else if (!strncasecmp(ptr, "on", 2) || !strncasecmp(ptr, "up", 2)) { + tmp = 1; + ptr += 2; + } else if (!strncasecmp(ptr, "yes", 3)) { + tmp = 1; + ptr += 3; + } else if (!strncasecmp(ptr, "toggle", 6)) { + tmp = snd_ctl_elem_value_get_boolean(control, idx); + tmp = tmp > 0 ? 0 : 1; + ptr += 6; + } else if (isdigit(*ptr)) { + tmp = atoi(ptr) > 0 ? 1 : 0; + while (isdigit(*ptr)) + ptr++; + } else { + while (*ptr && *ptr != ',') + ptr++; + } + snd_ctl_elem_value_set_boolean(control, idx, tmp); + break; + case SND_CTL_ELEM_TYPE_INTEGER: + tmp = atom_isfloat?((int)atom_float):get_integer(&ptr, + snd_ctl_elem_info_get_min(info), + snd_ctl_elem_info_get_max(info)); + snd_ctl_elem_value_set_integer(control, idx, tmp); + break; + case SND_CTL_ELEM_TYPE_INTEGER64: + tmp = atom_isfloat?((int)atom_float):get_integer64(&ptr, + snd_ctl_elem_info_get_min64(info), + snd_ctl_elem_info_get_max64(info)); + snd_ctl_elem_value_set_integer64(control, idx, tmp); + break; + case SND_CTL_ELEM_TYPE_ENUMERATED: + tmp = atom_isfloat?((int)atom_float):get_integer(&ptr, 0, snd_ctl_elem_info_get_items(info) - 1); + snd_ctl_elem_value_set_enumerated(control, idx, tmp); + break; + case SND_CTL_ELEM_TYPE_BYTES: + tmp = atom_isfloat?((int)atom_float):get_integer(&ptr, 0, 255); + snd_ctl_elem_value_set_byte(control, idx, tmp); + break; + default: + break; + } + if (!strchr(atom_getsymbol(argv+1)->s_name, ',')) + ptr = atom_getsymbol(argv+1)->s_name; + else if (*ptr == ',') + ptr++; + } + if ((err = snd_ctl_elem_write(handle, control)) < 0) { + error("Control %s element write error: %s\n", x->card, snd_strerror(err)); + return err; + } + } + snd_ctl_close(handle); + + + if (1) { + snd_hctl_t *hctl; + snd_hctl_elem_t *elem; + if ((err = snd_hctl_open(&hctl, x->card, 0)) < 0) { + error("Control %s open error: %s\n", x->card, snd_strerror(err)); + return err; + } + if ((err = snd_hctl_load(hctl)) < 0) { + error("Control %s load error: %s\n", x->card, snd_strerror(err)); + return err; + } + elem = snd_hctl_find_elem(hctl, id); + if (elem) + show_control(x->card, " ", elem, LEVEL_BASIC | LEVEL_ID); + else + printf("Could not find the specified element\n"); + snd_hctl_close(hctl); + } +} +static void amixer_cget(t_amixer *x, t_symbol *s, int argc, t_atom *argv) +{ + amixer_control(x, argc, argv, 1); +} +static void amixer_cset(t_amixer *x, t_symbol *s, int argc, t_atom *argv) +{ + amixer_control(x, argc, argv, 0); +} + +static void amixer_sget(t_amixer *x, t_symbol *s, int argc, t_atom *argv) +{ +} +static void amixer_sset(t_amixer *x, t_symbol *s, int argc, t_atom *argv) +{ +} +static void amixer_card(t_amixer *x, t_symbol *s, int argc, t_atom *argv) +{ + if(1==argc){ + int id=-1; + if(A_FLOAT==argv->a_type)id=atom_getint(argv); + else id=snd_card_get_index(atom_getsymbol(argv)->s_name); + if (id >= 0 && id < 32) + sprintf(x->card, "hw:%i", id); + else { + pd_error(x, "invalid card %d", id); + return; + } + + + } else error("amixer: can only be integer of symbol"); +} + +#endif /* ALSA */ + +static void amixer_free(t_amixer *x){ +#ifdef HAVE_ALSA + + +#endif /* ALSA */ +} + + +static void *amixer_new(void) +{ + t_amixer *x = (t_amixer *)pd_new(amixer_class); + outlet_new(&x->x_obj, 0); + x->x_error=outlet_new(&x->x_obj, 0); + +#ifndef HAVE_ALSA + error("amixer: compiled without ALSA-suppor !!"); + error("amixer: no functionality enabled!"); +#else + sprintf(x->card, "default"); + +#endif /* !ALSA */ + + return (x); +} + + +void amixer_setup(void) +{ + post("amixer: ALSA soundcard control"); + post(" Copyright (C) 1999-2000 Jaroslav Kysela"); + post(" ported to pure-data by IOhannes m zmölnig 2005"); + post(" institute of electronic music and acoustics (iem)"); + post(" published under the GNU General Public License version 2"); +#ifdef AMIXER_VERSION + startpost(" version:"AMIXER_VERSION); +#endif + post("\tcompiled: "__DATE__""); + + amixer_class = class_new(gensym("amixer"), (t_newmethod)amixer_new, (t_method)amixer_free, + sizeof(t_amixer), 0, 0); +#ifdef HAVE_ALSA + class_addmethod(amixer_class, (t_method)amixer_card,gensym("card"), A_GIMME, 0); + class_addmethod(amixer_class, (t_method)amixer_cget,gensym("get"), A_GIMME, 0); + class_addmethod(amixer_class, (t_method)amixer_cset,gensym("set"), A_GIMME, 0); + class_addmethod(amixer_class, (t_method)amixer_cget,gensym("cget"), A_GIMME, 0); + class_addmethod(amixer_class, (t_method)amixer_cset,gensym("cset"), A_GIMME, 0); + class_addmethod(amixer_class, (t_method)amixer_sget,gensym("sget"), A_GIMME, 0); + class_addmethod(amixer_class, (t_method)amixer_sset,gensym("sset"), A_GIMME, 0); + + // class_addmethod(amixer_class, (t_method)amixer_listdevices,gensym(""), A_DEFSYM, 0); + class_addbang(amixer_class, (t_method)amixer_bang); +#endif /* ALSA */ +} + -- cgit v1.2.1