choose.c misc.c control.c properties.c menu.c \
log.c progress.c login.c rtp.c help.c \
../lib/memgc.c settings.c
-disobedience_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBGC) $(LIBGCRYPT)
+disobedience_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBGC) $(LIBGCRYPT) \
+ $(LIBASOUND) $(COREAUDIO)
disobedience_LDFLAGS=$(GTK_LIBS)
install-exec-hook:
.IP
You can also specify channels by number, if you know the right value.
.IP
-For \fBapi alsa\fR, volume setting is not currently supported.
+For \fBapi alsa\fR, this is the name of the mixer control to use. The default
+is \fBPCM\fR. Use \fBamixer scontrols\fR or similar to get a full list.
.IP
For \fBapi coreaudio\fR, volume setting is not currently supported.
.TP
.IP
For \fBapi coreaudio\fR this is currently ignored.
.IP
-The default is \fBdefault\fR.
+The default is \fBdefault\fR, which is intended to map to whatever the system's
+default is.
.TP
.B gap \fISECONDS\fR
Specifies the number of seconds to leave between tracks. The default
For \fBapi oss\fR this should be the path to the mixer device and the default
is \fI/dev/mixer\fR.
.IP
-For \fBapi alsa\fR, volume setting is not currently supported.
+For \fBapi alsa\fR, this is the index of the mixer control to use. The default
+is 0.
.IP
For \fBapi coreaudio\fR, volume setting is not currently supported.
.TP
logfd.c logfd.h \
mem.c mem.h mem-impl.h \
mime.h mime.c \
- mixer.c mixer.h mixer-oss.c \
+ mixer.c mixer.h mixer-oss.c mixer-alsa.c \
plugin.c plugin.h \
printf.c printf.h \
asprintf.c fprintf.c snprintf.c \
r |= RIGHT_REMOVE_ANY;
c->default_rights = rights_string(r);
}
- if(!c->mixer)
- c->mixer = xstrdup(mixer_default_device(c->api));
- if(!c->channel)
- c->channel = xstrdup(mixer_default_channel(c->api));
}
/** @brief (Re-)read the config file
config_postdefaults(c, server);
/* everything is good so we shall use the new config */
config_free(config);
- /* final error checking */
- if(c->mixer && !mixer_valid_device(c->api, c->mixer)) {
- error(0, "invalid mixer device: %s", c->mixer);
- return -1;
- }
- if(c->channel && !mixer_valid_channel(c->api, c->channel)) {
- error(0, "invalid mixer channel: %s", c->channel);
- return -1;
- }
/* warn about obsolete directives */
if(c->restrictions)
error(0, "'restrict' will be removed in a future version");
--- /dev/null
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2007 Richard Kettlewell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+/** @file lib/mixer-alsa.c
+ * @brief ALSA mixer support
+ *
+ * The documentation for ALSA's mixer support is completely hopeless,
+ * which is a particular nuisnace given it's got an incredibly verbose
+ * API. Much of this code is cribbed from
+ * alsa-utils-1.0.13/amixer/amixer.c.
+ *
+ * Mono output devices are supported, but the support is not tested
+ * (as I don't one).
+ */
+
+#include <config.h>
+
+#if HAVE_ALSA_ASOUNDLIB_H
+
+#include "types.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <sys/ioctl.h>
+#include <alsa/asoundlib.h>
+
+#include "configuration.h"
+#include "mixer.h"
+#include "log.h"
+#include "syscalls.h"
+
+/** @brief Shared state for ALSA mixer support */
+struct alsa_mixer_state {
+ /** @brief Mixer handle */
+ snd_mixer_t *handle;
+
+ /** @brief Mixer control */
+ snd_mixer_elem_t *elem;
+
+ /** @brief Left channel */
+ snd_mixer_selem_channel_id_t left;
+
+ /** @brief Right channel */
+ snd_mixer_selem_channel_id_t right;
+
+ /** @brief Minimum level */
+ long min;
+
+ /** @brief Maximum level */
+ long max;
+};
+
+/** @brief Destroy a @ref alsa_mixer_state */
+static void alsa_close(struct alsa_mixer_state *h) {
+ /* TODO h->elem */
+ if(h->handle)
+ snd_mixer_close(h->handle);
+}
+
+/** @brief Initialize a @ref alsa_mixer_state */
+static int alsa_open(struct alsa_mixer_state *h) {
+ int err;
+ snd_mixer_selem_id_t *id;
+
+ snd_mixer_selem_id_alloca(&id);
+ memset(h, 0, sizeof h);
+ if((err = snd_mixer_open(&h->handle, 0))) {
+ error(0, "snd_mixer_open: %s", snd_strerror(err));
+ return -1;
+ }
+ if((err = snd_mixer_attach(h->handle, config->device))) {
+ error(0, "snd_mixer_attach %s: %s",
+ config->device, snd_strerror(err));
+ goto error;
+ }
+ if((err = snd_mixer_selem_register(h->handle, 0/*options*/, 0/*classp*/))) {
+ error(0, "snd_mixer_selem_register %s: %s",
+ config->device, snd_strerror(err));
+ goto error;
+ }
+ if((err = snd_mixer_load(h->handle))) {
+ error(0, "snd_mixer_load %s: %s",
+ config->device, snd_strerror(err));
+ goto error;
+ }
+ snd_mixer_selem_id_set_name(id, config->channel);
+ snd_mixer_selem_id_set_index(id, atoi(config->mixer));
+ if(!(h->elem = snd_mixer_find_selem(h->handle, id))) {
+ error(0, "snd_mixer_find_selem returned NULL");
+ goto error;
+ }
+ if(!snd_mixer_selem_has_playback_volume(h->elem)) {
+ error(0, "configured mixer control has no playback volume");
+ goto error;
+ }
+ if(snd_mixer_selem_is_playback_mono(h->elem)) {
+ h->left = h->right = SND_MIXER_SCHN_MONO;
+ } else {
+ h->left = SND_MIXER_SCHN_FRONT_LEFT;
+ h->right = SND_MIXER_SCHN_FRONT_RIGHT;
+ }
+ if(!snd_mixer_selem_has_playback_channel(h->elem, h->left)
+ || !snd_mixer_selem_has_playback_channel(h->elem, h->right)) {
+ error(0, "configured mixer control lacks required playback channels");
+ goto error;
+ }
+ snd_mixer_selem_get_playback_volume_range(h->elem, &h->min, &h->max);
+ return 0;
+error:
+ alsa_close(h);
+ return -1;
+}
+
+/** @brief Convert a level to a percentage */
+static int to_percent(const struct alsa_mixer_state *h, long n) {
+ return (n - h->min) * 100 / (h->max - h->min);
+}
+
+/** @brief Get ALSA volume */
+static int alsa_get(int *left, int *right) {
+ struct alsa_mixer_state h[1];
+ long l, r;
+ int err;
+
+ if(alsa_open(h))
+ return -1;
+ if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
+ || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
+ error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
+ goto error;
+ }
+ *left = to_percent(h, l);
+ *right = to_percent(h, r);
+ alsa_close(h);
+ return 0;
+error:
+ alsa_close(h);
+ return -1;
+}
+
+/** @brief Convert a percentage to a level */
+static int from_percent(const struct alsa_mixer_state *h, int n) {
+ return h->min + n * (h->max - h->min) / 100;
+}
+
+/** @brief Set ALSA volume */
+static int alsa_set(int *left, int *right) {
+ struct alsa_mixer_state h[1];
+ long l, r;
+ int err;
+
+ if(alsa_open(h))
+ return -1;
+ /* Set the volume */
+ if(h->left == h->right) {
+ /* Mono output - just use the loudest */
+ if((err = snd_mixer_selem_set_playback_volume
+ (h->elem, h->left,
+ from_percent(h, *left > *right ? *left : *right)))) {
+ error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
+ goto error;
+ }
+ } else {
+ /* Stereo output */
+ if((err = snd_mixer_selem_set_playback_volume
+ (h->elem, h->left, from_percent(h, *left)))
+ || (err = snd_mixer_selem_set_playback_volume
+ (h->elem, h->right, from_percent(h, *right)))) {
+ error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
+ goto error;
+ }
+ }
+ /* Read it back to see what we ended up at */
+ if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
+ || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
+ error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
+ goto error;
+ }
+ *left = to_percent(h, l);
+ *right = to_percent(h, r);
+ alsa_close(h);
+ return 0;
+error:
+ alsa_close(h);
+ return -1;
+}
+
+/** @brief ALSA mixer vtable */
+const struct mixer mixer_alsa = {
+ BACKEND_ALSA,
+ alsa_get,
+ alsa_set,
+ "0",
+ "PCM"
+};
+#endif
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+End:
+*/
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
+/** @file lib/mixer-oss.c
+ * @brief OSS mixer support
+ *
+ * Mono output devices aren't explicitly supported (but may work
+ * nonetheless).
+ */
#include <config.h>
+
+#if HAVE_SYS_SOUNDCARD_H
+
#include "types.h"
#include <stdio.h>
#include <errno.h>
#include <stddef.h>
#include <sys/ioctl.h>
+#include <sys/soundcard.h>
#include "configuration.h"
#include "mixer.h"
#include "log.h"
#include "syscalls.h"
-#if HAVE_SYS_SOUNDCARD_H
-#include <sys/soundcard.h>
-
/* documentation does not match implementation! */
#ifndef SOUND_MIXER_READ
# define SOUND_MIXER_READ(x) MIXER_READ(x)
# define SOUND_MIXER_WRITE(x) MIXER_WRITE(x)
#endif
+/** @brief Channel names */
static const char *channels[] = SOUND_DEVICE_NAMES;
+/** @brief Convert channel name to number */
static int mixer_channel(const char *c) {
unsigned n;
}
}
-static int oss_validate_channel(const char *c) {
- if(mixer_channel(c) != -1)
- return 1;
- else
- return 0;
-}
-
-static int oss_validate_device(const char *d) {
- struct stat sb;
-
- if(stat(d, &sb) < 0) {
- error(errno, "%s", d);
- return 0;
- }
- if(!S_ISCHR(sb.st_mode)) {
- error(0, "%s: not a character device", d);
- return 0;
- }
- return 1;
-}
-
+/** @brief Open the OSS mixer device and return its fd */
static int oss_do_open(void) {
int fd;
return fd;
}
+/** @brief Get the OSS mixer setting */
static int oss_do_get(int *left, int *right, int fd, int ch) {
int r;
return 0;
}
+/** @brief Get OSS volume */
static int oss_get(int *left, int *right) {
int ch, fd;
return -1;
}
+/** @brief Set OSS volume */
static int oss_set(int *left, int *right) {
int ch, fd, r;
return -1;
}
+/** @brief OSS mixer vtable */
const struct mixer mixer_oss = {
BACKEND_OSS,
- oss_validate_device,
- oss_validate_channel,
oss_get,
oss_set,
"/dev/mixer",
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
+/** @file lib/mixer.c
+ * @brief Mixer support
+ */
#include <config.h>
#include "types.h"
-#include <stdio.h>
-#include <string.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <stddef.h>
-#include <sys/ioctl.h>
-
#include "configuration.h"
#include "mixer.h"
#include "log.h"
-#include "syscalls.h"
+#include "mem.h"
+/** @brief Whether lack of volume support has been reported yet */
+static int none_reported;
+
+/** @brief Get/set volume stub if volume control is not supported */
+static int none_get_set(int attribute((unused)) *left,
+ int attribute((unused)) *right) {
+ if(!none_reported) {
+ error(0, "don't know how to get/set volume with this api");
+ none_reported = 1;
+ }
+ return -1;
+}
+
+/** @brief Stub mixer control */
+static const struct mixer mixer_none = {
+ -1,
+ none_get_set,
+ none_get_set,
+ "",
+ ""
+};
+
+/** @brief Table of mixer definitions */
static const struct mixer *mixers[] = {
#if HAVE_SYS_SOUNDCARD_H
- &mixer_oss
+ &mixer_oss,
+#endif
+#if HAVE_ALSA_ASOUNDLIB_H
+ &mixer_alsa,
#endif
+ &mixer_none /* make sure array is never empty */
};
+/** @brief Number of mixer definitions */
#define NMIXERS (sizeof mixers / sizeof *mixers)
+/** @brief Find the mixer definition */
static const struct mixer *find_mixer(int api) {
size_t n;
for(n = 0; n < NMIXERS; ++n)
if(mixers[n]->api == api)
return mixers[n];
- return 0;
-}
-
-int mixer_valid_channel(int api, const char *c) {
- const struct mixer *const m = find_mixer(api);
-
- return m ? m->validate_channel(c) : 1;
-}
-
-int mixer_valid_device(int api, const char *d) {
- const struct mixer *const m = find_mixer(api);
-
- return m ? m->validate_device(d) : 1;
+ return &mixer_none;
}
+/** @brief Get/set volume
+ * @param left Left channel level, 0-100
+ * @param right Right channel level, 0-100
+ * @param set Set volume if non-0
+ * @return 0 on success, non-0 on error
+ *
+ * If getting the volume then @p left and @p right are filled in.
+ *
+ * If setting the volume then the target levels are read from @p left and
+ * @p right, and the actual level set is stored in them.
+ */
int mixer_control(int *left, int *right, int set) {
const struct mixer *const m = find_mixer(config->api);
- if(m) {
- if(set)
- return m->set(left, right);
- else
- return m->get(left, right);
- } else {
- static int reported;
-
- if(!reported) {
- error(0, "don't know how to get/set volume with this api");
- reported = 1;
- }
- return -1;
- }
-}
-
-const char *mixer_default_device(int api) {
- const struct mixer *const m = find_mixer(api);
-
- return m ? m->device : "";
-}
-
-const char *mixer_default_channel(int api) {
- const struct mixer *const m = find_mixer(api);
-
- return m ? m->channel : "";
+ /* We impose defaults bizarrely late, but this has the advantage of
+ * not making everything depend on sound libraries */
+ if(!config->mixer)
+ config->mixer = xstrdup(m->device);
+ if(!config->channel)
+ config->channel = xstrdup(m->channel);
+ if(set)
+ return m->set(left, right);
+ else
+ return m->get(left, right);
}
/*
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
+/** @file lib/mixer.h
+ * @brief Mixer support
+ */
#ifndef MIXER_H
#define MIXER_H
/** @brief API used by this mixer */
int api;
- /** @brief Return non-0 iff @p d is a valid device name */
- int (*validate_device)(const char *d);
-
- /** @brief Return non-0 iff @p c is a valid channel name */
- int (*validate_channel)(const char *c);
-
/** @brief Get the volume
* @param left Where to store left-channel volume
* @param right Where to store right-channel volume
const char *channel;
};
-int mixer_valid_device(int api, const char *d);
-int mixer_valid_channel(int api, const char *c);
int mixer_control(int *left, int *right, int set);
const char *mixer_default_device(int api);
const char *mixer_default_channel(int api);
extern const struct mixer mixer_oss;
+extern const struct mixer mixer_alsa;
#endif /* MIXER_H */
exports.c \
../lib/memgc.c
disorderd_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
- $(LIBPCRE) $(LIBDB) $(LIBAO) $(LIBGC) $(LIBGCRYPT) $(LIBICONV)
+ $(LIBPCRE) $(LIBDB) $(LIBAO) $(LIBGC) $(LIBGCRYPT) $(LIBICONV) \
+ $(LIBASOUND) $(COREAUDIO)
disorderd_LDFLAGS=-export-dynamic
disorderd_DEPENDENCIES=../lib/libdisorder.a