]> mj.ucw.cz Git - ursary.git/blob - ursaryd.c
7f9d00c879d40374d8ce0393fc80533a712d022e
[ursary.git] / ursaryd.c
1 /*
2  *      The Ursary Audio Controls
3  *
4  *      (c) 2014 Martin Mares <mj@ucw.cz>
5  */
6
7 #define LOCAL_DEBUG
8
9 #include <ucw/lib.h>
10 #include <ucw/clists.h>
11 #include <ucw/mainloop.h>
12 #include <ucw/stkstring.h>
13
14 #include <stdio.h>
15 #include <string.h>
16 #include <stdlib.h>
17
18 #include <pulse/pulseaudio.h>
19
20 #include "ursaryd.h"
21
22 /*** High-level logic ***/
23
24 static struct main_timer update_timer;
25
26 static double volume_from_pa(pa_volume_t vol)
27 {
28   return (double) vol / PA_VOLUME_NORM;
29 }
30
31 static pa_volume_t volume_to_pa(double vol)
32 {
33   return vol * PA_VOLUME_NORM + 0.0001;
34 }
35
36 static void update_ring_from_sink(int ring, const char *sink_name)
37 {
38   struct pulse_sink *s = pulse_sink_by_name(sink_name);
39   if (!s)
40     {
41       noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
42       noct_set_button(ring, 0);
43       return;
44     }
45
46   if (s->mute)
47     {
48       noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
49       noct_set_button(ring, 1);
50       return;
51     }
52
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);
56 }
57
58 static void update_sink_from_rotary(int delta, const char *sink_name)
59 {
60   struct pulse_sink *s = pulse_sink_by_name(sink_name);
61   if (!s)
62     return;
63
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)
67     return;
68   pa_cvolume cvol;
69   pa_cvolume_set(&cvol, s->channels, pavol);
70
71   DBG("## Setting volume of sink %s to %d", s->name, cvol.values[0]);
72   pulse_sink_set_volume(s->idx, &cvol);
73 }
74
75 struct client_map {
76   int rotary;
77   const char *client;
78   const char *host;
79   double range;
80 };
81
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 },
87 };
88
89 #define NUM_CLIENTS ARRAY_SIZE(client_map)
90
91 struct client_state {
92   double volume;
93   bool have_muted[2];
94 };
95
96 static struct client_state client_state[NUM_CLIENTS];
97
98 static int find_client_by_rotary(int rotary)
99 {
100   uns i;
101   for (i=0; i < NUM_CLIENTS; i++)
102     if (client_map[i].rotary == rotary)
103       return i;
104   return -1;
105 }
106
107 static void calc_clients(void)
108 {
109   bzero(client_state, sizeof(client_state));
110
111   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
112     {
113       s->noct_client_idx = -1;
114
115       if (s->client_idx < 0 || s->sink_idx < 0)
116         continue;
117
118       struct pulse_client *c = pulse_client_by_idx(s->client_idx);
119       if (!c)
120         continue;
121
122       for (uns i=0; i < NUM_CLIENTS; i++)
123         {
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)))
128             {
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;
133               break;
134             }
135         }
136     }
137 }
138
139 static void update_clients(void)
140 {
141   calc_clients();
142
143   for (uns i=0; i < NUM_CLIENTS; i++)
144     {
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])
148         {
149           noct_set_ring(cm->rotary, RING_MODE_LEFT, 0);
150           noct_set_button(cm->rotary, 0);
151         }
152       else if (!cs->have_muted[0])
153         {
154           noct_set_ring(cm->rotary, RING_MODE_SINGLE_ON, 0x7f);
155           noct_set_button(cm->rotary, 1);
156         }
157       else
158         {
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);
164         }
165     }
166 }
167
168 static void update_client_from_rotary(int rotary, int delta)
169 {
170   int i = find_client_by_rotary(rotary);
171   if (i < 0)
172     return;
173   struct client_map *cm = &client_map[i];
174   struct client_state *cs = &client_state[i];
175
176   calc_clients();
177   double vol = volume_from_pa(cs->volume) + delta*0.02;
178   pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, cm->range));
179
180   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
181     {
182       if (s->noct_client_idx == i && s->volume != pavol)
183         {
184           DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol);
185           pa_cvolume cvol;
186           pa_cvolume_set(&cvol, s->channels, pavol);
187           pulse_sink_input_set_volume(s->idx, &cvol);
188         }
189     }
190 }
191
192 static void do_update(struct main_timer *t)
193 {
194   timer_del(t);
195   if (!noct_is_ready())
196     {
197       DBG("## UPDATE: Nocturn is not ready");
198       return;
199     }
200
201   static bool dead;
202   if (pulse_state != PS_ONLINE)
203     {
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++)
208         {
209           noct_set_button(i, 1);
210           noct_set_button(i+8, 0);
211         }
212       dead = 1;
213       return;
214     }
215   if (dead)
216     {
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);
222       dead = 0;
223     }
224
225   DBG("## UPDATE");
226   pulse_dump();
227
228   update_ring_from_sink(0, "ursarium");
229   update_ring_from_sink(1, "catarium");
230   update_clients();
231 }
232
233 void schedule_update(void)
234 {
235   timer_add_rel(&update_timer, 10);
236 }
237
238 void notify_rotary(int rotary, int delta)
239 {
240   if (pulse_state != PS_ONLINE)
241     {
242       DBG("## NOTIFY: Pulse is not online");
243       return;
244     }
245
246   switch (rotary)
247     {
248     case 0:
249       update_sink_from_rotary(delta, "ursarium");
250       break;
251     case 1:
252       update_sink_from_rotary(delta, "catarium");
253       break;
254     case 8:
255       update_sink_from_rotary(delta, "ursarium");
256       update_sink_from_rotary(delta, "catarium");
257       break;
258     default:
259       update_client_from_rotary(rotary, delta);
260     }
261 }
262
263 static void update_sink_mute_from_button(int on, const char *sink_name)
264 {
265   if (!on)
266     return;
267
268   struct pulse_sink *s = pulse_sink_by_name(sink_name);
269   if (!s)
270     return;
271
272   DBG("## Setting mute of sink %s to %d", s->name, !s->mute);
273   pulse_sink_set_mute(s->idx, !s->mute);
274 }
275
276 static void update_client_from_button(int button, int on)
277 {
278   if (button >= 8 || !on)
279     return;
280
281   int i = find_client_by_rotary(button);
282   if (i < 0)
283     return;
284   struct client_state *cs = &client_state[i];
285
286   calc_clients();
287   if (!cs->have_muted[0] && !cs->have_muted[1])
288     return;
289   uns mute = !cs->have_muted[1];
290
291   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
292     {
293       if (s->noct_client_idx == i)
294         {
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);
297         }
298     }
299 }
300
301 void notify_button(int button, int on)
302 {
303   if (pulse_state != PS_ONLINE)
304     {
305       DBG("## NOTIFY: Pulse is not online");
306       return;
307     }
308
309   switch (button)
310     {
311     case 0:
312       update_sink_mute_from_button(on, "ursarium");
313       break;
314     case 1:
315       update_sink_mute_from_button(on, "catarium");
316       break;
317     default:
318       update_client_from_button(button, on);
319     }
320 }
321
322 int main(int argc UNUSED, char **argv)
323 {
324   log_init(argv[0]);
325   main_init();
326   update_timer.handler = do_update;
327
328   noct_init();
329
330   msg(L_INFO, "Initializing PulseAudio");
331   pulse_init();
332
333   msg(L_INFO, "Entering main loop");
334   main_loop();
335
336   return 0;
337 }