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