home *** CD-ROM | disk | FTP | other *** search
- /*
- * controller.c - Main loop and interactive interface.
- *
- * (C) 1994 Mikael Nordqvist (d91mn@efd.lth.se, mech@df.lth.se)
- */
-
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <sys/time.h>
- #include <sys/wait.h>
- #include <errno.h>
- #include <time.h>
- #include <sys/soundcard.h>
- #include <dirent.h>
-
- #include "mod.h"
- #include "message.h"
- #include "ncurses.h"
-
- /* External variables */
-
- SEQ_DECLAREBUF();
- extern int seqfd, gus_dev;
- extern struct mod_info M;
- extern struct options default_opt, opt;
- extern char workdir[PATH_MAX+1];
-
- extern pid_t pid_main, pid_player;
- extern int total_time; /* Total time spent playing modules */
-
- extern int nr_songs, nr_files;
- extern int swin_LINES; /* For page-DN/UP */
- extern int nr_dirs;
-
- /* Variables */
-
- int ack_pipe[2]; /* Player acks main */
- int player2main_synced[2]; /* Message from player to main (synced) */
- int main2player[2]; /* Message from main to player */
-
- int nr_visible_voices; /* Maximum number of visible voices */
- char loaded; /* True when a module has been loaded */
- char quit; /* Set when program is aborting */
-
- /* Local variables */
-
- static int maxfd;
- static int first_voice; /* First voice to be shown on screen */
-
- static int first_sample; /* First sample shown on screen */
- static char playing; /* Are playing or idling */
- static char selecting; /* Are we selecting a new song? */
-
- static int starting_time; /* When module was started */
- static int restart_time; /* When player had to be restarted last */
-
- static int cur_filenr; /* Current file active (real order) */
- static int saved_filenr; /* Saved file# in case we abort file-select */
- static char saved_dir[PATH_MAX+1]; /* Ditto with workdir */
-
- static char voice_toggle; /* Voice-toggling (2 keys) */
-
- /* Variables for commands requiring player to back up */
-
- static int pending_restart, pending_info;
- static int last_speed, last_tempo, last_pos, last_line;
-
- /* Constants */
-
- #define STATUS_READY 1
- #define STATUS_PLAYING 2
-
-
- void main_loop(void)
- {
- fd_set rset;
- int nr;
-
- if(opt.quiet && nr_songs == 0)
- return;
-
- maxfd=getdtablesize();
- draw_screen();
- set_voice_detail(1); /* Start with highest detail */
-
- set_status(STATUS_READY);
- selecting=quit=0;
- cur_filenr=first_voice=0;
-
- if(opt.play_list) {
- if(opt.auto_next) {
- cur_filenr=seqidx_to_fileidx(0);
- load_next_module();
- }
- else {
- start_selecting();
- print_selectingbar();
- print_files(cur_filenr);
- }
- }
- else {
- strcpy(saved_dir, workdir);
- init_dir(workdir);
- start_selecting();
- print_selectingbar();
- print_files(cur_filenr);
- }
-
- while(!quit) {
- FD_ZERO(&rset);
- FD_SET(STDIN_FILENO, &rset);
- if(loaded)
- FD_SET(seqfd, &rset);
- if((nr=select(maxfd, &rset, 0, 0, 0)) >= 0) {
- if(FD_ISSET(STDIN_FILENO, &rset))
- read_key();
- else if(FD_ISSET(seqfd, &rset))
- read_sequencer();
- if(!nr)
- warning("nr == 0 ?!?!?\n");
- }
- else {
- if(errno != EINTR)
- error("select() failed (errno=%d).\n", errno);
- }
- }
- }
-
-
- void load_next_module(void)
- {
- struct mod_message mess;
- char buf[80];
- int nr, tmp;
-
- voice_toggle=-1; /* Forget previous keypress */
-
- /* Release resources used by the previous module */
- if(loaded) {
- mess.type=MESSAGE_EXIT;
- send_to_player(&mess);
-
- drain_pipes(0);
- do {
- if((nr=waitpid(pid_player, 0, 0)) == -1 && errno != EINTR)
- error("waitpid() failed in main.\n");
- }
- while(nr != pid_player);
- drain_pipes(1);
-
- free_module();
- loaded=0;
- }
-
- first_voice=0;
- print_channelnumbers(first_voice);
- clear_screen();
- clear_all_info();
-
- /* Make sure sequencer is clean */
- cleanup_sound();
- init_sound();
-
- /* Try loading a module until we succeed or abort */
- while(!quit) {
- get_module(fileidx_to_seqidx(cur_filenr));
- print_filename(fileidx_to_seqidx(cur_filenr));
- tmp=load_module();
- chdir(workdir); /* Restore current directory after load_module() */
- if(!tmp) {
- if(quit != QUIT_SIGNAL) {
- print_status("Loading failed");
- sleep(1);
- free_module();
- loaded=0;
- if(should_autoselect_next()) {
- select_next_file();
- continue;
- }
- else {
- clear_all_info();
- set_status(STATUS_READY);
- start_selecting();
- print_selectingbar();
- print_files(cur_filenr);
- return;
- }
- }
- else {
- return; /* Leave if we got a Ctrl-C */
- }
- }
- else
- break; /* Found module, leave load-loop */
- }
-
- if(quit) { /* Is this one still needed? */
- return;
- }
-
- switch(pid_player=fork()) {
- case -1:
- error("Fork failed.\n");
- break;
-
- case 0: /* Child - player */
- play_module();
- cleanup_sound();
- exit(0);
- break;
-
- default: /* Parent */
- }
-
- starting_time=restart_time=time(0);
-
- pending_restart=0;
- first_sample=1;
- loaded=1;
-
- last_speed=6; /* Defaults if a key is hit b4 1st line is played */
- last_tempo=125;
-
- set_voice_detail(opt.voice_detail);
-
- clear_screen();
- print_samples(1);
- set_status(STATUS_PLAYING);
- print_channelnumbers(first_voice);
-
- read(STDIN_FILENO, buf, 80); /* Flush input */
- }
-
-
- char *basename(char *s)
- {
- int i;
- for(i=strlen(s)-1; i >=0 && s[i] != '/'; --i)
- ;
- return &s[++i];
- }
-
-
- void read_key(void)
- {
- struct mod_message mess;
- int ch, tmp;
- char tmpname[NAME_MAX+1];
- char c;
- char dotdot=0, done;
-
- if(opt.verbose || opt.quiet) {
- read(STDIN_FILENO, &c, 1);
- ch=c;
- if(opt.quiet)
- return;
- }
- else
- ch=getch();
-
- if(!(ch >= '0' && ch <= '9'))
- voice_toggle=-1;
-
- /* Keys always available */
- done=1;
- switch(ch) {
- case KEY_F(1):
- case 'h':
- print_minihelp();
- break;
- case 'q':
- mess.type=MESSAGE_EXIT;
- send_to_player(&mess);
- quit=QUIT_QUIT;
- if(playing)
- total_time+=time(0)-restart_time;
- break;
- case '+':
- change_mastervolume(5);
- break;
- case '-':
- change_mastervolume(-5);
- break;
- case 'm':
- change_mastervolume(0);
- break;
- #if 0
- case '*':
- debug("Main sending NOP.\n");
- mess.type=MESSAGE_NOP;
- write(main2player[PIPE_WRITE], &mess, sizeof(mess));
- break;
- #endif
- default:
- done=0;
- }
-
- if(done) /* Ugly but saves time */
- goto skip;
-
- if(selecting) {
- switch(ch) {
- case KEY_UP:
- case 'p':
- cur_filenr=MAX(cur_filenr-1, 0);
- print_files(cur_filenr);
- break;
- case KEY_DOWN:
- case 'n':
- cur_filenr=MIN(cur_filenr+1, nr_files-1);
- print_files(cur_filenr);
- break;
- case KEY_PPAGE:
- case 'P':
- cur_filenr=MAX(cur_filenr-swin_LINES/2, 0);
- print_files(cur_filenr);
- break;
- case KEY_NPAGE:
- case 'N':
- cur_filenr=MIN(cur_filenr+swin_LINES/2, nr_files-1);
- print_files(cur_filenr);
- break;
- case KEY_HOME:
- cur_filenr=0;
- print_files(cur_filenr);
- break;
- case KEY_END:
- cur_filenr=nr_files-1;
- print_files(cur_filenr);
- break;
- case '\n':
- if(!is_dir(cur_filenr)) { /* is_dir() == 0 when opt.play_list */
- do_select_file();
- if(playing)
- total_time+=time(0)-restart_time;
- load_next_module();
- }
- else {
- if(!strcmp("..", get_dirnamestring(cur_filenr)))
- dotdot=1;
- chdir(get_dirnamestring(cur_filenr));
- yucky_goto:
- strcpy(tmpname, basename(workdir));
- getcwd(workdir, PATH_MAX); /* Change working directory */
- init_dir(workdir);
- cur_filenr=(dotdot ? name_to_filenr(tmpname) : 0);
- print_selectingbar();
- print_files(cur_filenr);
- }
- break;
- case 'u':
- if(opt.play_list || !strcmp("/", workdir))
- break;
- chdir("..");
- dotdot=1;
- goto yucky_goto;
- case 'l':
- if(!loaded)
- return;
-
- abort_selecting();
- print_channelnumbers(first_voice);
- if(playing && !default_opt.noscroll)
- print_line(last_pos, last_line, first_voice);
- else
- clear_screen();
- break;
- default: /* All other keys ignored */
- }
- }
- else {
- /* The following keys are available only when we have a loaded song */
- if(loaded) {
- switch(ch) {
- case KEY_RIGHT:
- first_voice=
- MAX(0, MIN(first_voice+1, M.nr_voices-nr_visible_voices));
- print_channelnumbers(first_voice);
- break;
- case KEY_LEFT:
- first_voice=MAX(first_voice-1, 0);
- print_channelnumbers(first_voice);
- break;
- case 'f':
- if(playing) {
- pending_restart=MESSAGE_JUMPPATTERN;
- pending_info=1;
- if(!selecting)
- clear_screen();
- }
- else {
- last_pos=MIN(last_pos+1, M.songlength-1);
- print_pos(last_pos, 0);
- }
- break;
- case 'b':
- if(playing) {
- pending_info=-1;
- pending_restart=MESSAGE_JUMPPATTERN;
- if(!selecting)
- clear_screen();
- }
- else {
- last_pos=MAX(last_pos-1, 0);
- print_pos(last_pos, 0);
- }
- break;
- case 'r':
- set_status(STATUS_PLAYING);
- pending_restart=MESSAGE_RESTART;
- handle_restart(42); /* Done here as no lines coming */
- clear_screen();
- break;
- case KEY_UP:
- case 'a':
- first_sample=MAX(first_sample-1, 1);
- print_samples(first_sample);
- break;
- case KEY_DOWN:
- case 'z':
- first_sample=MAX(1, MIN(first_sample+1, M.nr_samples-7));
- print_samples(first_sample);
- break;
- case KEY_NPAGE:
- first_sample=MAX(1, MIN(first_sample+8, M.nr_samples-7));
- print_samples(first_sample);
- break;
- case KEY_PPAGE:
- first_sample=MAX(first_sample-8, 1);
- print_samples(first_sample);
- break;
- case KEY_HOME:
- first_sample=1;
- print_samples(first_sample);
- break;
- case KEY_END:
- first_sample=MAX(1, M.nr_samples-7);
- print_samples(first_sample);
- break;
- case '\n':
- if(playing) {
- set_status(STATUS_READY);
- pending_restart=MESSAGE_STOP;
- total_time+=time(0)-restart_time; /* Must be done here
- * as status=ready.
- */
- clear_screen();
- }
- else {
- starting_time=time(0);
- pending_restart=MESSAGE_JUMPPATTERN;
- pending_info=0;
- handle_restart(last_pos); /* Done here as no lines are
- * sent from player.
- */
- set_status(STATUS_PLAYING); /* _after_ restart */
- }
- break;
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- if(M.nr_voices <= 9) { /* Single-key mode */
- tmp=ch-'0';
- voice_toggle=-1;
- if(tmp == 0 || tmp > M.nr_voices)
- break;
- }
- else { /* Double-key mode */
- if(voice_toggle == -1) { /* First key */
- voice_toggle=ch-'0';
- if(voice_toggle*10 > M.nr_voices)
- voice_toggle=-1;
- break;
- }
- else { /* Second key */
- tmp=voice_toggle*10+ch-'0';
- voice_toggle=-1;
- if(!tmp || tmp > M.nr_voices)
- break;
- }
- }
- tmp--;
- tmp=1<<tmp;
- opt.active_voices=(opt.active_voices&(~tmp))|
- ((~(opt.active_voices&tmp))&tmp);
-
- if(playing) {
- pending_info=0;
- pending_restart=MESSAGE_JUMPPATTERN;
- if(!selecting)
- clear_screen();
- }
- print_channelnumbers(first_voice);
- break;
- default:
- }
- }
-
- /* Keys available anytime we are not selecting a new file */
- switch(ch) {
- case ' ':
- case 'n':
- select_next_file();
- if(playing)
- total_time+=time(0)-restart_time;
- load_next_module();
- break;
- case 'p':
- select_prev_file();
- if(playing)
- total_time+=time(0)-restart_time;
- load_next_module();
- break;
- case 'l':
- start_selecting();
- print_selectingbar();
- print_files(cur_filenr);
- break;
-
- case 's':
- if(default_opt.noscroll) {
- default_opt.noscroll=0;
- if(playing)
- print_line(last_pos, last_line, first_voice);
- }
- else {
- clear_screen();
- default_opt.noscroll=1;
- }
- break;
- case 'v':
- if(nr_visible_voices == 4)
- nr_visible_voices=9;
- else if(nr_visible_voices == 9)
- nr_visible_voices=17;
- else
- nr_visible_voices=4;
-
- first_voice=
- MAX(0, MIN(first_voice, M.nr_voices-nr_visible_voices));
-
- print_channelnumbers(first_voice);
- if(playing && !default_opt.noscroll)
- print_line(last_pos, last_line, first_voice);
- break;
- default:
- }
- }
- skip:
- #ifdef FLUSH_INPUT
- read(STDIN_FILENO, tmpname, 80); /* Flush input (for slow CPU's) */
- #endif
- }
-
-
- void start_selecting(void)
- {
- saved_filenr=cur_filenr;
- if(!opt.play_list)
- getcwd(saved_dir, PATH_MAX); /* Store working directory */
- selecting=1;
- }
-
- /* Do what is necessery when selecting a file (except perform the actual
- * loading).
- */
-
- void do_select_file(void)
- {
- if(!opt.play_list)
- strcpy(saved_dir, workdir); /* Set the new working directory */
- selecting=0;
- }
-
-
- void abort_selecting(void)
- {
- if(!opt.play_list) {
- if(strcmp(saved_dir, workdir)) {
- strcpy(workdir, saved_dir);
- chdir(workdir);
- init_dir(workdir);
- }
- }
- cur_filenr=saved_filenr;
- selecting=0;
- }
-
-
- int should_autoselect_next(void)
- {
- if(!opt.auto_next)
- return 0;
-
- /* No forever when there is no playlist */
- if(opt.play_list && opt.replay_forever)
- return 1;
-
- if(opt.play_list) {
- if(fileidx_to_seqidx(cur_filenr) == nr_songs-1) {
- return 0;
- }
- }
- else {
- if(cur_filenr == nr_files-1) {
- return 0;
- }
- }
- return 1;
- }
-
-
- void select_next_file(void)
- {
- int tmp;
-
- if(opt.play_list) {
- if((tmp=fileidx_to_seqidx(cur_filenr)) == nr_songs-1) {
- if(opt.replay_forever) {
- cur_filenr=seqidx_to_fileidx(0); /* Take the first one again */
- }
- else
- return; /* Replay last song */
- }
- else
- cur_filenr=seqidx_to_fileidx(tmp+1);
- }
- else {
- cur_filenr=MIN(cur_filenr+1, nr_files-1);
- }
- }
-
-
- void select_prev_file(void)
- {
- int tmp;
-
- if(opt.play_list) {
- if(!(tmp=fileidx_to_seqidx(cur_filenr)))
- return; /* Replay first song */
- cur_filenr=seqidx_to_fileidx(tmp-1);
- }
- else {
- cur_filenr=MAX(cur_filenr-1, nr_dirs);
- }
- }
-
-
- void read_sequencer(void)
- {
- char buf[4];
- int msg;
- struct mod_message mess;
-
- safe_read(seqfd, (struct mod_message *)buf, 4);
-
- msg=(*(unsigned long *)buf)>>8;
- switch(msg) {
- case MESSAGE_PRINTLINE:
- case MESSAGE_PRINTSPEED:
- read_syncedmessage();
- break;
- case MESSAGE_DONE: /* Song played to the end */
- total_time+=time(0)-restart_time;
-
- if(opt.loop_module) {
- pending_restart=MESSAGE_RESTART;
- handle_restart(42); /* Done here as no lines coming */
- }
- else {
- set_status(STATUS_READY);
- last_pos=last_line=0;
- print_pos(0, 0);
- if(!selecting) {
- if(should_autoselect_next()) {
- select_next_file();
- load_next_module();
- }
- else {
- clear_screen();
- /* Exit if we are in batch-mode */
- if(opt.quiet) {
- mess.type=MESSAGE_EXIT;
- send_to_player(&mess);
- quit=QUIT_QUIT;
- }
- }
- }
- }
- break;
- default:
- error("Invalid message %d\n", msg);
- }
- }
-
-
- void read_syncedmessage(void)
- {
- struct mod_message mess;
- unsigned int t;
-
- safe_read(player2main_synced[PIPE_READ], &mess, sizeof(mess));
- switch(mess.type) {
- case MESSAGE_PRINTSPEED:
- last_speed=mess.a1.speed;
- last_tempo=mess.a2.tempo;
- print_speed(last_speed, last_tempo);
- break;
- case MESSAGE_PRINTLINE:
- if(pending_restart)
- handle_restart(mess.a1.songpos);
- else {
- last_pos=mess.a1.songpos;
- last_line=mess.a2.line;
- if(!pending_syncedmessage()) {
- if(!selecting && !default_opt.noscroll)
- print_line(last_pos, last_line, first_voice);
- }
- print_pos(last_pos, last_line);
- t=time(0)-starting_time;
- if(opt.maxtime && t > opt.maxtime) {
- total_time+=time(0)-restart_time-1;
- set_status(STATUS_READY);
- last_pos=last_line=0;
- print_pos(0, 0);
- if(selecting) {
- pending_restart=MESSAGE_STOP;
- handle_restart(42);
- }
- else {
- clear_screen();
- if(should_autoselect_next()) {
- select_next_file();
- load_next_module();
- }
- else {
- /* Exit if we are in batch-mode */
- if(opt.quiet) {
- mess.type=MESSAGE_EXIT;
- send_to_player(&mess);
- quit=QUIT_QUIT;
- }
- else {
- pending_restart=MESSAGE_STOP;
- handle_restart(42);
- }
- }
- }
- }
- else
- print_time(t);
- }
- break;
- default:
- error("Illegal synced message received by main (%d).\n", mess.type);
- }
- }
-
-
- int pending_syncedmessage(void)
- {
- fd_set rset;
- int nr;
- struct timeval null_time={0,0};
-
- FD_ZERO(&rset);
- FD_SET(seqfd, &rset);
- if((nr=select(seqfd+1, &rset, 0, 0, &null_time)) >= 0) {
- if(FD_ISSET(seqfd, &rset))
- return 1;
- else
- return 0;
- }
- else {
- if(errno != EINTR)
- error("select() failed (errno=%d).\n", errno);
- }
- return 0; /* Remove warning */
- }
-
-
- void handle_restart(int songpos)
- {
- struct mod_message mess;
-
- if(playing)
- total_time+=time(0)-restart_time;
-
- mess.type=pending_restart;
- switch(pending_restart) {
- case MESSAGE_STOP:
- case MESSAGE_RESTART:
- send_to_player(&mess);
- if(pending_restart == MESSAGE_RESTART)
- send_to_player_generic(&opt.active_voices,
- sizeof(opt.active_voices));
- wait_ack();
- if(pending_restart != MESSAGE_STOP) {
- set_status(STATUS_PLAYING);
- last_line=0;
- }
- starting_time=time(0);
- opt.maxtime=0;
- break;
- case MESSAGE_JUMPPATTERN:
- mess.a1.songpos=MIN(MAX(0, songpos+pending_info), M.songlength-1);
- send_to_player(&mess);
- mess.a1.speed=last_speed;
- mess.a2.tempo=last_tempo;
- send_to_player(&mess);
- send_to_player_generic(&opt.active_voices, sizeof(opt.active_voices));
- last_line=0;
- wait_ack();
- opt.maxtime=0;
- break;
- default:
- error("Internal error in handle_restart()");
- }
- restart_time=time(0);
- pending_restart=0;
- }
-
-
- /* If outgoing_too is false this function will hang until somone drains
- * the main2player-pipe (busy-wait, but this should only be for very
- * short periods of time if everything is working as it should).
- */
-
- void drain_pipes(char outgoing_too)
- {
- fd_set rset;
- char buf[256];
- int nr;
- struct timeval nulltime;
-
- while(1) {
- nulltime.tv_sec=0;
- nulltime.tv_usec=0;
- FD_ZERO(&rset);
-
- FD_SET(main2player[PIPE_READ], &rset);
- FD_SET(player2main_synced[PIPE_READ], &rset);
- FD_SET(seqfd, &rset);
- FD_SET(STDIN_FILENO, &rset);
-
- if((nr=select(maxfd, &rset, 0, 0, &nulltime)) > 0) {
- if(outgoing_too && FD_ISSET(main2player[PIPE_READ], &rset))
- read(main2player[PIPE_READ], buf, 256);
- if(FD_ISSET(player2main_synced[PIPE_READ], &rset))
- read(player2main_synced[PIPE_READ], buf, 256);
- if(FD_ISSET(seqfd, &rset))
- read(seqfd, buf, 256);
- if(FD_ISSET(STDIN_FILENO, &rset))
- read(STDIN_FILENO, buf, 256);
- }
- else {
- if(nr < 0)
- error("Select problems (%d).\n", errno);
- break;
- }
- }
- }
-
-
- /* S between 1 and 3. If 0 and loaded we select a "good" one */
-
- void set_voice_detail(int s)
- {
- int detail[]={4, 9, 17};
-
- if(!s) {
- if(loaded) {
- if(M.nr_voices <=4)
- s=1;
- else if(M.nr_voices <=9)
- s=2;
- else
- s=3;
- }
- else
- s=1;
- }
- nr_visible_voices=detail[s-1];
- }
-
-
- void set_status(int s)
- {
- switch(s) {
- case STATUS_PLAYING:
- print_status("Playing");
- playing=1;
- break;
- case STATUS_READY:
- print_status("Ready");
- playing=0;
- break;
- default:
- error("Unknown status (%d).\n", s);
- }
- }
-
-
- void send_to_player(struct mod_message *m)
- {
- safe_write(main2player[PIPE_WRITE], m, sizeof(struct mod_message));
- }
-
-
- void send_to_player_generic(void *m, int size)
- {
- safe_write(main2player[PIPE_WRITE], (struct mod_message *)m, size);
- }
-
-
- /* Handshake-functions */
-
- void send_ack()
- {
- char data;
- safe_write(ack_pipe[PIPE_WRITE], (struct mod_message *)&data, 1);
- }
-
-
- void wait_ack()
- {
- char data;
- safe_read(ack_pipe[PIPE_READ], (struct mod_message *)&data, 1);
- }
-