chiark / gitweb /
copyright dates
[disorder] / disobedience / control.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2006, 2007 Richard Kettlewell
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18  * USA
19  */
20
21 #include "disobedience.h"
22
23 /* Forward declartions ----------------------------------------------------- */
24
25 struct icon;
26
27 static void update_pause(const struct icon *);
28 static void update_play(const struct icon *);
29 static void update_scratch(const struct icon *);
30 static void update_random_enable(const struct icon *);
31 static void update_random_disable(const struct icon *);
32 static void update_enable(const struct icon *);
33 static void update_disable(const struct icon *);
34 static void clicked_icon(GtkButton *, gpointer);
35
36 static double left(double v, double b);
37 static double right(double v, double b);
38 static double volume(double l, double r);
39 static double balance(double l, double r);
40
41 static void volume_adjusted(GtkAdjustment *a, gpointer user_data);
42 static gchar *format_volume(GtkScale *scale, gdouble value);
43 static gchar *format_balance(GtkScale *scale, gdouble value);
44
45 /* Control bar ------------------------------------------------------------- */
46
47 static int suppress_set_volume;
48 /* Guard against feedback loop in volume control */
49
50 static struct icon {
51   const char *icon;
52   const char *tip;
53   void (*clicked)(GtkButton *button, gpointer userdata);
54   void (*update)(const struct icon *i);
55   int (*action)(disorder_eclient *c,
56                 disorder_eclient_no_response *completed,
57                 void *v);
58   GtkWidget *button;
59 } icons[] = {
60   { "pause.png", "Pause playing track", clicked_icon, update_pause,
61     disorder_eclient_pause, 0 },
62   { "play.png", "Resume playing track", clicked_icon, update_play,
63     disorder_eclient_resume, 0 },
64   { "cross.png", "Cancel playing track", clicked_icon, update_scratch,
65     disorder_eclient_scratch_playing, 0 },
66   { "random.png", "Enable random play", clicked_icon, update_random_enable,
67     disorder_eclient_random_enable, 0 },
68   { "randomcross.png", "Disable random play", clicked_icon, update_random_disable,
69     disorder_eclient_random_disable, 0 },
70   { "notes.png", "Enable play", clicked_icon, update_enable,
71     disorder_eclient_enable, 0 },
72   { "notescross.png", "Disable play", clicked_icon, update_disable,
73     disorder_eclient_disable, 0 },
74 };
75 #define NICONS (int)(sizeof icons / sizeof *icons)
76
77 GtkAdjustment *volume_adj, *balance_adj;
78
79 /* Create the control bar */
80  GtkWidget *control_widget(void) {
81   GtkWidget *hbox = gtk_hbox_new(FALSE, 1), *vbox;
82   GtkWidget *content;
83   GdkPixbuf *pb;
84   GtkWidget *v, *b;
85   GtkTooltips *tips = gtk_tooltips_new();
86   int n;
87
88   D(("control_widget"));
89   for(n = 0; n < NICONS; ++n) {
90     icons[n].button = gtk_button_new();
91     if((pb = find_image(icons[n].icon)))
92       content = gtk_image_new_from_pixbuf(pb);
93     else
94       content = gtk_label_new(icons[n].icon);
95     gtk_container_add(GTK_CONTAINER(icons[n].button), content);
96     gtk_tooltips_set_tip(tips, icons[n].button, icons[n].tip, "");
97     g_signal_connect(G_OBJECT(icons[n].button), "clicked",
98                      G_CALLBACK(icons[n].clicked), &icons[n]);
99     /* pop the icon in a vbox so it doesn't get vertically stretch if there are
100      * taller things in the control bar */
101     vbox = gtk_vbox_new(FALSE, 0);
102     gtk_box_pack_start(GTK_BOX(vbox), icons[n].button, TRUE, FALSE, 0);
103     gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
104   }
105   /* create the adjustments for the volume control */
106   volume_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, goesupto,
107                                                  goesupto / 20, goesupto / 20,
108                                                  0));
109   balance_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -1, 1,
110                                                   0.2, 0.2, 0));
111   /* the volume control */
112   v = gtk_hscale_new(volume_adj);
113   b = gtk_hscale_new(balance_adj);
114   gtk_scale_set_digits(GTK_SCALE(v), 10);
115   gtk_scale_set_digits(GTK_SCALE(b), 10);
116   gtk_widget_set_size_request(v, 192, -1);
117   gtk_widget_set_size_request(b, 192, -1);
118   gtk_tooltips_set_tip(tips, v, "Volume", "");
119   gtk_tooltips_set_tip(tips, b, "Balance", "");
120   gtk_box_pack_start(GTK_BOX(hbox), v, FALSE, TRUE, 0);
121   gtk_box_pack_start(GTK_BOX(hbox), b, FALSE, TRUE, 0);
122   /* space updates rather than hammering the server */
123   gtk_range_set_update_policy(GTK_RANGE(v), GTK_UPDATE_DELAYED);
124   gtk_range_set_update_policy(GTK_RANGE(b), GTK_UPDATE_DELAYED);
125   /* notice when the adjustments are changed */
126   g_signal_connect(G_OBJECT(volume_adj), "value-changed",
127                    G_CALLBACK(volume_adjusted), 0);
128   g_signal_connect(G_OBJECT(balance_adj), "value-changed",
129                    G_CALLBACK(volume_adjusted), 0);
130   /* format the volume/balance values ourselves */
131   g_signal_connect(G_OBJECT(v), "format-value",
132                    G_CALLBACK(format_volume), 0);
133   g_signal_connect(G_OBJECT(b), "format-value",
134                    G_CALLBACK(format_balance), 0);
135   return hbox;
136 }
137
138 /* Update the control bar after some kind of state change */
139 void control_update(void) {
140   int n;
141   double l, r;
142
143   D(("control_update"));
144   for(n = 0; n < NICONS; ++n)
145     icons[n].update(&icons[n]);
146   l = volume_l / 100.0;
147   r = volume_r / 100.0;
148   ++suppress_set_volume;;
149   gtk_adjustment_set_value(volume_adj, volume(l, r) * goesupto);
150   gtk_adjustment_set_value(balance_adj, balance(l, r));
151   --suppress_set_volume;
152 }
153
154 static void update_icon(GtkWidget *button, 
155                         int visible, int attribute((unused)) usable) {
156   (visible ? gtk_widget_show : gtk_widget_hide)(button);
157   /* TODO: show usability */
158 }
159
160 static void update_pause(const struct icon *icon) {
161   int visible = !(last_state & DISORDER_TRACK_PAUSED);
162   int usable = playing;                 /* TODO: might be a lie */
163   update_icon(icon->button, visible, usable);
164 }
165
166 static void update_play(const struct icon *icon) {
167   int visible = !!(last_state & DISORDER_TRACK_PAUSED);
168   int usable = playing;
169   update_icon(icon->button, visible, usable);
170 }
171
172 static void update_scratch(const struct icon *icon) {
173   int visible = 1;
174   int usable = playing;
175   update_icon(icon->button, visible, usable);
176 }
177
178 static void update_random_enable(const struct icon *icon) {
179   int visible = !(last_state & DISORDER_RANDOM_ENABLED);
180   int usable = 1;
181   update_icon(icon->button, visible, usable);
182 }
183
184 static void update_random_disable(const struct icon *icon) {
185   int visible = !!(last_state & DISORDER_RANDOM_ENABLED);
186   int usable = 1;
187   update_icon(icon->button, visible, usable);
188 }
189
190 static void update_enable(const struct icon *icon) {
191   int visible = !(last_state & DISORDER_PLAYING_ENABLED);
192   int usable = 1;
193   update_icon(icon->button, visible, usable);
194 }
195
196 static void update_disable(const struct icon *icon) {
197   int visible = !!(last_state & DISORDER_PLAYING_ENABLED);
198   int usable = 1;
199   update_icon(icon->button, visible, usable);
200 }
201
202 static void clicked_icon(GtkButton attribute((unused)) *button,
203                          gpointer userdata) {
204   const struct icon *icon = userdata;
205
206   icon->action(client, 0, 0);
207 }
208
209 static void volume_adjusted(GtkAdjustment attribute((unused)) *a,
210                             gpointer attribute((unused)) user_data) {
211   double v = gtk_adjustment_get_value(volume_adj) / goesupto;
212   double b = gtk_adjustment_get_value(balance_adj);
213
214   if(suppress_set_volume)
215     /* This is the result of an update from the server, not a change from the
216      * user.  Don't feedback! */
217     return;
218   D(("volume_adjusted"));
219   /* force to 'stereotypical' values */
220   v = nearbyint(100 * v) / 100;
221   b = nearbyint(5 * b) / 5;
222   /* Set the volume.  We don't want a reply, we'll get the actual new volume
223    * from the log. */
224   disorder_eclient_volume(client, 0,
225                           nearbyint(left(v, b) * 100),
226                           nearbyint(right(v, b) * 100),
227                           0);
228 }
229
230 /* Called to format the volume value */
231 static gchar *format_volume(GtkScale attribute((unused)) *scale,
232                             gdouble value) {
233   char s[32];
234
235   snprintf(s, sizeof s, "%.1f", (double)value);
236   return g_strdup(s);
237 }
238
239 /* Called to format the balance value. */
240 static gchar *format_balance(GtkScale attribute((unused)) *scale,
241                              gdouble value) {
242   char s[32];
243
244   if(fabs(value) < 0.1)
245     return g_strdup("0");
246   snprintf(s, sizeof s, "%+.1f", (double)value);
247   return g_strdup(s);
248 }
249
250 /* Volume mapping.  We consider left, right, volume to be in [0,1]
251  * and balance to be in [-1,1].
252  * 
253  * First, we just have volume = max(left, right).
254  *
255  * Balance we consider to linearly represent the amount by which the quieter
256  * channel differs from the louder.  In detail:
257  *
258  *  if right > left then balance > 0:
259  *   balance = 0 => left = right  (as an endpoint, not an instance)
260  *   balance = 1 => left = 0
261  *   fitting to linear, left = right * (1 - balance)
262  *                so balance = 1 - left / right
263  *   (right > left => right > 0 so no division by 0.)
264  * 
265  *  if left > right then balance < 0:
266  *   balance = 0 => right = left  (same caveat as above)
267  *   balance = -1 => right = 0
268  *   again fitting to linear, right = left * (1 + balance)
269  *                       so balance = right / left - 1
270  *   (left > right => left > 0 so no division by 0.)
271  *
272  *  if left = right then we just have balance = 0.
273  *
274  * Thanks to Clive and Andrew.
275  */
276
277 static double max(double x, double y) {
278   return x > y ? x : y;
279 }
280
281 static double left(double v, double b) {
282   if(b > 0)                             /* volume = right */
283     return v * (1 - b);
284   else                                  /* volume = left */
285     return v;
286 }
287
288 static double right(double v, double b) {
289   if(b > 0)                             /* volume = right */
290     return v;
291   else                                  /* volume = left */
292     return v * (1 + b);
293 }
294
295 static double volume(double l, double r) {
296   return max(l, r);
297 }
298
299 static double balance(double l, double r) {
300   if(l > r)
301     return r / l - 1;
302   else if(r > l)
303     return 1 - l / r;
304   else                                  /* left = right */
305     return 0;
306 }
307
308 /*
309 Local Variables:
310 c-basic-offset:2
311 comment-column:40
312 fill-column:79
313 indent-tabs-mode:nil
314 End:
315 */