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