diff options
Diffstat (limited to 'src/mixer.c')
-rw-r--r-- | src/mixer.c | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/src/mixer.c b/src/mixer.c new file mode 100644 index 0000000..317f12c --- /dev/null +++ b/src/mixer.c @@ -0,0 +1,264 @@ +#include <alsa/asoundlib.h> +#include <sndio.h> +#include <stdbool.h> +#include <stdio.h> +#include "util/sysex.h" +#include "util/util.h" + +struct _snd_mixer_elem { + snd_mixer_elem_t *next; + char name[SYSEX_NAMELEN]; + unsigned int index, vol; +}; + +struct _snd_mixer { + struct mio_hdl *hdl; + snd_mixer_elem_t *controls; +}; + +#define MIXER_MAX_CHANNELS 16 + +static bool +onsysex(unsigned char *buf, unsigned len, snd_mixer_elem_t controls[MIXER_MAX_CHANNELS + 1]) +{ + if (len < SYSEX_SIZE(empty)) + return false; + + union { + struct sysex x; + unsigned char buf[sizeof(struct sysex)]; + } u; + + memcpy(u.buf, buf, len); + + if (u.x.type == SYSEX_TYPE_RT && u.x.id0 == SYSEX_CONTROL && u.x.id1 == SYSEX_MASTER) { + if (len == SYSEX_SIZE(master)) { + controls[0].vol = u.x.u.master.coarse; + snprintf(controls[0].name, sizeof(controls[0].name), "master"); + } + return false; + } + + if (u.x.type != SYSEX_TYPE_EDU || u.x.id0 != SYSEX_AUCAT) + return false; + + switch (u.x.id1) { + case SYSEX_AUCAT_MIXINFO: { + unsigned cn = u.x.u.mixinfo.chan; + if (cn >= MIXER_MAX_CHANNELS || !memchr(u.x.u.mixinfo.name, '\0', SYSEX_NAMELEN)) + return false; + + snprintf(controls[cn + 1].name, sizeof(controls[cn + 1].name), "%s", u.x.u.mixinfo.name); + break; + } + + case SYSEX_AUCAT_DUMPEND: + return true; + } + + return false; +} + +static void +oncommon(unsigned char *buf, unsigned len, snd_mixer_elem_t controls[MIXER_MAX_CHANNELS + 1]) +{ +#define MIDI_CMDMASK 0xf0 +#define MIDI_CTL 0xb0 +#define MIDI_CTLVOL 7 + if ((buf[0] & MIDI_CMDMASK) != MIDI_CTL || buf[1] != MIDI_CTLVOL) + return; + +#define MIDI_CHANMASK 0x0f + unsigned cn = (buf[0] & MIDI_CHANMASK); + if (cn >= MIXER_MAX_CHANNELS) + return; + + controls[cn + 1].vol = buf[2]; +} + +static void +get_controls(snd_mixer_t *mixer) +{ + const unsigned char dumpreq[] = { + SYSEX_START, + SYSEX_TYPE_EDU, + 0, + SYSEX_AUCAT, + SYSEX_AUCAT_DUMPREQ, + SYSEX_END, + }; + + static snd_mixer_elem_t controls[MIXER_MAX_CHANNELS + 1]; + memset(controls, 0, sizeof(controls)); + + unsigned char buf[0x100]; + mio_write(mixer->hdl, dumpreq, sizeof(dumpreq)); + for (int ready = 0; !ready;) { + size_t size; + if (!(size = mio_read(mixer->hdl, buf, sizeof(buf)))) { + WARNX1("mio_read failed"); + return; + } + + static unsigned voice_len[] = { 3, 3, 3, 3, 2, 2, 3 }; + static unsigned common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 }; + + unsigned char mmsg[0x100]; + unsigned int mst = 0, midx = 0, mlen = 0; + for (unsigned char *p = buf; size > 0; --size, ++p) { + if (*p >= 0xf8) { + /* clock events */ + } else if (*p >= 0xf0) { + if (mst == SYSEX_START && *p == SYSEX_END && midx < sizeof(mmsg)) { + mmsg[midx++] = *p; + ready = onsysex(mmsg, midx, controls); + continue; + } + + mmsg[0] = *p; + mlen = common_len[*p & 7]; + mst = *p; + midx = 1; + } else if (*p >= 0x80) { + mmsg[0] = *p; + mlen = voice_len[(*p >> 4) & 7]; + mst = *p; + midx = 1; + } else if (mst) { + if (midx == sizeof(mmsg)) + continue; + + if (midx == 0) + mmsg[midx++] = mst; + + mmsg[midx++] = *p; + + if (midx == mlen) { + oncommon(mmsg, midx, controls); + midx = 0; + } + } + } + } + + snd_mixer_elem_t **tail = &mixer->controls; + for (size_t i = 0; i < ARRAY_SIZE(controls); ++i) { + if (!controls[i].name[0]) + continue; + + *tail = &controls[i]; + tail = &(*tail)->next; + } +} + +int +snd_mixer_open(snd_mixer_t **mixer, int mode) +{ + if (!(*mixer = calloc(1, sizeof(**mixer)))) { + WARNX1("calloc"); + return -1; + } + + if (!((*mixer)->hdl = mio_open("snd/0", MIO_OUT | MIO_IN, 0))) { + WARNX1("mio_open failed"); + goto fail; + } + + get_controls(*mixer); + return 0; + +fail: + free(*mixer); + return -1; +} + +int +snd_mixer_close(snd_mixer_t *mixer) +{ + mio_close(mixer->hdl); + free(mixer); + return 0; +} + +struct _snd_mixer_selem_id { + char noop; +}; + +int +snd_mixer_selem_id_malloc(snd_mixer_selem_id_t **ptr) +{ + if (!(*ptr = calloc(1, sizeof(**ptr)))) + return -1; + + return 0; +} + +void +snd_mixer_selem_id_free(snd_mixer_selem_id_t *obj) +{ + free(obj); +} + +snd_mixer_elem_t* +snd_mixer_first_elem(snd_mixer_t *mixer) +{ + return mixer->controls; +} + +snd_mixer_elem_t* +snd_mixer_elem_next(snd_mixer_elem_t *elem) +{ + return elem->next; +} + +const char* +snd_mixer_selem_get_name(snd_mixer_elem_t *elem) +{ + return elem->name; +} + +int +snd_mixer_selem_get_playback_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value) +{ + if (value) *value = ((float)elem->vol / (float)0x7f) * 100; + return 0; +} + +int +snd_mixer_selem_get_playback_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value) +{ + if (value) *value = (long)elem->vol - 0x7f; + return 0; +} + +int +snd_mixer_selem_get_playback_dB_range(snd_mixer_elem_t *elem, long *min, long *max) +{ + if (max) *max = 0; + if (min) *min = -127; + return 0; +} + +int +snd_mixer_selem_has_playback_volume(snd_mixer_elem_t *elem) +{ + return true; +} + +int +snd_mixer_selem_has_playback_volume_joined(snd_mixer_elem_t *elem) +{ + return true; +} + +int +snd_mixer_selem_has_playback_switch(snd_mixer_elem_t *elem) +{ + return true; +} + +int +snd_mixer_selem_has_playback_switch_joined(snd_mixer_elem_t *elem) +{ + return true; +} |