/* ============================================================================
 *
 * File:	rawseq.c
 * Project:	rawseq
 * Started:	20.09.94
 * Changed:	21.09.94
 * Author:	Christian Bolik (zzhibol@rrzn-user.uni-hannover.de)
 *
 * Description:	A frontend to the soundIt library that reads sequences
 *		from ascii files.  The soundIt library was written by
 *		Brad Pitzel and is really cool.  Great stuff!
 *
 * ========================================================================== */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>	/* MAXPATHLEN */
#include <sys/time.h>	/* setitimer() */
#include <sys/stat.h>
#include <signal.h>
#include <unistd.h>
#include "soundIt.h"


#define LINESIZE 256	/* max. size of a line */
#define ELSIZE 64	/* max. size of word inside a line */
#define MAXSOUNDS 64	/* max. # of sounds */
#define NUMCHANNELS 8	/* number of sound channels */


/* ==== Typedefs ============================================================ */

typedef struct _sound {
    char filename[MAXPATHLEN];	/* name of sound file */
    char nickname[32];		/* nickname for this sound */
    int channel;		/* on which channel this sound will play */
} SOUND;


/* ==== Global Variables ==================================================== */

int sample_freq = 11000;	/* the sample frequency in Hz */
int beats_per_minute = 120;	/* bpm */
int resolution = 16;		/* ticks per measure */
int num_sounds = 0;		/* number of sounds loaded (cheers 8-) */
int last_measure = 0;		/* measure number of last sound event */
int last_ticks = 0;		/* tick number of last sound event */
int seq_init_called = 0;

SOUND sound[MAXSOUNDS];		/* the sounds */
Sample sample[MAXSOUNDS];	/* the samples */

int cmdl_bpm = 0;		/* -t parameter (command line: bpm) */
int cmdl_loop = 0;		/* -l (command line: loop) */
int cmdl_quiet = 0;		/* -q (command line: quiet) */


/* ==== Prototypes ========================================================= */

void seq_exit (int status);
void interrupt_handler (int signum);
void alarm_handler (int signum);


/* ==== Procedures ========================================================== */

/* ============================================================================
 * Name: usage
 * Desc: Prints the usage string.
 * In  : 
 * Out : 
 * -------------------------------------------------------------------------- */

void usage (char *progname)
{
    printf ("Usage: %s [options] <file>\n", progname);
    puts ("options are:");
    puts ("	-t <bpm>	Tempo (beats per minute)");
    puts ("	-l		Loop the whole sequence");
    puts ("	-q		Quiet (no text output)");
} /* usage */


/* ============================================================================
 * Name: seq_init
 * Desc: Initialize the sequencer.
 * In  : 
 * Out : 
 * -------------------------------------------------------------------------- */

void seq_init ( void )
{
int i;

    signal (SIGALRM, alarm_handler);    
    signal (SIGINT, interrupt_handler);    

    /*
     * Read sound files:
     */

    for (i = 0; i < num_sounds; i++) {
	if (Snd_loadRawSample (sound[i].filename, &sample[i]) < 0) {
	    fprintf (stderr, "Couldn't read %s!\n", sound[i].filename);
	    seq_exit (1);
	}
    }

    Snd_init (num_sounds, sample, sample_freq, NUMCHANNELS, "/dev/dsp");
    sleep (1);
} /* seq_init */


/* ============================================================================
 * Name: seq_exit
 * Desc: exit-replacement
 * In  : status - exit code
 * Out : 
 * -------------------------------------------------------------------------- */

void seq_exit (int status)
{
    if (seq_init_called) {
	sleep (1);
	Snd_restore ();
    }

    exit (status);
} /* seq_exit */


/* ============================================================================
 * Name: interrupt_handler
 * Desc: just that
 * In  : 
 * Out : 
 * -------------------------------------------------------------------------- */

void interrupt_handler (int signum)
{
    signal (SIGINT, SIG_IGN);
    seq_exit (0);
    return;
} /* alarm_handler */


/* ============================================================================
 * Name: alarm_handler
 * Desc: just that
 * In  : 
 * Out : 
 * -------------------------------------------------------------------------- */

void alarm_handler (int signum)
{
    signal (SIGALRM, alarm_handler);
    return;
} /* alarm_handler */


/* ============================================================================
 * Name: load_sound
 * Desc: Loads a sound into memory.
 * In  : filename - name of soundfile
 *	 nickname - name for this sound
 * Out : 
 * -------------------------------------------------------------------------- */

void load_sound (char *filename, char *nickname, int channel)
{
int i;

    if (num_sounds == MAXSOUNDS) {
	printf ("Can't load more than %d sounds.", MAXSOUNDS);
	return;
    }

    i = num_sounds;

    strcpy (sound[i].filename, filename);
    strcpy (sound[i].nickname, nickname);

    if (channel < 0)
	channel = num_sounds;
    sound[i].channel = channel % NUMCHANNELS;

    num_sounds++;

#   ifdef DEBUG
    fprintf (stderr, "sound[%d].filename = %s\n", i, filename);
    fprintf (stderr, "sound[%d].nickname = %s\n", i, nickname);
    fprintf (stderr, "sound[%d].channel = %d\n", i, channel);
#   endif
} /* load_sound */


