1 // projectM.cpp
2 // weed plugin
3 // (c) G. Finch (salsaman) 2014 - 2020
4 //
5 // released under the GNU GPL 3 or later
6 // see file COPYING or www.gnu.org for details
7
8 ///////////////////////////////////////////////////////////////////
9
10 static int package_version = 2; // version of this package
11
12 //////////////////////////////////////////////////////////////////
13
14 #define NEED_AUDIO
15 #define NEED_PALETTE_UTILS
16 #define NEED_RANDOM
17
18 #ifndef NEED_LOCAL_WEED_PLUGIN
19 #include <weed/weed-plugin.h>
20 #include <weed/weed-utils.h> // optional
21 #include <weed/weed-plugin-utils.h> // optional
22 #else
23 #include "../../libweed/weed-plugin.h"
24 #include "../../libweed/weed-utils.h" // optional
25 #include "../../libweed/weed-plugin-utils.h" // optional
26 #endif
27
28 #include "weed-plugin-utils.c" // optional
29
30 static int verbosity = WEED_VERBOSITY_ERROR;
31 #define WORKER_TIMEOUT_SEC 30 /// how long to wait for worker thread startup
32 #define MAX_AUDLEN 2048 /// this is defined by projectM itself, increasing the value above 2048 will only result in jumps in the audio
33 #define DEF_SENS 1. /// beat sensitivity 0. -> 5. (lower is more sensitive); too high -> less dynamic, too low - nothing w. silence
34 /////////////////////////////////////////////////////////////
35
36 #define USE_DBLBUF 1
37
38 #include <libprojectM/projectM.hpp>
39
40 #include <GL/gl.h>
41
42 #include <SDL.h>
43
44 #ifndef HAVE_SDL2
45 #include <SDL_syswm.h>
46 #endif
47
48 #include <pthread.h>
49
50 #include <limits.h>
51
52 #include <sys/time.h>
53
54 #include <errno.h>
55 #include <unistd.h>
56
57 #include "projectM-ConfigFile.h"
58 #include "projectM-getConfigFilename.h"
59 #include <X11/extensions/Xrender.h>
60 #include <X11/Xatom.h>
61
62 #define PREF_FPS 50.
63 #define MESHSIZE 128
64
65 static int copies = 0;
66
67 static const GLushort RGB2ARGB[8] = {0, 1, 1, 2, 2, 3, 4, 0};
68 static const GLushort RGB2BGR[4] = {0, 2, 2, 0};
69
70 static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
71 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
72 static pthread_mutex_t cond_mutex = PTHREAD_MUTEX_INITIALIZER;
73 static struct timespec ts;
74
75 static int count = 0;
76 static int pcount = 0;
77 static int blanks = 0;
78
79 typedef struct {
80 projectM *globalPM;
81 GLubyte *fbuffer;
82 int textureHandle;
83 int width, height, texsize;
84 int palette, psize;
85 int rowstride;
86 volatile bool worker_ready;
87 volatile bool worker_active;
88 int pidx, opidx;
89 int nprs;
90 char **prnames;
91 uint8_t *bad_programs;
92 int program;
93 bool bad_prog, checkforblanks;
94 pthread_mutex_t mutex, pcm_mutex;
95 pthread_t thread;
96 size_t audio_frames, abufsize, audio_offs;
97 int achans;
98 float *audio;
99 float fps, tfps;
100 float ncycs;
101 int cycadj;
102 volatile bool die;
103 volatile bool failed;
104 bool update_size, update_psize;
105 volatile bool needs_more;
106 volatile bool rendering;
107 volatile bool needs_update;
108 bool set_update;
109 bool got_first;
110 #ifdef HAVE_SDL2
111 SDL_Window *win;
112 SDL_GLContext glCtx;
113 #endif
114 weed_error_t error;
115 double timer;
116 weed_timecode_t timestamp;
117 } _sdata;
118
119 static _sdata *statsd;
120
121 static int maxwidth, maxheight;
122
123 static int inited = 0;
124
125 #ifndef HAVE_SDL2
winhide()126 static void winhide() {
127 SDL_SysWMinfo info;
128
129 Atom atoms[2];
130 SDL_VERSION(&info.version);
131 if (SDL_GetWMInfo(&info)) {
132 Window win = info.info.x11.wmwindow;
133 Display *dpy = info.info.x11.display;
134 info.info.x11.lock_func();
135
136 atoms[0] = XInternAtom(dpy, "_NET_WM_STATE_BELOW", False);
137 atoms[1] = XInternAtom(dpy, "_NET_WM_STATE_DESKTOP", False);
138 XChangeProperty(dpy, win, XInternAtom(dpy, "_NET_WM_STATE", False), XA_ATOM, 32,
139 PropModeReplace, (const unsigned char *) &atoms, 2);
140
141 XIconifyWindow(dpy, win, 0);
142
143 XFlush(dpy);
144 info.info.x11.unlock_func();
145 }
146 }
147
148
resize_display(int width,int height)149 static int resize_display(int width, int height) {
150 int flags = SDL_OPENGL | SDL_HWSURFACE | SDL_RESIZABLE;
151
152 // 0 : use current bits per pixel
153 if (!SDL_SetVideoMode(width, height, 0, flags)) {
154 if (verbosity >= WEED_VERBOSITY_CRITICAL)
155 fprintf(stderr, "{ProjectM plugin: Video mode set failed: %s\n", SDL_GetError());
156 return 1;
157 }
158
159 winhide();
160
161 return 0;
162 }
163 #endif
164
165
change_size(_sdata * sdata)166 static int change_size(_sdata *sdata) {
167 int ret = 0;
168 int newsize = sdata->width;
169 if (sdata->height > newsize) newsize = sdata->height;
170 //std::cerr << "Set new size to " << newsize << std::endl;
171
172 // must be done in this exact order, else projectM (SOIL) crashes...
173
174 sdata->globalPM->projectM_resetGL(sdata->width, sdata->height);
175
176 #ifdef HAVE_SDL2
177 SDL_SetWindowSize(sdata->win, sdata->width, sdata->height);
178 #else
179 ret = resize_display(sdata->width, sdata->height);
180 #endif
181 sdata->texsize = newsize;
182
183 sdata->globalPM->changeTextureSize(newsize);
184 sdata->globalPM->projectM_resetTextures();
185 //sdata->textureHandle = sdata->globalPM->initRenderToTexture();
186 return ret;
187 }
188
189
init_display(_sdata * sd)190 static int init_display(_sdata *sd) {
191 int defwidth = sd->width;
192 int defheight = sd->height;
193
194 /* First, initialize SDL's video subsystem. */
195 if (SDL_Init(SDL_INIT_VIDEO) < 0) {
196 if (verbosity >= WEED_VERBOSITY_CRITICAL)
197 fprintf(stderr, "{ProjectM plugin: Video initialization failed: %s\n",
198 SDL_GetError());
199 return 1;
200 }
201
202 /* Let's get some video information. */
203 #ifdef HAVE_SDL2
204 SDL_Rect rect;
205 SDL_GetDisplayBounds(0, &rect);
206 maxwidth = rect.w;
207 maxheight = rect.h;
208 if (verbosity >= WEED_VERBOSITY_DEBUG)
209 fprintf(stderr, "ProjectM running with SDL 2\n");
210 #else
211 const SDL_VideoInfo *info = SDL_GetVideoInfo();
212 if (verbosity >= WEED_VERBOSITY_DEBUG)
213 fprintf(stderr, "ProjectM running with SDL 1\n");
214 if (!info) {
215 /* This should probably never happen. */
216 if (verbosity >= WEED_VERBOSITY_CRITICAL)
217 fprintf(stderr, "ProjectM plugin: Video query failed: %s\n",
218 SDL_GetError());
219 return 2;
220 }
221 maxwidth = info->current_w;
222 maxheight = info->current_h;
223 #endif
224
225 if (verbosity >= WEED_VERBOSITY_DEBUG)
226 printf("Screen Resolution: %d x %d\n", maxwidth, maxheight);
227
228 if (defwidth > maxwidth) defwidth = maxwidth;
229 if (defheight > maxheight) defheight = maxheight;
230
231 //SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
232 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
233 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
234 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
235 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
236 SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32);
237 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 8);
238 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, USE_DBLBUF);
239
240 #ifdef HAVE_SDL2
241 sd->win = SDL_CreateWindow("projectM", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, defwidth, defheight,
242 SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN);
243 sd->glCtx = SDL_GL_CreateContext(sd->win);
244 #else
245 if (resize_display(defwidth, defheight)) return 3;
246 #endif
247 //if (change_size(sd)) return 4;
248
249 return 0;
250 }
251
252
resize_buffer(_sdata * sd)253 bool resize_buffer(_sdata *sd) {
254 size_t align = 1;
255 if ((sd->rowstride & 0X01) == 0) {
256 if ((sd->rowstride & 0X03) == 0) {
257 if ((sd->rowstride & 0X07) == 0) {
258 if ((sd->rowstride & 0X0F) == 0) {
259 align = 16;
260 } else align = 8;
261 } else align = 4;
262 } else align = 2;
263 }
264 if (sd->fbuffer) weed_free(sd->fbuffer);
265 sd->fbuffer = (GLubyte *)weed_calloc(sizeof(GLubyte) * sd->rowstride * sd->height / align, align);
266 if (!sd->fbuffer) return false;
267 return true;
268 }
269
270
render_frame(_sdata * sd)271 static int render_frame(_sdata *sd) {
272 float yscale = (float)sd->height / sd->texsize * 2.;
273 float xscale = (float)sd->width / sd->texsize * 2.;
274 bool checked_audio = false;
275
276 if (sd->needs_update || !sd->got_first) {
277 if (sd->needs_update) {
278 pthread_mutex_lock(&cond_mutex);
279 sd->needs_update = false;
280 pthread_cond_signal(&cond);
281 while (!sd->set_update) {
282 pthread_cond_wait(&cond, &cond_mutex);
283 }
284 pthread_mutex_unlock(&cond_mutex);
285 sd->set_update = false;
286 if (sd->update_size) {
287 glFlush();
288 change_size(sd);
289 sd->update_size = false;
290 }
291 if (sd->update_psize) {
292 if (!resize_buffer(sd)) {
293 sd->error = WEED_ERROR_MEMORY_ALLOCATION;
294 sd->update_psize = false;
295 sd->needs_more = false;
296 pthread_mutex_lock(&cond_mutex);
297 pthread_cond_signal(&cond);
298 pthread_mutex_unlock(&cond_mutex);
299 return 1;
300 }
301 }
302 }
303 if (sd->update_psize || !sd->got_first) {
304 sd->update_psize = false;
305 glPixelStorei(GL_UNPACK_ROW_LENGTH, sd->rowstride / sd->psize);
306 if (sd->psize == 4) {
307 if (sd->palette == WEED_PALETTE_BGRA32) {
308 glPixelMapusv(GL_PIXEL_MAP_I_TO_I, 4, RGB2BGR);
309 } else if (sd->palette == WEED_PALETTE_ARGB32) {
310 glPixelMapusv(GL_PIXEL_MAP_I_TO_I, 8, RGB2ARGB);
311 }
312 } else {
313 if (sd->palette == WEED_PALETTE_BGR24) {
314 glPixelMapusv(GL_PIXEL_MAP_I_TO_I, 4, RGB2BGR);
315 }
316 }
317 }
318 sd->needs_more = true;
319 }
320
321 glFlush();
322
323 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
324 //glEnable(GL_DEPTH_TEST);
325 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
326 glEnable(GL_BLEND);
327
328 glViewport(0, 0, sd->width, sd->height);
329
330 #define NORM_AUDIO
331
332 if (sd->needs_more || sd->audio_frames > sd->audio_offs) {
333 size_t audlen = MAX_AUDLEN;
334 #ifdef NORM_AUDIO
335 float maxvol = 0., myvol;
336 size_t i;
337 #endif
338 pthread_mutex_lock(&sd->pcm_mutex);
339 if (sd->audio_frames - sd->audio_offs < audlen)
340 audlen = sd->audio_frames - sd->audio_offs;
341 if (audlen) {
342 #ifdef NORM_AUDIO
343 for (i = 0; i < audlen; i++) {
344 if ((myvol = fabsf(sd->audio[sd->audio_offs + i]) > maxvol)) maxvol = myvol;
345 if (maxvol > .8) break;
346 }
347 if (i == audlen && maxvol > 0.05) {
348 for (i = 0; i < audlen; i++) {
349 sd->audio[sd->audio_offs + i] /= maxvol;
350 }
351 }
352 #endif
353 sd->globalPM->pcm()->addPCMfloat(sd->audio + sd->audio_offs, audlen);
354 sd->audio_offs += audlen;
355 }
356 pthread_mutex_unlock(&sd->pcm_mutex);
357 checked_audio = true;
358 if (sd->ncycs > 1.) sd->cycadj = -(int)(sd->ncycs);
359 }
360
361 sd->globalPM->renderFrame();
362 pcount++;
363
364 if (sd->needs_more && checked_audio) {
365 glMatrixMode(GL_PROJECTION);
366 glOrtho(0, 1, 0, 1, -1, 1);
367 glLoadIdentity();
368
369 glMatrixMode(GL_MODELVIEW);
370 glLoadIdentity();
371
372 glEnable(GL_TEXTURE_2D);
373 glMatrixMode(GL_TEXTURE);
374 glLoadIdentity();
375
376 glBindTexture(GL_TEXTURE_2D, sd->textureHandle);
377
378 glBegin(GL_QUADS);
379 glTexCoord2i(0, 0);
380 glVertex2d(-1., yscale - 1.);
381 glTexCoord2i(0, 1);
382 glVertex2i(-1, -1);
383 glTexCoord2i(1, 1);
384 glVertex2d(xscale - 1., -1.);
385 glTexCoord2i(1, 0);
386 glVertex2d(xscale - 1., yscale - 1.);
387 glEnd();
388
389 glDisable(GL_TEXTURE_2D);
390 glMatrixMode(GL_MODELVIEW);
391
392 #if USE_DBLBUF
393 #ifdef HAVE_SDL2
394 SDL_GL_SwapWindow(sd->win);
395 #else
396 SDL_GL_SwapBuffers();
397 #endif
398 #endif
399 pthread_mutex_lock(&buffer_mutex);
400 glReadPixels(0, 0, sd->rowstride / sd->psize, sd->height, sd->psize == 4
401 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, sd->fbuffer);
402 sd->needs_more = false;
403 pthread_mutex_unlock(&buffer_mutex);
404 pthread_mutex_lock(&cond_mutex);
405 pthread_cond_signal(&cond);
406 pthread_mutex_unlock(&cond_mutex);
407 sd->got_first = true;
408 if (sd->fps > 0.) sd->ncycs += sd->tfps / sd->fps - 1.;
409 sd->ncycs += sd->cycadj;
410 if (sd->ncycs < 0.) sd->ncycs = 0.;
411 if (sd->pidx == -1 && sd->checkforblanks) {
412 /// check for blank frames: if the first and second from a new program are both blank, mark the program as "bad"
413 /// and pick another (not sure why the blank frames happen, but generally if the first two come back blank, so do all the
414 /// rest. Possibly we need an image texture to load, which we don't have; more investigation needed).
415 register int i;
416 ssize_t frmsize = sd->rowstride * sd->height;
417 if (sd->psize == 4) {
418 for (i = 0; i < frmsize;
419 i += sd->psize) if (((sd->fbuffer[i] | sd->fbuffer[i + 1] | sd->fbuffer[i + 2]) & sd->fbuffer[i + 3]) > 0) break;
420 } else {
421 for (i = 0; i < frmsize; i += sd->psize) if ((sd->fbuffer[i] | sd->fbuffer[i + 1] | sd->fbuffer[i + 2]) > 0) break;
422 }
423 if (i >= frmsize) blanks++;
424 else {
425 blanks = 0;
426 //sd->check = false;
427 sd->bad_programs[sd->program] = 2;
428 }
429 if (blanks > 1) sd->bad_prog = true;
430 }
431 } else {
432 if (sd->ncycs > 1.) {
433 sd->ncycs--;
434 if (sd->ncycs < 1. && sd->cycadj < 0) sd->cycadj++;
435 }
436 }
437 return 0;
438 }
439
440
do_exit(void)441 static void do_exit(void) {
442 //pthread_mutex_lock(&cond_mutex);
443 //pthread_cond_signal(&cond);
444 //pthread_mutex_unlock(&cond_mutex);
445
446 if (inited && statsd != NULL) {
447 statsd->die = true;
448 }
449 }
450
451
worker(void * data)452 static void *worker(void *data) {
453 std::string prname;
454 projectM::Settings settings;
455 bool rerand = true;
456 _sdata *sd = (_sdata *)data;
457 float hwratio = (float)sd->height / (float)sd->width;
458 // int new_stdout, new_stderr;
459
460 if (init_display(sd)) {
461 sd->failed = true;
462 sd->worker_ready = true;
463
464 // tell main thread we are ready
465 pthread_mutex_lock(&cond_mutex);
466 pthread_cond_signal(&cond);
467 pthread_mutex_unlock(&cond_mutex);
468 SDL_Quit();
469 return NULL;
470 }
471
472 atexit(do_exit);
473
474 settings.windowWidth = sd->width;
475 settings.windowHeight = sd->height;
476 settings.meshX = sd->width / 64;
477 settings.meshY = ((int)(settings.meshX * hwratio + 1) >> 1) << 1;
478 settings.fps = sd->fps;
479 settings.smoothPresetDuration = 2.;
480 settings.presetDuration = 60;
481 settings.beatSensitivity = DEF_SENS;
482 settings.aspectCorrection = 1;
483 settings.softCutRatingsEnabled = 1;
484 settings.shuffleEnabled = 0;
485 settings.presetURL = "/usr/share/projectM/presets";
486
487 if (!access("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf", R_OK)) {
488 settings.menuFontURL = "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansMono.ttf";
489 settings.titleFontURL = "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansMono.ttf";
490 } else {
491 settings.menuFontURL = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf";
492 settings.titleFontURL = "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf";
493 }
494 settings.easterEgg = 1;
495
496 if (sd->width >= sd->height)
497 settings.textureSize = sd->texsize = sd->width;
498 else
499 settings.textureSize = sd->texsize = sd->height;
500
501 if (sd->failed) {
502 // can happen if the host is overloaded and the caller timed out
503 SDL_Quit();
504 pthread_mutex_lock(&cond_mutex);
505 pthread_cond_signal(&cond);
506 pthread_mutex_unlock(&cond_mutex);
507 return NULL;
508 }
509
510 // can fail here
511 sd->globalPM = new projectM(settings, 0);
512 sd->globalPM->setPresetLock(true);
513 sd->textureHandle = sd->globalPM->initRenderToTexture();
514 sd->nprs = sd->globalPM->getPlaylistSize() + 1;
515 sd->checkforblanks = true;
516 sd->cycadj = 0;
517
518 sd->prnames = (char **volatile)weed_malloc(sd->nprs * sizeof(char *));
519 if (!sd->prnames) sd->error = WEED_ERROR_MEMORY_ALLOCATION;
520 else sd->bad_programs = (uint8_t *)weed_calloc((sd->nprs - 1), 1);
521 if (!sd->prnames) sd->error = WEED_ERROR_MEMORY_ALLOCATION;
522 if (sd->error == WEED_ERROR_MEMORY_ALLOCATION) sd->rendering = false;
523 else {
524 sd->prnames[0] = strdup("- Random -");
525 for (int i = 1; i < sd->nprs; i++) sd->prnames[i] = strdup((sd->globalPM->getPresetName(i - 1)).c_str());
526 }
527
528 if (sd->failed) {
529 // can happen if the host is overloaded and the caller timed out
530 SDL_Quit();
531 pthread_mutex_lock(&cond_mutex);
532 pthread_cond_signal(&cond);
533 pthread_mutex_unlock(&cond_mutex);
534 return NULL;
535 }
536
537 pthread_mutex_lock(&sd->mutex);
538 sd->worker_ready = true;
539
540 // tell main thread we are ready
541 pthread_mutex_lock(&cond_mutex);
542 pthread_cond_signal(&cond);
543 pthread_mutex_unlock(&cond_mutex);
544
545 while (!sd->die) {
546 if (!sd->rendering) {
547 // deinit() will only exit once the mutex is unlocked, we need to make sure we are idling
548 // else we can pick up the wm response for the playback plugin and cause it to hang
549 sd->worker_active = false;
550 sd->timer = 0;
551 sd->timestamp = 0;
552 pthread_mutex_lock(&cond_mutex);
553 pthread_mutex_unlock(&sd->mutex);
554 pthread_cond_wait(&cond, &cond_mutex);
555 pthread_mutex_unlock(&cond_mutex);
556 pthread_mutex_lock(&sd->mutex);
557 if (sd->die) break;
558 sd->worker_active = true;
559 rerand = true;
560 continue;
561 }
562
563 sd->worker_active = true;
564 if (sd->timer > 10.) {
565 sd->timer = 0.;
566 rerand = true;
567 }
568 if (sd->pidx == -1) {
569 if (rerand) {
570 sd->cycadj = 0;
571 while (1) {
572 sd->program = fastrand_int(sd->nprs - 2);
573 if (sd->bad_programs[sd->program] != 1) {
574 sd->globalPM->selectPreset(sd->program, sd->bad_prog);
575 break;
576 }
577 //sd->globalPM->selectRandom(true);
578 }
579 rerand = false;
580 sd->bad_prog = false;
581 blanks = 0;
582 if (sd->bad_programs[sd->program] == 0) sd->checkforblanks = true;
583 }
584 } else if (sd->pidx != sd->opidx) {
585 sd->globalPM->setPresetLock(true);
586 sd->globalPM->selectPreset(sd->pidx);
587 }
588
589 sd->opidx = sd->pidx;
590
591 if (sd->needs_update || sd->needs_more || !sd->got_first || sd->ncycs > 1.) {
592 if (render_frame(sd)) {
593 if (sd->error == WEED_ERROR_MEMORY_ALLOCATION) {
594 sd->rendering = false;
595 }
596 } else if (sd->pidx == -1) {
597 if (sd->bad_prog) {
598 sd->bad_programs[sd->program] = 1;
599 rerand = true;
600 }
601 }
602 }
603 }
604 sd->worker_active = false;
605 pthread_mutex_unlock(&sd->mutex);
606
607 if (sd->globalPM) delete (sd->globalPM);
608 sd->globalPM = NULL;
609 SDL_Quit();
610
611 pthread_mutex_lock(&cond_mutex);
612 pthread_cond_signal(&cond);
613 pthread_mutex_unlock(&cond_mutex);
614
615 return NULL;
616 }
617
618
projectM_deinit(weed_plant_t * inst)619 static weed_error_t projectM_deinit(weed_plant_t *inst) {
620 _sdata *sd = (_sdata *)weed_get_voidptr_value(inst, "plugin_internal", NULL);
621 if (sd) {
622 sd->rendering = false;
623 pthread_mutex_lock(&sd->mutex);
624 pthread_mutex_unlock(&sd->mutex);
625 if (sd->audio) {
626 weed_free(sd->audio);
627 sd->audio = NULL;
628 }
629 if (sd->error == WEED_ERROR_MEMORY_ALLOCATION) {
630 sd->die = true;
631 pthread_mutex_lock(&cond_mutex);
632 pthread_cond_signal(&cond);
633 pthread_mutex_unlock(&cond_mutex);
634
635 pthread_mutex_lock(&cond_mutex);
636 pthread_cond_wait(&cond, &cond_mutex);
637 pthread_mutex_unlock(&cond_mutex);
638 if (sd->audio) weed_free(sd->audio);
639 if (sd->bad_programs) weed_free(sd->bad_programs);
640 weed_free(sd);
641 weed_set_voidptr_value(inst, "plugin_internal", NULL);
642 inited = 0;
643 } else {
644 if (sd->failed) {
645 weed_free(sd);
646 inited = 0;
647 }
648 }
649 }
650 copies--;
651 weed_set_voidptr_value(inst, "plugin_internal", NULL);
652 return WEED_SUCCESS;
653 }
654
655
projectM_init(weed_plant_t * inst)656 static weed_error_t projectM_init(weed_plant_t *inst) {
657 if (copies == 1) return WEED_ERROR_TOO_MANY_INSTANCES;
658 else {
659 _sdata *sd;
660 weed_plant_t *out_channel = weed_get_out_channel(inst, 0);
661 weed_plant_t **iparams = weed_get_in_params(inst, NULL);
662 int height = weed_channel_get_height(out_channel);
663 int rowstride = weed_channel_get_stride(out_channel);
664 int width = weed_channel_get_width(out_channel);
665 int palette = weed_channel_get_palette(out_channel);
666 int psize = pixel_size(palette);
667
668 copies++;
669
670 if (!inited) {
671 int rc = 0;
672 sd = (_sdata *)weed_calloc(1, sizeof(_sdata));
673 if (!sd) return WEED_ERROR_MEMORY_ALLOCATION;
674
675 sd->error = WEED_SUCCESS;
676 sd->fbuffer = NULL;
677 weed_set_voidptr_value(inst, "plugin_internal", sd);
678
679 sd->pidx = sd->opidx = -1;
680
681 sd->fps = PREF_FPS;
682 if (weed_plant_has_leaf(inst, WEED_LEAF_FPS)) sd->fps = weed_get_double_value(inst, WEED_LEAF_FPS, NULL);
683 if (weed_plant_has_leaf(inst, WEED_LEAF_TARGET_FPS)) sd->fps = weed_get_double_value(inst, WEED_LEAF_TARGET_FPS, NULL);
684
685 pthread_mutex_init(&sd->mutex, NULL);
686 pthread_mutex_init(&sd->pcm_mutex, NULL);
687
688 sd->nprs = 0;
689 sd->prnames = NULL;
690 sd->worker_ready = false;
691 sd->worker_active = false;
692
693 sd->die = sd->failed = false;
694 sd->rendering = false;
695 sd->width = width;
696 sd->height = height;
697 sd->rowstride = rowstride;
698 sd->bad_programs = NULL;
699
700 // kick off a thread to init screean and render
701 pthread_create(&sd->thread, NULL, worker, sd);
702
703 clock_gettime(CLOCK_REALTIME, &ts);
704 ts.tv_sec += WORKER_TIMEOUT_SEC;
705
706 //#define DEBUG
707 #ifdef DEBUG
708 ts.tv_sec *= 100;
709 #endif
710
711 // wait for worker thread ready
712 pthread_mutex_lock(&cond_mutex);
713 while (!sd->worker_ready && rc != ETIMEDOUT) {
714 rc = pthread_cond_timedwait(&cond, &cond_mutex, &ts);
715 }
716 pthread_mutex_unlock(&cond_mutex);
717
718 if (rc == ETIMEDOUT && !sd->worker_ready) {
719 // if we timedout then die
720 sd->failed = true;
721 }
722
723 if (sd->failed) {
724 projectM_deinit(inst);
725 return WEED_ERROR_PLUGIN_INVALID;
726 }
727
728 if (!weed_plant_has_leaf(iparams[0], WEED_LEAF_GUI)) {
729 weed_plant_t *iparamgui = weed_param_get_gui(iparams[0]);
730 weed_set_string_array(iparamgui, WEED_LEAF_CHOICES, sd->nprs, (char **)sd->prnames);
731 }
732 weed_free(iparams);
733
734 statsd = sd;
735 inited = 1;
736 } else {
737 sd = statsd;
738 weed_set_voidptr_value(inst, "plugin_internal", sd);
739 }
740
741 sd->rendering = false;
742
743 if (!sd->fbuffer || sd->height != height || sd->rowstride != rowstride) {
744 if (!resize_buffer(sd) && sd->error == WEED_ERROR_MEMORY_ALLOCATION) {
745 projectM_deinit(inst);
746 return WEED_ERROR_MEMORY_ALLOCATION;
747 }
748 }
749
750 sd->audio = (float *)weed_calloc(MAX_AUDLEN * 2, sizeof(float));
751 if (!sd->audio) {
752 projectM_deinit(inst);
753 return WEED_ERROR_MEMORY_ALLOCATION;
754 }
755 sd->abufsize = 4096;
756
757 sd->got_first = false;
758 sd->width = width;
759 sd->height = height;
760 sd->psize = psize;
761 sd->palette = palette;
762 sd->rowstride = 0;
763 sd->update_size = false;
764 sd->update_psize = false;
765 sd->needs_more = false;
766 sd->needs_update = sd->set_update = false;
767 sd->audio_frames = sd->audio_offs = 0;
768 pcount = count = 0;
769 sd->rendering = true;
770 sd->tfps = 0.;
771 sd->ncycs = 0.;
772 sd->bad_prog = false;
773 sd->timer = 0.;
774 sd->timestamp = 0.;
775
776 pthread_mutex_lock(&cond_mutex);
777 pthread_cond_signal(&cond);
778 pthread_mutex_unlock(&cond_mutex);
779 }
780 return WEED_SUCCESS;
781 }
782
783
projectM_process(weed_plant_t * inst,weed_timecode_t timestamp)784 static weed_error_t projectM_process(weed_plant_t *inst, weed_timecode_t timestamp) {
785 _sdata *sd = (_sdata *)weed_get_voidptr_value(inst, "plugin_internal", NULL);
786 weed_plant_t *in_channel = weed_get_in_channel(inst, 0);
787 weed_plant_t *out_channel = weed_get_out_channel(inst, 0);
788 weed_plant_t **inparams = weed_get_in_params(inst, NULL);
789 weed_plant_t *inparam = inparams[0];
790 unsigned char *dst = (unsigned char *)weed_channel_get_pixel_data(out_channel);
791
792 int width = weed_channel_get_width(out_channel);
793 int height = weed_channel_get_height(out_channel);
794
795 int palette = weed_channel_get_palette(out_channel);
796 int psize = pixel_size(palette);
797 int rowstride = weed_channel_get_stride(out_channel);
798 bool did_update = false;
799 static double ltt;
800 double timer;
801
802 weed_free(inparams);
803
804 if (sd->error == WEED_ERROR_MEMORY_ALLOCATION) {
805 projectM_deinit(inst);
806 return WEED_ERROR_MEMORY_ALLOCATION;
807 }
808
809 if (verbosity >= WEED_VERBOSITY_DEBUG) {
810 count++;
811 if (count == 1 || count == 101) {
812 double tt;
813 int ppcount = pcount;
814 pcount = 0;
815 clock_gettime(CLOCK_REALTIME, &ts);
816 tt = (double)(ts.tv_sec * 1000000000 + ts.tv_nsec);
817 if (count == 101) {
818 double period = (tt - ltt) / 1000000000.;
819 if (period > 0.)
820 fprintf(stderr, "projectM running at display rate of %f fps (%f), engine rendering at %f fps\n",
821 100. / period, sd->fps, ppcount / period);
822 count = 1;
823 }
824 ltt = tt;
825 }
826 }
827
828 if (sd->die) return WEED_ERROR_REINIT_NEEDED;
829
830 while (!sd->worker_active) {
831 sd->rendering = true;
832 pthread_mutex_lock(&cond_mutex);
833 pthread_cond_signal(&cond);
834 pthread_mutex_unlock(&cond_mutex);
835 }
836
837 if (width > maxwidth) width = maxwidth;
838 if (height > maxheight) height = maxheight;
839
840 if (sd->width != width || sd->height != height || sd->psize != psize || sd->rowstride != rowstride || sd->palette != palette) {
841 /// we must update size / pal, this has to be done before reading the buffer
842 pthread_mutex_lock(&cond_mutex);
843 sd->needs_update = true;
844 /// wait for worker thread to aknowledge the update request
845 while (sd->needs_update) {
846 pthread_cond_wait(&cond, &cond_mutex);
847 }
848 pthread_mutex_unlock(&cond_mutex);
849 //sd->updating = true;
850 /// now we can set new values
851 if (sd->width != width || sd->height != height) {
852 sd->height = height;
853 sd->width = width;
854 sd->update_size = true;
855 }
856 if (sd->palette != palette || sd->rowstride != rowstride || sd->psize != psize) {
857 sd->update_psize = true;
858 sd->psize = psize;
859 sd->palette = palette;
860 sd->rowstride = rowstride;
861 }
862 /// let the worker thread know that the values have been updated
863 sd->needs_more = true;
864 pthread_mutex_lock(&cond_mutex);
865 sd->set_update = true;
866 pthread_cond_signal(&cond);
867 pthread_mutex_unlock(&cond_mutex);
868 did_update = true;
869 }
870
871 /// get the program number:
872 // ex. if nprs == 10, we have 9 programs 1 - 9 and 0 is random
873 // first we shift the vals. down by 1, so -1 is random and prgs 0 - 8
874 // 0 - 9, we just use the value - 1
875 // else val % (nprs - 1) .e.g 9 mod 9 is 0, 10 mod 9 is 1, etc
876 sd->pidx = (weed_param_get_value_int(inparam) - 1) % (sd->nprs - 1);
877
878 if (0) {
879 /// TODO - we can control the player with fake keystrokes
880 projectMEvent evt;
881 projectMKeycode key;
882 projectMModifier mod;
883 evt = PROJECTM_KEYDOWN;
884 //mod=PROJECTM_KMOD_LSHIFT;
885 key = PROJECTM_K_n;
886 // send any keystrokes to projectM
887 sd->globalPM->key_handler(evt, key, mod);
888 }
889
890 /// read the fps value from the player if we have that
891 if (weed_plant_has_leaf(inst, WEED_LEAF_FPS)) sd->fps = weed_get_double_value(inst, WEED_LEAF_FPS, NULL);
892 if (weed_plant_has_leaf(inst, WEED_LEAF_TARGET_FPS)) sd->tfps = weed_get_double_value(inst, WEED_LEAF_TARGET_FPS, NULL);
893
894 if (sd->fps) {
895 sd->timer += 1. / sd->fps;
896 } else {
897 if (sd->timestamp > 0) sd->timer += (double)(timestamp - sd->timestamp) / 100000000.;
898 }
899 timer = sd->timer;
900 if (sd->timer < timer) sd->timer = timer;
901 sd->timestamp = timestamp;
902 /// update audio
903
904 if (in_channel) {
905 /// fill the audio buffer for the following frame(s)
906 int achans;
907 int adlen = weed_channel_get_audio_length(in_channel);
908 float **adata = (float **)weed_channel_get_audio_data(in_channel, &achans);
909 pthread_mutex_lock(&sd->pcm_mutex);
910 if (adlen > 0 && adata && adata[0]) {
911 if (!sd->audio || (sd->abufsize < (size_t)adlen)) {
912 if (sd->audio) weed_free(sd->audio);
913 sd->audio = (float *)weed_calloc(adlen, 4);
914 if (!sd->audio) {
915 sd->error = WEED_ERROR_MEMORY_ALLOCATION;
916 pthread_mutex_unlock(&sd->pcm_mutex);
917 projectM_deinit(inst);
918 return WEED_ERROR_MEMORY_ALLOCATION;
919 }
920 }
921 sd->abufsize = adlen;
922 weed_memcpy(sd->audio, adata[0], adlen * 4);
923 } else adlen = 0;
924 sd->audio_frames = adlen;
925 sd->audio_offs = 0;
926 pthread_mutex_unlock(&sd->pcm_mutex);
927 if (adata) weed_free(adata);
928 }
929
930 if (did_update) {
931 /// if we updated we MUST wait for the buffer resize to finish before reading
932 /// (optionally we could return WEED_ERROR_NOT_READY)
933 pthread_mutex_lock(&cond_mutex);
934 while (sd->needs_more) pthread_cond_wait(&cond, &cond_mutex);
935 pthread_mutex_unlock(&cond_mutex);
936 }
937
938 if (sd->error == WEED_ERROR_MEMORY_ALLOCATION) {
939 projectM_deinit(inst);
940 return WEED_ERROR_MEMORY_ALLOCATION;
941 }
942
943 /// we did all the updates so...
944
945 /// copy the buffer now, whether we have a new frame or not
946 /// optionally we could return WEED_ERROR_NOT_READY if there is no new frame
947 //if (!sd->got_first) return WEED_ERROR_NOT_READY;
948
949 pthread_mutex_lock(&buffer_mutex);
950 weed_memcpy(dst, sd->fbuffer, rowstride * height);
951 sd->needs_more = true;
952 pthread_mutex_unlock(&buffer_mutex);
953
954 if (sd->error == WEED_ERROR_MEMORY_ALLOCATION) {
955 projectM_deinit(inst);
956 return WEED_ERROR_MEMORY_ALLOCATION;
957 }
958
959 return WEED_SUCCESS;
960 }
961
962
963 WEED_SETUP_START(200, 200) {
964 if (access("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", R_OK)
965 && access("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf", R_OK)) {
966 return NULL;
967 }
968
969 int palette_list[] = {WEED_PALETTE_RGBA32, WEED_PALETTE_RGB24, WEED_PALETTE_BGRA32,
970 WEED_PALETTE_BGR24, WEED_PALETTE_END
971 };
972 const char *xlist[3] = {"- Random -", "Choose...", NULL};
973 weed_plant_t *in_params[] = {weed_string_list_init("preset", "_Preset", 0, xlist), NULL};
974 weed_plant_t *in_chantmpls[] = {weed_audio_channel_template_init("In audio", WEED_CHANNEL_OPTIONAL), NULL};
975 weed_plant_t *out_chantmpls[] = {weed_channel_template_init("out channel 0", 0), NULL};
976 weed_plant_t *filter_class = weed_filter_class_init("projectM", "salsaman/projectM authors", 1,
977 0, palette_list, projectM_init,
978 projectM_process, projectM_deinit, in_chantmpls, out_chantmpls, in_params, NULL);
979 weed_plant_t *gui = weed_paramtmpl_get_gui(in_params[0]);
980 weed_gui_set_flags(gui, WEED_GUI_CHOICES_SET_ON_INIT);
981 //weed_set_int_value(in_chantmpls[0], WEED_LEAF_MAX_AUDIO_LENGTH, 2048); // we can accept more audio since now it is buffered
982 weed_set_int_value(in_params[0], WEED_LEAF_MAX, INT_MAX);
983 weed_set_double_value(filter_class, WEED_LEAF_PREFERRED_FPS, PREF_FPS); // set reasonable default fps
984 weed_plugin_info_add_filter_class(plugin_info, filter_class);
985 verbosity = weed_get_host_verbosity(weed_get_host_info(plugin_info));
986 weed_set_int_value(plugin_info, WEED_LEAF_VERSION, package_version);
987 statsd = NULL;
988 XInitThreads();
989 }
990 WEED_SETUP_END;
991
992
993 WEED_DESETUP_START {
994 if (inited && statsd) {
995 statsd->die = true;
996 if (!statsd->rendering) {
997 pthread_mutex_lock(&cond_mutex);
998 pthread_cond_signal(&cond);
999 pthread_mutex_unlock(&cond_mutex);
1000 }
1001 pthread_join(statsd->thread, NULL);
1002 if (statsd->fbuffer) weed_free(statsd->fbuffer);
1003 if (statsd->audio) weed_free(statsd->audio);
1004 if (statsd->prnames) {
1005 for (int i = 0; i < statsd->nprs; i++) {
1006 free(statsd->prnames[i]);
1007 }
1008 weed_free(statsd->prnames);
1009 }
1010 if (statsd->bad_programs) weed_free(statsd->bad_programs);
1011 pthread_mutex_destroy(&statsd->mutex);
1012 pthread_mutex_destroy(&statsd->pcm_mutex);
1013 weed_free(statsd);
1014 statsd = NULL;
1015 }
1016 inited = 0;
1017 }
1018 WEED_DESETUP_END;
1019
1020