Mercurial > audlegacy-plugins
view src/rootvis/rootvis.c @ 1244:d0f1e147cf62
now it saves VMIX volume between sessions
author | Cristi Magherusan <majeru@atheme-project.org> |
---|---|
date | Thu, 12 Jul 2007 18:21:23 +0300 |
parents | 359cd2c10405 |
children | 761e17b23e0c |
line wrap: on
line source
#include <string.h> #include <math.h> #include <pthread.h> #include <time.h> #include "rootvis.h" // as imlib2 uses X definitions, it has to be included after the X includes, which are done in rootvis.h #include <Imlib2.h> #include "config.h" extern Window ToonGetRootWindow(Display*, int, Window*); // Forward declarations static void rootvis_init(void); static void rootvis_cleanup(void); static void rootvis_about(void); static void rootvis_configure(void); static void rootvis_playback_start(void); static void rootvis_playback_stop(void); static void rootvis_render_freq(gint16 freq_data[2][256]); static gboolean plugin_is_initted = FALSE; // Callback functions VisPlugin rootvis_vtable = { 0, // Handle, filled in by xmms 0, // Filename, filled in by xmms "Root Spectrum Analyzer 0.0.8", // description 0, // # of PCM channels for render_pcm() 2, // # of freq channels wanted for render_freq() rootvis_init, // Called when plugin is enabled rootvis_cleanup, // Called when plugin is disabled NULL,//rootvis_about, // Show the about box rootvis_configure, // Show the configure box NULL, // Called to disable plugin, filled in by xmms rootvis_playback_start, // Called when playback starts rootvis_playback_stop, // Called when playback stops NULL, // Render the PCM data, must return quickly rootvis_render_freq // Render the freq data, must return quickly }; VisPlugin *rootvis_vplist[] = { &rootvis_vtable, NULL }; DECLARE_PLUGIN(rootvis, NULL, NULL, NULL, NULL, NULL, NULL, rootvis_vplist); // X related struct rootvis_x { int screen; Display *display; Window rootWin, Parent; Pixmap rootBg; GC gc; Visual *vis; Colormap cm; Imlib_Image background; Imlib_Image buffer; }; // thread talk struct rootvis_threads { gint16 freq_data[2][256]; pthread_t worker[2]; pthread_mutex_t mutex1; enum {GO, STOP} control; char dirty; /*** dirty flaglist *** 1: channel 1 geometry change 2: channel 1 color change 4: channel 2 geometry change 8: channel 2 color change 16: no data yet (don't do anything) 32: switch mono/stereo */ } threads; // For use in config_backend: void threads_lock(void) { print_status("Locking"); pthread_mutex_lock(&threads.mutex1); } void threads_unlock(char dirty) { print_status("Unlocking"); threads.dirty = threads.dirty & dirty; pthread_mutex_unlock(&threads.mutex1); } // Some helper stuff void clean_data(void) { pthread_mutex_lock(&threads.mutex1); memset(threads.freq_data, 0, sizeof(gint16) * 2 * 256); pthread_mutex_unlock(&threads.mutex1); } void print_status(char msg[]) { if (conf.debug == 1) printf(">> rootvis >> %s\n", msg); // for debug purposes, but doesn't tell much anyway } void error_exit(char msg[]) { printf("*** ERROR (rootvis): %s\n", msg); rootvis_vtable.disable_plugin(&rootvis_vtable); } void initialize_X(struct rootvis_x* drw, char* display) { print_status("Opening X Display"); drw->display = XOpenDisplay(display); if (drw->display == NULL) { fprintf(stderr, "cannot connect to X server %s\n", getenv("DISPLAY") ? getenv("DISPLAY") : "(default)"); error_exit("Connecting to X server failed"); pthread_exit(NULL); } print_status("Getting screen and window"); drw->screen = DefaultScreen(drw->display); drw->rootWin = ToonGetRootWindow(drw->display, drw->screen, &drw->Parent); print_status("Initializing Imlib2"); drw->vis = DefaultVisual(drw->display, drw->screen); drw->cm = DefaultColormap(drw->display, drw->screen); imlib_context_set_display(drw->display); imlib_context_set_visual(drw->vis); imlib_context_set_colormap(drw->cm); imlib_context_set_dither(0); imlib_context_set_blend(1); } void draw_init(struct rootvis_x* drw, unsigned short damage_coords[4]) { Atom tmp_rootmapid, tmp_type; int tmp_format; unsigned long tmp_length, tmp_after; unsigned char *data = NULL; if ((tmp_rootmapid = XInternAtom(drw->display, "_XROOTPMAP_ID", True)) != None) { int ret = XGetWindowProperty(drw->display, drw->rootWin, tmp_rootmapid, 0L, 1L, False, AnyPropertyType, &tmp_type, &tmp_format, &tmp_length, &tmp_after,&data); if ((ret == Success)&&(tmp_type == XA_PIXMAP)&&((drw->rootBg = *((Pixmap *)data)) != None)) { pthread_mutex_lock(&threads.mutex1); imlib_context_set_drawable(drw->rootBg); drw->background = imlib_create_image_from_drawable(0, damage_coords[0], damage_coords[1], damage_coords[2], damage_coords[3], 1); pthread_mutex_unlock(&threads.mutex1); } if (drw->background == NULL) error_exit("Initial image could not be created"); } } void draw_close(struct rootvis_x* drw, unsigned short damage_coords[4]) { pthread_mutex_lock(&threads.mutex1); imlib_context_set_image(drw->background); imlib_render_image_on_drawable(damage_coords[0], damage_coords[1]); XClearArea(drw->display, drw->rootWin, damage_coords[0], damage_coords[1], damage_coords[2], damage_coords[3], True); imlib_free_image(); pthread_mutex_unlock(&threads.mutex1); } void draw_start(struct rootvis_x* drw, unsigned short damage_coords[4]) { imlib_context_set_image(drw->background); drw->buffer = imlib_clone_image(); imlib_context_set_image(drw->buffer); } void draw_end(struct rootvis_x* drw, unsigned short damage_coords[4]) { imlib_context_set_drawable(drw->rootWin); imlib_render_image_on_drawable(damage_coords[0], damage_coords[1]); imlib_free_image(); } void draw_bar(struct rootvis_x* drw, int t, int i, unsigned short level, unsigned short oldlevel, unsigned short peak, unsigned short oldpeak) { /* to make following cleaner, we work with redundant helper variables this also avoids some calculations */ register int a, b, c, d; float angle; Imlib_Color_Range range = imlib_create_color_range(); if (conf.geo[t].orientation < 2) { a = i*(conf.bar[t].width + conf.bar[t].shadow + conf.geo[t].space); c = conf.bar[t].width; b = d = 0; } else { b = (conf.data[t].cutoff/conf.data[t].div - i - 1) *(conf.bar[t].width + conf.bar[t].shadow + conf.geo[t].space); d = conf.bar[t].width; a = c = 0; } if (conf.geo[t].orientation == 0) { b = conf.geo[t].height - level; d = level; } else if (conf.geo[t].orientation == 1) { b = 0; d = level; } else if (conf.geo[t].orientation == 2) { a = 0; c = level; } else { a = conf.geo[t].height - level; c = level; } if (conf.bar[t].shadow > 0) { imlib_context_set_color(conf.bar[t].shadow_color[0], conf.bar[t].shadow_color[1], conf.bar[t].shadow_color[2], conf.bar[t].shadow_color[3]); if (conf.bar[t].gradient) imlib_image_fill_rectangle(a + conf.bar[t].shadow, b + conf.bar[t].shadow, c, d); else if (conf.bar[t].bevel) imlib_image_draw_rectangle(a + conf.bar[t].shadow, b + conf.bar[t].shadow, c, d); if (conf.peak[t].shadow > 0) { int aa = a, bb = b, cc = c, dd = d; if (conf.geo[t].orientation == 0) { bb = conf.geo[t].height - peak; dd = 1; } else if (conf.geo[t].orientation == 1) { bb = peak - 1; dd = 1; } else if (conf.geo[t].orientation == 2) { aa = peak - 1; cc = 1; } else { aa = conf.geo[t].height - peak; cc = 1; } imlib_image_fill_rectangle(aa + conf.bar[t].shadow, bb + conf.bar[t].shadow, cc, dd); } } if (conf.bar[t].gradient) { switch (conf.geo[t].orientation) { case 0: angle = 0.0; break; case 1: angle = 180.0; break; case 2: angle = 90.0; break; case 3: default: angle = -90.0; } imlib_context_set_color_range(range); 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]); imlib_add_color_to_color_range(0); 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]); imlib_add_color_to_color_range(level * 2 / 5); 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]); imlib_add_color_to_color_range(level * 4 / 5); 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]); imlib_add_color_to_color_range(level); imlib_image_fill_color_range_rectangle(a, b, c, d, angle); imlib_free_color_range(); } if (conf.bar[t].bevel) { imlib_context_set_color(conf.bar[t].bevel_color[0], conf.bar[t].bevel_color[1], conf.bar[t].bevel_color[2], conf.bar[t].bevel_color[3]); imlib_image_draw_rectangle(a, b, c, d); } if (peak > 0) { if (conf.geo[t].orientation == 0) { b = conf.geo[t].height - peak; d = 1; } else if (conf.geo[t].orientation == 1) { b = peak - 1; d = 1; } else if (conf.geo[t].orientation == 2) { a = peak - 1; c = 1; } else { a = conf.geo[t].height - peak; c = 1; } imlib_context_set_color(conf.peak[t].color[0], conf.peak[t].color[1], conf.peak[t].color[2], conf.peak[t].color[3]); imlib_image_fill_rectangle(a, b, c, d); } } // Our worker thread void* worker_func(void* threadnump) { struct rootvis_x draw; gint16 freq_data[256]; double scale = 0.0, x00 = 0.0, y00 = 0.0; unsigned int threadnum, i, j, level; unsigned short damage_coords[4]; unsigned short *level1 = NULL, *level2 = NULL, *levelsw, *peak1 = NULL, *peak2 = NULL, *peakstep; int barcount = 0; if (threadnump == NULL) threadnum = 0; else threadnum = 1; print_status("Memory allocations"); level1 = (unsigned short*)calloc(256, sizeof(short)); // need to be zeroed out level2 = (unsigned short*)malloc(256*sizeof(short)); peak1 = (unsigned short*)calloc(256, sizeof(short)); // need to be zeroed out peak2 = (unsigned short*)calloc(256, sizeof(short)); // need to be zeroed out for disabled peaks peakstep = (unsigned short*)calloc(256, sizeof(short)); // need to be zeroed out if ((level1 == NULL)||(level2 == NULL)||(peak1 == NULL)||(peak2 == NULL)||(peakstep == NULL)) { error_exit("Allocation of memory failed"); pthread_exit(NULL); } print_status("Allocations done"); draw.display = NULL; while (threads.control != STOP) { { //print_status("start sleep"); struct timespec sleeptime; sleeptime.tv_sec = 0; sleeptime.tv_nsec = 999999999 / conf.data[threadnum].fps; while (nanosleep(&sleeptime, &sleeptime) == -1) {}; //print_status("INTR"); //print_status("end sleep"); } /* we will unset our own dirty flags after receiving them */ pthread_mutex_lock(&threads.mutex1); memcpy(&freq_data, &threads.freq_data[threadnum], sizeof(gint16)*256); i = threads.dirty; if ((i & 16) == 0) threads.dirty = i & (~(3 + threadnum*9)); pthread_mutex_unlock(&threads.mutex1); if ((i & 16) == 0) { // we've gotten data if (draw.display == NULL) initialize_X(&draw, conf.geo[threadnum].display); else if (i & (1 + threadnum*3)) draw_close(&draw, damage_coords); if (i & (1 + threadnum*3)) { // geometry has changed damage_coords[0] = conf.geo[threadnum].posx; damage_coords[1] = conf.geo[threadnum].posy; if (conf.geo[threadnum].orientation < 2) { damage_coords[2] = conf.data[threadnum].cutoff/conf.data[threadnum].div *(conf.bar[threadnum].width + conf.bar[threadnum].shadow + conf.geo[threadnum].space); damage_coords[3] = conf.geo[threadnum].height + conf.bar[threadnum].shadow; } else { damage_coords[2] = conf.geo[threadnum].height + conf.bar[threadnum].shadow; damage_coords[3] = conf.data[threadnum].cutoff/conf.data[threadnum].div *(conf.bar[threadnum].width + conf.bar[threadnum].shadow + conf.geo[threadnum].space); } print_status("Geometry recalculations"); scale = conf.geo[threadnum].height / (log((1 - conf.data[threadnum].linearity) / conf.data[threadnum].linearity) * 4); x00 = conf.data[threadnum].linearity*conf.data[threadnum].linearity*32768.0 / (2*conf.data[threadnum].linearity - 1); y00 = -log(-x00) * scale; barcount = conf.data[threadnum].cutoff/conf.data[threadnum].div; memset(level1, 0, 256*sizeof(short)); memset(peak1, 0, 256*sizeof(short)); memset(peak2, 0, 256*sizeof(short)); draw_init(&draw, damage_coords); } /*if (i & (2 + threadnum*6)) { // colors have changed }*/ /* instead of copying the old level array to the second array, we just tell the first is now the second one */ levelsw = level1; level1 = level2; level2 = levelsw; levelsw = peak1; peak1 = peak2; peak2 = levelsw; for (i = 0; i < barcount; i++) { level = 0; for (j = i*conf.data[threadnum].div; j < (i+1)*conf.data[threadnum].div; j++) if (level < freq_data[j]) level = freq_data[j]; level = level * (i*conf.data[threadnum].div + 1); level = floor(abs(log(level - x00)*scale + y00)); if (level < conf.geo[threadnum].height) { if ((level2[i] > conf.bar[threadnum].falloff)&&(level < level2[i] - conf.bar[threadnum].falloff)) level1[i] = level2[i] - conf.bar[threadnum].falloff; else level1[i] = level; } else level1[i] = conf.geo[threadnum].height; if (conf.peak[threadnum].enabled) { if (level1[i] > peak2[i] - conf.peak[threadnum].falloff) { peak1[i] = level1[i]; peakstep[i] = 0; } else if (peakstep[i] == conf.peak[threadnum].step) if (peak2[i] > conf.peak[threadnum].falloff) peak1[i] = peak2[i] - conf.peak[threadnum].falloff; else peak1[i] = 0; else { peak1[i] = peak2[i]; peakstep[i]++; } } } pthread_mutex_lock(&threads.mutex1); draw_start(&draw, damage_coords); for (i = 0; i < barcount; i++) draw_bar(&draw, threadnum, i, level1[i], level2[i], peak1[i], peak2[i]); draw_end(&draw, damage_coords); pthread_mutex_unlock(&threads.mutex1); } } print_status("Worker thread: Exiting"); if (draw.display != NULL) { draw_close(&draw, damage_coords); XCloseDisplay(draw.display); } free(level1); free(level2); free(peak1); free(peak2); free(peakstep); return NULL; } // da xmms functions static void rootvis_init(void) { int rc1; print_status("Initializing"); pthread_mutex_init(&threads.mutex1, NULL); threads.control = GO; clean_data(); config_init(); threads.dirty = 31; // this means simply everything has changed and there was no data if ((rc1 = pthread_create(&threads.worker[0], NULL, worker_func, NULL))) { fprintf(stderr, "Thread creation failed: %d\n", rc1); error_exit("Thread creation failed"); } if ((conf.stereo)&&(rc1 = pthread_create(&threads.worker[1], NULL, worker_func, &rc1))) { fprintf(stderr, "Thread creation failed: %d\n", rc1); error_exit("Thread creation failed"); } plugin_is_initted = TRUE; } static void rootvis_cleanup(void) { if ( plugin_is_initted ) { print_status("Cleanup... "); threads.control = STOP; pthread_join(threads.worker[0], NULL); if (conf.stereo) pthread_join(threads.worker[1], NULL); print_status("Clean Exit"); plugin_is_initted = FALSE; } } static void rootvis_about(void) { print_status("About"); } static void rootvis_configure(void) { print_status("Configuration trigger"); config_init(); config_show(2); } static void rootvis_playback_start(void) { print_status("Playback starting"); } static void rootvis_playback_stop(void) { clean_data(); } static void rootvis_render_freq(gint16 freq_data[2][256]) { int channel, bucket; pthread_mutex_lock(&threads.mutex1); threads.dirty = threads.dirty & (~(16)); // unset no data yet flag for (channel = 0; channel < 2; channel++) { for (bucket = 0; bucket < 256; bucket++) { if (conf.stereo) threads.freq_data[channel][bucket] = freq_data[channel][bucket]; else if (channel == 0) threads.freq_data[0][bucket] = freq_data[channel][bucket] / 2; else threads.freq_data[0][bucket] += freq_data[channel][bucket] / 2; } } pthread_mutex_unlock(&threads.mutex1); }