/* ============================================================================
 * Name: play_sound
 * Desc: Plays the sound given by nickname
 * In  : nickname
 * Out : 
 * -------------------------------------------------------------------------- */

void play_sound (char *nickname)
{
int i;

    for (i = 0; i < num_sounds; i++) {
	if (strcmp (nickname, sound[i].nickname) == 0) {
	    Snd_effect (i, sound[i].channel);
	}
    }    
} /* play_sound */


/* ============================================================================
 * Name: wait_a_while
 * Desc: Waits until it's time for the next sound event.
 * In  : measure - measure of next event
 *	 ticks - ticks for next event
 * Out : 
 * -------------------------------------------------------------------------- */

void wait_a_while (int measure, int ticks)
{
int m2wait, t2wait;
long msec2wait;
struct itimerval itv;

    m2wait = measure - last_measure;
    t2wait = ticks - last_ticks;
    if (t2wait < 0) {
	t2wait = resolution + t2wait;
	m2wait--;
    }

    if (m2wait < 0 || t2wait < 0) {
	/* fprintf (stderr, "Bad event time: %d %d\n", measure, ticks); */
	/* seq_exit (1); */
	m2wait = t2wait = 0;
    }
    last_measure = measure;
    last_ticks = ticks;

    /*
     * Compute total time to wait (in milliseconds):
     */

    t2wait += m2wait * resolution;
#   ifdef DEBUG_T
    fprintf (stderr, "t2wait = %d\n", t2wait);
#   endif


    if (t2wait > 0) {
        /* msec2wait = (60 / beats_per_minute) * \
		(4 /resolution) * 1000 * t2wait; */
        msec2wait = (240000 * t2wait) / (beats_per_minute * resolution);
    
        itv.it_value.tv_sec = msec2wait / 1000;
        itv.it_value.tv_usec = (msec2wait * 1000) % 1000000;
        itv.it_interval.tv_sec = 0;
        itv.it_interval.tv_usec = 0;
    
#   	ifdef DEBUG_T
        fprintf (stderr, "tv_sec = %ld, tv_usec = %ld\n", 
    		itv.it_value.tv_sec, itv.it_value.tv_usec);
#   	endif
    
        setitimer (ITIMER_REAL, &itv, NULL);
    
        /*
         * And now go to sleep until we're waked up again (if we have to wait):
         */
        pause ();
    }

} /* wait_a_while */


/* ============================================================================
 * Name: read_file
 * Desc: Opens and reads the given file.
 * In  : file - filename
 * Out : -
 * -------------------------------------------------------------------------- */

