/* Player.c  ----- by Teijo Kinnunen (1989) */
/* This is a code for MED song player. You can freely use it in both
   commercial and non-commercial programs.
   
   Tässä on MED-soittajan lähdekoodi. Sitä saa vapaasti käyttää sekä
   kaupallisissa, että ei-kaupallisissa ohjelmissa.
*/
#include <exec/types.h>
#include <exec/io.h>
#include <exec/interrupts.h>
#include <devices/timer.h>
#include <devices/audio.h>
#include <hardware/cia.h>
#include <dos.h>
#include <stdlib.h>
#include "med.h"
#ifdef LATTICE
#include <proto/exec.h>
#endif
extern struct CIA far ciaa; /* Remove 'far' if your compiler doesn't
			      support it. --- Poista 'far' jos kääntäjäsi
			      ei tunne sitä. */
static void SWareIntr();
UWORD soittorivi,soittolohko,soittolohkonnum,soittotila;
struct Task *player;
extern struct CLohko lohko[];
UBYTE playerlopettanut = FALSE;
UBYTE varattava[] = { 0x01,0x02,0x04,0x08 };
UBYTE laiteavattu[] = { FALSE, FALSE, FALSE, FALSE };
extern UBYTE *samples[];
extern struct Kappale song;
extern ULONG soittimenpituus[];
ULONG seurnuotsignmsk,askelm;
UWORD periodit[] = { 856,808,762,720,678,640,604,570,538,508,480,453,
		     428,404,381,360,339,320,302,285,269,254,240,226,
		     214,202,190,180,170,160,151,143,135,127,120,113,
		     107,101,95, 90, 85, 80, 75, 72, 68, 64, 60, 57 };
struct IOAudio *audioioreq[4],audiopervolreq[4],audiostopreq[4],audiorepreq[4];
struct Aloitusviesti aloitusviesti;
struct Soittokasky kasky;
struct MsgPort *audioport[4],*playerport=NULL,*plreply;

static void SoitaNuotti(kanava,taajuus,voimakkuus,soitinid)
UBYTE kanava;
UWORD taajuus,voimakkuus,soitinid;
{
  register struct IOAudio *audioreq = audioioreq[kanava];
  register struct IOAudio *repreq = &audiorepreq[kanava];
  BeginIO((struct IORequest *)&audiostopreq[kanava]);
  while(GetMsg(audioport[kanava]));
  audioreq->ioa_Data = samples[soitinid];
  if(song.stoisto[soitinid])
      audioreq->ioa_Length = song.stoisto[soitinid];
  else audioreq->ioa_Length = soittimenpituus[soitinid];
  audioreq->ioa_Request.io_Flags = ADIOF_PERVOL;
  audioreq->ioa_Period = taajuus;
  audioreq->ioa_Volume = voimakkuus;
  BeginIO((struct IORequest *)audioreq);
  if(song.stoisto[soitinid]) { /* toistetaan ikuisesti */
    repreq->ioa_Data = (UBYTE *)(samples[soitinid] +
				song.stoisto[soitinid]);
    repreq->ioa_Length = song.stoistonpit[soitinid];
    BeginIO((struct IORequest *)repreq);
  }
}

