2 * The Ursary Audio Controls
4 * (c) 2014 Martin Mares <mj@ucw.cz>
10 #include <ucw/clists.h>
11 #include <ucw/mainloop.h>
12 #include <ucw/stkstring.h>
18 #include <pulse/pulseaudio.h>
22 /*** High-level logic ***/
24 static struct main_timer update_timer;
26 static double volume_from_pa(pa_volume_t vol)
28 return (double) vol / PA_VOLUME_NORM;
31 static pa_volume_t volume_to_pa(double vol)
33 return vol * PA_VOLUME_NORM + 0.0001;
36 static void update_ring_from_sink(int ring, const char *sink_name)
38 struct pulse_sink *s = pulse_sink_by_name(sink_name);
41 noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
42 noct_set_button(ring, 0);
48 noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
49 noct_set_button(ring, 1);
53 double vol = CLAMP(volume_from_pa(s->volume), 0, 1);
54 noct_set_ring(ring, RING_MODE_LEFT, CLAMP((int)(0x7f * vol), 12, 0x7f));
55 noct_set_button(ring, 0);
58 static void update_sink_from_rotary(int delta, const char *sink_name)
60 struct pulse_sink *s = pulse_sink_by_name(sink_name);
64 double vol = volume_from_pa(s->volume) + delta * 0.02;
65 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, 1));
66 if (pavol == s->volume)
69 pa_cvolume_set(&cvol, s->channels, pavol);
71 DBG("## Setting volume of sink %s to %d", s->name, cvol.values[0]);
72 pulse_sink_set_volume(s->idx, &cvol);
82 static struct client_map client_map[] = {
83 { 4, "Music Player Daemon", "albireo", 1 },
84 { 5, "MPlayer", NULL, 1 },
85 { 6, NULL, "ogion", 1 },
86 { 7, NULL, "ursula", 1 },
89 #define NUM_CLIENTS ARRAY_SIZE(client_map)
96 static struct client_state client_state[NUM_CLIENTS];
98 static int find_client_by_rotary(int rotary)
101 for (i=0; i < NUM_CLIENTS; i++)
102 if (client_map[i].rotary == rotary)
107 static void calc_clients(void)
109 bzero(client_state, sizeof(client_state));
111 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
113 s->noct_client_idx = -1;
115 if (s->client_idx < 0 || s->sink_idx < 0)
118 struct pulse_client *c = pulse_client_by_idx(s->client_idx);
122 for (uns i=0; i < NUM_CLIENTS; i++)
124 struct client_map *cm = &client_map[i];
125 struct client_state *cs = &client_state[i];
126 if ((!cm->client || !strcmp(cm->client, c->name)) &&
127 (!cm->host || !strcmp(cm->host, c->host)))
129 // DBG("@@ Client #%d, sink input #%d -> rotary %d", s->client_idx, s->idx, cm->rotary);
130 s->noct_client_idx = i;
131 cs->volume = MAX(cs->volume, s->volume);
132 cs->have_muted[!!s->mute] = 1;
139 static void update_clients(void)
143 for (uns i=0; i < NUM_CLIENTS; i++)
145 struct client_map *cm = &client_map[i];
146 struct client_state *cs = &client_state[i];
147 if (!cs->have_muted[0] && !cs->have_muted[1])
149 noct_set_ring(cm->rotary, RING_MODE_LEFT, 0);
150 noct_set_button(cm->rotary, 0);
152 else if (!cs->have_muted[0])
154 noct_set_ring(cm->rotary, RING_MODE_SINGLE_ON, 0x7f);
155 noct_set_button(cm->rotary, 1);
159 double vol = CLAMP(volume_from_pa(cs->volume), 0, cm->range);
160 int val = 0x7f * vol / cm->range;
161 val = CLAMP(val, 12, 0x7f);
162 noct_set_ring(cm->rotary, RING_MODE_LEFT, val);
163 noct_set_button(cm->rotary, 0);
168 static void update_client_from_rotary(int rotary, int delta)
170 int i = find_client_by_rotary(rotary);
173 struct client_map *cm = &client_map[i];
174 struct client_state *cs = &client_state[i];
177 double vol = volume_from_pa(cs->volume) + delta*0.02;
178 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, cm->range));
180 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
182 if (s->noct_client_idx == i && s->volume != pavol)
184 DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol);
186 pa_cvolume_set(&cvol, s->channels, pavol);
187 pulse_sink_input_set_volume(s->idx, &cvol);
192 static void do_update(struct main_timer *t)
195 if (!noct_is_ready())
197 DBG("## UPDATE: Nocturn is not ready");
202 if (pulse_state != PS_ONLINE)
204 DBG("## UPDATE: Pulse is not online");
205 for (int i=0; i<=8; i++)
206 noct_set_ring(i, RING_MODE_LEFT, 0);
207 for (int i=0; i<8; i++)
209 noct_set_button(i, 1);
210 noct_set_button(i+8, 0);
217 DBG("## UPDATE: Waking up from the dead");
218 for (int i=0; i<=8; i++)
219 noct_set_ring(i, RING_MODE_LEFT, 0);
220 for (int i=0; i<16; i++)
221 noct_set_button(i, 0);
228 update_ring_from_sink(0, "ursarium");
229 update_ring_from_sink(1, "catarium");
233 void schedule_update(void)
235 timer_add_rel(&update_timer, 10);
238 void notify_rotary(int rotary, int delta)
240 if (pulse_state != PS_ONLINE)
242 DBG("## NOTIFY: Pulse is not online");
249 update_sink_from_rotary(delta, "ursarium");
252 update_sink_from_rotary(delta, "catarium");
255 update_sink_from_rotary(delta, "ursarium");
256 update_sink_from_rotary(delta, "catarium");
259 update_client_from_rotary(rotary, delta);
263 static void update_sink_mute_from_button(int on, const char *sink_name)
268 struct pulse_sink *s = pulse_sink_by_name(sink_name);
272 DBG("## Setting mute of sink %s to %d", s->name, !s->mute);
273 pulse_sink_set_mute(s->idx, !s->mute);
276 static void update_client_from_button(int button, int on)
278 if (button >= 8 || !on)
281 int i = find_client_by_rotary(button);
284 struct client_state *cs = &client_state[i];
287 if (!cs->have_muted[0] && !cs->have_muted[1])
289 uns mute = !cs->have_muted[1];
291 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
293 if (s->noct_client_idx == i)
295 DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute);
296 pulse_sink_input_set_mute(s->idx, mute);
301 void notify_button(int button, int on)
303 if (pulse_state != PS_ONLINE)
305 DBG("## NOTIFY: Pulse is not online");
312 update_sink_mute_from_button(on, "ursarium");
315 update_sink_mute_from_button(on, "catarium");
318 update_client_from_button(button, on);
322 int main(int argc UNUSED, char **argv)
326 update_timer.handler = do_update;
330 msg(L_INFO, "Initializing PulseAudio");
333 msg(L_INFO, "Entering main loop");