void read_file (char *file)
{
FILE *fp;
char linestr[LINESIZE];
char lineel[10][ELSIZE];
char *filebuf, *readptr, *cr, *repeat_ptr = NULL;
int numel, i, repeat_num = 0, repeat_measure, repeat_ticks;
int repeat_needs_times, channel;
struct stat fstat;

    fp = fopen (file, "r");
    if (fp == NULL) {
	perror (file);
	seq_exit (1);
    }

    stat (file, &fstat);
    filebuf = (char *) malloc (fstat.st_size + 2);
    *(filebuf + fstat.st_size) = 0;
    if (filebuf == NULL) {
	fprintf (stderr, "Out of memory");
	seq_exit (1);
    }

    fread (filebuf, sizeof(char), fstat.st_size, fp);
    fclose (fp);

    readptr = filebuf;

read_loop:

    while (readptr < filebuf + fstat.st_size) {
	cr = strchr (readptr, '\n');
	if (cr) {
	    strncpy (linestr, readptr, cr - readptr);
	    *(linestr + (cr - readptr)) = 0;
	    readptr = cr + 1;
	} else {
	    strcpy (linestr, readptr);
	    readptr = filebuf + fstat.st_size;
	}

	numel = sscanf (linestr, " %s %s %s %s %s %s %s %s %s %s",
			lineel[0], lineel[1], lineel[2], lineel[3], 
			lineel[4], lineel[5], lineel[6], lineel[7], 
			lineel[8], lineel[9]);

	/*
	 * Now see what kind of line we've just read:
	 */

	if (numel < 1)
	    continue;

	switch (lineel[0][0]) {
	    /*
	     * Determine type by looking at first non-blank char of line:
	     */

	    case 'S':	/* definition of a sound */
		if (seq_init_called)
		    break;

		if (numel != 2 && numel != 3) {
		    fprintf (stderr, "S needs 2 arguments, not %d!\n", 
				numel - 1);
		    seq_exit (1);
		}

		if (lineel[0][1])
		    channel = (int) lineel[0][1] - 0x30 - 1;
		else
		    channel = -1;

		load_sound (lineel[1], lineel[2], channel);
		break;

	    case 'F':	/* sample frequency */
		if (seq_init_called)
		    break;

		if (numel != 2) {
		    fprintf (stderr, "F needs one argument, not %d!\n", 
				numel - 1);
		    seq_exit (1);
		}
		sample_freq = atoi (lineel[1]);
		if (!cmdl_quiet)
		    printf ("sample_freq = %d\n", sample_freq);
		break;

	    case 'T':	/* bpm and resolution */
		if (seq_init_called)
		    break;

		if (numel != 2 && numel != 3) {
		    fprintf (stderr, "T needs 1 or 2 arguments, not %d!\n", 
				numel - 1);
		    seq_exit (1);
		}

		if (!cmdl_bpm) {
		    beats_per_minute = atoi (lineel[1]);
		    cmdl_bpm = 0;	/* enable tempo variations */
		}

		if (numel == 3) {
		    resolution = atoi (lineel[2]);
		}

		if (!cmdl_quiet) {
		    printf ("beats_per_minute = %d\n", 
					beats_per_minute);
		    printf ("resolution = %d\n", resolution);
		}
		break;

	    case 'R':	/* repeat start/end */
		if (lineel[0][1] == 'S') {
		    /* repeat start */

		    repeat_ptr = readptr;
		    repeat_needs_times = 1;
		    if (numel > 1)
			repeat_num = atoi (lineel[1]);
		    else
			repeat_num = -1;	/* loop 4ever */

#		    ifdef DEBUG
		    fprintf (stderr, "repeat start (%d)\n", repeat_num);
#		    endif
		} else if (lineel[0][1] == 'E') {
		    /* repeat end */

		    if (repeat_ptr) {
			if (repeat_num > 0) {
			    repeat_num--;
			    readptr = repeat_ptr;
			    last_measure = repeat_measure;
			    last_ticks = repeat_ticks;
			} else if (repeat_num == 0) {
			    repeat_ptr = NULL;
			} else {
			    readptr = repeat_ptr;
			    last_measure = repeat_measure;
			    last_ticks = repeat_ticks;
			}
#		    	ifdef DEBUG
			if (repeat_ptr)
		    	    fprintf (stderr, "repeat end (%d)\n", repeat_num);
#		    	endif
		    }
		}
		break;

	    case '#':	/* comment */
		break;

	    case '0':   /* sound event */
	    case '1':
	    case '2':
	    case '3':
	    case '4':
	    case '5':
	    case '6':
	    case '7':
	    case '8':
	    case '9':
		if (numel < 2) {
		    fprintf (stderr, "Events need at least 2 arguments");
		    seq_exit (1);
		}

		if (!seq_init_called) {
    		    seq_init ();
		    seq_init_called = 1;
		}

		if (repeat_needs_times) {
		    repeat_needs_times = 0;
		    repeat_measure = atoi (lineel[0]);
		    repeat_ticks = atoi (lineel[1]);
		}

		wait_a_while (atoi (lineel[0]), atoi (lineel[1]));
		if (!cmdl_quiet)
		    printf ("%s\n", linestr); 

		for (i = 2; i < numel; i++)
		    play_sound (lineel[i]);

		break;
	}
    }

    if (cmdl_loop) {
	readptr = filebuf;
	last_measure = 0;
	last_ticks = 0;
	goto read_loop;
    }

    free (filebuf);
} /* read_file */


/* ============================================================================
 * Name: main
 * Desc: main routine
 * In  : 
 * Out : 
 * -------------------------------------------------------------------------- */

int main (int argc, char *argv[])
{
int i = 1;
char *file = NULL;

    if (argc < 2) {
	usage (argv[0]);
	seq_exit (1);
    }

    /*
     * Read command line arguments
     */

    while (i < argc) {
	if (argv[i][0] == '-') {
	    switch (argv[i][1]) {
		case 't':
		    if (i == argc - 1) {
			usage (argv[0]);
			seq_exit (1);
		    }
		    beats_per_minute = cmdl_bpm = atoi (argv[++i]);
#		    ifdef DEBUG
		    fprintf (stderr, "cmdl_bpm: %d\n", cmdl_bpm);
#		    endif
		    break;

		case 'l':
		    cmdl_loop = 1;
#		    ifdef DEBUG
		    fprintf (stderr, "cmdl_loop: %d\n", cmdl_loop);
#		    endif
		    break;

		case 'q':
		    cmdl_quiet = 1;
#		    ifdef DEBUG
		    fprintf (stderr, "cmdl_quiet %d\n", cmdl_quiet);
#		    endif
		    break;

		default:
		    usage (argv[0]);
		    seq_exit (1);
	    }
	} else
	    file = argv[i];

	i++;
    }

    if (file == NULL) {
	usage (argv[0]);
	seq_exit (1);
    }

    read_file (file);
    seq_exit (0);    
} /* main */
