#include #include #include #include #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; }