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>
20 /*** Sink controls ***/
22 static double volume_from_pa(pa_volume_t vol)
24 return (double) vol / PA_VOLUME_NORM;
27 static pa_volume_t volume_to_pa(double vol)
29 return vol * PA_VOLUME_NORM + 0.0001;
32 static void update_ring_from_sink(int ring, const char *sink_name)
34 struct pulse_sink *s = pulse_sink_by_name(sink_name);
37 noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
38 noct_set_button(ring, 0);
44 noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
45 noct_set_button(ring, 1);
49 double vol = CLAMP(volume_from_pa(s->volume), 0, 1);
50 noct_set_ring(ring, RING_MODE_LEFT, CLAMP((int)(0x7f * vol), 12, 0x7f));
51 noct_set_button(ring, 0);
54 static void update_sink_from_rotary(int delta, const char *sink_name)
56 struct pulse_sink *s = pulse_sink_by_name(sink_name);
60 double vol = volume_from_pa(s->volume) + delta * 0.02;
61 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, 1));
62 if (pavol == s->volume)
65 pa_cvolume_set(&cvol, s->channels, pavol);
67 DBG("## Setting volume of sink %s to %d", s->name, cvol.values[0]);
68 pulse_sink_set_volume(s->idx, &cvol);
71 static void update_sink_mute_from_button(int on, const char *sink_name)
76 struct pulse_sink *s = pulse_sink_by_name(sink_name);
80 DBG("## Setting mute of sink %s to %d", s->name, !s->mute);
81 pulse_sink_set_mute(s->idx, !s->mute);
84 /*** Client controls ***/
93 static struct client_map client_map[] = {
94 { 4, "Music Player Daemon", "albireo", 1 },
95 { 5, "MPlayer", NULL, 1 },
96 { 6, NULL, "ogion", 1 },
97 { 7, NULL, "ursula", 1 },
100 #define NUM_CLIENTS ARRAY_SIZE(client_map)
102 struct client_state {
107 static struct client_state client_state[NUM_CLIENTS];
109 static int find_client_by_rotary(int rotary)
112 for (i=0; i < NUM_CLIENTS; i++)
113 if (client_map[i].rotary == rotary)
118 static void calc_clients(void)
120 bzero(client_state, sizeof(client_state));
122 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
124 s->noct_client_idx = -1;
126 if (s->client_idx < 0 || s->sink_idx < 0)
129 struct pulse_client *c = pulse_client_by_idx(s->client_idx);
133 for (uns i=0; i < NUM_CLIENTS; i++)
135 struct client_map *cm = &client_map[i];
136 struct client_state *cs = &client_state[i];
137 if ((!cm->client || !strcmp(cm->client, c->name)) &&
138 (!cm->host || !strcmp(cm->host, c->host)))
140 // DBG("@@ Client #%d, sink input #%d -> rotary %d", s->client_idx, s->idx, cm->rotary);
141 s->noct_client_idx = i;
142 cs->volume = MAX(cs->volume, s->volume);
143 cs->have_muted[!!s->mute] = 1;
150 static void update_clients(void)
154 for (uns i=0; i < NUM_CLIENTS; i++)
156 struct client_map *cm = &client_map[i];
157 struct client_state *cs = &client_state[i];
158 if (!cs->have_muted[0] && !cs->have_muted[1])
160 noct_set_ring(cm->rotary, RING_MODE_LEFT, 0);
161 noct_set_button(cm->rotary, 0);
163 else if (!cs->have_muted[0])
165 noct_set_ring(cm->rotary, RING_MODE_SINGLE_ON, 0x7f);
166 noct_set_button(cm->rotary, 1);
170 double vol = CLAMP(volume_from_pa(cs->volume), 0, cm->range);
171 int val = 0x7f * vol / cm->range;
172 val = CLAMP(val, 12, 0x7f);
173 noct_set_ring(cm->rotary, RING_MODE_LEFT, val);
174 noct_set_button(cm->rotary, 0);
179 static void update_client_from_rotary(int rotary, int delta)
181 int i = find_client_by_rotary(rotary);
184 struct client_map *cm = &client_map[i];
185 struct client_state *cs = &client_state[i];
188 double vol = volume_from_pa(cs->volume) + delta*0.02;
189 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, cm->range));
191 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
193 if (s->noct_client_idx == i && s->volume != pavol)
195 DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol);
197 pa_cvolume_set(&cvol, s->channels, pavol);
198 pulse_sink_input_set_volume(s->idx, &cvol);
203 static void update_client_from_button(int button, int on)
205 if (button >= 8 || !on)
208 int i = find_client_by_rotary(button);
211 struct client_state *cs = &client_state[i];
214 if (!cs->have_muted[0] && !cs->have_muted[1])
216 uns mute = !cs->have_muted[1];
218 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
220 if (s->noct_client_idx == i)
222 DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute);
223 pulse_sink_input_set_mute(s->idx, mute);
228 static int find_touched_client(void)
232 for (uns i=0; i < NUM_CLIENTS; i++)
233 if (noct_rotary_touched[client_map[i].rotary])
242 /*** Default sink controls ***/
244 static const char *get_client_sink(int i)
246 const char *sink = NULL;
248 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
249 if (s->noct_client_idx == i)
251 struct pulse_sink *sk = (s->sink_idx >= 0) ? pulse_sink_by_idx(s->sink_idx) : NULL;
252 const char *ss = sk ? sk->name : NULL;
255 else if (strcmp(sink, ss))
261 static void update_default_sink(void)
263 int i = find_touched_client();
266 sink = get_client_sink(i);
268 sink = pulse_default_sink_name ? : "?";
270 if (!strcmp(sink, "ursarium"))
272 noct_set_button(8, 1);
273 noct_set_button(9, 0);
275 else if (!strcmp(sink, "catarium"))
277 noct_set_button(8, 0);
278 noct_set_button(9, 1);
282 noct_set_button(8, 0);
283 noct_set_button(9, 0);
287 static void update_default_sink_from_button(int button, int on)
292 int i = find_touched_client();
295 sink = get_client_sink(i);
297 sink = pulse_default_sink_name ? : "?";
299 const char *switch_to = NULL;
302 if (!strcmp(sink, "ursarium"))
303 switch_to = "burrow";
305 switch_to = "ursarium";
307 else if (button == 9)
309 if (!strcmp(sink, "catarium"))
310 switch_to = "burrow";
312 switch_to = "catarium";
320 struct pulse_sink *sk = pulse_sink_by_name(switch_to);
324 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
325 if (s->noct_client_idx == i)
327 DBG("Moving input #%d to sink #%d", s->idx, sk->idx);
328 pulse_sink_input_move(s->idx, sk->idx);
333 DBG("Switching default sink to %s", switch_to);
334 pulse_server_set_default_sink(switch_to);
338 /*** Main update routines ***/
340 static struct main_timer update_timer;
342 static void do_update(struct main_timer *t)
345 if (!noct_is_ready())
347 DBG("## UPDATE: Nocturn is not ready");
352 if (pulse_state != PS_ONLINE)
354 DBG("## UPDATE: Pulse is not online");
355 for (int i=0; i<=8; i++)
356 noct_set_ring(i, RING_MODE_LEFT, 0);
357 for (int i=0; i<8; i++)
359 noct_set_button(i, 1);
360 noct_set_button(i+8, 0);
367 DBG("## UPDATE: Waking up from the dead");
368 for (int i=0; i<=8; i++)
369 noct_set_ring(i, RING_MODE_LEFT, 0);
370 for (int i=0; i<16; i++)
371 noct_set_button(i, 0);
380 update_ring_from_sink(0, "ursarium");
381 update_ring_from_sink(1, "catarium");
383 update_default_sink();
386 void schedule_update(void)
388 timer_add_rel(&update_timer, 10);
391 void notify_rotary(int rotary, int delta)
393 if (pulse_state != PS_ONLINE)
395 DBG("## NOTIFY: Pulse is not online");
402 update_sink_from_rotary(delta, "ursarium");
405 update_sink_from_rotary(delta, "catarium");
408 update_sink_from_rotary(delta, "ursarium");
409 update_sink_from_rotary(delta, "catarium");
412 update_client_from_rotary(rotary, delta);
416 void notify_button(int button, int on)
418 if (pulse_state != PS_ONLINE)
420 DBG("## NOTIFY: Pulse is not online");
427 update_sink_mute_from_button(on, "ursarium");
430 update_sink_mute_from_button(on, "catarium");
434 update_default_sink_from_button(button, on);
437 update_client_from_button(button, on);
441 void notify_touch(int rotary, int on UNUSED)
443 if (pulse_state != PS_ONLINE)
445 DBG("## NOTIFY: Pulse is not online");
449 // Rotary touches switch meaning of LEDs, this is handled inside display updates
450 if (rotary >= 4 && rotary < 8)
454 /*** Main entry point ***/
456 int main(int argc UNUSED, char **argv)
460 update_timer.handler = do_update;
465 msg(L_DEBUG, "Entering main loop");