
/****************************************************************
 cool_mzx.c

 2003-06-06 / yzi : korjauksia omiin tarpeisiin! stereo, bpmtempo toimii yms.

 19980523
 19980630 (Linux)
 jotain (SDL)
 2.9.2002 (SDL)
 ****************************************************************/


#include <SDL.h>
#include "cool_mzx.h"
#include "sd_main.h"
#include "sd_mix.h"
#include "pt.h"


extern int        pt_curperiod[32],    // kanavan nykyinen periodi
                  pt_curnote[32],      // kanavan nykyinen sävel
                  pt_curvol[32],       // kanavan nykyinen voimakkuus
                  pt_cursample[32],    // kanavan nykyinen sample
                  pt_tporta[32],       // tone portamenton nopeus
                  pt_curtarget[32],    // tone portamenton kohdeperiodi
                  pt_vibs[32],         // vibraton nopeus
                  pt_vibd[32],         // vibraton amplitudi ("depth")
                  pt_vibc[32],         // vibraton laskuri (0..63)
                  pt_trms[32],         // tremolon nopeus
                  pt_trmd[32],         // tremolon amplitudi
                  pt_trmc[32],         // tremolon laskuri (0..63)
                  pt_curfine[32],      // kanavan nyk. finetune
                  pt_glissando[32],    // onko glissando?
                  pt_notedelay[32],    // delayn pituus (ec, e9, ed)
                  pt_pattdelay[32],    // montako riviä täytyy odotella
                  pt_pattloop[32],     // montako looppia on tekemättä
                  pt_pattloopstart[32],// ja mille riville loopataan
                  pt_pan[32];          // kanavan panorointi

extern unsigned int   pt_songpos,          // missä ollaan orderissa
		  pt_repstart,	       // mistä aletaan, kun orderi wrappaa
		  pt_firstline,        // miltä riviltä seuraava patt. alkaa
		  pt_vbcount,	       // lasketaan tickejä (0..vbtempo-1)
		  pt_curpattern,       // nykyisen patternin numero
//                  pt_bpmtempo,         // tempo, (bpmtempo = 2.5 * ticks/sec)
		  pt_vbtempo,	       // montako tickiä per rivi
                  pt_rowcount;         // millä rivillä ollaan

extern unsigned char *pt_lineptr;        // osoitin riville

extern   unsigned pt_channels;       // kanavien lukumäärä
extern    unsigned pt_slen[32],       // samplen pituus
                  pt_loops[32],      // loopin alkukohta
                  pt_loope[32],      // loopin pituus (<=2, jos ei ole)
                  pt_svol[32];       // volume
extern    unsigned char  pt_finetune[32],   // finetune
                  pt_songlen,        // pituus orderissa
                  pt_lastpattern,    // korkein patternin numero
                  pt_order[128];     // "song order"
extern    unsigned char *pt_patterns[128];  // patternidata


#define KAUAN 999999

    // (yzi) aika tickeinä siihen, kun edellisen kerran pistettiin
    // sample numero [se_ja_se] soimaan
    volatile int
        aikaa_siita_kun_viimeksi_tavattu[32] =
        {KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,
         KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,
         KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,KAUAN,
         KAUAN,KAUAN,KAUAN,KAUAN,KAUAN};

volatile Uint32	tix=0;

//extern void vbiproc1();

volatile MZX_CALLBACKREC *callbacks[MAXCALLBACKS];
volatile int cbhandles[MAXCALLBACKS];
volatile int callbackcount=0, curhandle=1;

volatile unsigned int ticksfromstart = 0;

static int	laite=0,jacaja,screenHz,samples_per_tick;

// we assume that the patterns only contain 64 rows (plain old MOD format)
#define ROWS_PER_PATTERN (64)

#ifndef NO_MAGIC_MUSHROOMS

// this should be enough for everyone
#define MAX_POSITION_TIMES (10000)
#define POSITION_NOT_YET_PLAYED (9999999)

// Array of row start time values (from SDL_GetTicks(), i.e. in milliseconds), one for each pattern row played.
// The time value is the moment when the row started.
// The array is filled with the position_times_update() function, called by the playroutine as it goes.
static int position_times[MAX_POSITION_TIMES]; 

// The position (index) of the "write-head": the first unwritten position
static volatile int positions_write_pos = 0; 

// The position (index) of the "read-head": the latest position that the 
// routine thinks (estimates) has been actually played through the audio system.
static volatile int positions_read_pos = -1; 

void position_times_init()
{
    int i;
    for (i=0; i<MAX_POSITION_TIMES; i++)
        position_times[i] = POSITION_NOT_YET_PLAYED;
    positions_write_pos = 0;
    positions_read_pos = -1;
}