void MEDPlayer()
{
  struct Soittokasky *tamakasky;
  ULONG data,signmsk,plrprtmsk,odotusmsk;
  long snsn = AllocSignal(-1); /* seuraavan nuotin signaalin numero */
  register UWORD counter = 0,period,textra;
  register UBYTE raitano;
  UBYTE argumentti[4],efekti[4],kaskynaani;
  UWORD edellinensoitin[4] = { 0,0,0,0 };
  WORD edellinenvoim[4] = { 0,0,0,0 };
  UWORD komento,edarpgpernum,laskuri,kaskynsoitin;
  UWORD edellinenperiod[4] = { 0,0,0,0 };
  BOOL vaihdalohkoa = FALSE,ylsoitin,eikaynnisty = FALSE;
  BYTE ioerror;
  geta4();
  soittotila = ALASOITA;
  aloitusviesti.viesti_msg.mn_ReplyPort = NULL;
  aloitusviesti.viesti_msg.mn_Node.ln_Type = NT_MESSAGE;
  aloitusviesti.viesti_msg.mn_Length = sizeof(struct Aloitusviesti);
  for(laskuri = 0; laskuri < 4; laskuri++) {
    if(!(audioport[laskuri] = CreatePort(0,0))) { eikaynnisty = TRUE; break; }
    if(!(audioioreq[laskuri] = (struct IOAudio *)CreateExtIO(audioport[laskuri],
      sizeof(struct IOAudio)))) { eikaynnisty = TRUE; break; }
    audioioreq[laskuri]->ioa_Request.io_Message.mn_Node.ln_Pri = 50;
    audioioreq[laskuri]->ioa_Data = &varattava[laskuri];
    audioioreq[laskuri]->ioa_Length = 1;
    ioerror = OpenDevice(AUDIONAME,0,(struct IORequest *)audioioreq[laskuri],0);
    if(ioerror) { eikaynnisty = TRUE; break; }
    laiteavattu[laskuri] = TRUE;
    memcpy((char *)&audiopervolreq[laskuri],(char *)audioioreq[laskuri],
	sizeof(struct IOAudio));
    memcpy((char *)&audiostopreq[laskuri],(char *)audioioreq[laskuri],
	sizeof(struct IOAudio));
    memcpy((char *)&audiorepreq[laskuri],(char *)audioioreq[laskuri],
	sizeof(struct IOAudio));
    audiopervolreq[laskuri].ioa_Request.io_Command = ADCMD_PERVOL;
    audiostopreq[laskuri].ioa_Request.io_Command = CMD_FLUSH;
    audioioreq[laskuri]->ioa_Request.io_Command = CMD_WRITE;
    audiorepreq[laskuri].ioa_Request.io_Command = CMD_WRITE;
    audioioreq[laskuri]->ioa_Cycles = 1;
    audiorepreq[laskuri].ioa_Cycles = 0;
    audiostopreq[laskuri].ioa_Request.io_Flags = IOF_QUICK;
    audiopervolreq[laskuri].ioa_Request.io_Flags = IOF_QUICK;
  }
  if(eikaynnisty || snsn == -1 || !(playerport = CreatePort(0,0))) {
	aloitusviesti.viesti_tilanne = PIELEENMENI;
	PutMsg(plreply,(struct Message *)&aloitusviesti);
	goto playerinloppu;
  }
  seurnuotsignmsk = 1 << snsn;
  plrprtmsk = 1 << playerport->mp_SigBit;
  odotusmsk = seurnuotsignmsk | plrprtmsk; /* säästää aikaa laskea se nyt */
  aloitusviesti.viesti_tilanne = KAIKKIHYVIN;
  PutMsg(plreply,(struct Message *)&aloitusviesti);
  for(;;) {
    signmsk = Wait(odotusmsk);
    if(signmsk & plrprtmsk) {
      tamakasky = (struct Soittokasky *)GetMsg(playerport);
      komento = tamakasky->kasky_komento;
      data = tamakasky->kasky_data;
      kaskynaani = tamakasky->kasky_aani;
      kaskynsoitin = tamakasky->kasky_soitin;
      ReplyMsg((struct Message *)tamakasky);
      if(komento == OHJELMANLOPPU) break;
      if(komento == SOITALOHKO) {
	soittotila = SOITALOHKOA;
	soittolohko = data;
      }
      else if(komento == SOITAKAPPALE) {
	soittotila = SOITAKAPPALETTA;
	soittolohkonnum = data;
	soittolohko = song.soittojarjestys[soittolohkonnum];
      }
      else if(komento == SOITANUOTTI)
	SoitaNuotti(kaskynaani,(UWORD)data,
	  (UWORD)song.soittimenvoimakkuus[kaskynsoitin],kaskynsoitin);
      else if(komento == SEIS)	soittotila = ALASOITA;
    }
    if(signmsk & seurnuotsignmsk) {
      if(soittotila == ALASOITA) continue;
      if(++counter == 6) {
	counter = 0;
	for(raitano = 0; raitano < 4; raitano++) {
	  period=lohko[soittolohko].pala[soittorivi].savel[raitano].savel_period;
	  textra=lohko[soittolohko].pala[soittorivi].savel[raitano].savel_extra;
	  argumentti[raitano] = (textra & 0x00ff);
	  if(period & 0x8000) { /* jos periodin ylin bitti asetettu */
		period &= ~0x8000; /* otetaan se pois */
		ylsoitin = TRUE;
	  }
	  else ylsoitin = FALSE;
	  if(textra & 0xf000 || ylsoitin) {
		edellinensoitin[raitano]=((textra&0xf000)>>12)+(ylsoitin?16:0);
		edellinenvoim[raitano] =
		  song.soittimenvoimakkuus[edellinensoitin[raitano]];
	  }
	  if(efekti[raitano] = ((textra & 0x0f00) >> 8)) {
	    if(efekti[raitano] == 0x0f) {
		if(argumentti[raitano]) askelm = 625000 / argumentti[raitano];
		else vaihdalohkoa = TRUE;
	    }
	    else if(efekti[raitano] == 0x0c)
		edellinenvoim[raitano] = /* vvv-kerrotaan kymmenellä */
			((argumentti[raitano]>>1)&0xf8)+((argumentti[raitano]>>3)&0xfe)+
			(argumentti[raitano] & 0x0f);
	  }
	  if(period) {
	    edellinenperiod[raitano] = period;
	    SoitaNuotti(raitano,period,edellinenvoim[raitano],
		edellinensoitin[raitano]);
	  }
	}
	if(++soittorivi > 63 || vaihdalohkoa) {
	  if(soittotila == SOITALOHKOA) soittorivi = 0;
	  else if(soittotila == SOITAKAPPALETTA) {
	    soittorivi = 0;
	    if(++soittolohkonnum > song.kappaleen_pituus-1)
			soittolohkonnum = 0;
	    if((soittolohko = song.soittojarjestys[soittolohkonnum]) >
		song.lohkoja - 1) soittolohko = song.lohkoja - 1;
	  }
	  vaihdalohkoa = FALSE;
	}
      }
      for(raitano = 0; raitano < 4; raitano++) {
	period = 0;
	switch(efekti[raitano]) {
	  case 0x01: /* liukuu ylös.. */
	    if(song.vaihtoja == 5 && !counter) break;
	    edellinenperiod[raitano] -= argumentti[raitano];
	    if(edellinenperiod[raitano] < 113) edellinenperiod[raitano] = 113;
	    break;
	  case 0x02: /* sama alas */
	    if(song.vaihtoja == 5 && !counter) break;
	    edellinenperiod[raitano] += argumentti[raitano];
	    if(edellinenperiod[raitano] > 856) edellinenperiod[raitano] = 856;
	    break;
	  case 0x03: /* vibrato */
	    if(counter < 3)
		period = edellinenperiod[raitano] - argumentti[raitano];
	    else period = edellinenperiod[raitano];
	    break;
	  case 0x0d: case 0x0e: /* voimakkuuden muutos */
	    if(argumentti[raitano] >> 4 == 0) { /* hiljennä */
		if((edellinenvoim[raitano] -= argumentti[raitano]) < 0)
			edellinenvoim[raitano] = 0;
	    } else { /* suurenna */
		if((edellinenvoim[raitano] += (argumentti[raitano] >> 4)) > 64)
			edellinenvoim[raitano] = 64;
	    }
	    break;
	  case 0x00: /* arpeggio */
	    if(!argumentti[raitano]) continue;
	    switch(counter) {
		case 0:
		    for(edarpgpernum = 0; edarpgpernum < 36; edarpgpernum++)
		      if(edellinenperiod[raitano] >= periodit[edarpgpernum]) break;
		case 3:
		    period = periodit[edarpgpernum + (argumentti[raitano] & 0x0f)];
		    break;
		case 1: case 4:
		    period = periodit[edarpgpernum + (argumentti[raitano] >> 4)];
		    break;
		default: /* 2 ja 5 */
		    period = edellinenperiod[raitano];
		    break;
	    }
	    break;
	  case 0x0c:
	    break;
	  default:
	    continue;
	}
	if(period) audiopervolreq[raitano].ioa_Period = period;
	else audiopervolreq[raitano].ioa_Period = edellinenperiod[raitano];
	audiopervolreq[raitano].ioa_Volume = edellinenvoim[raitano];
	BeginIO((struct IORequest *)&audiopervolreq[raitano]);
      }
    }
  }
playerinloppu:
  for(laskuri = 0; laskuri < 4; laskuri++) {
    if(laiteavattu[laskuri]) {
      if(!CheckIO((struct IORequest *)audioioreq[laskuri]))
	AbortIO((struct IORequest *)audioioreq[laskuri]);
      audioioreq[laskuri]->ioa_Request.io_Command = CMD_RESET;
      DoIO((struct IORequest *)audioioreq[laskuri]);
      CloseDevice((struct IORequest *)audioioreq[laskuri]);
    }
    if(audioioreq[laskuri])
	DeleteExtIO((struct IORequest *)audioioreq[laskuri]);
    if(audioport[laskuri]) DeletePort(audioport[laskuri]);
  }
  if(playerport) DeletePort(playerport);
  if(snsn != -1) FreeSignal(snsn);
  playerlopettanut = TRUE;
}

