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>
23 * rotary red button green button
24 * 0 sink Ursarium mute select as default (or assign to client selected by touch)
25 * 1 sink Catarium mute dtto
26 * 2 sink Compress - dtto
28 * 4 MPD mute play/pause/stop
37 /*** Sink controls ***/
39 static double volume_from_pa(pa_volume_t vol)
41 return (double) vol / PA_VOLUME_NORM;
44 static pa_volume_t volume_to_pa(double vol)
46 return vol * PA_VOLUME_NORM + 0.0001;
49 static void update_ring_from_sink(int ring, const char *sink_name)
51 struct pulse_sink *s = pulse_sink_by_name(sink_name);
54 noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
55 noct_set_button(ring, 0);
61 noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
62 noct_set_button(ring, 1);
66 double vol = CLAMP(volume_from_pa(s->volume), 0, 1);
67 noct_set_ring(ring, RING_MODE_LEFT, CLAMP((int)(0x7f * vol), 12, 0x7f));
68 noct_set_button(ring, 0);
71 static void update_sink_from_rotary(int delta, const char *sink_name)
73 struct pulse_sink *s = pulse_sink_by_name(sink_name);
77 double vol = volume_from_pa(s->volume) + delta * 0.02;
78 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, 1));
79 if (pavol == s->volume)
82 pa_cvolume_set(&cvol, s->channels, pavol);
84 DBG("## Setting volume of sink %s to %d", s->name, cvol.values[0]);
85 pulse_sink_set_volume(s->idx, &cvol);
88 static void update_sink_mute_from_button(int on, const char *sink_name)
93 struct pulse_sink *s = pulse_sink_by_name(sink_name);
97 DBG("## Setting mute of sink %s to %d", s->name, !s->mute);
98 pulse_sink_set_mute(s->idx, !s->mute);
101 /*** Client controls ***/
110 static struct client_map client_map[] = {
111 { 4, "Music Player Daemon", "albireo", 1.5 },
112 { 5, NULL, "albireo", 1.5 },
113 { 6, NULL, "ogion", 1.5 },
114 { 7, NULL, "ursula", 1.5 },
117 #define NUM_CLIENTS ARRAY_SIZE(client_map)
119 struct client_state {
124 static struct client_state client_state[NUM_CLIENTS];
126 static int find_client_by_rotary(int rotary)
129 for (i=0; i < NUM_CLIENTS; i++)
130 if (client_map[i].rotary == rotary)
135 static void calc_clients(void)
137 bzero(client_state, sizeof(client_state));
139 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
141 s->noct_client_idx = -1;
143 if (s->client_idx < 0 || s->sink_idx < 0)
146 struct pulse_client *c = pulse_client_by_idx(s->client_idx);
150 for (uns i=0; i < NUM_CLIENTS; i++)
152 struct client_map *cm = &client_map[i];
153 struct client_state *cs = &client_state[i];
154 if ((!cm->client || !strcmp(cm->client, c->name)) &&
155 (!cm->host || !strcmp(cm->host, c->host)))
157 // DBG("@@ Client #%d, sink input #%d -> rotary %d", s->client_idx, s->idx, cm->rotary);
158 s->noct_client_idx = i;
159 cs->volume = MAX(cs->volume, s->volume);
160 cs->have_muted[!!s->mute] = 1;
167 static void update_clients(void)
171 for (uns i=0; i < NUM_CLIENTS; i++)
173 struct client_map *cm = &client_map[i];
174 struct client_state *cs = &client_state[i];
175 if (!cs->have_muted[0] && !cs->have_muted[1])
177 noct_set_ring(cm->rotary, RING_MODE_LEFT, 0);
178 noct_set_button(cm->rotary, 0);
180 else if (!cs->have_muted[0])
182 noct_set_ring(cm->rotary, RING_MODE_SINGLE_ON, 0x7f);
183 noct_set_button(cm->rotary, 1);
187 double vol = CLAMP(volume_from_pa(cs->volume), 0, cm->range);
188 int val = 0x7f * vol / cm->range;
189 val = CLAMP(val, 12, 0x7f);
190 noct_set_ring(cm->rotary, RING_MODE_LEFT, val);
191 noct_set_button(cm->rotary, 0);
196 static void update_client_from_rotary(int rotary, int delta)
198 int i = find_client_by_rotary(rotary);
201 struct client_map *cm = &client_map[i];
202 struct client_state *cs = &client_state[i];
205 double vol = volume_from_pa(cs->volume) + delta*0.02;
206 pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, cm->range));
208 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
210 if (s->noct_client_idx == i && s->volume != pavol)
212 DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol);
214 pa_cvolume_set(&cvol, s->channels, pavol);
215 pulse_sink_input_set_volume(s->idx, &cvol);
220 static void update_client_from_button(int button, int on)
222 if (button >= 8 || !on)
225 int i = find_client_by_rotary(button);
228 struct client_state *cs = &client_state[i];
231 if (!cs->have_muted[0] && !cs->have_muted[1])
233 uns mute = !cs->have_muted[1];
235 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
237 if (s->noct_client_idx == i)
239 DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute);
240 pulse_sink_input_set_mute(s->idx, mute);
245 static int find_touched_client(void)
249 for (uns i=0; i < NUM_CLIENTS; i++)
250 if (noct_rotary_touched[client_map[i].rotary])
259 /*** Default sink controls ***/
261 static const char *get_client_sink(int i)
263 const char *sink = NULL;
265 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
266 if (s->noct_client_idx == i)
268 struct pulse_sink *sk = (s->sink_idx >= 0) ? pulse_sink_by_idx(s->sink_idx) : NULL;
269 const char *ss = sk ? sk->name : NULL;
272 else if (strcmp(sink, ss))
278 static void update_default_sink(void)
280 int i = find_touched_client();
283 sink = get_client_sink(i);
285 sink = pulse_default_sink_name ? : "?";
287 if (!strcmp(sink, "ursarium"))
289 noct_set_button(8, 1);
290 noct_set_button(9, 0);
291 noct_set_button(10, 0);
293 else if (!strcmp(sink, "catarium"))
295 noct_set_button(8, 0);
296 noct_set_button(9, 1);
297 noct_set_button(10, 0);
299 else if (!strcmp(sink, "compress"))
301 noct_set_button(8, 0);
302 noct_set_button(9, 0);
303 noct_set_button(10, 1);
307 noct_set_button(8, 0);
308 noct_set_button(9, 0);
309 noct_set_button(10, 0);
313 static void update_default_sink_from_button(int button, int on)
318 int i = find_touched_client();
321 sink = get_client_sink(i);
323 sink = pulse_default_sink_name ? : "?";
325 const char *switch_to = NULL;
328 if (!strcmp(sink, "ursarium"))
329 switch_to = "burrow";
331 switch_to = "ursarium";
333 else if (button == 9)
335 if (!strcmp(sink, "catarium"))
336 switch_to = "burrow";
338 switch_to = "catarium";
340 else if (button == 10)
342 if (!strcmp(sink, "compress"))
343 switch_to = "burrow";
345 switch_to = "compress";
353 struct pulse_sink *sk = pulse_sink_by_name(switch_to);
357 CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
358 if (s->noct_client_idx == i)
360 DBG("Moving input #%d to sink #%d", s->idx, sk->idx);
361 pulse_sink_input_move(s->idx, sk->idx);
366 DBG("Switching default sink to %s", switch_to);
367 pulse_server_set_default_sink(switch_to);
371 /*** MPD controls ***/
373 static bool mpd_flash_state;
375 static void mpd_flash_timeout(struct main_timer *t)
377 mpd_flash_state ^= 1;
378 noct_set_button(12, mpd_flash_state);
379 timer_add_rel(t, 500);
382 static struct main_timer mpd_flash_timer = {
383 .handler = mpd_flash_timeout,
386 static void update_mpd(void)
388 const char *state = mpd_get_player_state();
389 if (!strcmp(state, "play"))
391 noct_set_button(12, 1);
392 timer_del(&mpd_flash_timer);
394 else if (!strcmp(state, "pause"))
396 if (!timer_is_active(&mpd_flash_timer))
399 mpd_flash_timeout(&mpd_flash_timer);
404 noct_set_button(12, 0);
405 timer_del(&mpd_flash_timer);
409 static void mpd_button_timeout(struct main_timer *t)
416 static void update_mpd_from_button(int button UNUSED, int on)
418 static struct main_timer mpd_button_timer = {
419 .handler = mpd_button_timeout,
422 const char *state = mpd_get_player_state();
426 if (timer_is_active(&mpd_button_timer))
428 timer_del(&mpd_button_timer);
429 if (!strcmp(state, "play"))
434 else if (!strcmp(state, "pause"))
443 if (!strcmp(state, "stop"))
450 DBG("MPD starting button timer");
451 timer_add_rel(&mpd_button_timer, 1000);
455 /*** Main update routines ***/
457 static struct main_timer update_timer;
458 static timestamp_t last_touch_time;
467 static enum update_state update_state;
469 static bool want_sleep_p(void)
471 CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list)
477 static void do_update(struct main_timer *t)
479 DBG("## UPDATE in state %u", update_state);
483 if (!noct_is_ready())
485 DBG("## UPDATE: Nocturn is not ready");
486 update_state = US_OFFLINE;
489 else if (update_state == US_OFFLINE)
491 DBG("## UPDATE: Going online");
492 update_state = US_ONLINE;
496 if (!pulse_is_ready())
498 DBG("## UPDATE: Pulse is not online");
499 if (update_state != US_PULSE_DEAD)
501 update_state = US_PULSE_DEAD;
503 for (int i=0; i<8; i++)
504 noct_set_button(i, 1);
508 else if (update_state == US_PULSE_DEAD)
510 DBG("## UPDATE: Waking up from the dead");
511 update_state = US_ONLINE;
520 bool want_sleep = want_sleep_p();
522 last_touch_time = main_get_now();
523 timestamp_t since_touch = last_touch_time ? main_get_now() - last_touch_time : 0;
524 timestamp_t sleep_in = 5000;
525 if (since_touch >= sleep_in)
527 DBG("UPDATE: Sleeping");
528 if (update_state == US_ONLINE)
530 update_state = US_SLEEPING;
532 noct_set_ring(8, RING_MODE_LEFT, 127);
538 if (update_state == US_SLEEPING)
540 DBG("UPDATE: Waking up");
541 update_state = US_ONLINE;
546 timestamp_t t = sleep_in - since_touch + 10;
547 DBG("UPDATE: Scheduling sleep in %d ms", (int) t);
548 timer_add_rel(&update_timer, t);
553 update_ring_from_sink(0, "ursarium");
554 update_ring_from_sink(1, "catarium");
556 update_default_sink();
560 void schedule_update(void)
562 timer_add_rel(&update_timer, 10);
565 static bool prepare_notify(void)
567 if (!pulse_is_ready())
569 DBG("## NOTIFY: Pulse is not online");
573 last_touch_time = main_get_now();
574 if (update_state == US_SLEEPING)
576 DBG("## NOTIFY: Scheduling wakeup");
583 void notify_rotary(int rotary, int delta)
585 if (!prepare_notify())
591 update_sink_from_rotary(delta, "ursarium");
594 update_sink_from_rotary(delta, "catarium");
597 update_sink_from_rotary(delta, "ursarium");
598 update_sink_from_rotary(delta, "catarium");
601 update_client_from_rotary(rotary, delta);
605 void notify_button(int button, int on)
607 if (!prepare_notify())
613 update_sink_mute_from_button(on, "ursarium");
616 update_sink_mute_from_button(on, "catarium");
621 update_default_sink_from_button(button, on);
624 update_mpd_from_button(button, on);
627 update_client_from_button(button, on);
631 void notify_touch(int rotary, int on UNUSED)
633 if (!prepare_notify())
636 // Rotary touches switch meaning of LEDs, this is handled inside display updates
637 if (rotary >= 4 && rotary < 8)
641 /*** Main entry point ***/
643 int main(int argc UNUSED, char **argv)
647 update_timer.handler = do_update;
653 msg(L_DEBUG, "Entering main loop");