void position_times_update(int songpos, int rowcount, int ticks)
{
    if ((songpos < 0) || (songpos > 128) || (rowcount < 0) || (rowcount >= ROWS_PER_PATTERN)) return;
    int pos = songpos * ROWS_PER_PATTERN + rowcount;
    while (pos >= positions_write_pos)
    {
        if (position_times[positions_write_pos] == POSITION_NOT_YET_PLAYED)
            position_times[positions_write_pos] = ticks;
        positions_write_pos++;
    }
}

#endif

static volatile unsigned int mzx_latest_position = 0xFFFFFFFF; // viimeisin mzx_internal_position()
// static volatile unsigned int mzx_previous_position = 0xFFFFFFFF; // viimeistä edellinen mzx_internal_position()

void update_samples_per_tick()
{
	samples_per_tick = 44100 * 125 / (pt_bpmtempo * 50);
}

/* yzi  Palauttaa modinsoiton vaiheen, eli 100*songcount + rowcount. 
Huom. Tämä on pt.c:n sisäinen vaihe, joka menee siinä tahdissa kuin SDL uusia buffereita pyytää.
Ja se voi olla varsinkin Windowsissa ilman DirectSoundia _aivan_ eri kuin juuri sillä hetkellä ulos tuleva ääni.
*/
unsigned int mzx_internal_position()
{
        return ((pt_songpos * 100) + pt_rowcount);
} /* mzx_internal_position */

static volatile int _mzx_position=-1;

/* Returns the (millisecond) time of the given position. IF it has already occurred (=time has been calculated). 
   If the given position has not occurred yet, then this will return POSITION_NOT_YET_PLAYED. 
 (this behaviour could be improved by pre-scanning the whole song in a precalc phase in the beginning)
 */
int mzx_time_of_position(int position)
{
    return (position_times[(position / 100) * ROWS_PER_PATTERN + (position % 100)]);
}

/* Returns the current music position in the format 100*songcount + rowcount. */
unsigned int mzx_position()
{
    return _mzx_position;
}

// Updates the internal position variable safely. (locks the audio thread)
unsigned int mzx_update_position()
{
    // make sure the audio callback doesn't update the variables during the execution of this function
    SDL_LockAudio(); 
    
#ifndef NO_MAGIC_MUSHROOMS
    int ticks;
        
    ticks = SDL_GetTicks();
    // advance the read position to the latest position that hasn't been actually played yet
    while (position_times[positions_read_pos+1] < ticks)
        positions_read_pos++;
    if (positions_read_pos<0) 
        _mzx_position=0;
    else
        _mzx_position=((positions_read_pos / ROWS_PER_PATTERN) * 100 + (positions_read_pos % ROWS_PER_PATTERN));
#else
    _mzx_position = mzx_latest_position;
#endif

    SDL_UnlockAudio();
    return _mzx_position;
} /* mzx_update_position */


/* palauttaa toden, jos i on annetulla välillä tai "määrittelemätön" */
int inrange(int i, MZX_RANGE range)
{                         
        return (range.max < 0) ||
               ((i >= range.min) && (i <= range.max));
}

int ehto_toteutuu(MZX_CONDITION *cond)
{
        int toteutuneita, c;

        toteutuneita = 0;

        for (c=0; (c<pt_channels) && (!toteutuneita); c++)
        {
                toteutuneita =
                        (inrange(c, cond->channel))
                        && (inrange(pt_cursample[c], cond->instrument))
                        && (inrange(pt_lineptr[c*4+2] & 15, cond->command))
                        && (inrange(pt_lineptr[c*4+3], cond->parameter))
                        && (inrange(pt_songpos, cond->songpos))
                        && (inrange(pt_rowcount % cond->rowmod, cond->rowmodeq))
                        && (inrange(pt_vbcount, cond->tick));
        }

        return toteutuneita; /* jos vähintään yksi toteutui */

} /* ehto_toteutuu */

static void update_sample_trig_times()
{
        static int i, sanbale;

            if (!pt_vbcount) // ollaanko rivin alussa
            for (i=0; i<pt_channels; i++)
            {
                sanbale = pt_lineptr[i*4]; // samplen numero

                if ((pt_lineptr[i*4+1]) && // nuotti mukana
                    (pt_lineptr[i*4+2] != 3) && // ei tone portamento
                    (pt_lineptr[i*4+2] != 5) // eikä tone portamento + volslide
                   )
                aikaa_siita_kun_viimeksi_tavattu[sanbale] = -1;
            }

            for (sanbale=0;sanbale<32;sanbale++)
            {
                aikaa_siita_kun_viimeksi_tavattu[sanbale]++;
            }
}

Uint32 timeri(Uint32 interval)
{
	if(!laite)
	{
		pt_play();

                update_sample_trig_times();
	}
	return(interval);
}

