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);
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] */
21 bool has_playback_switch
;
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
)
44 static int mixer_callback(__attribute__((unused
)) snd_mixer_t
*ctl
,
46 snd_mixer_elem_t
*elem
)
48 if (mask
& SND_CTL_EVENT_MASK_ADD
) {
49 snd_mixer_elem_set_callback(elem
, elem_callback
);
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
])) {
66 void mixer_alsa_init(const char *mixer_device
, bool verbose
, const char * exclude
[])
69 static struct snd_mixer_selem_regopt selem_regopt
= {
71 .abstract
= SND_MIXER_SABSTRACT_NONE
,
73 selem_regopt
.device
= mixer_device
;
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");
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
);
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
);
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
);
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
)) {
114 printf(" x: '%s' - disabled\n", element
[f
].name
);
115 elem
= snd_mixer_elem_next(elem
);
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
);
143 elem
= snd_mixer_elem_next(elem
);
148 printf(" %d: '%s'%s%s%s%s %s (%ld - %ld)\n",
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
);
166 static bool element_is_muted(int e
)
168 if (!element
[e
].has_playback_switch
)
171 snd_mixer_elem_t
*elem
= element
[e
].element
;
173 snd_mixer_selem_get_playback_switch(elem
, SND_MIXER_SCHN_FRONT_LEFT
, &left_on
);
176 if (element
[e
].is_stereo
) {
178 snd_mixer_selem_get_playback_switch(elem
, SND_MIXER_SCHN_FRONT_RIGHT
, &right_on
);
185 static bool element_is_recording(int e
)
187 if (!element
[e
].has_capture_switch
)
190 snd_mixer_elem_t
*elem
= element
[e
].element
;
192 snd_mixer_selem_get_capture_switch(elem
, SND_MIXER_SCHN_FRONT_LEFT
, &left_on
);
195 if (element
[e
].is_stereo
) {
197 snd_mixer_selem_get_capture_switch(elem
, SND_MIXER_SCHN_FRONT_RIGHT
, &right_on
);
204 static bool get_mixer_state(void)
208 needs_update
= false;
210 for (int e
= 0; e
< n_elements
; e
++) {
211 snd_mixer_elem_t
*elem
= element
[e
].element
;
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
));
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);
234 static void set_mixer_state(void)
237 long dev_left_volume
, dev_right_volume
;
238 if (!element
[cur_element
].has_playback
&& !element
[cur_element
].has_capture
)
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
?
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
?
255 if (element
[cur_element
].is_stereo
)
256 vb_to_lr(element
[cur_element
].volume
,
257 element
[cur_element
].balance
, &left
, &right
);
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
,
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
,
274 bool mixer_alsa_is_changed(void)
276 return get_mixer_state();
279 int mixer_alsa_get_channel_count(void)
284 int mixer_alsa_get_channel(void)
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
;
307 void mixer_alsa_set_channel_rel(int delta_element
)
309 cur_element
= (cur_element
+ delta_element
) % n_elements
;
311 cur_element
+= n_elements
;
315 float mixer_alsa_get_volume(void)
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
;
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);
335 float mixer_alsa_get_balance(void)
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
;
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);
360 void mixer_alsa_toggle_mute(void)
362 if (element
[cur_element
].has_playback_switch
) {
363 element
[cur_element
].is_muted
^= 1;
368 void mixer_alsa_toggle_rec(void)
370 if (element
[cur_element
].has_capture_switch
) {
371 element
[cur_element
].is_recording
^= 1;
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
);