chiark / gitweb /
Partial untested FreeBSD and Linux support for scripts/setup.
[disorder] / lib / mixer-alsa.c
1 /*
2  * This file is part of DisOrder
3  * Copyright (C) 2007 Richard Kettlewell
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18  * USA
19  */
20 /** @file lib/mixer-alsa.c
21  * @brief ALSA mixer support
22  *
23  * The documentation for ALSA's mixer support is completely hopeless,
24  * which is a particular nuisnace given it's got an incredibly verbose
25  * API.  Much of this code is cribbed from
26  * alsa-utils-1.0.13/amixer/amixer.c.
27  *
28  * Mono output devices are supported, but the support is not tested
29  * (as I don't one).
30  */
31
32 #include <config.h>
33
34 #if HAVE_ALSA_ASOUNDLIB_H
35
36 #include "types.h"
37
38 #include <stdio.h>
39 #include <string.h>
40 #include <fcntl.h>
41 #include <unistd.h>
42 #include <stdlib.h>
43 #include <errno.h>
44 #include <stddef.h>
45 #include <ctype.h>
46 #include <sys/ioctl.h>
47 #include <alsa/asoundlib.h>
48
49 #include "configuration.h"
50 #include "mixer.h"
51 #include "log.h"
52 #include "syscalls.h"
53
54 /** @brief Shared state for ALSA mixer support */
55 struct alsa_mixer_state {
56   /** @brief Mixer handle */
57   snd_mixer_t *handle;
58
59   /** @brief Mixer control */
60   snd_mixer_elem_t *elem;
61
62   /** @brief Left channel */
63   snd_mixer_selem_channel_id_t left;
64
65   /** @brief Right channel */
66   snd_mixer_selem_channel_id_t right;
67
68   /** @brief Minimum level */
69   long min;
70
71   /** @brief Maximum level */
72   long max;
73 };
74
75 /** @brief Destroy a @ref alsa_mixer_state */
76 static void alsa_close(struct alsa_mixer_state *h) {
77   /* TODO h->elem */
78   if(h->handle)
79     snd_mixer_close(h->handle);
80 }
81
82 /** @brief Initialize a @ref alsa_mixer_state */
83 static int alsa_open(struct alsa_mixer_state *h) {
84   int err;
85   snd_mixer_selem_id_t *id;
86
87   snd_mixer_selem_id_alloca(&id);
88   memset(h, 0, sizeof h);
89   if((err = snd_mixer_open(&h->handle, 0))) {
90     error(0, "snd_mixer_open: %s", snd_strerror(err));
91     return -1;
92   }
93   if((err = snd_mixer_attach(h->handle, config->device))) {
94     error(0, "snd_mixer_attach %s: %s",
95           config->device, snd_strerror(err));
96     goto error;
97   }
98   if((err = snd_mixer_selem_register(h->handle, 0/*options*/, 0/*classp*/))) {
99     error(0, "snd_mixer_selem_register %s: %s",
100           config->device, snd_strerror(err));
101     goto error;
102   }
103   if((err = snd_mixer_load(h->handle))) {
104     error(0, "snd_mixer_load %s: %s",
105           config->device, snd_strerror(err));
106     goto error;
107   }
108   snd_mixer_selem_id_set_name(id, config->channel);
109   snd_mixer_selem_id_set_index(id, atoi(config->mixer));
110   if(!(h->elem = snd_mixer_find_selem(h->handle, id))) {
111     error(0, "device '%s' mixer control '%s,%s' does not exist",
112           config->device, config->channel, config->mixer);
113     goto error;
114   }
115   if(!snd_mixer_selem_has_playback_volume(h->elem)) {
116     error(0, "device '%s' mixer control '%s,%s' has no playback volume",
117           config->device, config->channel, config->mixer);
118     goto error;
119   }
120   if(snd_mixer_selem_is_playback_mono(h->elem)) {
121     h->left = h->right = SND_MIXER_SCHN_MONO;
122   } else {
123     h->left = SND_MIXER_SCHN_FRONT_LEFT;
124     h->right = SND_MIXER_SCHN_FRONT_RIGHT;
125   }
126   if(!snd_mixer_selem_has_playback_channel(h->elem, h->left)
127      || !snd_mixer_selem_has_playback_channel(h->elem, h->right)) {
128     error(0, "device '%s' mixer control '%s,%s' lacks required playback channels",
129           config->device, config->channel, config->mixer);
130     goto error;
131   }
132   snd_mixer_selem_get_playback_volume_range(h->elem, &h->min, &h->max);
133   return 0;
134 error:
135   alsa_close(h);
136   return -1;
137 }
138
139 /** @brief Convert a level to a percentage */
140 static int to_percent(const struct alsa_mixer_state *h, long n) {
141   return (n - h->min) * 100 / (h->max - h->min);
142 }
143
144 /** @brief Get ALSA volume */
145 static int alsa_get(int *left, int *right) {
146   struct alsa_mixer_state h[1];
147   long l, r;
148   int err;
149   
150   if(alsa_open(h))
151     return -1;
152   if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
153      || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
154     error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
155     goto error;
156   }
157   *left = to_percent(h, l);
158   *right = to_percent(h, r);
159   alsa_close(h);
160   return 0;
161 error:
162   alsa_close(h);
163   return -1;
164 }
165
166 /** @brief Convert a percentage to a level */
167 static int from_percent(const struct alsa_mixer_state *h, int n) {
168   return h->min + n * (h->max - h->min) / 100;
169 }
170
171 /** @brief Set ALSA volume */
172 static int alsa_set(int *left, int *right) {
173   struct alsa_mixer_state h[1];
174   long l, r;
175   int err;
176   
177   if(alsa_open(h))
178     return -1;
179   /* Set the volume */
180   if(h->left == h->right) {
181     /* Mono output - just use the loudest */
182     if((err = snd_mixer_selem_set_playback_volume
183         (h->elem, h->left,
184          from_percent(h, *left > *right ? *left : *right)))) {
185       error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
186       goto error;
187     }
188   } else {
189     /* Stereo output */
190     if((err = snd_mixer_selem_set_playback_volume
191         (h->elem, h->left, from_percent(h, *left)))
192        || (err = snd_mixer_selem_set_playback_volume
193            (h->elem, h->right, from_percent(h, *right)))) {
194       error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
195       goto error;
196     }
197   }
198   /* Read it back to see what we ended up at */
199   if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
200      || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
201     error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
202     goto error;
203   }
204   *left = to_percent(h, l);
205   *right = to_percent(h, r);
206   alsa_close(h);
207   return 0;
208 error:
209   alsa_close(h);
210   return -1;
211 }
212
213 /** @brief ALSA mixer vtable */
214 const struct mixer mixer_alsa = {
215   BACKEND_ALSA,
216   alsa_get,
217   alsa_set,
218   "0",
219   "PCM"
220 };
221 #endif
222
223 /*
224 Local Variables:
225 c-basic-offset:2
226 comment-column:40
227 End:
228 */