wmusic: Port to playerctl for MPRIS support.
[dockapps.git] / wmix / mixer-alsa.c
blob686b3b7a177ff4324040aaf7d0470daf209b3568
1 #include "include/common.h"
2 #include "include/misc.h"
3 #include "include/mixer-alsa.h"
5 #include <alsa/asoundlib.h>
7 static bool get_mixer_state(void);
9 struct mixer_element {
10 const char *name; /* name of channel */
11 const char *sname; /* short name of the channel */
12 snd_mixer_elem_t *element; /* Mixer element pointer */
13 long min; /* Min volume */
14 long max; /* Max volume */
15 int dev; /* channel device number */
16 int prev_dev_lr_volume; /* last known left/right volume
17 * (in device format) */
18 float volume; /* volume, in [0, 1] */
19 float balance; /* balance, in [-1, 1] */
20 bool has_playback;
21 bool has_playback_switch;
22 bool has_capture;
23 bool has_capture_switch;
24 bool is_recording; /* is it recording? */
25 bool is_stereo; /* capable of stereo? */
26 bool is_muted; /* is it muted? */
27 int (*get_volume)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
28 int (*set_volume)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long);
31 static snd_mixer_t *mixer;
32 static struct mixer_element *element;
33 static int cur_element = 0;
34 static int n_elements;
35 static bool needs_update = true;
37 static int elem_callback(__attribute__((unused)) snd_mixer_elem_t *elem,
38 __attribute__((unused)) unsigned int mask)
40 needs_update = true;
41 return 0;
44 static int mixer_callback(__attribute__((unused)) snd_mixer_t *ctl,
45 unsigned int mask,
46 snd_mixer_elem_t *elem)
48 if (mask & SND_CTL_EVENT_MASK_ADD) {
49 snd_mixer_elem_set_callback(elem, elem_callback);
50 needs_update = true;
52 return 0;
55 static bool is_exclude(const char *short_name,
56 const char *exclude[])
58 for (int i = 0; exclude[i] != NULL; i++) {
59 if (!strcmp(short_name, exclude[i])) {
60 return true;
63 return false;
66 void mixer_alsa_init(const char *mixer_device, bool verbose, const char * exclude[])
68 int err;
69 static struct snd_mixer_selem_regopt selem_regopt = {
70 .ver = 1,
71 .abstract = SND_MIXER_SABSTRACT_NONE,
73 selem_regopt.device = mixer_device;
74 if (verbose) {
75 printf("Sound card: %s\n", mixer_device);
76 puts("Supported elements:");
79 if ((err = snd_mixer_open(&mixer, 0)) < 0) {
80 fprintf(stderr, "snd_mixer_open error");
81 mixer = NULL;
82 return;
85 if ((err = snd_mixer_selem_register(mixer, &selem_regopt, NULL)) < 0) {
86 fprintf(stderr, "snd_mixer_selem_register error");
87 snd_mixer_close(mixer);
88 mixer = NULL;
89 return;
92 snd_mixer_set_callback(mixer, mixer_callback);
94 if ((err = snd_mixer_load(mixer)) < 0) {
95 fprintf(stderr, "snd_mixer_load error");
96 snd_mixer_close(mixer);
97 mixer = NULL;
98 return;
101 int all_elements = snd_mixer_get_count(mixer);
102 element = (struct mixer_element *)malloc(all_elements *
103 sizeof(struct mixer_element));
104 memset(element, 0, all_elements * sizeof(struct mixer_element));
105 snd_mixer_elem_t *elem = snd_mixer_first_elem(mixer);
107 int f = 0;
108 for (int e = 0; e < all_elements; e++) {
109 element[f].element = elem;
110 element[f].name = snd_mixer_selem_get_name(elem);
112 if (is_exclude(element[f].name, exclude)) {
113 if (verbose)
114 printf(" x: '%s' - disabled\n", element[f].name);
115 elem = snd_mixer_elem_next(elem);
116 continue;
119 element[f].sname = element[f].name;
121 element[f].has_capture = snd_mixer_selem_has_capture_volume(elem);
122 element[f].has_capture &= snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_FRONT_LEFT);
124 element[f].has_playback = snd_mixer_selem_has_playback_volume(elem);
125 element[f].has_playback &= snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_FRONT_LEFT);
127 if (element[f].has_playback) {
128 snd_mixer_selem_get_playback_volume_range(elem, &(element[f].min), &(element[f].max));
129 element[f].is_stereo = !snd_mixer_selem_is_playback_mono(elem);
130 element[f].is_stereo &= snd_mixer_selem_has_playback_channel(elem,SND_MIXER_SCHN_FRONT_RIGHT);
131 element[f].get_volume = snd_mixer_selem_get_playback_volume;
132 element[f].set_volume = snd_mixer_selem_set_playback_volume;
133 element[f].has_playback_switch = snd_mixer_selem_has_playback_switch(elem);
135 else if (element[f].has_capture) {
136 snd_mixer_selem_get_capture_volume_range(elem, &(element[f].min), &(element[f].max));
137 element[f].is_stereo = !snd_mixer_selem_is_capture_mono(element[f].element);
138 element[f].is_stereo &= snd_mixer_selem_has_capture_channel(elem,SND_MIXER_SCHN_FRONT_RIGHT);
139 element[f].get_volume = snd_mixer_selem_get_capture_volume;
140 element[f].set_volume = snd_mixer_selem_set_capture_volume;
141 element[f].has_capture_switch = snd_mixer_selem_has_capture_switch(elem);
142 } else {
143 elem = snd_mixer_elem_next(elem);
144 continue;
147 if (verbose) {
148 printf(" %d: '%s'%s%s%s%s %s (%ld - %ld)\n",
150 element[f].name,
151 element[f].has_playback? " pvolume" : "",
152 element[f].has_playback_switch? " pswitch" : "",
153 element[f].has_capture? " cvolume" : "",
154 element[f].has_capture_switch? " cswitch" : "",
155 element[f].is_stereo? "Stereo" : "Mono",
156 element[f].min, element[f].max);
159 elem = snd_mixer_elem_next(elem);
160 f++;
162 n_elements = f;
163 get_mixer_state();
166 static bool element_is_muted(int e)
168 if (!element[e].has_playback_switch)
169 return false;
171 snd_mixer_elem_t *elem = element[e].element;
172 int left_on;
173 snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, &left_on);
174 if (left_on)
175 return false;
176 if (element[e].is_stereo) {
177 int right_on;
178 snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_FRONT_RIGHT, &right_on);
179 if (right_on)
180 return false;
182 return true;
185 static bool element_is_recording(int e)
187 if (!element[e].has_capture_switch)
188 return false;
190 snd_mixer_elem_t *elem = element[e].element;
191 int left_on;
192 snd_mixer_selem_get_capture_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, &left_on);
193 if (left_on)
194 return true;
195 if (element[e].is_stereo) {
196 int right_on;
197 snd_mixer_selem_get_capture_switch(elem, SND_MIXER_SCHN_FRONT_RIGHT, &right_on);
198 if (right_on)
199 return true;
201 return false;
204 static bool get_mixer_state(void)
206 if (!needs_update)
207 return false;
208 needs_update = false;
210 for (int e = 0; e < n_elements; e++) {
211 snd_mixer_elem_t *elem = element[e].element;
212 long left, right;
214 element[e].get_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &left);
215 float fleft = 1.0 * (left-element[e].min) / (element[e].max-element[e].min);
216 if (element[e].is_stereo) {
217 element[e].get_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &right);
218 float fright = 1.0 * (right-element[e].min) /
219 (element[e].max-element[e].min);
220 lr_to_vb(fleft, fright, &(element[e].volume), &(element[e].balance));
221 } else {
222 element[e].volume = fleft;
223 element[e].balance = 0.0;
226 element[e].is_muted = element_is_muted(e);
227 element[e].is_recording = element_is_recording(e);
229 //printf("Channel %s, has_vol: %d, stereo: %d, muted: %d, max: %ld, min: %ld, left: %ld, right: %ld, vol: %f, bal: %f\n", element[e].name, element[e].has_playback, element[e].is_stereo, element[e].is_muted, element[e].max, element[e].min, left, element[e].is_stereo?right:-1, element[e].volume, element[e].balance);
231 return true;
234 static void set_mixer_state(void)
236 float left, right;
237 long dev_left_volume, dev_right_volume;
238 if (!element[cur_element].has_playback && !element[cur_element].has_capture)
239 return;
241 bool muted = element_is_muted(cur_element);
242 if (muted != element[cur_element].is_muted) {
243 snd_mixer_selem_set_playback_switch_all(element[cur_element].element,
244 element[cur_element].is_muted?
245 0:1);
248 bool recording = element_is_recording(cur_element);
249 if (recording != element[cur_element].is_recording) {
250 snd_mixer_selem_set_capture_switch_all(element[cur_element].element,
251 element[cur_element].is_recording?
252 1:0);
255 if (element[cur_element].is_stereo)
256 vb_to_lr(element[cur_element].volume,
257 element[cur_element].balance, &left, &right);
258 else
259 left = element[cur_element].volume;
261 long range = element[cur_element].max - element[cur_element].min;
262 dev_left_volume = element[cur_element].min + (long) (range * left);
263 element[cur_element].set_volume(element[cur_element].element,
264 SND_MIXER_SCHN_FRONT_LEFT,
265 dev_left_volume);
266 if (element[cur_element].is_stereo) {
267 dev_right_volume = element[cur_element].min + (long) (range * right);
268 element[cur_element].set_volume(element[cur_element].element,
269 SND_MIXER_SCHN_FRONT_RIGHT,
270 dev_right_volume);
274 bool mixer_alsa_is_changed(void)
276 return get_mixer_state();
279 int mixer_alsa_get_channel_count(void)
281 return n_elements;
284 int mixer_alsa_get_channel(void)
286 return cur_element;
289 const char *mixer_alsa_get_channel_name(void)
291 return element[cur_element].name;
294 const char *mixer_alsa_get_short_name(void)
296 return element[cur_element].sname;
299 void mixer_alsa_set_channel(int element)
301 assert((element >= 0) && (element < n_elements));
303 cur_element = element;
304 get_mixer_state();
307 void mixer_alsa_set_channel_rel(int delta_element)
309 cur_element = (cur_element + delta_element) % n_elements;
310 if (cur_element < 0)
311 cur_element += n_elements;
312 get_mixer_state();
315 float mixer_alsa_get_volume(void)
317 get_mixer_state();
318 return element[cur_element].volume;
321 void mixer_alsa_set_volume(float volume)
323 assert((volume >= 0.0) && (volume <= 1.0));
324 element[cur_element].volume = volume;
325 set_mixer_state();
328 void mixer_alsa_set_volume_rel(float delta_volume)
330 element[cur_element].volume += delta_volume;
331 element[cur_element].volume = CLAMP(element[cur_element].volume, 0.0, 1.0);
332 set_mixer_state();
335 float mixer_alsa_get_balance(void)
337 get_mixer_state();
338 return element[cur_element].balance;
341 void mixer_alsa_set_balance(float balance)
343 assert((balance >= -1.0) && (balance <= 1.0));
344 if (element[cur_element].is_stereo) {
345 element[cur_element].balance = balance;
346 set_mixer_state();
350 void mixer_alsa_set_balance_rel(float delta_balance)
352 if (element[cur_element].is_stereo) {
353 element[cur_element].balance += delta_balance;
354 element[cur_element].balance =
355 CLAMP(element[cur_element].balance, -1.0, 1.0);
356 set_mixer_state();
360 void mixer_alsa_toggle_mute(void)
362 if (element[cur_element].has_playback_switch) {
363 element[cur_element].is_muted ^= 1;
364 set_mixer_state();
368 void mixer_alsa_toggle_rec(void)
370 if (element[cur_element].has_capture_switch) {
371 element[cur_element].is_recording ^= 1;
372 set_mixer_state();
376 bool mixer_alsa_is_muted(void)
378 return element[cur_element].is_muted;
381 bool mixer_alsa_is_stereo(void)
383 return element[cur_element].is_stereo;
386 bool mixer_alsa_is_rec(void)
388 return element[cur_element].is_recording;
391 bool mixer_alsa_can_rec(void)
393 return element[cur_element].has_capture;
396 void mixer_alsa_tick(void)
398 snd_mixer_handle_events(mixer);