/*      Alustaa musiikkisysteemin, kyselee laitteet yms.        */
int mzx_init(int samplerate, int blocksize)
{
//	char	pag[100];

	laite=2;
	if(sd_dsp16_init(samplerate,blocksize)==SD_ERR)
		return(0); 

    callbackcount = 0;
    sd_init(samplerate, sd_buffer_length);
	sd_setmainvolume(64); 
	sd_start();
    
#ifndef NO_MAGIC_MUSHROOMS
    position_times_init();
#endif
    
    return 1;
} /* mzx_init */

/*      Ottaa biisin muistista  */
int mzx_get(void *data)
{
	pt_songpos=pt_rowcount=0;
        if (!pt_takemod((unsigned char *)data))
        { /* maailmanloppu */
                return 0;
        }
        pt_init();
        return 1;
} /* mzx_get */

/*      Aloittaa soiton ja käynnistää timerin taajuudella freq 	*/
void mzx_start(int freq)
{
        tix = SDL_GetTicks();
        //vbiInit(&vbiproc0, &vbiproc1, &vbiproc2);

	screenHz=freq;
	update_samples_per_tick();
	jacaja=1000/freq;
	if(!laite)
	{
		SDL_Init(SDL_INIT_TIMER);
		SDL_SetTimer(jacaja, timeri);
		return;
	}
	SDL_PauseAudio(0);
} /* mzx_start */

/*	SIGCHLD-handleri	*/
void mix_newhand(void *udata, Uint8 *stream, int len)
{
	static int	off,samples_left_before_tick=0, pttime;
    
#ifdef TIMING_TEST
    static volatile int prevticks=0;
#endif
    
	short	*oso=(short*)stream;
	int pala;

    static volatile int starting_ticks;
    // should we add some magic fine-adjustment time here??
    starting_ticks = SDL_GetTicks();
    
	if(laite==2)
	{	
		len/=4;
		off=0;
		
//                mzx_previous_position = mzx_latest_position;
                mzx_latest_position = mzx_internal_position();
        
		while (off < len)
		{
#ifndef NO_MAGIC_MUSHROOMS
			
			// some empirically adjusted special case code, seems to be different for each system..!?!!
#ifdef WIN32
            position_times_update(pt_songpos, pt_rowcount, starting_ticks + (1000 * (off + len - 3600)) / 44100);

#elif MACOSX
            position_times_update(pt_songpos, pt_rowcount, starting_ticks + (1000 * (off - 2100)) / 44100);
//            position_times_update(pt_songpos, pt_rowcount, starting_ticks + (1000 * (off )) / 44100);
#else
            position_times_update(pt_songpos, pt_rowcount, starting_ticks + (1000 * (off + len - 4096)) / 44100);
#endif
#endif

			/* miksataan SDL:n puskuri täyteen */
			pttime = mzx_internal_position();
			if (samples_left_before_tick <= 0) 
			{
				pt_play();
				pttime = mzx_internal_position();
	                        update_sample_trig_times(); /* sana "sample" kahdessa eri merkityksessä... */
				update_samples_per_tick(); /* laskee bpm-temposta uuden tick-ajan */
				samples_left_before_tick = samples_per_tick;
			}

			/* Kumpi tulee ensin, seuraava tick vai puskurin loppu? */
			if (len - off >= samples_left_before_tick)
				pala = samples_left_before_tick;
			else
				pala = len - off;

			if (!(oso))
				sd_mix_stereo(oso, pala, 1, pttime);
			else
				sd_mix_stereo(&oso[off*2], pala, 1, pttime);

			samples_left_before_tick -= pala;
			off += pala;
		}
	}

#ifdef TIMING_TEST
    fprintf(stdout, "len: %d, start SDL_GetTicks(): %d, diff. from prev buffer: %d, mix time: %d\n", 
            len, starting_ticks, starting_ticks - prevticks, SDL_GetTicks() - starting_ticks);
    prevticks = starting_ticks;
#endif
    
} // mzx_newhand

/*      Lopettaa soiton ja sulkee laitteen      */
void mzx_close(void)
{
	if(!laite)
	{
		SDL_SetTimer(0,NULL);
		return;
	}

//        pt_stop();
        sd_stop();
        sd_shutdown();
} /* mzx_close */

/*      Palauttaa musiikin soitosta alkaen kuluneet tikit */
unsigned int mzx_time(void)
{
	return((SDL_GetTicks()-tix)/jacaja);
} /* mzx_time */

/* yzi  Asettaa master volumen (feidejä varten) */
void mzx_set_mastervolume(double vol)
{
        sd_setmainvolume(vol * 63);
} /* mzx_set_mastervolume */

/* yzi  Kaikkien callbackien äiti. callbackrecin muistia ei saa vapauttaa! */
int mzx_set_callback(MZX_CALLBACKREC *callbackrec)
{
        if (callbackcount >= MAXCALLBACKS) { return 0; }
        callbacks[callbackcount] = callbackrec;
        cbhandles[callbackcount] = curhandle;
        callbackcount++;
        return curhandle++;
} /* mzx_set_callback */
