mood.c: Trivial cosmetics for int_sqrt().
[paraslash.git] / alsa_mix.c
1 /*
2 * Copyright (C) 2012 Andre Noll <maan@tuebingen.mpg.de>
3 *
4 * Licensed under the GPL v2. For licencing details see COPYING.
5 */
6
7 /** \file alsa_mix.c The ALSA mixer plugin. */
8
9 #include <regex.h>
10 #include <alsa/asoundlib.h>
11
12 #include "para.h"
13 #include "error.h"
14 #include "mix.h"
15 #include "string.h"
16
17 struct mixer_handle {
18 /* The ALSA mixer handle */
19 snd_mixer_t *mixer;
20 /* Copy of the alsa device name, e.g. "hw:0". */
21 char *card;
22 /* ALSA's representation of the given mixer channel. */
23 snd_mixer_elem_t *elem;
24 /* Set in ->set_channel(), used for ->get() and ->set(). */
25 long pmin, pmax;
26 };
27
28 static void alsa_mix_close(struct mixer_handle **handle)
29 {
30 struct mixer_handle *h;
31
32 if (!handle)
33 return;
34 h = *handle;
35 if (h) {
36 PARA_INFO_LOG("closing mixer handle\n");
37 if (h->mixer) /* nec. */
38 snd_mixer_close(h->mixer);
39 free(h->card);
40 free(h);
41 /*
42 * The global ALSA configuration is cached for next usage,
43 * which causes valgrind to report many memory leaks. Calling
44 * snd_config_update_free_global() frees the cache.
45 */
46 snd_config_update_free_global();
47 }
48 *handle = NULL;
49 }
50
51 static int alsa_mix_open(const char *dev, struct mixer_handle **handle)
52 {
53 int ret;
54 char *msg;
55 struct mixer_handle *h;
56
57 PARA_INFO_LOG("snd_mixer_{open,attach,register,load}\n");
58 *handle = NULL;
59 h = para_calloc(sizeof(*h));
60 h->card = para_strdup(dev? dev : "hw:0");
61 ret = snd_mixer_open(&h->mixer, 0);
62 if (ret < 0) {
63 msg = make_message("snd_mixer_open() failed: %s",
64 snd_strerror(ret));
65 goto fail;
66 }
67 ret = snd_mixer_attach(h->mixer, h->card);
68 if (ret < 0) {
69 msg = make_message("mixer attach error (%s): %s", h->card,
70 snd_strerror(ret));
71 goto fail;
72 }
73 ret = snd_mixer_selem_register(h->mixer, NULL, NULL);
74 if (ret < 0) {
75 msg = make_message("mixer register error (%s): %s", h->card,
76 snd_strerror(ret));
77 goto fail;
78 }
79 ret = snd_mixer_load(h->mixer);
80 if (ret < 0) {
81 msg = make_message("mixer load error (%s): %s", h->card,
82 snd_strerror(ret));
83 goto fail;
84 }
85 /* success */
86 *handle = h;
87 return 1;
88 fail:
89 PARA_NOTICE_LOG("%s\n", msg);
90 free(msg);
91 alsa_mix_close(&h);
92 return -E_ALSA_MIX_OPEN;
93 }
94
95 static bool channel_has_playback(snd_mixer_selem_channel_id_t chn,
96 snd_mixer_elem_t *elem)
97 {
98 return snd_mixer_selem_has_playback_channel(elem, chn);
99 }
100
101 static char *alsa_mix_get_channels(struct mixer_handle *handle)
102 {
103 int ret;
104 char *list = NULL;
105 snd_mixer_selem_id_t *sid;
106 snd_mixer_elem_t *elem;
107
108 ret = snd_mixer_selem_id_malloc(&sid);
109 assert(ret >= 0);
110 elem = snd_mixer_first_elem(handle->mixer);
111 for (; elem; elem = snd_mixer_elem_next(elem)) {
112 char *tmp = list;
113 const char *name;
114
115 if (!channel_has_playback(0, elem))
116 continue;
117 if (!snd_mixer_selem_has_playback_volume(elem))
118 continue;
119 snd_mixer_selem_get_id(elem, sid);
120 name = snd_mixer_selem_id_get_name(sid);
121 list = make_message("%s%s%s",
122 list? list : "",
123 list? ", " : "",
124 name);
125 free(tmp);
126 }
127 snd_mixer_selem_id_free(sid);
128 return list;
129 }
130
131 static int alsa_mix_set_channel(struct mixer_handle *h,
132 const char *mixer_channel)
133 {
134 int ret, selem_id = 0;
135 snd_mixer_selem_id_t *sid;
136
137 if (!mixer_channel)
138 mixer_channel = "Master";
139 ret = snd_mixer_selem_id_malloc(&sid);
140 assert(ret >= 0);
141 snd_mixer_selem_id_set_index(sid, selem_id);
142 snd_mixer_selem_id_set_name(sid, mixer_channel);
143 h->elem = snd_mixer_find_selem(h->mixer, sid);
144 if (!h->elem) {
145 PARA_NOTICE_LOG("unable to find simple control '%s',%u\n",
146 snd_mixer_selem_id_get_name(sid),
147 snd_mixer_selem_id_get_index(sid));
148 ret = -E_BAD_CHANNEL;
149 goto out;
150 }
151 ret = snd_mixer_selem_get_playback_volume_range(h->elem,
152 &h->pmin, &h->pmax);
153 if (ret < 0) {
154 PARA_NOTICE_LOG("unable to get %s range (%s): %s\n",
155 mixer_channel, h->card, snd_strerror(ret));
156 ret = -E_ALSA_MIX_RANGE;
157 goto out;
158 }
159 if (h->pmin >= h->pmax) {
160 PARA_NOTICE_LOG("alsa reported %s range %ld-%ld (%s)\n",
161 mixer_channel, h->pmin, h->pmax, h->card);
162 ret = -E_ALSA_MIX_RANGE;
163 goto out;
164 }
165 ret = 1;
166 out:
167 snd_mixer_selem_id_free(sid);
168 return ret;
169 }
170
171 static int alsa_mix_get(struct mixer_handle *h)
172 {
173 snd_mixer_selem_channel_id_t chn;
174 int n = 0;
175 float avg = 0;
176
177 /* compute average over all channels */
178 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
179 long val;
180 int ret;
181
182 if (!channel_has_playback(chn, h->elem))
183 continue;
184 ret = snd_mixer_selem_get_playback_volume(h->elem, chn, &val);
185 if (ret < 0) {
186 PARA_NOTICE_LOG("unable to get value for channel %d (%s): %s\n",
187 (int)chn, h->card, snd_strerror(ret));
188 return -E_ALSA_MIX_GET_VAL;
189 }
190 /* update the rolling average */
191 avg = (val + n * avg) / (n + 1);
192 n++;
193 }
194 /* scale to 0..100 */
195 avg = 100 * (avg - h->pmin) / (h->pmax - h->pmin);
196 return avg;
197 }
198
199 static int alsa_mix_set(struct mixer_handle *h, int val)
200 {
201 int ret;
202 snd_mixer_selem_channel_id_t chn;
203 long scaled = val / 100.0 * (h->pmax - h->pmin) + h->pmin;
204
205 PARA_INFO_LOG("new value: %d\n", val);
206 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
207 if (!channel_has_playback(chn, h->elem))
208 continue;
209 ret = snd_mixer_selem_set_playback_volume(h->elem, chn, scaled);
210 if (ret < 0) {
211 PARA_NOTICE_LOG("unable to set value for channel %d (%s): %s\n",
212 (int)chn, h->card, snd_strerror(ret));
213 return -E_ALSA_MIX_SET_VAL;
214 }
215 }
216 return 1;
217 }
218
219 /** The mixer operations for the ALSA mixer. */
220 const struct mixer alsa_mixer = {
221 .name = "alsa",
222 .open = alsa_mix_open,
223 .get_channels = alsa_mix_get_channels,
224 .set_channel = alsa_mix_set_channel,
225 .close = alsa_mix_close,
226 .get = alsa_mix_get,
227 .set = alsa_mix_set
228 };