struct timerequest *timereq;
BOOL timerauki = FALSE;

struct Interrupt sware = { { NULL,NULL,0,32,"MEDSWInt" },NULL,SWareIntr };
struct MsgPort timerport = { { NULL,NULL,NT_MSGPORT,0,NULL },PA_SOFTINT,0,
				(struct Task *)&sware,{ 0 } };

void SWareIntr()
{
	geta4();
	Signal(player,seurnuotsignmsk);
	(void)GetMsg(&timerport);
	timereq->tr_time.tv_micro = askelm;
	timereq->tr_time.tv_secs = 0L;
	SendIO((struct IORequest *)timereq);
}

LONG PLRInit() /* returns -1 if error, 0 if ok */
{
	struct Aloitusviesti *alkuviesti;
	askelm = 625000 / song.tempo;
	if(!(plreply = CreatePort(0,0))) return(-1);
	kasky.kasky_msg.mn_Node.ln_Type = NT_MESSAGE;
	kasky.kasky_msg.mn_ReplyPort = plreply;
	kasky.kasky_msg.mn_Length = sizeof(struct Soittokasky);
	if(!(player = CreateTask("MEDPlayer.task",21,(APTR)MEDPlayer,5000))) return(-1);
	WaitPort(plreply);
	alkuviesti = (struct Aloitusviesti *)GetMsg(plreply);
	if(alkuviesti->viesti_tilanne == PIELEENMENI) return(-1);
 	NewList(&(timerport.mp_MsgList));
	if(!(timereq = (struct timerequest *)CreateExtIO(&timerport,
		sizeof(struct timerequest)))) return(-1);
	if(OpenDevice(TIMERNAME,UNIT_MICROHZ,(struct IORequest *)timereq,0))
		return(-1);
	timerauki = TRUE;
	timereq->tr_node.io_Command = TR_ADDREQUEST;
	Cause(&sware);
	if(song.liput & LIPPU_SUODATINPAALLA) ciaa.ciapra &= ~CIAF_LED;
	else ciaa.ciapra |= CIAF_LED;
	return(0);
}

void PLRPlaySong()
{
	kasky.kasky_komento = SOITAKAPPALE;
	kasky.kasky_data = 0;
	PutMsg(playerport,(struct Message *)&kasky);
	WaitPort(plreply);
	(void)GetMsg(plreply);
}

void PLRStop()
{
	kasky.kasky_komento = SEIS;
	PutMsg(playerport,(struct Message *)&kasky);
	WaitPort(plreply);
	(void)GetMsg(plreply);
}

void PLRRemove()
{
	timerport.mp_Flags = PA_IGNORE;
	if(!CheckIO((struct IORequest *)timereq))
		AbortIO((struct IORequest *)timereq);
	if(timerauki) CloseDevice((struct IORequest *)timereq);
	if(timereq) DeleteExtIO((struct IORequest *)timereq);
	ciaa.ciapra &= ~CIAF_LED;
	if(playerport) {
		kasky.kasky_komento = OHJELMANLOPPU;
		PutMsg(playerport,(struct Message *)&kasky);
		WaitPort(plreply);
		(void)GetMsg(plreply);
		while(!playerlopettanut);
	}
	if(plreply) DeletePort(plreply);
}