]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/audio/zuke/zuke.c
zuke: treat toggle as play in stopped state
[plan9front.git] / sys / src / cmd / audio / zuke / zuke.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <thread.h>
5 #include <draw.h>
6 #include <mouse.h>
7 #include <keyboard.h>
8 #include <plumb.h>
9 #include <ctype.h>
10 #include "plist.h"
11
12 #define MAX(a,b) ((a)>=(b)?(a):(b))
13 #define MIN(a,b) ((a)<=(b)?(a):(b))
14 #define CLAMP(x,min,max) MAX(min, MIN(max, x))
15
16 typedef struct Color Color;
17 typedef struct Player Player;
18 typedef struct Playlist Playlist;
19
20 enum
21 {
22         Cstart = 1,
23         Cstop,
24         Ctoggle,
25         Cseekrel,
26
27         Everror = 1,
28         Evready,
29
30         Seek = 10, /* 10 seconds */
31         Seekfast = 60, /* a minute */
32
33         Bps = 44100*2*2, /* 44100KHz, stereo, s16 for a sample */
34         Relbufsz = Bps/2, /* 0.5s */
35
36         Dback = 0,
37         Dfhigh,
38         Dfmed,
39         Dflow,
40         Dfinv,
41         Dbmed,
42         Dblow,
43         Dbinv,
44         Numcolors,
45 };
46
47 struct Color {
48         u32int rgb;
49         Image *im;
50 };
51
52 struct Player
53 {
54         Channel *ctl;
55         Channel *ev;
56         Channel *img;
57         double seek;
58         int pcur;
59 };
60
61 struct Playlist
62 {
63         Meta *m;
64         int n;
65         char *raw;
66         int rawsz;
67 };
68
69 int mainstacksize = 32768;
70
71 static int debug;
72 static int audio = -1;
73 static int volume;
74 static int pnotifies;
75 static Playlist *pl;
76 static Player *playernext;
77 static Player *playercurr;
78 static vlong byteswritten;
79 static int pcur, pcurplaying;
80 static int scroll, scrollsz;
81 static Font *f;
82 static Image *cover;
83 static Channel *playc;
84 static Mousectl *mctl;
85 static Keyboardctl *kctl;
86 static int colwidth[10];
87 static int mincolwidth[10];
88 static char *cols = "AatD";
89 static int colspath;
90 static int *shuffle;
91 static Rectangle seekbar;
92 static int seekmx, newseekmx = -1;
93 static double seekoff; /* ms */
94 static Lock audiolock;
95 static int audioerr = 0;
96 static Biobuf out;
97 static char *covers[] =
98 {
99         "art", "folder", "cover", "Cover", "scans/CD", "Scans/Front", "Covers/Front"
100 };
101
102 static Color colors[Numcolors] =
103 {
104         [Dback]  = {0xf0f0f0},
105         [Dfhigh] = {0xffffff},
106         [Dfmed]  = {0x343434},
107         [Dflow]  = {0xa5a5a5},
108         [Dfinv]  = {0x323232},
109         [Dbmed]  = {0x72dec2},
110         [Dblow]  = {0x404040},
111         [Dbinv]  = {0xffb545},
112 };
113
114 static int Scrollwidth;
115 static int Scrollheight;
116 static int Coversz;
117
118 static void
119 audioon(void)
120 {
121         lock(&audiolock);
122         if(audio < 0 && (audio = open("/dev/audio", OWRITE|OCEXEC)) < 0 && audioerr == 0){
123                 fprint(2, "%r\n");
124                 audioerr = 1;
125         }
126         unlock(&audiolock);
127 }
128
129 static void
130 audiooff(void)
131 {
132         lock(&audiolock);
133         close(audio);
134         audio = -1;
135         audioerr = 0;
136         unlock(&audiolock);
137 }
138
139 #pragma varargck type "P" uvlong
140 static int
141 positionfmt(Fmt *f)
142 {
143         char *s, tmp[16];
144         u64int sec;
145
146         s = tmp;
147         sec = va_arg(f->args, uvlong);
148         if(sec >= 3600){
149                 s = seprint(s, tmp+sizeof(tmp), "%02lld:", sec/3600);
150                 sec %= 3600;
151         }
152         s = seprint(s, tmp+sizeof(tmp), "%02lld:", sec/60);
153         sec %= 60;
154         seprint(s, tmp+sizeof(tmp), "%02lld", sec);
155
156         return fmtstrcpy(f, tmp);
157 }
158
159 static char *
160 getcol(Meta *m, int c)
161 {
162         static char tmp[32];
163
164         switch(c){
165         case Palbum: return m->album;
166         case Partist: return m->artist[0];
167         case Pdate: return m->date;
168         case Ptitle: return (!colspath && *m->title == 0) ? m->basename : m->title;
169         case Ptrack: snprint(tmp, sizeof(tmp), "%4s", m->track); return m->track ? tmp : nil;
170         case Ppath: return m->path;
171         case Pduration:
172                 tmp[0] = 0;
173                 if(m->duration > 0)
174                         snprint(tmp, sizeof(tmp), "%8P", m->duration/1000);
175                 return tmp;
176         default: sysfatal("invalid column '%c'", c);
177         }
178
179         return nil;
180 }
181
182 static void
183 adjustcolumns(void)
184 {
185         int i, n, x, total, width;
186
187         if(mincolwidth[0] == 0){
188                 for(i = 0; cols[i] != 0; i++)
189                         mincolwidth[i] = 1;
190                 for(n = 0; n < pl->n; n++){
191                         for(i = 0; cols[i] != 0; i++){
192                                 if((x = stringwidth(f, getcol(pl->m+n, cols[i]))) > mincolwidth[i])
193                                         mincolwidth[i] = x;
194                         }
195                 }
196         }
197
198         total = 0;
199         n = 0;
200         width = Dx(screen->r);
201         for(i = 0; cols[i] != 0; i++){
202                 if(cols[i] == Pduration || cols[i] == Pdate || cols[i] == Ptrack)
203                         width -= mincolwidth[i] + 8;
204                 else{
205                         total += mincolwidth[i];
206                         n++;
207                 }
208         }
209         colspath = 0;
210         for(i = 0; cols[i] != 0; i++){
211                 if(cols[i] == Ppath || cols[i] == Pbasename)
212                         colspath = 1;
213                 if(cols[i] == Pduration || cols[i] == Pdate || cols[i] == Ptrack)
214                         colwidth[i] = mincolwidth[i];
215                 else
216                         colwidth[i] = (width - Scrollwidth - n*8) * mincolwidth[i] / total;
217         }
218 }
219
220 static Meta *
221 getmeta(int i)
222 {
223         return &pl->m[shuffle != nil ? shuffle[i] : i];
224 }
225
226 static void
227 updatescrollsz(void)
228 {
229         scrollsz = Dy(screen->r)/f->height - 2;
230 }
231
232 static void
233 redraw(int full)
234 {
235         Image *col;
236         Point p, sp;
237         Rectangle sel, r;
238         int i, j, left, right, scrollcenter, w;
239         uvlong dur, msec;
240         char tmp[32];
241
242         lockdisplay(display);
243         updatescrollsz();
244         scroll = CLAMP(scroll, 0, pl->n - scrollsz);
245         left = screen->r.min.x;
246         if(scrollsz < pl->n) /* adjust for scrollbar */
247                 left += Scrollwidth + 1;
248
249         if(full){
250                 draw(screen, screen->r, colors[Dback].im, nil, ZP);
251
252                 adjustcolumns();
253                 if(scrollsz < pl->n){ /* scrollbar */
254                         p.x = sp.x = screen->r.min.x + Scrollwidth;
255                         p.y = screen->r.min.y;
256                         sp.y = screen->r.max.y;
257                         line(screen, p, sp, Endsquare, Endsquare, 0, colors[Dflow].im, ZP);
258
259                         r = screen->r;
260                         r.max.x = r.min.x + Scrollwidth - 1;
261                         r.min.x += 1;
262                         if(scroll < 1)
263                                 scrollcenter = 0;
264                         else
265                                 scrollcenter = (Dy(screen->r)-Scrollheight*5/4)*scroll / (pl->n - scrollsz);
266                         r.min.y += scrollcenter + Scrollheight/4;
267                         r.max.y = r.min.y + Scrollheight;
268                         draw(screen, r, colors[Dblow].im, nil, ZP);
269                 }
270
271                 p.x = sp.x = left;
272                 p.y = 0;
273                 sp.y = screen->r.max.y;
274                 for(i = 0; cols[i+1] != 0; i++){
275                         p.x += colwidth[i] + 4;
276                         sp.x = p.x;
277                         line(screen, p, sp, Endsquare, Endsquare, 0, colors[Dflow].im, ZP);
278                         p.x += 4;
279                 }
280
281                 sp.x = sp.y = 0;
282                 p.x = left + 2;
283                 p.y = screen->r.min.y + 2;
284
285                 for(i = scroll; i < pl->n; i++, p.y += f->height){
286                         if(i < 0)
287                                 continue;
288                         if(p.y > screen->r.max.y)
289                                 break;
290
291                         if(pcur == i){
292                                 sel.min.x = left;
293                                 sel.min.y = p.y;
294                                 sel.max.x = screen->r.max.x;
295                                 sel.max.y = p.y + f->height;
296                                 draw(screen, sel, colors[Dbinv].im, nil, ZP);
297                                 col = colors[Dfinv].im;
298                         }else{
299                                 col = colors[Dfmed].im;
300                         }
301
302                         sel = screen->r;
303
304                         p.x = left + 2 + 3;
305                         for(j = 0; cols[j] != 0; j++){
306                                 sel.max.x = p.x + colwidth[j];
307                                 replclipr(screen, 0, sel);
308                                 string(screen, p, col, sp, f, getcol(getmeta(i), cols[j]));
309                                 p.x += colwidth[j] + 8;
310                         }
311                         replclipr(screen, 0, screen->r);
312
313                         if(pcurplaying == i){
314                                 Point rightp, leftp;
315                                 leftp.y = rightp.y = p.y - 1;
316                                 leftp.x = left;
317                                 rightp.x = screen->r.max.x;
318                                 line(screen, leftp, rightp, 0, 0, 0, colors[Dflow].im, sp);
319                                 leftp.y = rightp.y = p.y + f->height;
320                                 line(screen, leftp, rightp, 0, 0, 0, colors[Dflow].im, sp);
321                         }
322                 }
323         }
324
325         msec = 0;
326         dur = getmeta(pcurplaying)->duration;
327         if(pcurplaying >= 0){
328                 msec = byteswritten*1000/Bps;
329                 if(dur > 0){
330                         snprint(tmp, sizeof(tmp), "%s%P/%P 100%%",
331                                 shuffle != nil ? "∫ " : "",
332                                 dur/1000, dur/1000);
333                         w = stringwidth(f, tmp);
334                         msec = MIN(msec, dur);
335                         snprint(tmp, sizeof(tmp), "%s%P/%P %d%%",
336                                 shuffle != nil ? "∫ " : "",
337                                 (uvlong)(newseekmx >= 0 ? seekoff : msec)/1000,
338                                 dur/1000, volume);
339                 }else{
340                         snprint(tmp, sizeof(tmp), "%s%P %d%%",
341                                 shuffle != nil ? "∫ " : "",
342                                 msec/1000, 100);
343                         w = stringwidth(f, tmp);
344                         snprint(tmp, sizeof(tmp), "%s%P %d%%",
345                                 shuffle != nil ? "∫ " : "",
346                                 msec/1000, volume);
347                 }
348         }else{
349                 snprint(tmp, sizeof(tmp), "%s%d%%", shuffle != nil ? "∫ " : "", 100);
350                 w = stringwidth(f, tmp);
351                 snprint(tmp, sizeof(tmp), "%s%d%%", shuffle != nil ? "∫ " : "", volume);
352         }
353         r = screen->r;
354         right = r.max.x - w - 4;
355         r.min.x = left;
356         r.min.y = r.max.y - f->height - 4;
357         if(pcurplaying < 0 || dur == 0)
358                 r.min.x = right;
359         draw(screen, r, colors[Dblow].im, nil, ZP);
360         p = addpt(Pt(r.max.x-stringwidth(f, tmp)-4, r.min.y), Pt(2, 2));
361         r.max.x = right;
362         string(screen, p, colors[Dfhigh].im, sp, f, tmp);
363         sel = r;
364
365         if(cover != nil && full){
366                 r.max.x = r.min.x;
367                 r.min.x = screen->r.max.x - cover->r.max.x - 8;
368                 draw(screen, r, colors[Dblow].im, nil, ZP);
369                 r = screen->r;
370                 r.min.x = r.max.x - cover->r.max.x - 8;
371                 r.min.y = r.max.y - cover->r.max.y - 8 - f->height - 4;
372                 r.max.y = r.min.y + cover->r.max.y + 8;
373                 draw(screen, r, colors[Dblow].im, nil, ZP);
374                 draw(screen, insetrect(r, 4), cover, nil, ZP);
375         }
376
377         /* seek bar */
378         seekbar = ZR;
379         if(pcurplaying >= 0 && dur > 0){
380                 r = insetrect(sel, 3);
381                 draw(screen, r, colors[Dback].im, nil, ZP);
382                 seekbar = r;
383                 r.max.x = r.min.x + Dx(r) * (double)msec / (double)dur;
384                 draw(screen, r, colors[Dbmed].im, nil, ZP);
385         }
386
387         flushimage(display, 1);
388         unlockdisplay(display);
389 }
390
391 static void
392 coverload(void *player_)
393 {
394         int p[2], pid, fd, i;
395         char *prog, *path, *s, tmp[32];
396         Meta *m;
397         Channel *ch;
398         Player *player;
399         Image *newcover;
400
401         threadsetname("cover");
402         player = player_;
403         m = getmeta(player->pcur);
404         pid = -1;
405         ch = player->img;
406         fd = -1;
407         prog = nil;
408
409         if(m->imagefmt != nil && m->imagereader == 0){
410                 if(strcmp(m->imagefmt, "image/png") == 0)
411                         prog = "png";
412                 else if(strcmp(m->imagefmt, "image/jpeg") == 0)
413                         prog = "jpg";
414         }
415
416         if(prog == nil){
417                 path = strdup(m->path);
418                 if(path != nil && (s = utfrrune(path, '/')) != nil){
419                         *s = 0;
420
421                         for(i = 0; i < nelem(covers) && prog == nil; i++){
422                                 if((s = smprint("%s/%s.jpg", path, covers[i])) != nil && (fd = open(s, OREAD)) >= 0)
423                                         prog = "jpg";
424                                 free(s);
425                                 s = nil;
426                                 if(fd < 0 && (s = smprint("%s/%s.png", path, covers[i])) != nil && (fd = open(s, OREAD)) >= 0)
427                                         prog = "png";
428                                 free(s);
429                         }
430                 }
431                 free(path);
432         }
433
434         if(prog == nil)
435                 goto done;
436
437         if(fd < 0){
438                 fd = open(m->path, OREAD);
439                 seek(fd, m->imageoffset, 0);
440         }
441         pipe(p);
442         if((pid = rfork(RFPROC|RFFDG|RFNOTEG|RFCENVG|RFNOWAIT)) == 0){
443                 dup(fd, 0); close(fd);
444                 dup(p[1], 1); close(p[1]);
445                 if(!debug){
446                         dup(fd = open("/dev/null", OWRITE), 2);
447                         close(fd);
448                 }
449                 snprint(tmp, sizeof(tmp), "%s -9t | resample -x%d", prog, Coversz);
450                 execl("/bin/rc", "rc", "-c", tmp, nil);
451                 sysfatal("execl: %r");
452         }
453         close(fd);
454         close(p[1]);
455
456         if(pid > 0){
457                 newcover = readimage(display, p[0], 1);
458                 sendp(ch, newcover);
459         }
460         close(p[0]);
461 done:
462         if(pid < 0)
463                 sendp(ch, nil);
464         chanclose(ch);
465         chanfree(ch);
466         if(pid >= 0)
467                 postnote(PNGROUP, pid, "interrupt");
468         threadexits(nil);
469 }
470
471 static int
472 playerret(Player *player)
473 {
474         return recvul(player->ev) == Everror ? -1 : 0;
475 }
476
477 static void
478 pnotify(Player *p)
479 {
480         Meta *m;
481         char *s;
482         int i;
483
484         if(!pnotifies)
485                 return;
486
487         if(p != nil){
488                 m = getmeta(p->pcur);
489                 for(i = 0; cols[i] != 0; i++)
490                         Bprint(&out, "%s\t", (s = getcol(m, cols[i])) ? s : "");
491         }
492         Bprint(&out, "\n");
493         Bflush(&out);
494 }
495
496 static void
497 stop(Player *player)
498 {
499         if(player == nil)
500                 return;
501
502         if(player == playernext)
503                 playernext = nil;
504         if(!getmeta(player->pcur)->filefmt[0])
505                 playerret(player);
506         if(player == playercurr)
507                 pnotify(nil);
508         sendul(player->ctl, Cstop);
509 }
510
511 static void
512 start(Player *player)
513 {
514         if(player == nil)
515                 return;
516         if(!getmeta(player->pcur)->filefmt[0])
517                 playerret(player);
518         pnotify(player);
519         sendul(player->ctl, Cstart);
520 }
521
522 static void playerthread(void *player_);
523
524 static Player *
525 newplayer(int pcur, int loadnext)
526 {
527         Player *player;
528
529         if(playernext != nil && loadnext){
530                 if(pcur == playernext->pcur){
531                         player = playernext;
532                         playernext = nil;
533                         goto done;
534                 }
535                 stop(playernext);
536                 playernext = nil;
537         }
538
539         player = mallocz(sizeof(*player), 1);
540         player->ctl = chancreate(sizeof(ulong), 0);
541         player->ev = chancreate(sizeof(ulong), 0);
542         player->pcur = pcur;
543
544         threadcreate(playerthread, player, 4096);
545         if(getmeta(pcur)->filefmt[0] && playerret(player) < 0)
546                 return nil;
547
548 done:
549         if(pcur < pl->n-1 && playernext == nil && loadnext)
550                 playernext = newplayer(pcur+1, 0);
551
552         return player;
553 }
554
555 static void
556 playerthread(void *player_)
557 {
558         char *buf, cmd[64], seekpos[12], *fmt;
559         Player *player;
560         Ioproc *io;
561         Image *thiscover;
562         ulong c;
563         int p[2], fd, pid, noinit, trycoverload;
564         long n, r;
565         vlong boffset, boffsetlast;
566         Meta *cur;
567
568         threadsetname("player");
569         player = player_;
570         noinit = 0;
571         boffset = 0;
572         buf = nil;
573         trycoverload = 1;
574         io = nil;
575         pid = -1;
576
577 restart:
578         cur = getmeta(player->pcur);
579         fmt = cur->filefmt;
580         fd = -1;
581         if(*fmt){
582                 if((fd = open(cur->path, OREAD)) < 0){
583                         fprint(2, "%r\n");
584                         sendul(player->ev, Everror);
585                         chanclose(player->ev);
586                         goto freeplayer;
587                 }
588         }else{
589                 sendul(player->ev, Evready);
590                 chanclose(player->ev);
591         }
592
593         pipe(p);
594         if((pid = rfork(RFPROC|RFFDG|RFNOTEG|RFCENVG|RFNOWAIT)) == 0){
595                 close(p[1]);
596                 if(fd < 0)
597                         fd = open("/dev/null", OREAD);
598                 dup(fd, 0); close(fd);
599                 dup(p[0], 1); close(p[0]);
600                 if(!debug){
601                         dup(fd = open("/dev/null", OWRITE), 2);
602                         close(fd);
603                 }
604                 if(*fmt){
605                         snprint(cmd, sizeof(cmd), "/bin/audio/%sdec", fmt);
606                         snprint(seekpos, sizeof(seekpos), "%g", (double)boffset/Bps);
607                         execl(cmd, cmd, boffset ? "-s" : nil, seekpos, nil);
608                 }else{
609                         execl("/bin/play", "play", "-o", "/fd/1", cur->path, nil);
610                 }
611                 close(0);
612                 close(1);
613                 exits("%r");
614         }
615         if(pid < 0)
616                 sysfatal("rfork: %r");
617         if(fd >= 0)
618                 close(fd);
619         close(p[0]);
620
621         c = 0;
622         if(!noinit){
623                 if(*fmt){
624                         sendul(player->ev, Evready);
625                         chanclose(player->ev);
626                 }
627                 buf = malloc(Relbufsz);
628                 if((io = ioproc()) == nil)
629                         sysfatal("player: %r");
630                 if((n = ioreadn(io, p[1], buf, Relbufsz)) < 0)
631                         fprint(2, "player: %r\n");
632                 if(recv(player->ctl, &c) < 0 || c != Cstart)
633                         goto freeplayer;
634                 if(n < 1)
635                         goto next;
636                 audioon();
637                 boffset = iowrite(io, audio, buf, n);
638                 noinit = 1;
639         }
640
641         boffsetlast = boffset;
642         byteswritten = boffset;
643         pcurplaying = player->pcur;
644         if(c != Cseekrel)
645                 redraw(1);
646
647         while(1){
648                 n = ioread(io, p[1], buf, Relbufsz);
649                 if(n <= 0)
650                         break;
651
652                 thiscover = nil;
653                 if(player->img != nil && nbrecv(player->img, &thiscover) != 0){
654                         freeimage(cover);
655                         cover = thiscover;
656                         redraw(1);
657                         player->img = nil;
658                 }
659                 r = nbrecv(player->ctl, &c);
660                 if(r < 0){
661                         audiooff();
662                         goto stop;
663                 }else if(r != 0){
664                         if(c == Ctoggle){
665                                 audiooff();
666                                 if(recv(player->ctl, &c) < 0)
667                                         goto stop;
668                         }
669                         if(c == Cseekrel){
670                                 boffset = MAX(0, boffset + player->seek*Bps);
671                                 n = 0;
672                                 break;
673                         }else if(c == Cstop){
674                                 audiooff();
675                                 goto stop;
676                         }
677                 }
678
679                 boffset += n;
680                 byteswritten = boffset;
681                 audioon();
682                 iowrite(io, audio, buf, n);
683                 if(trycoverload){
684                         trycoverload = 0;
685                         player->img = chancreate(sizeof(Image*), 0);
686                         proccreate(coverload, player, 4096);
687                 }
688                 if(labs(boffset/Relbufsz - boffsetlast/Relbufsz) > 0){
689                         boffsetlast = boffset;
690                         redraw(0);
691                 }
692         }
693
694         if(n < 1){ /* seeking backwards or end of the song */
695                 close(p[1]);
696                 p[1] = -1;
697                 if(c != Cseekrel || (getmeta(pcurplaying)->duration && boffset >= getmeta(pcurplaying)->duration/1000*Bps)){
698 next:
699                         playercurr = nil;
700                         playercurr = newplayer((player->pcur+1) % pl->n, 1);
701                         start(playercurr);
702                         goto stop;
703                 }
704                 goto restart;
705         }
706
707 stop:
708         if(player->img != nil)
709                 freeimage(recvp(player->img));
710 freeplayer:
711         chanfree(player->ctl);
712         chanfree(player->ev);
713         if(pid >= 0)
714                 postnote(PNGROUP, pid, "interrupt");
715         closeioproc(io);
716         if(p[1] >= 0)
717                 close(p[1]);
718         if(player == playercurr)
719                 playercurr = nil;
720         if(player == playernext)
721                 playernext = nil;
722         free(buf);
723         free(player);
724         threadexits(nil);
725 }
726
727 static int
728 toggle(Player *player)
729 {
730         return (player != nil && sendul(player->ctl, Ctoggle) == 1) ? 0 : -1;
731 }
732
733 static void
734 seekrel(Player *player, double off)
735 {
736         if(player != nil && *getmeta(pcurplaying)->filefmt){
737                 player->seek = off;
738                 sendul(player->ctl, Cseekrel);
739         }
740 }
741
742 static void
743 writeplist(void)
744 {
745         int i;
746
747         for(i = 0; i < pl->n; i++)
748                 printmeta(&out, pl->m+i);
749 }
750
751 static void
752 freeplist(Playlist *pl)
753 {
754         if(pl != nil){
755                 free(pl->m);
756                 free(pl->raw);
757         }
758         free(pl);
759 }
760
761 static char *
762 readall(int f)
763 {
764         int bufsz, sz, n;
765         char *s;
766
767         bufsz = 1023;
768         s = nil;
769         for(sz = 0;; sz += n){
770                 if(bufsz-sz < 1024){
771                         bufsz *= 2;
772                         s = realloc(s, bufsz);
773                 }
774                 if((n = readn(f, s+sz, bufsz-sz-1)) < 1)
775                         break;
776         }
777         if(n < 0 || sz < 1){
778                 free(s);
779                 return nil;
780         }
781         s[sz] = 0;
782
783         return s;
784 }
785
786 static Playlist *
787 readplist(int fd)
788 {
789         char *raw, *s, *e, *a[5], *b;
790         Playlist *pl;
791         int plsz;
792         Meta *m;
793
794         if((raw = readall(fd)) == nil)
795                 return nil;
796
797         plsz = 0;
798         for(s = raw; (s = strchr(s, '\n')) != nil; s++){
799                 if(*(++s) == '\n')
800                         plsz++;
801         }
802
803         if((pl = calloc(1, sizeof(*pl))) == nil || (pl->m = calloc(plsz+1, sizeof(Meta))) == nil){
804                 freeplist(pl);
805                 werrstr("no memory");
806                 return nil;
807         }
808
809         pl->raw = raw;
810         for(s = pl->raw, m = pl->m;; s = e){
811                 if((e = strchr(s, '\n')) == nil)
812                         break;
813                 s += 2;
814                 *e++ = 0;
815                 switch(s[-2]){
816                 case 0:
817                         if(m->path != nil){
818                                 pl->n++;
819                                 m++;
820                         }
821                         break;
822                 case Pimage:
823                         if(tokenize(s, a, nelem(a)) >= 4){
824                                 m->imageoffset = atoi(a[0]);
825                                 m->imagesize = atoi(a[1]);
826                                 m->imagereader = atoi(a[2]);
827                                 m->imagefmt = a[3];
828                         }
829                         break;
830                 case Pduration:
831                         m->duration = strtoull(s, nil, 0);
832                         break;
833                 case Partist:
834                         if(m->numartist < Maxartist)
835                                 m->artist[m->numartist++] = s;
836                         break;
837                 case Pfilefmt: m->filefmt = s; break;
838                 case Palbum:   m->album = s; break;
839                 case Pdate:    m->date = s; break;
840                 case Ptitle:   m->title = s; break;
841                 case Ptrack:   m->track = s; break;
842                 case Ppath:
843                         m->path = s;
844                         m->basename = (b = utfrrune(s, '/')) == nil ? s : b+1;
845                         break;
846                 }
847         }
848         if(m != nil && m->path != nil)
849                 pl->n++;
850
851         return pl;
852 }
853
854 static void
855 recenter(void)
856 {
857         updatescrollsz();
858         scroll = pcur - scrollsz/2 + 1;
859 }
860
861 static void
862 search(char d)
863 {
864         Meta *m;
865         static char buf[64];
866         static int sz;
867         int inc, i, a, cycle;
868
869         inc = (d == '/' || d == 'n') ? 1 : -1;
870         if(d == '/' || d == '?')
871                 sz = enter(inc > 0 ? "forward:" : "backward:", buf, sizeof(buf), mctl, kctl, nil);
872         if(sz < 1)
873                 return;
874
875         cycle = 1;
876         for(i = pcur+inc; i >= 0 && i < pl->n;){
877                 m = getmeta(i);
878                 for(a = 0; a < m->numartist; a++){
879                         if(cistrstr(m->artist[a], buf) != nil)
880                                 break;
881                 }
882                 if(m->album != nil && cistrstr(m->album, buf) != nil)
883                         break;
884                 if(m->title != nil && cistrstr(m->title, buf) != nil)
885                         break;
886                 if(cistrstr(m->path, buf) != nil)
887                         break;
888 onemore:
889                 i += inc;
890         }
891         if(i >= 0 && i < pl->n){
892                 pcur = i;
893                 recenter();
894                 redraw(1);
895         }else if(cycle && i+inc < 0){
896                 cycle = 0;
897                 i = pl->n;
898                 goto onemore;
899         }else if(cycle && i+inc >= pl->n){
900                 cycle = 0;
901                 i = -1;
902                 goto onemore;
903         }
904 }
905
906 static void
907 chvolume(int d)
908 {
909         int f, l, r, ol, or;
910         Biobuf b;
911         char *s, *a[4];
912
913         if((f = open("/dev/volume", ORDWR)) < 0)
914                 return;
915         Binit(&b, f, OREAD);
916
917         l = r = 0;
918         for(; (s = Brdline(&b, '\n')) != nil;){
919                 if(strncmp(s, "master", 6) == 0 && tokenize(s, a, 3) == 3){
920                         l = ol = atoi(a[1]);
921                         r = or = atoi(a[2]);
922                         for(;;){
923                                 l += d;
924                                 r += d;
925                                 fprint(f, "master %d %d\n", l, r);
926                                 Bseek(&b, 0, 0);
927                                 for(; (s = Brdline(&b, '\n')) != nil;){
928                                         if(strncmp(s, "master", 6) == 0 && tokenize(s, a, 3) == 3){
929                                                 if(atoi(a[1]) == l && atoi(a[2]) == r)
930                                                         goto end;
931                                                 if(atoi(a[1]) != ol && atoi(a[2]) != or)
932                                                         goto end;
933                                                 if(l < 0 || r < 0 || l > 100 || r > 100)
934                                                         goto end;
935                                                 break;
936                                         }
937                                 }
938                         }
939                 }
940         }
941
942 end:
943         volume = (l+r)/2;
944         if(volume > 100)
945                 volume = 100;
946         else if(volume < 0)
947                 volume = 0;
948
949         Bterm(&b);
950         close(f);
951 }
952
953 static void
954 toggleshuffle(void)
955 {
956         int i, m, xi, a, c, pcurnew, pcurplayingnew;
957
958         if(shuffle == nil){
959                 if(pl->n < 2)
960                         return;
961
962                 m = pl->n;
963                 if(pl->n < 4){
964                         a = 1;
965                         c = 3;
966                         m = 7;
967                 }else{
968                         m += 1;
969                         m |= m >> 1;
970                         m |= m >> 2;
971                         m |= m >> 4;
972                         m |= m >> 8;
973                         m |= m >> 16;
974                         a = 1 + nrand(m/4)*4;     /* 1 â‰¤ a < m   && a mod 4 = 1 */
975                         c = 3 + nrand((m-2)/2)*2; /* 3 â‰¤ c < m-1 && c mod 2 = 1 */
976                 }
977
978                 shuffle = malloc(pl->n*sizeof(*shuffle));
979                 xi = pcurplaying < 0 ? pcur : pcurplaying;
980                 pcurplayingnew = -1;
981                 pcurnew = 0;
982                 for(i = 0; i < pl->n;){
983                         if(xi < pl->n){
984                                 if(pcur == xi)
985                                         pcurnew = i;
986                                 if(pcurplaying == xi)
987                                         pcurplayingnew = i;
988                                 shuffle[i++] = xi;
989                         }
990                         xi = (a*xi + c) & m;
991                 }
992                 pcur = pcurnew;
993                 pcurplaying = pcurplayingnew;
994         }else{
995                 pcur = shuffle[pcur];
996                 if(pcurplaying >= 0)
997                         pcurplaying = shuffle[pcurplaying];
998                 free(shuffle);
999                 shuffle = nil;
1000         }
1001
1002         stop(playernext);
1003         if(pcur < pl->n-1)
1004                 playernext = newplayer(pcur+1, 0);
1005 }
1006
1007 static void
1008 plumbaudio(void *kbd)
1009 {
1010         int i, f, pf;
1011         Playlist *p;
1012         Plumbmsg *m;
1013         char *s, *e;
1014         Rune c;
1015
1016         threadsetname("audio/plumb");
1017         if((f = plumbopen("audio", OREAD)) >= 0){
1018                 while((m = plumbrecv(f)) != nil){
1019                         s = m->data;
1020                         if(strncmp(s, "key", 3) == 0 && isspace(s[3])){
1021                                 for(s = s+4; isspace(*s); s++);
1022                                 for(; (i = chartorune(&c, s)) > 0 && c != Runeerror; s += i)
1023                                         sendul(kbd, c);
1024                                 continue;
1025                         } 
1026                         if(*s != '/' && m->wdir != nil)
1027                                 s = smprint("%s/%.*s", m->wdir, m->ndata, m->data);
1028
1029                         if((e = strrchr(s, '.')) != nil && strcmp(e, ".plist") == 0 && (pf = open(s, OREAD)) >= 0){
1030                                 p = readplist(pf);
1031                                 close(pf);
1032                                 if(p == nil)
1033                                         continue;
1034
1035                                 freeplist(pl);
1036                                 pl = p;
1037                                 memset(mincolwidth, 0, sizeof(mincolwidth)); /* readjust columns */
1038                                 sendul(playc, 0);
1039                         }else{
1040                                 for(i = 0; i < pl->n; i++){
1041                                         if(strcmp(pl->m[i].path, s) == 0){
1042                                                 sendul(playc, i);
1043                                                 break;
1044                                         }
1045                                 }
1046                         }
1047
1048                         if(s != m->data)
1049                                 free(s);
1050                         plumbfree(m);
1051                 }
1052         }
1053
1054         threadexits(nil);
1055 }
1056
1057 static void
1058 usage(void)
1059 {
1060         fprint(2, "usage: %s [-s] [-c aAdDtTp]\n", argv0);
1061         sysfatal("usage");
1062 }
1063
1064 void
1065 threadmain(int argc, char **argv)
1066 {
1067         Rune key;
1068         Mouse m;
1069         ulong ind;
1070         enum {
1071                 Emouse,
1072                 Eresize,
1073                 Ekey,
1074                 Eplay,
1075         };
1076         Alt a[] = {
1077                 { nil, &m, CHANRCV },
1078                 { nil, nil, CHANRCV },
1079                 { nil, &key, CHANRCV },
1080                 { nil, &ind, CHANRCV },
1081                 { nil, nil, CHANEND },
1082         };
1083         int n, scrolling, oldpcur, oldbuttons, pnew, shuffled;
1084         char buf[64];
1085
1086         shuffled = 0;
1087         ARGBEGIN{
1088         case 'd':
1089                 debug++;
1090                 break;
1091         case 's':
1092                 shuffled = 1;
1093                 break;
1094         case 'c':
1095                 cols = EARGF(usage());
1096                 if(strlen(cols) >= nelem(colwidth))
1097                         sysfatal("max %d columns allowed", nelem(colwidth));
1098                 break;
1099         default:
1100                 usage();
1101                 break;
1102         }ARGEND;
1103
1104         if((pl = readplist(0)) == nil){
1105                 fprint(2, "playlist: %r\n");
1106                 sysfatal("playlist error");
1107         }
1108         close(0);
1109
1110         Binit(&out, 1, OWRITE);
1111         pnotifies = fd2path(1, buf, sizeof(buf)) == 0 && strcmp(buf, "/dev/cons") != 0;
1112
1113         if(initdraw(nil, nil, "zuke") < 0)
1114                 sysfatal("initdraw: %r");
1115         f = display->defaultfont;
1116         Scrollwidth = MAX(14, stringwidth(f, "#"));
1117         Scrollheight = MAX(16, f->height);
1118         Coversz = MAX(64, stringwidth(f, "∫ 00:00:00/00:00:00 100%"));
1119         unlockdisplay(display);
1120         display->locking = 1;
1121         if((mctl = initmouse(nil, screen)) == nil)
1122                 sysfatal("initmouse: %r");
1123         if((kctl = initkeyboard(nil)) == nil)
1124                 sysfatal("initkeyboard: %r");
1125
1126         a[0].c = mctl->c;
1127         a[1].c = mctl->resizec;
1128         a[2].c = kctl->c;
1129         a[3].c = chancreate(sizeof(ind), 0);
1130         playc = a[3].c;
1131
1132         for(n = 0; n < Numcolors; n++)
1133                 colors[n].im = allocimage(display, Rect(0,0,1,1), RGB24, 1, colors[n].rgb<<8 | 0xff);
1134
1135         srand(time(0));
1136         pcurplaying = -1;
1137         chvolume(0);
1138         fmtinstall('P', positionfmt);
1139         threadsetname("zuke");
1140
1141         if(shuffled){
1142                 pcur = nrand(pl->n);
1143                 toggleshuffle();
1144                 recenter();
1145         }
1146
1147         redraw(1);
1148         m.buttons = 0;
1149         scrolling = 0;
1150
1151         proccreate(plumbaudio, kctl->c, 4096);
1152
1153         for(;;){
1154                 oldpcur = pcur;
1155                 if(seekmx != newseekmx){
1156                         seekmx = newseekmx;
1157                         redraw(0);
1158                 }
1159
1160                 oldbuttons = m.buttons;
1161                 switch(alt(a)){
1162                 case Emouse:
1163                         if(ptinrect(m.xy, seekbar)){
1164                                 seekoff = getmeta(pcurplaying)->duration * (double)(m.xy.x-1-seekbar.min.x) / (double)Dx(seekbar);
1165                                 if(seekoff < 0)
1166                                         seekoff = 0;
1167                                 newseekmx = m.xy.x;
1168                         }else{
1169                                 newseekmx = -1;
1170                         }
1171
1172                         if(m.buttons != 2)
1173                                 scrolling = 0;
1174                         if(m.buttons == 0)
1175                                 break;
1176                         if(m.buttons == 8){
1177                                 scroll = MAX(scroll-scrollsz/4-1, 0);
1178                                 redraw(1);
1179                                 break;
1180                         }else if(m.buttons == 16){
1181                                 scroll = MIN(scroll+scrollsz/4+1, pl->n-scrollsz);
1182                                 redraw(1);
1183                                 break;
1184                         }
1185
1186                         n = (m.xy.y - screen->r.min.y)/f->height;
1187
1188                         if(m.xy.x <= screen->r.min.x+Scrollwidth){
1189                                 if(m.buttons == 1){
1190                                         scroll = MAX(0, scroll-n-1);
1191                                         redraw(1);
1192                                         break;
1193                                 }else if(m.buttons == 4){
1194                                         scroll = MIN(scroll+n+1, pl->n-scrollsz);
1195                                         redraw(1);
1196                                         break;
1197                                 }else if(m.buttons == 2){
1198                                         scrolling = 1;
1199                                 }
1200                         }
1201
1202                         if(!scrolling && ptinrect(m.xy, insetrect(seekbar, -4))){
1203                                 if(ptinrect(m.xy, seekbar))
1204                                         seekrel(playercurr, seekoff/1000.0 - byteswritten/Bps);
1205                                 break;
1206                         }
1207
1208                         if(scrolling){
1209                                 if(scrollsz >= pl->n)
1210                                         break;
1211                                 scroll = (m.xy.y - screen->r.min.y - Scrollheight/4)*(pl->n-scrollsz) / (Dy(screen->r)-Scrollheight/2);
1212                                 scroll = CLAMP(scroll, 0, pl->n-scrollsz);
1213                                 redraw(1);
1214                         }else if(m.buttons == 1 || m.buttons == 2){
1215                                 n += scroll;
1216                                 if(n < pl->n){
1217                                         pcur = n;
1218                                         if(m.buttons == 2 && oldbuttons == 0){
1219                                                 stop(playercurr);
1220                                                 playercurr = newplayer(pcur, 1);
1221                                                 start(playercurr);
1222                                         }
1223                                 }
1224                         }
1225                         break;
1226                 case Eresize: /* resize */
1227                         if(getwindow(display, Refnone) < 0)
1228                                 sysfatal("getwindow: %r");
1229                         redraw(1);
1230                         break;
1231                 case Ekey:
1232                         switch(key){
1233                         case Kleft:
1234                                 seekrel(playercurr, -(double)Seek);
1235                                 break;
1236                         case Kright:
1237                                 seekrel(playercurr, Seek);
1238                                 break;
1239                         case ',':
1240                                 seekrel(playercurr, -(double)Seekfast);
1241                                 break;
1242                         case '.':
1243                                 seekrel(playercurr, Seekfast);
1244                                 break;
1245                         case Kup:
1246                                 pcur--;
1247                                 break;
1248                         case Kpgup:
1249                                 pcur -= scrollsz;
1250                                 break;
1251                         case Kdown:
1252                                 pcur++;
1253                                 break;
1254                         case Kpgdown:
1255                                 pcur += scrollsz;
1256                                 break;
1257                         case Kend:
1258                                 pcur = pl->n-1;
1259                                 scroll = pl->n-scrollsz;
1260                                 break;
1261                         case Khome:
1262                                 pcur = 0;
1263                                 break;
1264                         case '\n':
1265 playcur:
1266                                 stop(playercurr);
1267                                 playercurr = newplayer(pcur, 1);
1268                                 start(playercurr);
1269                                 break;
1270                         case 'q':
1271                         case Kdel:
1272                                 stop(playercurr);
1273                                 goto end;
1274                         case 'i':
1275                         case 'o':
1276                                 if(pcur == pcurplaying)
1277                                         oldpcur = -1;
1278                                 pcur = pcurplaying;
1279                                 recenter();
1280                                 break;
1281                         case 'b':
1282                         case '>':
1283                                 if(playercurr == nil)
1284                                         break;
1285                                 pnew = pcurplaying;
1286                                 if(++pnew >= pl->n)
1287                                         pnew = 0;
1288                                 stop(playercurr);
1289                                 playercurr = newplayer(pnew, 1);
1290                                 start(playercurr);
1291                                 redraw(1);
1292                                 break;
1293                         case 'z':
1294                         case '<':
1295                                 if(playercurr == nil)
1296                                         break;
1297                                 pnew = pcurplaying;
1298                                 if(--pnew < 0)
1299                                         pnew = pl->n-1;
1300                                 stop(playercurr);
1301                                 playercurr = newplayer(pnew, 1);
1302                                 start(playercurr);
1303                                 redraw(1);
1304                                 break;
1305                         case '-':
1306                                 chvolume(-1);
1307                                 redraw(0);
1308                                 break;
1309                         case '+':
1310                         case '=':
1311                                 chvolume(+1);
1312                                 redraw(0);
1313                                 break;
1314                         case 'v':
1315                                 stop(playercurr);
1316                                 playercurr = nil;
1317                                 pcurplaying = -1;
1318                                 freeimage(cover);
1319                                 cover = nil;
1320                                 redraw(1);
1321                                 break;
1322                         case 's':
1323                                 toggleshuffle();
1324                                 recenter();
1325                                 redraw(1);
1326                                 break;
1327                         case 'c':
1328                         case 'p':
1329                         case ' ':
1330                                 if(toggle(playercurr) != 0)
1331                                         goto playcur;
1332                                 break;
1333                         case '/':
1334                         case '?':
1335                         case 'n':
1336                         case 'N':
1337                                 search(key);
1338                                 break;
1339                         }
1340                         break;
1341                 case Eplay:
1342                         pcur = ind;
1343                         recenter();
1344                         if(playercurr != nil)
1345                                 goto playcur;
1346                         break;
1347                 }
1348
1349                 if(pcur != oldpcur){
1350                         pcur = CLAMP(pcur, 0, pl->n-1);
1351                         if(pcur < scroll)
1352                                 scroll = pcur;
1353                         else if(pcur > scroll + scrollsz)
1354                                 scroll = pcur - scrollsz;
1355                         scroll = CLAMP(scroll, 0, pl->n-scrollsz);
1356
1357                         if(pcur != oldpcur)
1358                                 redraw(1);
1359                 }
1360         }
1361
1362 end:
1363         threadexitsall(nil);
1364 }