1 /* aylet 0.4, a .AY music file player.
2  * Copyright (C) 2001-2005 Russell Marks and Ian Collier.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18 
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <getopt.h>
24 #include "main.h"
25 #include "sound.h"
26 #include "ui.h"
27 #include "z80.h"
28 
29 #define AYLET_VER	"0.5"
30 
31 
32 #define FRAME_STATES_48		(3500000/50)
33 #define FRAME_STATES_128	(3546900/50)
34 #define FRAME_STATES_CPC	(4000000/50)
35 
36 
37 /* see main.h */
38 struct aydata_tag aydata;
39 struct time_tag tunetime;
40 
41 char *progname;
42 
43 /* start stopping (errr) after this many sec, 0=never.
44  * This is too short to allow some things to finish (Agent X 2
45  * springs to mind), but *so* many tunes repeat after less than 2 mins
46  * that I think this is a sensible default.
47  */
48 int stopafter=3*60;
49 int fadetime=10;	/* fadeout time *after* that in sec, 0=none */
50 static int done_fade=0;
51 
52 /* the memory is a flat all-RAM 64k */
53 unsigned char mem[64*1024];
54 unsigned long tstates=0,tsmax=FRAME_STATES_128;
55 int ay_current_reg=0;
56 int silent_max=4*50;	/* max frames of silence before skipping */
57 
58 int highspeed=0;
59 int playing=1;
60 int paused=0;
61 int want_quit=0;
62 
63 int use_ui=1;
64 int play_to_stdout=0;
65 int list_only=0;
66 
67 char **ay_filenames=NULL;	/* for ptrs to filenames */
68 int ay_num_files=0;
69 int ay_file=0;
70 int ay_track=0;
71 
72 /* for prev-track - when we skip back a file, this flag
73  * indicates that we want to start at the last track.
74  */
75 int go_to_last=0;
76 
77 /* -1 to run as speccy, allowing only speccy ports;
78  *  0 to run as speccy, and allow speccy and CPC ports (initial value);
79  *  1 to run as CPC, allowing only CPC ports.
80  */
81 int do_cpc=0;
82 
83 /* 0 if playing "normal" (or with curses/GTK UI)
84  * # of track otherwise
85  * track number passed through additional parameter (-t)
86  */
87 int play_one_track_only=0;
88 
89 
90 
in(int h,int l)91 unsigned int in(int h,int l)
92 {
93 /* presumably nothing? XXX */
94 
95 return(255);
96 }
97 
98 
out(int h,int l,int a)99 unsigned int out(int h,int l,int a)
100 {
101 static int cpc_f4=0;
102 
103 /* unlike a real speccy, it seems we should only emulate exact port
104  * number matches, rather than using bitmasks.
105  */
106 if(do_cpc<1)
107   switch(l)
108     {
109     case 0xfd:
110       switch(h)
111         {
112         case 0xff:
113           do_cpc=-1;
114         write_reg:
115           ay_current_reg=(a&15);
116           break;
117         case 0xbf:
118           do_cpc=-1;
119         write_dat:
120           sound_ay_write(ay_current_reg,a,tstates);
121           break;
122         default:
123           /* ok, since we do at least have low byte=FDh,
124            * do bitmask for top byte to allow for
125            * crappy .ay conversions. But don't disable
126            * CPC autodetect, just in case.
127            */
128           if((h&0xc0)==0xc0) goto write_reg;
129           if((h&0xc0)==0x80) goto write_dat;
130         }
131       break;
132 
133     case 0xfe:
134       do_cpc=-1;
135       sound_beeper(a&0x10);
136       break;
137     }
138 
139 if(do_cpc>-1)
140   switch(h)
141     {
142     case 0xf6:
143       switch(a&0xc0)
144         {
145         case 0x80:	/* write */
146           sound_ay_write(ay_current_reg,cpc_f4,tstates);
147           break;
148 
149         case 0xc0:	/* select */
150           ay_current_reg=(cpc_f4&15);
151           break;
152         }
153       break;
154 
155     case 0xf4:
156       cpc_f4=a;
157       if(!do_cpc)
158         {
159         /* restart as a more CPC-ish emulation */
160         do_cpc=1;
161         sound_ay_reset_cpc();
162         tsmax=FRAME_STATES_CPC;
163         if(tstates>tsmax) tstates-=tsmax;
164         }
165       break;
166     }
167 
168 return(0);	/* additional t-states */
169 }
170 
171 
172 
mem_init(int track)173 void mem_init(int track)
174 {
175 static unsigned char intz[]=
176   {
177   0xf3,		/* di */
178   0xcd,0,0,	/* call init */
179   0xed,0x5e,	/* loop: im 2 */
180   0xfb,		/* ei */
181   0x76,		/* halt */
182   0x18,0xfa	/* jr loop */
183   };
184 static unsigned char intnz[]=
185   {
186   0xf3,		/* di */
187   0xcd,0,0,	/* call init */
188   0xed,0x56,	/* loop: im 1 */
189   0xfb,		/* ei */
190   0x76,		/* halt */
191   0xcd,0,0,	/* call interrupt */
192   0x18,0xf7	/* jr loop */
193   };
194 int init,ay_1st_block,ourinit,interrupt;
195 unsigned char *ptr;
196 int addr,len,ofs;
197 
198 #define GETWORD(x) (((*(x))<<8)|(*(x+1)))
199 
200 init=GETWORD(aydata.tracks[track].data_stacketc+2);
201 interrupt=GETWORD(aydata.tracks[track].data_stacketc+4);
202 ay_1st_block=GETWORD(aydata.tracks[track].data_memblocks);
203 
204 memset(mem+0x0000,0xc9,0x0100);
205 memset(mem+0x0100,0xff,0x3f00);
206 memset(mem+0x4000,0x00,0xc000);
207 mem[0x38]=0xfb;		/* ei */
208 
209 /* call first AY block if no init */
210 ourinit=(init?init:ay_1st_block);
211 
212 if(!interrupt)
213   memcpy(mem,intz,sizeof(intz));
214 else
215   {
216   memcpy(mem,intnz,sizeof(intnz));
217   mem[ 9]=interrupt%256;
218   mem[10]=interrupt/256;
219   }
220 
221 mem[2]=ourinit%256;
222 mem[3]=ourinit/256;
223 
224 /* now put the memory blocks in place */
225 ptr=aydata.tracks[track].data_memblocks;
226 while((addr=GETWORD(ptr))!=0)
227   {
228   len=GETWORD(ptr+2);
229   ofs=GETWORD(ptr+4);
230   if(ofs>=0x8000) ofs=-0x10000+ofs;
231 
232   /* range check */
233   if(ptr-4-aydata.filedata+ofs>=aydata.filelen ||
234      ptr-4-aydata.filedata+ofs<0)
235     {
236     ptr+=6;
237     continue;
238     }
239 
240   /* fix any broken length */
241   if(ptr+4+ofs+len>=aydata.filedata+aydata.filelen)
242     len=aydata.filedata+aydata.filelen-(ptr+4+ofs);
243   if(addr+len>0x10000)
244     len=0x10000-addr;
245 
246   memcpy(mem+addr,ptr+4+ofs,len);
247   ptr+=6;
248   }
249 }
250 
251 
252 /* do action requested by UI code. returns zero if we need to
253  * exit the current track.
254  */
action_callback(enum cb_action_tag action)255 int action_callback(enum cb_action_tag action)
256 {
257 switch(action)
258   {
259   case cb_quit:
260     /* we have to pretend to be playing to get out... :-) */
261     playing=1; paused=0;
262     want_quit=1;
263     return(0);	/* exit track */
264 
265   case cb_highspeed:
266     highspeed=!highspeed;
267     ui_change_notify();
268     break;
269 
270   case cb_prev_file:
271   do_prev_file:
272     if(ay_file<=0)
273       {
274       go_to_last=0;	/* wouldn't need that now */
275       goto do_stop;
276       }
277     ay_file--;
278     goto do_move;
279 
280   case cb_next_file:
281   do_next_file:
282     if(ay_file>=ay_num_files-1)
283       goto do_stop;
284     ay_file++;
285     goto do_move;
286 
287   case cb_prev_track:
288     if(ay_track<=0)
289       {
290       go_to_last=1;
291       goto do_prev_file;
292       }
293     ay_track--;
294     goto do_move;
295 
296   case cb_next_track:
297     if(ay_track>=aydata.num_tracks-1)
298       goto do_next_file;
299     ay_track++;
300   do_move:
301     paused=0;
302     ui_change_notify();
303     return(0);
304 
305   case cb_play:
306     playing=1;
307     if(paused) paused=0;
308     ui_change_notify();
309     break;
310 
311   case cb_pause:
312     if(playing)
313       paused=!paused,ui_change_notify();
314     break;
315 
316   case cb_stop:
317   do_stop:
318     if(playing)
319       {
320       playing=paused=0;
321       ui_change_notify();
322       return(0);
323       }
324     break;
325 
326   case cb_restart:
327     ui_change_notify();
328     return(0);
329 
330   case cb_dec_stopafter:
331     if(!(stopafter%30))
332       stopafter-=30;
333     else
334       stopafter-=stopafter%30;
335     if(stopafter<0) stopafter=10*60;
336     ui_change_notify();
337     break;
338 
339   case cb_inc_stopafter:
340     if(!(stopafter%30))
341       stopafter+=30;
342     else
343       stopafter+=(30-stopafter%30);
344     if(stopafter>10*60) stopafter=0;
345     ui_change_notify();
346     break;
347 
348   case cb_dec_fadetime:
349     fadetime--;
350     if(fadetime<1) fadetime=20;
351     ui_change_notify();
352     break;
353 
354   case cb_inc_fadetime:
355     fadetime++;
356     if(fadetime>20) fadetime=1;
357     ui_change_notify();
358     break;
359 
360   case cb_dec_vol:
361   case cb_inc_vol:
362     /* XXX NYI */
363     break;
364 
365   default:	/* for cb_none */
366     break;
367   }
368 
369 return(1);
370 }
371 
372 
373 /* rets zero if we want to exit the emulation (i.e. exit track) */
do_interrupt(void)374 int do_interrupt(void)
375 {
376 static int count=0;
377 static int silent_for=0;
378 
379 count++;
380 if(count>=4) count=0;
381 
382 if(!playing || paused)
383   usleep(20000);
384 else
385   {
386   /* check for fade needed */
387   if(!done_fade && stopafter && tunetime.min*60+tunetime.sec>=stopafter)
388     {
389     done_fade=1;
390     sound_start_fade(fadetime);
391     }
392 
393   /* incr time */
394   tunetime.subsecframes++;
395   if(tunetime.subsecframes>=50)
396     {
397     tunetime.subsecframes=0;
398     tunetime.sec++;
399     if(tunetime.sec>=60)
400       {
401       tunetime.sec=0;
402       tunetime.min++;
403       }
404     }
405 
406   /* play frame, and stop if it's been silent for a while */
407   if(!sound_frame(count==0 || !highspeed))
408     silent_for++;
409   else
410     silent_for=0;
411   if(silent_for>=silent_max)
412     {
413     silent_for=0;
414     ui_change_notify();
415     /* do next track, or file, or just stop */
416     /* if play_one_track_only is set, then finish now */
417     if (play_one_track_only)
418       {
419       want_quit=1;
420       return(0);
421       }
422     ay_track++;
423     if(ay_track>=aydata.num_tracks)
424       {
425       ay_track=0;
426       ay_file++;
427       if(ay_file>=ay_num_files)
428         {
429         /* return to first file/track (except for non-UI,
430          * to save any pointless reload), and stop.
431          */
432         if(use_ui)
433           ay_file=ay_track=0;
434         else
435           ay_file--,ay_track=aydata.num_tracks-1;
436         playing=0;
437         }
438       }
439     return(0);
440     }
441   }
442 
443 return(ui_frame());
444 }
445 
446 
read_ay_file(char * filename)447 int read_ay_file(char *filename)
448 {
449 FILE *in;
450 unsigned char *data,*buf,*ptr,*ptr2;
451 int data_alloc=16384,buf_alloc=16384,data_ofs=0;
452 int data_len;
453 int ret,tmp,f;
454 
455 /* given the loopy format, it's much easier to deal with in memory.
456  * But I'm avoiding mmap() in case I want to tweak this to run from
457  * a pipe at some point.
458  */
459 if((buf=malloc(buf_alloc))==NULL)
460   return(0);
461 
462 if((data=malloc(data_alloc))==NULL)
463   {
464   free(buf);
465   return(0);
466   }
467 
468 if((in=fopen(filename,"rb"))==NULL)
469   {
470   free(buf);
471   free(data);
472   return(0);
473   }
474 
475 while((ret=fread(buf,1,buf_alloc,in))>0)
476   {
477   if(data_ofs+ret>=data_alloc)
478     {
479     unsigned char *oldptr=data;
480 
481     data_alloc+=buf_alloc;
482     if((data=realloc(data,data_alloc))==NULL)
483       {
484       fclose(in);
485       free(oldptr);
486       free(buf);
487       return(0);
488       }
489     }
490 
491   memcpy(data+data_ofs,buf,ret);
492   data_ofs+=ret;
493   }
494 
495 free(buf);
496 
497 if(ferror(in))
498   {
499   fclose(in);
500   free(data);
501   return(0);
502   }
503 
504 fclose(in);
505 
506 data_len=data_ofs;
507 
508 if(memcmp(data,"ZXAYEMUL",8)!=0)
509   {
510   free(data);
511   return(0);
512   }
513 
514 /* for the rest, we don't parse that much; just make copies of the
515  * offset `pointers' as real pointers, and save all the `top-level'
516  * stuff.
517  */
518 
519 aydata.tracks=NULL;
520 
521 #define READWORD(x)	(x)=256*(*ptr++); (x)|=*ptr++
522 #define READWORDPTR(x)	READWORD(tmp); \
523 		if(tmp>=0x8000) tmp=-0x10000+tmp; \
524 		if(ptr-data-2+tmp>=data_len || ptr-data-2+tmp<0) \
525 		  { \
526                   free(data); \
527                   if(aydata.tracks) free(aydata.tracks); \
528 		  return(0); \
529                   } \
530 		(x)=ptr-2+tmp
531 #define CHECK_ASCIIZ(x) \
532 		if(!memchr((x),0,data+data_len-(x))) \
533 		  { \
534                   free(data); \
535                   if(aydata.tracks) free(aydata.tracks); \
536 		  return(0); \
537                   }
538 
539 ptr=data+8;
540 aydata.filever=*ptr++;
541 aydata.playerver=*ptr++;
542 ptr+=2;		/* skip `custom player' stuff */
543 READWORDPTR(aydata.authorstr);
544 CHECK_ASCIIZ(aydata.authorstr);
545 READWORDPTR(aydata.miscstr);
546 CHECK_ASCIIZ(aydata.miscstr);
547 aydata.num_tracks=1+*ptr++;
548 aydata.first_track=*ptr++;
549 
550 /* skip to track info */
551 READWORDPTR(ptr2);
552 ptr=ptr2;
553 
554 if((aydata.tracks=malloc(aydata.num_tracks*sizeof(struct ay_track_tag)))==NULL)
555   {
556   free(data);
557   return(0);
558   }
559 
560 for(f=0;f<aydata.num_tracks;f++)
561   {
562   READWORDPTR(aydata.tracks[f].namestr);
563   CHECK_ASCIIZ(aydata.tracks[f].namestr);
564   READWORDPTR(aydata.tracks[f].data);
565   }
566 
567 for(f=0;f<aydata.num_tracks;f++)
568   {
569   if(aydata.tracks[f].data-data+10>data_len-4)
570     {
571     free(aydata.tracks);
572     free(data);
573     return(0);
574     }
575 
576   ptr=aydata.tracks[f].data+10;
577   READWORDPTR(aydata.tracks[f].data_stacketc);
578   READWORDPTR(aydata.tracks[f].data_memblocks);
579 
580   ptr=aydata.tracks[f].data+4;
581   READWORD(aydata.tracks[f].fadestart);
582   READWORD(aydata.tracks[f].fadelen);
583   }
584 
585 /* ok then, that's as much parsing as we do here. */
586 
587 aydata.filedata=data;
588 aydata.filelen=data_len;
589 return(1);
590 }
591 
592 
tunetime_reset(void)593 static void tunetime_reset(void)
594 {
595 tunetime.min=tunetime.sec=tunetime.subsecframes=0;
596 done_fade=0;
597 }
598 
599 
600 /* read a file and play it. sound_init() should already have been called,
601  * and sound device should still be open when we exit.
602  */
mainloop(void)603 void mainloop(void)
604 {
605 int oldfile=-1;
606 
607 aydata.filedata=NULL;
608 aydata.tracks=NULL;
609 
610 /* this is kind of a weird multi-level event loop (if
611  * you consider do_interrupt()); it's difficult to do it very
612  * differently without turning the Z80 emulation inside-out.
613  */
614 while(!want_quit)
615   {
616   /* load a new file if we need to */
617   if(ay_file!=oldfile)
618     {
619     if(aydata.tracks) free(aydata.tracks);
620     if(aydata.filedata) free(aydata.filedata);
621 
622     if(!read_ay_file(ay_filenames[ay_file]))
623       {
624       ui_end();
625       if(sound_enabled) sound_end();
626       fprintf(stderr,"%s: reading `%s' failed.\n",
627               progname,ay_filenames[ay_file]);
628       exit(1);
629       }
630 
631     if(!play_one_track_only)
632       ay_track=0;
633     else
634       {
635       ay_track=play_one_track_only-1;
636 
637       if(ay_track>=aydata.num_tracks)
638         {
639         ui_end();
640         if(sound_enabled) sound_end();
641         fprintf(stderr,"%s: `%s' has only %d track%s.\n",
642                 progname,ay_filenames[ay_file],
643                 aydata.num_tracks,(aydata.num_tracks==1)?"":"s");
644         exit(1);
645         }
646       }
647 
648     if(go_to_last)
649       {
650       go_to_last=0;
651       ay_track=aydata.num_tracks-1;
652       }
653     }
654 
655   oldfile=ay_file;
656 
657   /* only do the whole emulation thing if we're actually playing... */
658   if(playing)
659     {
660     /* re-enable sound after stopping, if needed */
661     if(!sound_enabled && !sound_init())
662       {
663       ui_end();
664       fprintf(stderr,"%s: couldn't reopen sound device.\n",progname);
665       exit(1);
666       }
667 
668     ay_current_reg=0;
669     sound_ay_reset();
670     mem_init(ay_track);
671     tunetime_reset();
672     tsmax=FRAME_STATES_128;
673     do_cpc=0;
674     z80loop(aydata.tracks[ay_track].data,
675             aydata.tracks[ay_track].data_stacketc);
676     }
677 
678   /* if stopped, close sound device */
679   if(sound_enabled && !playing)
680     sound_end();
681 
682   /* do reset now, so any paused/stopped status time makes sense */
683   tunetime_reset();
684   while((!playing || paused) && ay_file==oldfile)
685     do_interrupt();
686   }
687 
688 free(aydata.tracks);
689 free(aydata.filedata);
690 }
691 
692 
usage_help(void)693 void usage_help(void)
694 {
695 printf("%s " AYLET_VER
696 	" - copyright (C) 2001-2005 Russell Marks and Ian Collier.\n\n",progname);
697 printf("usage: %s [-BhlmnNsS] [-A stopafter] [-F fadetime] [-t tracknum]\n"
698 	"\t\tfile [file2 ...]\n",
699        progname);
700 puts("\n"
701 "	-A	set stop-after time in seconds (the time at which tracks\n"
702 "		start fading out), default 180 seconds (3 minutes).\n"
703 "\n"
704 "	-B	use ABC stereo (default ACB).\n"
705 "\n"
706 "	-e	force 8-bit output even if 16-bit playback is possible.\n"
707 "\n"
708 "	-F	set fade-out time in seconds (the time tracks take to\n"
709 "		fade out), default 10 seconds.\n"
710 "\n"
711 "	-h	give this help.\n"
712 "\n"
713 "	-l	list contents of the files, rather than playing them.\n"
714 "\n"
715 "	-m	use mono rather than the default stereo.\n"
716 "\n"
717 "	-n	use a simple batch-playing tty style, rather than the\n"
718 "		usual interface. (Only works in the curses version.)\n"
719 "\n"
720 "	-N	use narrower AY stereo separation.\n"
721 "\n"
722 "	-s	output sample data to stdout rather than playing it;\n"
723 "		implies `-n'. The sample is 44.1kHz 16-bit stereo using\n"
724 "		the machine's native byte ordering (or mono if using `-m',\n"
725 "		and 8-bit if using `-e').\n"
726 "\n"
727 "	-S	use fake pseudo-stereo for the beeper.\n"
728 "\n"
729 "	-t	play specified track, then quit.\n");
730 }
731 
732 
parseoptions(int argc,char * argv[])733 void parseoptions(int argc,char *argv[])
734 {
735 int done=0;
736 
737 opterr=0;
738 
739 do
740   switch(getopt(argc,argv,"A:BeF:hlmnNsSt:"))
741     {
742     case 'A':	/* stopafter */
743       stopafter=atoi(optarg);
744       if(stopafter<0) stopafter=0;
745       if(stopafter>10*60) stopafter=10*60;
746       break;
747     case 'B':	/* ABC stereo (not ACB) */
748       sound_stereo_ay_abc=1;
749       break;
750     case 'e':	/* eight-bit-only */
751       sixteenbit=0;
752       break;
753     case 'F':	/* fadetime */
754       fadetime=atoi(optarg);
755       if(fadetime<1) fadetime=1;
756       if(fadetime>20) fadetime=20;
757       break;
758     case 'h':
759       usage_help();
760       exit(0);
761     case 'l':	/* list tracks etc. rather than playing */
762       list_only=1;
763       break;
764     case 'm':	/* mono */
765       sound_stereo=0;
766       break;
767     case 'N':	/* narrow stereo separation */
768       sound_stereo_ay_narrow=1;
769       break;
770     case 'n':	/* no UI (effective in non-X ver only) */
771       use_ui=0;
772       break;
773     case 'S':	/* pseudostereo (for beeper) */
774       sound_stereo_beeper=1;
775       break;
776     case 's':	/* play to stdout */
777       use_ui=0;		/* implied */
778       play_to_stdout=1;
779       break;
780     case 't':
781       if (!(play_one_track_only=atoi(optarg))) {
782          fprintf(stderr, "%s: error parsing an argument.\n This should be "
783                          "a numerical value betwen 1 and num_tracks.\n",
784                          progname);
785          exit(1);
786       }
787       break;
788     case '?':
789       switch(optopt)
790         {
791         case 'A':
792           fprintf(stderr,"%s: "
793                   "the `-A' option requires a stop-after time (in seconds).\n",
794                   progname);
795           break;
796         case 'F':
797           fprintf(stderr,"%s: "
798                   "the `-F' option requires a fade-out time (in seconds).\n",
799                   progname);
800           break;
801         case 't':
802           fprintf(stderr,"%s: the -t option requires a track number to "
803                           "play.\n",progname);
804           break;
805         default:
806           fprintf(stderr,"%s: "
807                   "option `%c' not recognised.\n",
808                   progname,optopt);
809         }
810       exit(1);
811     case -1:
812       done=1;
813     }
814 while(!done);
815 
816 if(optind>=argc)	/* if no filenames given... */
817   {
818   fprintf(stderr,"%s: you must specify the file(s) to %s.\n",
819           progname,list_only?"list":"play");
820   exit(1);
821   }
822 
823 ay_filenames=argv+optind;
824 ay_num_files=argc-optind;
825 }
826 
827 
do_list(void)828 void do_list(void)
829 {
830 char *ptr;
831 int f,g;
832 
833 aydata.filedata=NULL;
834 aydata.tracks=NULL;
835 
836 for(f=0;f<ay_num_files;f++)
837   {
838   if(!read_ay_file(ay_filenames[f]))
839     {
840     fprintf(stderr,"%s: reading `%s' failed.\n",
841             progname,ay_filenames[f]);
842     continue;
843     }
844 
845   ptr=strrchr(ay_filenames[f],'/');
846   printf("\n  File:\t%s\n  Misc:\t%s\nAuthor:\t%s\nTracks:\t%3d\n",
847          ptr?ptr+1:ay_filenames[f],aydata.miscstr,aydata.authorstr,
848          aydata.num_tracks);
849   for(g=0;g<aydata.num_tracks;g++)
850     printf("\t%3d - %s\n",g+1,aydata.tracks[g].namestr);
851 
852   if(aydata.tracks) free(aydata.tracks);
853   if(aydata.filedata) free(aydata.filedata);
854   }
855 }
856 
857 
main(int argc,char * argv[])858 int main(int argc,char *argv[])
859 {
860 char *ptr;
861 
862 progname=argv[0]?argv[0]:"aylet";
863 if((ptr=strrchr(progname,'/'))!=NULL)
864   progname=ptr+1;
865 
866 parseoptions(argc,argv);
867 
868 if(list_only)
869   {
870   do_list();
871   exit(0);
872   }
873 
874 /* even when not using curses/GTK+ we go via the UI code
875  * (apart from the above :-)).
876  */
877 ui_init(argc,argv);
878 
879 if(!sound_init())
880   {
881   ui_end();
882   fprintf(stderr,"%s: couldn't open sound device.\n",progname);
883   exit(1);
884   }
885 
886 mainloop();
887 
888 if(sound_enabled)
889   sound_end();
890 
891 ui_end();
892 
893 exit(0);
894 }
895