/* Nebulus 0.8.0 - visualization plugin for XMMS * Copyright (C) 2002-2006 Pascal Brochart * * * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include "SDL.h" #include "SDL_thread.h" #include "SDL_opengl.h" #ifdef HAVE_LIBSDL_TTF #include "SDL_ttf.h" #endif #include #include #include #include #include #include #include #include #include "nebulus.h" gint32 loudness = 0, too_long; gint beat = 0, beat_compteur = 0; GLfloat scale, heights[16][16]; GLint maxtexsize; gint16 pcm_data[512]; float framerate = 50; Uint32 last_time_fps; char section_name[] = "nebulus"; static gint32 beathistory[BEAT_MAX]; static int beatbase; static SDL_Surface *opengl_screen = NULL; #ifdef HAVE_LIBSDL_TTF static SDL_Surface *text_screen = NULL, *fps_screen = NULL; #endif static SDL_Thread *draw_thread = NULL; static SDL_mutex *mutex = NULL; static void nebulus_init (void); static void nebulus_cleanup (void); static void nebulus_about (void); static void nebulus_stop (void); static void nebulus_render_pcm (gint16 data[][512]); static void nebulus_render_freq (gint16 data[][256]); nebulus general = { 640, 480, 0, 0, 4, 4, 150, 150, 0, 0, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE }; nebulus *point_general = &general; effect my_effect[EFFECT_NUMBER] = {{ "Knot", 100 }, { "Spectrum", 100 }, { "Face blur", 100 }, { "Glthreads", 100 }, { "Tunnel", 100 }, { "Tentacles", 100 }, { "Twist", 100 }, { "Child", 100 }, { "Energy", 100 }}; effect my_effect_old[EFFECT_NUMBER]; #ifdef HAVE_LIBSDL_TTF ttf_font my_ttf_font; #endif VisPlugin nebulus_vp = { NULL, NULL, 0, "Nebulus "VERSION, /* description */ 1, /* # of pcm channels wanted */ 1, /* # of freq channels wanted */ nebulus_init, /* init */ nebulus_cleanup, /* cleanup */ nebulus_about, /* about */ nebulus_config, /* configure */ NULL, /* disable_plugin */ NULL, /* playback_start */ nebulus_stop, /* playback_stop */ nebulus_render_pcm, /* render_pcm */ nebulus_render_freq /* render_freq */ }; void about_close_clicked (GtkWidget * w, GtkWidget ** window) { gtk_widget_destroy (*window); *window = NULL; } void about_closed (GtkWidget * w, GdkEvent * e, GtkWidget ** window) { about_close_clicked (w, window); } static void nebulus_about (void) { static GtkWidget *window_about = NULL; GtkWidget *vbox, *button, *close, *label; setlocale (LC_MESSAGES, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); if (window_about) return; window_about = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window_about), _("About Nebulus")); gtk_window_set_policy (GTK_WINDOW (window_about), FALSE, FALSE, FALSE); gtk_window_set_position (GTK_WINDOW (window_about), GTK_WIN_POS_MOUSE); vbox = gtk_vbox_new (FALSE, 4); gtk_container_add (GTK_CONTAINER (window_about), vbox); gtk_container_set_border_width (GTK_CONTAINER (vbox), 8); gtk_widget_show (vbox); label = gtk_label_new ("Nebulus "VERSION"\n\n\ Copyright (C) 2002-2006 Pascal Brochart\n Homepage: \ \n\ E-mail: \n\n\ This program is free software; you can redistribute it and/or\n\ modify it under the terms of the GNU General Public License\n\ as published by the Free Software Foundation; either version\n\ 2 of the Licence, or (at your option) any later version.\n\n\ You should have received a copy of the GNU General Public\n\ Licence along with this program; if not, write to the Free\n\ Software Foundation, Inc., 59 Temple Place, Suite 330,\n\ Boston, MA 02111-1307 USA"); gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 8); gtk_widget_show (label); button = gtk_hbutton_box_new (); gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 8); gtk_widget_show (button); close = gtk_button_new_with_label (_("Close")); GTK_WIDGET_SET_FLAGS (close, GTK_CAN_DEFAULT); gtk_window_set_default (GTK_WINDOW (window_about), close); gtk_hbutton_box_set_layout_default (GTK_BUTTONBOX_END); gtk_box_pack_end (GTK_BOX (button), close, FALSE, FALSE, 8); gtk_widget_show (close); gtk_signal_connect (GTK_OBJECT (close), "clicked", GTK_SIGNAL_FUNC (about_close_clicked), &window_about); gtk_signal_connect (GTK_OBJECT (window_about), "delete-event", GTK_SIGNAL_FUNC (about_closed), &window_about); gtk_widget_show (window_about); } VisPlugin * get_vplugin_info (void) { return &nebulus_vp; } int get_xmms_session() { return nebulus_vp.xmms_session; } gint disable_func (gpointer data) { nebulus_vp.disable_plugin (&nebulus_vp); return FALSE; } void init_mutexes (void) { mutex = SDL_CreateMutex(); } void destroy_mutexes (void) { SDL_DestroyMutex(mutex); } void create_window (int width, int height) { Uint32 flags; flags = SDL_OPENGL | SDL_RESIZABLE; SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); if (opengl_screen) SDL_FreeSurface(opengl_screen); opengl_screen = NULL; if (point_general->fullscreen) flags |= SDL_FULLSCREEN; if (!width || !height) { width = 32; height = 32; } opengl_screen = SDL_SetVideoMode(width, height, 16, flags); if (opengl_screen == NULL) { fprintf (stderr, "Graphic mode is not available: %s\n", SDL_GetError ()); point_general->finished = TRUE; point_general->in_thread = TRUE; } SDL_WM_SetCaption("Nebulus", NULL); } int detect_beat (gint32 loudness) { static gint32 aged; static gint32 lowest; static int elapsed; static int isquiet; static int prevbeat; int i, j; gint32 total; int sensitivity; int detected_beat; aged = (aged * 7 + loudness) >> 3; elapsed++; if (aged < 2000 || elapsed > BEAT_MAX) { elapsed = 0; lowest = aged; memset(beathistory, 0, sizeof beathistory); } else if (aged < lowest) lowest = aged; j = (beatbase + elapsed) % BEAT_MAX; beathistory[j] = loudness - aged; detected_beat = FALSE; if (elapsed > 15 && aged > 2000 && loudness * 4 > aged * 5) { for (i = BEAT_MAX / elapsed, total = 0; --i > 0; j = (j + BEAT_MAX - elapsed) % BEAT_MAX) { total += beathistory[j]; } total = total * elapsed / BEAT_MAX; sensitivity = 6; i = 3 - abs(elapsed - prevbeat)/2; if (i > 0) sensitivity += i; if (total * sensitivity > aged) { prevbeat = elapsed; beatbase = (beatbase + elapsed) % BEAT_MAX; lowest = aged; elapsed = 0; detected_beat = TRUE; } } if (aged < (isquiet ? 1500 : 500)) isquiet = TRUE; else isquiet = FALSE; return detected_beat; } void calc_fps (void) { Uint32 this_time; this_time = SDL_GetTicks(); framerate = (float)1000.0 / (this_time - last_time_fps); last_time_fps = this_time; } void calc_max_fps (void) { double target; if (point_general->max_fps) { if (framerate > point_general->max_fps) { target = (1.0 / point_general->max_fps) - (1.0 / framerate); xmms_usleep(target * 1000000); framerate = (float)point_general->max_fps; } } } int random_effect (void) { int i; gint32 rnd_effect = 0, new_effect = 0, effect = 0; for (i = 0; i < EFFECT_NUMBER; i++) rnd_effect += my_effect[i].value * 100; /* 100 pour + de precision */ if (!rnd_effect) return point_general->effect; new_effect = rand() % rnd_effect; rnd_effect = 0; for (i = 0; i < EFFECT_NUMBER; i++) { rnd_effect += my_effect[i].value * 100; /* 100 pour + de precision */ if (new_effect <= rnd_effect) { effect = i; if (!effect) effect = EFFECT_NUMBER; return effect; } } return point_general->effect; } void sdl_keypress (void) { SDL_Event event; while (SDL_PollEvent(&event)); switch (event.type) { case SDL_QUIT: point_general->finished = TRUE; point_general->in_thread = TRUE; break; case SDL_VIDEORESIZE: if (event.resize.h < point_general->HEIGHT || event.resize.h > point_general->HEIGHT || event.resize.w < point_general->WIDTH || event.resize.w > point_general->WIDTH) { point_general->WIDTH = (guint)event.resize.w; point_general->HEIGHT = (guint)event.resize.h; point_general->paused = FALSE; create_window(point_general->WIDTH, point_general->HEIGHT); } break; case SDL_KEYDOWN: switch (event.key.keysym.sym) { case SDLK_ESCAPE: point_general->mouse = !point_general->mouse; point_general->fullscreen = !point_general->fullscreen; point_general->paused = FALSE; SDL_WM_ToggleFullScreen(opengl_screen); SDL_ShowCursor(point_general->mouse); break; case SDLK_p: point_general->paused = !point_general->paused; break; case SDLK_f: point_general->freeze = !point_general->freeze; printf(" Freeze mode: "); if (point_general->freeze) printf("ON "); else printf("OFF "); break; case SDLK_z: xmms_remote_playlist_prev(0); break; case SDLK_x: xmms_remote_play(0); break; case SDLK_c: xmms_remote_pause(0); break; case SDLK_v: xmms_remote_stop(0); break; case SDLK_b: xmms_remote_playlist_next(0); break; case SDLK_s: xmms_remote_toggle_shuffle(0); break; case SDLK_r: xmms_remote_toggle_repeat(0); break; case SDLK_i: point_general->infos++; if (point_general->infos > 2) point_general->infos = 0; break; case SDLK_LEFT: xmms_remote_jump_to_time(0, xmms_remote_get_output_time(0) - 10000); break; case SDLK_RIGHT: xmms_remote_jump_to_time(0, xmms_remote_get_output_time(0) + 10000); break; case SDLK_F1: point_general->WIDTH = 640; point_general->HEIGHT = 480; point_general->paused = FALSE; create_window(point_general->WIDTH, point_general->HEIGHT); break; case SDLK_F2: point_general->WIDTH = 800; point_general->HEIGHT = 600; point_general->paused = FALSE; create_window(point_general->WIDTH, point_general->HEIGHT); break; case SDLK_F3: point_general->WIDTH = 1024; point_general->HEIGHT = 768; point_general->paused = FALSE; create_window(point_general->WIDTH, point_general->HEIGHT); break; case SDLK_F4: point_general->WIDTH = 1280; point_general->HEIGHT = 1024; point_general->paused = FALSE; create_window(point_general->WIDTH, point_general->HEIGHT); break; case SDLK_F5: point_general->WIDTH = 1600; point_general->HEIGHT = 1200; point_general->paused = FALSE; create_window(point_general->WIDTH, point_general->HEIGHT); break; default: break; } default: break; } } void calc_max_texture_size (void) { glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxtexsize); if (maxtexsize < 256) { printf("\rNeed at least 256x256 textures!"); gtk_idle_add (disable_func, NULL); } } void config_load(void) { ConfigFile *f; gchar *filename, *name; int i; filename = g_strdup_printf ("%s%s", g_get_home_dir(), "/.xmms/config"); if (!(f = xmms_cfg_open_file(filename))) return; for (i = 0; i < EFFECT_NUMBER; i++) { name = my_effect[i].name; xmms_cfg_read_int(f, section_name, name, &my_effect[i].value); if (my_effect[i].value > 100) my_effect[i].value = 100; } xmms_cfg_read_int(f, section_name, "Beat", &general.beat); xmms_cfg_read_int(f, section_name, "Fps", &general.max_fps); xmms_cfg_read_int(f, section_name, "Infos", &general.infos); xmms_cfg_read_int(f, section_name, "Width", &general.WIDTH); xmms_cfg_read_int(f, section_name, "Height", &general.HEIGHT); xmms_cfg_free(f); g_free(filename); if ((!point_general->beat) || (point_general->beat > 10)) point_general->beat = 4; if (point_general->max_fps > 200) point_general->max_fps = 120; if (point_general->infos > 2) point_general->infos = 2; if ((point_general->WIDTH < 32) || (point_general->HEIGHT < 32)) { point_general->WIDTH = 32; point_general->HEIGHT = 32; } } void load_ttf_font(void) { #ifdef HAVE_LIBSDL_TTF #ifdef TTF_FONT_NAME char *fontpath = TTF_FONT_NAME; #else char *fontpath = NULL; #endif if (!fontpath) { my_ttf_font.font = NULL; my_ttf_font.fps = NULL; return; } my_ttf_font.pos = xmms_remote_get_playlist_pos(get_xmms_session()); my_ttf_font.title = xmms_remote_get_playlist_title(get_xmms_session(), my_ttf_font.pos); my_ttf_font.size = 20; my_ttf_font.font = TTF_OpenFont(fontpath, my_ttf_font.size); my_ttf_font.fps = TTF_OpenFont(fontpath, my_ttf_font.size); if (my_ttf_font.font && my_ttf_font.fps) { TTF_SetFontStyle(my_ttf_font.font, TTF_STYLE_NORMAL); TTF_SetFontStyle(my_ttf_font.fps, TTF_STYLE_NORMAL); } #endif } void draw_infos(void) { #ifdef HAVE_LIBSDL_TTF char fps_string[32]; char *title; SDL_Color color = { 0xff, 0xff, 0xff }; sprintf(fps_string, "FPS: %.3d", (int)framerate); if (point_general->infos >= 1) { my_ttf_font.pos = xmms_remote_get_playlist_pos(get_xmms_session()); title = xmms_remote_get_playlist_title(get_xmms_session(), my_ttf_font.pos); my_ttf_font.title_refresh = FALSE; if (!my_ttf_font.title && title) { my_ttf_font.title = title; my_ttf_font.title_refresh = 1; } else if (my_ttf_font.title && title) { my_ttf_font.title_refresh = strcmp(my_ttf_font.title, title); if (my_ttf_font.title_refresh) my_ttf_font.title = title; } else if (my_ttf_font.title) my_ttf_font.title = NULL; if (my_ttf_font.title_refresh) { if (text_screen) SDL_FreeSurface(text_screen); text_screen = TTF_RenderText_Blended(my_ttf_font.font, my_ttf_font.title, color); } glEnable(GL_BLEND); glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA); glDisable(GL_NORMALIZE); glEnable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glDisable(GL_FOG); glDisable(GL_TEXTURE_2D); if (text_screen) { glPushMatrix(); viewortho(); glRasterPos2f(10.0f, 10.0f); glPixelZoom(1.0f, -1.0f); glDrawPixels(text_screen->w, text_screen->h, GL_BGRA, GL_UNSIGNED_BYTE, text_screen->pixels); viewperspective(); glPopMatrix(); } } if (point_general->infos >= 2) { if ((SDL_GetTicks() % 3 > 1) || !fps_screen) { if (fps_screen) SDL_FreeSurface(fps_screen); fps_screen = TTF_RenderText_Blended(my_ttf_font.fps, fps_string, color); } if (fps_screen) { glPushMatrix(); viewortho(); glRasterPos2f(10.0f, 10.0f + text_screen->h); glPixelZoom(1.0f, -1.0f); glDrawPixels(fps_screen->w, fps_screen->h, GL_BGRA, GL_UNSIGNED_BYTE, fps_screen->pixels); viewperspective(); glPopMatrix(); } } #endif } void draw_thread_func (void) { printf("\n-- Nebulus initialisation --"); point_general->paused = FALSE; point_general->finished = FALSE; point_general->init = FALSE; point_general->changement = FALSE; create_quadratic = FALSE; face_first = TRUE; tentacles_first = TRUE; child_first = TRUE; glthreads_first = TRUE; point_general->freeze = FALSE; if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_NOPARACHUTE) < 0) { printf ("\n\nSDL_Init... [FAILED]"); point_general->finished = TRUE; point_general->in_thread = TRUE; } else printf("\n\nSDL_Init... [OK]"); #ifdef HAVE_LIBSDL_TTF if (TTF_Init() == -1) { printf("\nSDL_Init TTF... [FAILED]"); point_general->finished = TRUE; point_general->in_thread = TRUE; } else printf("\nSDL_Init TTF... [OK]"); #endif init_mutexes(); point_general->active_mutex = TRUE; printf("\nSDL_Create mutex... [OK]"); if (!point_general->config_load) { point_general->config_load = TRUE; config_load(); } point_general->old_effect = point_general->effect; point_general->effect = random_effect(); if (point_general->effect > EFFECT_NUMBER - 1) point_general->effect = 0; #ifdef HAVE_LIBSDL_TTF load_ttf_font(); my_ttf_font.title = NULL; #endif printf("\nSDL_Create thread... [OK]"); if (tunnel_first) precalculate_tunnel(); if (glthreads_first) precalculate_glthreads(); if (!point_general->finished) { create_window(point_general->WIDTH, point_general->HEIGHT); init_gl(); printf("\nSDL_Create Gl Window... [OK]"); printf("\n\n"); calc_max_texture_size(); } while (!point_general->finished) { if (!point_general->paused) { draw_scene(); #ifdef HAVE_LIBSDL_TTF if (my_ttf_font.font && my_ttf_font.fps) draw_infos(); #endif glFinish(); calc_fps(); calc_max_fps(); printf("\rFPS: %.3d", (int)framerate); printf(" "); SDL_GL_SwapBuffers(); } else { calc_fps(); calc_max_fps(); } sdl_keypress(); } if (!face_first) glDeleteLists(facedl, 1); if (!tentacles_first) glDeleteLists(cubedl, 1); if (!child_first) glDeleteLists(childdl, 1); delete_gl_texture(knotbg); delete_gl_texture(tunnel); delete_gl_texture(tentacle); delete_gl_texture(twist); delete_gl_texture(twistbg); delete_gl_texture(texchild); delete_gl_texture(childbg); delete_gl_texture(energy); delete_gl_texture(glthreads); delete_gl_texture(particule); printf("\n\nSDL_Destroy thread... [OK]"); destroy_mutexes(); point_general->active_mutex = FALSE; printf("\nSDL_Destroy mutex... [OK]"); #ifdef HAVE_LIBSDL_TTF if (my_ttf_font.font && my_ttf_font.fps) { TTF_CloseFont(my_ttf_font.fps); my_ttf_font.fps = NULL; TTF_CloseFont(my_ttf_font.font); my_ttf_font.font = NULL; if (text_screen) SDL_FreeSurface(text_screen); text_screen = NULL; if (fps_screen) SDL_FreeSurface(fps_screen); fps_screen = NULL; TTF_Quit(); } printf("\nSDL_Quit TTF... [OK]"); #endif if (opengl_screen) SDL_FreeSurface(opengl_screen); opengl_screen = NULL; SDL_Quit(); printf("\nSDL_Quit... [OK]"); printf("\n"); if (point_general->in_thread) gtk_idle_add (disable_func, NULL); } static void nebulus_stop (void) { point_general->energy = 0; } static void nebulus_init (void) { draw_thread = SDL_CreateThread ((void *) draw_thread_func, NULL); } static void nebulus_cleanup (void) { point_general->finished = TRUE; point_general->in_thread = FALSE; if (draw_thread) SDL_WaitThread(draw_thread, NULL); } static void nebulus_render_pcm (gint16 data[2][512]) { if (point_general->active_mutex) SDL_mutexP(mutex); memcpy(pcm_data, data, sizeof(pcm_data)); if (point_general->active_mutex) SDL_mutexV(mutex); } static void nebulus_render_freq (gint16 data[2][256]) { gint i, c, y, tmp; GLfloat val, energy = 0; gint xscale[] = { 0, 1, 2, 3, 5, 7, 10, 14, 20, 28, 40, 54, 74, 101, 137, 187, 255 }; if (point_general->active_mutex) SDL_mutexP(mutex); for (y = 15; y > 0; y--) { for (i = 0; i < 16; i++) heights[y][i] = heights[y - 1][i]; } for (i = 0; i < NUM_BANDS; i++) { for (c = xscale[i], y = 0; c < xscale[i + 1]; c++) { if (data[0][c] > y) y = data[0][c]; } loudness += (y / (xscale[i + 1] - xscale[i] + 1)) * (abs (i - NUM_BANDS / 2) + NUM_BANDS / 2) * (4 + i); y >>= 7; if (y > 0) val = (log(y) * scale); else val = 0; heights[0][i] = val; } loudness /= (NUM_BANDS * 4); beat = detect_beat (loudness); too_long++; if (!point_general->freeze) { if (too_long > 1000) { too_long = 0; point_general->old_effect = point_general->effect; point_general->effect = random_effect(); point_general->changement = TRUE; } if (beat) { if (beat_compteur > point_general->beat - 1) { point_general->old_effect = point_general->effect; point_general->effect = random_effect(); point_general->changement = TRUE; beat_compteur = 0; too_long = 0; } beat_compteur += beat; } } for (i = 0; i < 256; i++) { tmp = data[0][i] >> 4; energy += tmp * tmp; } if (energy > 6) energy = 6; point_general->energy = energy; if (point_general->active_mutex) SDL_mutexV(mutex); }