comparison src/rootvis/rootvis.c @ 900:d985f0dcdeb0 trunk

[svn] - add a starting point for xmms-rootvis port. giacomo will need to finish this up, as my XLib skills are not enough at this time.
author nenolod
date Mon, 26 Mar 2007 01:19:26 -0700
parents
children 5aaf6c282617
comparison
equal deleted inserted replaced
899:68508f8cdf25 900:d985f0dcdeb0
1 #include <string.h>
2 #include <math.h>
3 #include <pthread.h>
4 #include <time.h>
5
6 #include "rootvis.h"
7 // as imlib2 uses X definitions, it has to be included after the X includes, which are done in rootvis.h
8 #include <Imlib2.h>
9 #include "config.h"
10
11 extern Window ToonGetRootWindow(Display*, int, Window*);
12
13 // Forward declarations
14 static void rootvis_init(void);
15 static void rootvis_cleanup(void);
16 static void rootvis_about(void);
17 static void rootvis_configure(void);
18 static void rootvis_playback_start(void);
19 static void rootvis_playback_stop(void);
20 static void rootvis_render_freq(gint16 freq_data[2][256]);
21
22 // Callback functions
23 VisPlugin rootvis_vtable = {
24 0, // Handle, filled in by xmms
25 0, // Filename, filled in by xmms
26
27 0, // Session ID
28 "Root Spectrum Analyzer 0.0.8", // description
29
30 0, // # of PCM channels for render_pcm()
31 2, // # of freq channels wanted for render_freq()
32
33 rootvis_init, // Called when plugin is enabled
34 rootvis_cleanup, // Called when plugin is disabled
35 NULL,//rootvis_about, // Show the about box
36 rootvis_configure, // Show the configure box
37 0, // Called to disable plugin, filled in by xmms
38 rootvis_playback_start, // Called when playback starts
39 rootvis_playback_stop, // Called when playback stops
40 0, // Render the PCM data, must return quickly
41 rootvis_render_freq // Render the freq data, must return quickly
42 };
43
44 // XMMS entry point
45 VisPlugin *get_vplugin_info(void) {
46 return &rootvis_vtable;
47 }
48
49 // X related
50 struct rootvis_x {
51 int screen;
52 Display *display;
53 Window rootWin, Parent;
54 Pixmap rootBg;
55 GC gc;
56
57 Visual *vis;
58 Colormap cm;
59 Imlib_Image background;
60 Imlib_Image buffer;
61 };
62
63 // thread talk
64
65 struct rootvis_threads {
66 gint16 freq_data[2][256];
67 pthread_t worker[2];
68 pthread_mutex_t mutex1;
69 enum {GO, STOP} control;
70 char dirty;
71 /*** dirty flaglist ***
72 1: channel 1 geometry change
73 2: channel 1 color change
74 4: channel 2 geometry change
75 8: channel 2 color change
76 16: no data yet (don't do anything)
77 32: switch mono/stereo
78 */
79 } threads;
80
81 // For use in config_backend:
82
83 void threads_lock(void) {
84 print_status("Locking");
85 pthread_mutex_lock(&threads.mutex1);
86 }
87
88 void threads_unlock(char dirty) {
89 print_status("Unlocking");
90 threads.dirty = threads.dirty & dirty;
91 pthread_mutex_unlock(&threads.mutex1);
92 }
93
94 // Some helper stuff
95
96 void clean_data(void) {
97 pthread_mutex_lock(&threads.mutex1);
98 memset(threads.freq_data, 0, sizeof(gint16) * 2 * 256);
99 pthread_mutex_unlock(&threads.mutex1);
100 }
101
102 void print_status(char msg[]) {
103 if (conf.debug == 1) printf(">> rootvis >> %s\n", msg); // for debug purposes, but doesn't tell much anyway
104 }
105
106 void error_exit(char msg[]) {
107 printf("*** ERROR (rootvis): %s\n", msg);
108 rootvis_vtable.disable_plugin(&rootvis_vtable);
109 }
110
111 void initialize_X(struct rootvis_x* drw, char* display) {
112 print_status("Opening X Display");
113 drw->display = XOpenDisplay(display);
114 if (drw->display == NULL) {
115 fprintf(stderr, "cannot connect to X server %s\n",
116 getenv("DISPLAY") ? getenv("DISPLAY") : "(default)");
117 error_exit("Connecting to X server failed");
118 pthread_exit(NULL);
119 }
120 print_status("Getting screen and window");
121 drw->screen = DefaultScreen(drw->display);
122 drw->rootWin = ToonGetRootWindow(drw->display, drw->screen, &drw->Parent);
123
124 print_status("Initializing Imlib2");
125
126 drw->vis = DefaultVisual(drw->display, drw->screen);
127 drw->cm = DefaultColormap(drw->display, drw->screen);
128
129 imlib_context_set_display(drw->display);
130 imlib_context_set_visual(drw->vis);
131 imlib_context_set_colormap(drw->cm);
132
133 imlib_context_set_dither(0);
134 imlib_context_set_blend(1);
135 }
136
137 void draw_init(struct rootvis_x* drw, unsigned short damage_coords[4])
138 {
139 Atom tmp_rootmapid, tmp_type;
140 int tmp_format;
141 unsigned long tmp_length, tmp_after;
142 unsigned char *data = NULL;
143
144 if ((tmp_rootmapid = XInternAtom(drw->display, "_XROOTPMAP_ID", True)) != None)
145 {
146 int ret = XGetWindowProperty(drw->display, drw->rootWin, tmp_rootmapid, 0L, 1L, False, AnyPropertyType,
147 &tmp_type, &tmp_format, &tmp_length, &tmp_after,&data);
148 if ((ret == Success)&&(tmp_type == XA_PIXMAP)&&((drw->rootBg = *((Pixmap *)data)) != None)) {
149 pthread_mutex_lock(&threads.mutex1);
150 imlib_context_set_drawable(drw->rootBg);
151 drw->background = imlib_create_image_from_drawable(0, damage_coords[0], damage_coords[1], damage_coords[2], damage_coords[3], 1);
152 pthread_mutex_unlock(&threads.mutex1);
153 }
154 if (drw->background == NULL)
155 error_exit("Initial image could not be created");
156 }
157 }
158
159 void draw_close(struct rootvis_x* drw, unsigned short damage_coords[4]) {
160 pthread_mutex_lock(&threads.mutex1);
161 imlib_context_set_image(drw->background);
162 imlib_render_image_on_drawable(damage_coords[0], damage_coords[1]);
163 XClearArea(drw->display, drw->rootWin, damage_coords[0], damage_coords[1], damage_coords[2], damage_coords[3], True);
164 imlib_free_image();
165 pthread_mutex_unlock(&threads.mutex1);
166 }
167
168 void draw_start(struct rootvis_x* drw, unsigned short damage_coords[4]) {
169 imlib_context_set_image(drw->background);
170 drw->buffer = imlib_clone_image();
171 imlib_context_set_image(drw->buffer);
172 }
173
174 void draw_end(struct rootvis_x* drw, unsigned short damage_coords[4]) {
175 imlib_context_set_drawable(drw->rootWin);
176 imlib_render_image_on_drawable(damage_coords[0], damage_coords[1]);
177 imlib_free_image();
178 }
179
180 void draw_bar(struct rootvis_x* drw, int t, int i, unsigned short level, unsigned short oldlevel, unsigned short peak, unsigned short oldpeak) {
181
182 /* to make following cleaner, we work with redundant helper variables
183 this also avoids some calculations */
184 register int a, b, c, d;
185 float angle;
186 Imlib_Color_Range range = imlib_create_color_range();
187
188 if (conf.geo[t].orientation < 2) {
189 a = i*(conf.bar[t].width + conf.bar[t].shadow + conf.geo[t].space);
190 c = conf.bar[t].width;
191 b = d = 0;
192 } else {
193 b = (conf.data[t].cutoff/conf.data[t].div - i - 1)
194 *(conf.bar[t].width + conf.bar[t].shadow + conf.geo[t].space);
195 d = conf.bar[t].width;
196 a = c = 0;
197 }
198
199 if (conf.geo[t].orientation == 0) { b = conf.geo[t].height - level; d = level; }
200 else if (conf.geo[t].orientation == 1) { b = 0; d = level; }
201 else if (conf.geo[t].orientation == 2) { a = 0; c = level; }
202 else { a = conf.geo[t].height - level; c = level; }
203
204 if (conf.bar[t].shadow > 0) {
205 imlib_context_set_color(conf.bar[t].shadow_color[0], conf.bar[t].shadow_color[1],
206 conf.bar[t].shadow_color[2], conf.bar[t].shadow_color[3]);
207 if (conf.bar[t].gradient)
208 imlib_image_fill_rectangle(a + conf.bar[t].shadow, b + conf.bar[t].shadow, c, d);
209 else if (conf.bar[t].bevel)
210 imlib_image_draw_rectangle(a + conf.bar[t].shadow, b + conf.bar[t].shadow, c, d);
211
212 if (conf.peak[t].shadow > 0)
213 {
214 int aa = a, bb = b, cc = c, dd = d;
215 if (conf.geo[t].orientation == 0) { bb = conf.geo[t].height - peak; dd = 1; }
216 else if (conf.geo[t].orientation == 1) { bb = peak - 1; dd = 1; }
217 else if (conf.geo[t].orientation == 2) { aa = peak - 1; cc = 1; }
218 else { aa = conf.geo[t].height - peak; cc = 1; }
219 imlib_image_fill_rectangle(aa + conf.bar[t].shadow, bb + conf.bar[t].shadow, cc, dd);
220 }
221 }
222
223 if (conf.bar[t].gradient)
224 {
225 switch (conf.geo[t].orientation) {
226 case 0: angle = 0.0; break;
227 case 1: angle = 180.0; break;
228 case 2: angle = 90.0; break;
229 case 3: default:
230 angle = -90.0;
231 }
232
233 imlib_context_set_color_range(range);
234 imlib_context_set_color(conf.bar[t].color[3][0], conf.bar[t].color[3][1], conf.bar[t].color[3][2], conf.bar[t].color[3][3]);
235 imlib_add_color_to_color_range(0);
236 imlib_context_set_color(conf.bar[t].color[2][0], conf.bar[t].color[2][1], conf.bar[t].color[2][2], conf.bar[t].color[2][3]);
237 imlib_add_color_to_color_range(level * 2 / 5);
238 imlib_context_set_color(conf.bar[t].color[1][0], conf.bar[t].color[1][1], conf.bar[t].color[1][2], conf.bar[t].color[1][3]);
239 imlib_add_color_to_color_range(level * 4 / 5);
240 imlib_context_set_color(conf.bar[t].color[0][0], conf.bar[t].color[0][1], conf.bar[t].color[0][2], conf.bar[t].color[0][3]);
241 imlib_add_color_to_color_range(level);
242 imlib_image_fill_color_range_rectangle(a, b, c, d, angle);
243 imlib_free_color_range();
244 }
245
246 if (conf.bar[t].bevel)
247 {
248 imlib_context_set_color(conf.bar[t].bevel_color[0], conf.bar[t].bevel_color[1],
249 conf.bar[t].bevel_color[2], conf.bar[t].bevel_color[3]);
250 imlib_image_draw_rectangle(a, b, c, d);
251 }
252
253 if (peak > 0) {
254 if (conf.geo[t].orientation == 0) { b = conf.geo[t].height - peak; d = 1; }
255 else if (conf.geo[t].orientation == 1) { b = peak - 1; d = 1; }
256 else if (conf.geo[t].orientation == 2) { a = peak - 1; c = 1; }
257 else { a = conf.geo[t].height - peak; c = 1; }
258 imlib_context_set_color(conf.peak[t].color[0], conf.peak[t].color[1], conf.peak[t].color[2], conf.peak[t].color[3]);
259 imlib_image_fill_rectangle(a, b, c, d);
260 }
261 }
262
263 // Our worker thread
264
265 void* worker_func(void* threadnump) {
266 struct rootvis_x draw;
267 gint16 freq_data[256];
268 double scale = 0.0, x00 = 0.0, y00 = 0.0;
269 unsigned int threadnum, i, j, level;
270 unsigned short damage_coords[4];
271 unsigned short *level1 = NULL, *level2 = NULL, *levelsw, *peak1 = NULL, *peak2 = NULL, *peakstep;
272 int barcount = 0;
273
274 if (threadnump == NULL) threadnum = 0; else threadnum = 1;
275
276 print_status("Memory allocations");
277 level1 = (unsigned short*)calloc(256, sizeof(short)); // need to be zeroed out
278 level2 = (unsigned short*)malloc(256*sizeof(short));
279 peak1 = (unsigned short*)calloc(256, sizeof(short)); // need to be zeroed out
280 peak2 = (unsigned short*)calloc(256, sizeof(short)); // need to be zeroed out for disabled peaks
281 peakstep = (unsigned short*)calloc(256, sizeof(short)); // need to be zeroed out
282 if ((level1 == NULL)||(level2 == NULL)||(peak1 == NULL)||(peak2 == NULL)||(peakstep == NULL)) {
283 error_exit("Allocation of memory failed");
284 pthread_exit(NULL);
285 }
286 print_status("Allocations done");
287
288 draw.display = NULL;
289
290 while (threads.control != STOP) {
291
292 {
293 //print_status("start sleep");
294 struct timespec sleeptime;
295 sleeptime.tv_sec = 0;
296 sleeptime.tv_nsec = 999999999 / conf.data[threadnum].fps;
297 while (nanosleep(&sleeptime, &sleeptime) == -1) {}; //print_status("INTR");
298 //print_status("end sleep");
299 }
300
301 /* we will unset our own dirty flags after receiving them */
302 pthread_mutex_lock(&threads.mutex1);
303 memcpy(&freq_data, &threads.freq_data[threadnum], sizeof(gint16)*256);
304 i = threads.dirty;
305 if ((i & 16) == 0) threads.dirty = i & (~(3 + threadnum*9));
306 pthread_mutex_unlock(&threads.mutex1);
307
308 if ((i & 16) == 0) { // we've gotten data
309 if (draw.display == NULL) initialize_X(&draw, conf.geo[threadnum].display);
310 else if (i & (1 + threadnum*3)) draw_close(&draw, damage_coords);
311
312 if (i & (1 + threadnum*3)) { // geometry has changed
313 damage_coords[0] = conf.geo[threadnum].posx;
314 damage_coords[1] = conf.geo[threadnum].posy;
315 if (conf.geo[threadnum].orientation < 2) {
316 damage_coords[2] = conf.data[threadnum].cutoff/conf.data[threadnum].div
317 *(conf.bar[threadnum].width + conf.bar[threadnum].shadow + conf.geo[threadnum].space);
318 damage_coords[3] = conf.geo[threadnum].height + conf.bar[threadnum].shadow;
319 } else {
320 damage_coords[2] = conf.geo[threadnum].height + conf.bar[threadnum].shadow;
321 damage_coords[3] = conf.data[threadnum].cutoff/conf.data[threadnum].div
322 *(conf.bar[threadnum].width + conf.bar[threadnum].shadow + conf.geo[threadnum].space);
323 }
324 print_status("Geometry recalculations");
325 scale = conf.geo[threadnum].height /
326 (log((1 - conf.data[threadnum].linearity) / conf.data[threadnum].linearity) * 4);
327 x00 = conf.data[threadnum].linearity*conf.data[threadnum].linearity*32768.0 /
328 (2*conf.data[threadnum].linearity - 1);
329 y00 = -log(-x00) * scale;
330 barcount = conf.data[threadnum].cutoff/conf.data[threadnum].div;
331 memset(level1, 0, 256*sizeof(short));
332 memset(peak1, 0, 256*sizeof(short));
333 memset(peak2, 0, 256*sizeof(short));
334
335 draw_init(&draw, damage_coords);
336 }
337 /*if (i & (2 + threadnum*6)) { // colors have changed
338 }*/
339
340 /* instead of copying the old level array to the second array,
341 we just tell the first is now the second one */
342 levelsw = level1;
343 level1 = level2;
344 level2 = levelsw;
345 levelsw = peak1;
346 peak1 = peak2;
347 peak2 = levelsw;
348
349 for (i = 0; i < barcount; i++) {
350 level = 0;
351 for (j = i*conf.data[threadnum].div; j < (i+1)*conf.data[threadnum].div; j++)
352 if (level < freq_data[j])
353 level = freq_data[j];
354 level = level * (i*conf.data[threadnum].div + 1);
355 level = floor(abs(log(level - x00)*scale + y00));
356 if (level < conf.geo[threadnum].height) {
357 if ((level2[i] > conf.bar[threadnum].falloff)&&(level < level2[i] - conf.bar[threadnum].falloff))
358 level1[i] = level2[i] - conf.bar[threadnum].falloff;
359 else level1[i] = level;
360 } else level1[i] = conf.geo[threadnum].height;
361 if (conf.peak[threadnum].enabled) {
362 if (level1[i] > peak2[i] - conf.peak[threadnum].falloff) {
363 peak1[i] = level1[i];
364 peakstep[i] = 0;
365 } else if (peakstep[i] == conf.peak[threadnum].step)
366 if (peak2[i] > conf.peak[threadnum].falloff)
367 peak1[i] = peak2[i] - conf.peak[threadnum].falloff;
368 else peak1[i] = 0;
369 else {
370 peak1[i] = peak2[i];
371 peakstep[i]++;
372 }
373 }
374 }
375
376 pthread_mutex_lock(&threads.mutex1);
377 draw_start(&draw, damage_coords);
378 for (i = 0; i < barcount; i++)
379 draw_bar(&draw, threadnum, i, level1[i], level2[i], peak1[i], peak2[i]);
380 draw_end(&draw, damage_coords);
381 pthread_mutex_unlock(&threads.mutex1);
382 }
383 }
384 print_status("Worker thread: Exiting");
385 if (draw.display != NULL) {
386 draw_close(&draw, damage_coords);
387 XCloseDisplay(draw.display);
388 }
389 free(level1); free(level2); free(peak1); free(peak2); free(peakstep);
390 return NULL;
391 }
392
393
394 // da xmms functions
395
396 static void rootvis_init(void) {
397 int rc1;
398 print_status("Initializing");
399 pthread_mutex_init(&threads.mutex1, NULL);
400 threads.control = GO;
401 clean_data();
402 config_init();
403 threads.dirty = 31; // this means simply everything has changed and there was no data
404 if ((rc1 = pthread_create(&threads.worker[0], NULL, worker_func, NULL))) {
405 fprintf(stderr, "Thread creation failed: %d\n", rc1);
406 error_exit("Thread creation failed");
407 }
408 if ((conf.stereo)&&(rc1 = pthread_create(&threads.worker[1], NULL, worker_func, &rc1))) {
409 fprintf(stderr, "Thread creation failed: %d\n", rc1);
410 error_exit("Thread creation failed");
411 }
412 }
413
414 static void rootvis_cleanup(void) {
415 print_status("Cleanup... ");
416 threads.control = STOP;
417 pthread_join(threads.worker[0], NULL);
418 if (conf.stereo) pthread_join(threads.worker[1], NULL);
419 print_status("Clean Exit");
420 }
421
422 static void rootvis_about(void)
423 {
424 print_status("About");
425 }
426
427 static void rootvis_configure(void)
428 {
429 print_status("Configuration trigger");
430 config_init();
431 config_show(2);
432 }
433
434 static void rootvis_playback_start(void)
435 {
436 print_status("Playback starting");
437 }
438
439 static void rootvis_playback_stop(void)
440 {
441 clean_data();
442 }
443
444 static void rootvis_render_freq(gint16 freq_data[2][256]) {
445 int channel, bucket;
446 pthread_mutex_lock(&threads.mutex1);
447 threads.dirty = threads.dirty & (~(16)); // unset no data yet flag
448 for (channel = 0; channel < 2; channel++) {
449 for (bucket = 0; bucket < 256; bucket++) {
450 if (conf.stereo) threads.freq_data[channel][bucket] = freq_data[channel][bucket];
451 else if (channel == 0) threads.freq_data[0][bucket] = freq_data[channel][bucket] / 2;
452 else threads.freq_data[0][bucket] += freq_data[channel][bucket] / 2;
453 }
454 }
455 pthread_mutex_unlock(&threads.mutex1);
456 }