diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/rootvis/rootvis.c	Mon Mar 26 01:19:26 2007 -0700
@@ -0,0 +1,456 @@
+#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]);
+
+// Callback functions
+VisPlugin rootvis_vtable = {
+	0, // Handle, filled in by xmms
+	0, // Filename, filled in by xmms
+
+	0,                     // Session ID
+	"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
+	0,                     // Called to disable plugin, filled in by xmms
+	rootvis_playback_start, // Called when playback starts
+	rootvis_playback_stop,  // Called when playback stops
+	0,                     // Render the PCM data, must return quickly
+	rootvis_render_freq     // Render the freq data, must return quickly
+};
+
+// XMMS entry point
+VisPlugin *get_vplugin_info(void) {
+	return &rootvis_vtable;
+}
+
+// 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");
+	}
+}
+
+static void rootvis_cleanup(void) {
+	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");
+}
+
+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);
+}