/*
 * Adplug - Replayer for many OPL2/OPL3 audio file formats.
 * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * ksm.cpp - KSM Player for AdPlug by Simon Peter <dn.tlp@gmx.net>
 */

#include <string.h>

#include "ksm.h"
#include "debug.h"

const unsigned int CksmPlayer::adlibfreq[63] = {
  0,
  2390,2411,2434,2456,2480,2506,2533,2562,2592,2625,2659,2695,
  3414,3435,3458,3480,3504,3530,3557,3586,3616,3649,3683,3719,
  4438,4459,4482,4504,4528,4554,4581,4610,4640,4673,4707,4743,
  5462,5483,5506,5528,5552,5578,5605,5634,5664,5697,5731,5767,
  6486,6507,6530,6552,6576,6602,6629,6658,6688,6721,6755,6791,
  7510};

/*** public methods **************************************/

CPlayer *CksmPlayer::factory(Copl *newopl)
{
  return new CksmPlayer(newopl);
}

bool CksmPlayer::load(const std::string &filename, const CFileProvider &fp)
{
  binistream	*f;
  int		i;
  char		*fn = new char[filename.length() + 9];

  // file validation section
  if(!fp.extension(filename, ".ksm")) {
    AdPlug_LogWrite("CksmPlayer::load(,\"%s\"): File doesn't have '.ksm' "
		    "extension! Rejected!\n", filename.c_str());
    delete [] fn;
    return false;
  }
  AdPlug_LogWrite("*** CksmPlayer::load(,\"%s\") ***\n", filename.c_str());

  // Load instruments from 'insts.dat'
  strcpy(fn, filename.c_str());
  for(i = strlen(fn) - 1; i >= 0; i--)
    if(fn[i] == '/' || fn[i] == '\\')
      break;
  strcpy(fn + i + 1, "insts.dat");
  AdPlug_LogWrite("Instruments file: \"%s\"\n", fn);
  f = fp.open(fn);
  delete [] fn;
  if(!f) {
    AdPlug_LogWrite("Couldn't open instruments file! Aborting!\n");
    AdPlug_LogWrite("--- CksmPlayer::load ---\n");
    return false;
  }
  loadinsts(f);
  fp.close(f);

  f = fp.open(filename); if(!f) return false;
  for(i = 0; i < 16; i++) trinst[i] = f->readInt(1);
  for(i = 0; i < 16; i++) trquant[i] = f->readInt(1);
  for(i = 0; i < 16; i++) trchan[i] = f->readInt(1);
  f->ignore(16);
  for(i = 0; i < 16; i++) trvol[i] = f->readInt(1);
  numnotes = f->readInt(2);
  note = new unsigned long [numnotes];
  for(i = 0; i < numnotes; i++) note[i] = f->readInt(4);
  fp.close(f);

  if(!trchan[11]) {
    drumstat = 0;
    numchans = 9;
  } else {
    drumstat = 32;
    numchans = 6;
  }

  rewind(0);
  AdPlug_LogWrite("--- CksmPlayer::load ---\n");
  return true;
}

bool CksmPlayer::update()
{
  int quanter,chan,drumnum,freq,track,volevel,volval;
  unsigned int i,j,bufnum;
  unsigned long temp,templong;

  count++;
  if (count >= countstop)
    {
      bufnum = 0;
      while (count >= countstop)
	{
	  templong = note[nownote];
	  track = (int)((templong>>8)&15);
	  if ((templong&192) == 0)
	    {
	      i = 0;

	      while ((i < numchans) &&
		     ((chanfreq[i] != (templong&63)) ||
		      (chantrack[i] != ((templong>>8)&15))))
		i++;
	      if (i < numchans)
		{
		  databuf[bufnum] = (char)0; bufnum++;
		  databuf[bufnum] = (unsigned char)(0xb0+i); bufnum++;
		  databuf[bufnum] = (unsigned char)((adlibfreq[templong&63]>>8)&223); bufnum++;
		  chanfreq[i] = 0;
		  chanage[i] = 0;
		}
	    }
	  else
	    {
	      volevel = trvol[track];
	      if ((templong&192) == 128)
		{
		  volevel -= 4;
		  if (volevel < 0)
		    volevel = 0;
		}
	      if ((templong&192) == 192)
		{
		  volevel += 4;
		  if (volevel > 63)
		    volevel = 63;
		}
	      if (track < 11)
		{
		  temp = 0;
		  i = numchans;
		  for(j=0;j<numchans;j++)
		    if ((countstop - chanage[j] >= temp) && (chantrack[j] == track))
		      {
			temp = countstop - chanage[j];
			i = j;
		      }
		  if (i < numchans)
		    {
		      databuf[bufnum] = (char)0, bufnum++;
		      databuf[bufnum] = (unsigned char)(0xb0+i); bufnum++;
		      databuf[bufnum] = (unsigned char)0; bufnum++;
		      volval = (inst[trinst[track]][1]&192)+(volevel^63);
		      databuf[bufnum] = (char)0, bufnum++;
		      databuf[bufnum] = (unsigned char)(0x40+op_table[i]+3); bufnum++;
		      databuf[bufnum] = (unsigned char)volval; bufnum++;
		      databuf[bufnum] = (char)0, bufnum++;
		      databuf[bufnum] = (unsigned char)(0xa0+i); bufnum++;
		      databuf[bufnum] = (unsigned char)(adlibfreq[templong&63]&255); bufnum++;
		      databuf[bufnum] = (char)0, bufnum++;
		      databuf[bufnum] = (unsigned char)(0xb0+i); bufnum++;
		      databuf[bufnum] = (unsigned char)((adlibfreq[templong&63]>>8)|32); bufnum++;
		      chanfreq[i] = templong&63;
		      chanage[i] = countstop;
		    }
		}
	      else if ((drumstat&32) > 0)
		{
		  freq = adlibfreq[templong&63];
		  switch(track)
		    {
		    case 11: drumnum = 16; chan = 6; freq -= 2048; break;
		    case 12: drumnum = 8; chan = 7; freq -= 2048; break;
		    case 13: drumnum = 4; chan = 8; break;
		    case 14: drumnum = 2; chan = 8; break;
		    case 15: drumnum = 1; chan = 7; freq -= 2048; break;
		    default: drumnum = 0; chan = 0; break; // should not be reachable
		    }
		  databuf[bufnum] = (char)0, bufnum++;
		  databuf[bufnum] = (unsigned char)(0xa0+chan); bufnum++;
		  databuf[bufnum] = (unsigned char)(freq&255); bufnum++;
		  databuf[bufnum] = (char)0, bufnum++;
		  databuf[bufnum] = (unsigned char)(0xb0+chan); bufnum++;
		  databuf[bufnum] = (unsigned char)((freq>>8)&223); bufnum++;
		  databuf[bufnum] = (char)0, bufnum++;
		  databuf[bufnum] = (unsigned char)(0xbd); bufnum++;
		  databuf[bufnum] = (unsigned char)(drumstat&(255-drumnum)); bufnum++;
		  drumstat |= drumnum;
		  if ((track == 11) || (track == 12) || (track == 14))
		    {
		      volval = (inst[trinst[track]][1]&192)+(volevel^63);
		      databuf[bufnum] = (char)0, bufnum++;
		      databuf[bufnum] = (unsigned char)(0x40+op_table[chan]+3); bufnum++;
		      databuf[bufnum] = (unsigned char)(volval); bufnum++;
		    }
		  else
		    {
		      volval = (inst[trinst[track]][6]&192)+(volevel^63);
		      databuf[bufnum] = (char)0, bufnum++;
		      databuf[bufnum] = (unsigned char)(0x40+op_table[chan]); bufnum++;
		      databuf[bufnum] = (unsigned char)(volval); bufnum++;
		    }
		  databuf[bufnum] = (char)0, bufnum++;
		  databuf[bufnum] = (unsigned char)(0xbd); bufnum++;
		  databuf[bufnum] = (unsigned char)(drumstat); bufnum++;
		}
	    }
	  nownote++;
	  if (nownote >= numnotes) {
	    nownote = 0;
	    songend = true;
	  }
	  templong = note[nownote];
	  if (nownote == 0)
	    count = (templong>>12)-1;
	  quanter = (240/trquant[(templong>>8)&15]);
	  countstop = (((templong>>12)+(quanter>>1)) / quanter) * quanter;
	}
      for(i=0;i<bufnum;i+=3)
	opl->write(databuf[i+1],databuf[i+2]);
    }
  return !songend;
}

void CksmPlayer::rewind(int subsong)
{
  unsigned int i,j,k;
  unsigned char instbuf[11];
  unsigned long templong;

  songend = false;
  opl->init(); opl->write(1,32); opl->write(4,0); opl->write(8,0); opl->write(0xbd,drumstat);

  if (trchan[11] == 1) {
    for(i=0;i<11;i++)
      instbuf[i] = inst[trinst[11]][i];
    instbuf[1] = ((instbuf[1]&192)|(trvol[11]^63));
    setinst(6,instbuf[0],instbuf[1],instbuf[2],instbuf[3],instbuf[4],instbuf[5],instbuf[6],instbuf[7],instbuf[8],instbuf[9],instbuf[10]);
    for(i=0;i<5;i++)
      instbuf[i] = inst[trinst[12]][i];
    for(i=5;i<11;i++)
      instbuf[i] = inst[trinst[15]][i];
    instbuf[1] = ((instbuf[1]&192)|(trvol[12]^63));
    instbuf[6] = ((instbuf[6]&192)|(trvol[15]^63));
    setinst(7,instbuf[0],instbuf[1],instbuf[2],instbuf[3],instbuf[4],instbuf[5],instbuf[6],instbuf[7],instbuf[8],instbuf[9],instbuf[10]);
    for(i=0;i<5;i++)
      instbuf[i] = inst[trinst[14]][i];
    for(i=5;i<11;i++)
      instbuf[i] = inst[trinst[13]][i];
    instbuf[1] = ((instbuf[1]&192)|(trvol[14]^63));
    instbuf[6] = ((instbuf[6]&192)|(trvol[13]^63));
    setinst(8,instbuf[0],instbuf[1],instbuf[2],instbuf[3],instbuf[4],instbuf[5],instbuf[6],instbuf[7],instbuf[8],instbuf[9],instbuf[10]);
  }

  for(i=0;i<numchans;i++)
    {
      chantrack[i] = 0;
      chanage[i] = 0;
    }
  j = 0;
  for(i=0;i<16;i++)
    if ((trchan[i] > 0) && (j < numchans))
      {
	k = trchan[i];
	while ((j < numchans) && (k > 0))
	  {
	    chantrack[j] = i;
	    k--;
	    j++;
	  }
      }
  for(i=0;i<numchans;i++)
    {
      for(j=0;j<11;j++)
	instbuf[j] = inst[trinst[chantrack[i]]][j];
      instbuf[1] = ((instbuf[1]&192)|(63-trvol[chantrack[i]]));
      setinst(i,instbuf[0],instbuf[1],instbuf[2],instbuf[3],instbuf[4],instbuf[5],instbuf[6],instbuf[7],instbuf[8],instbuf[9],instbuf[10]);
      chanfreq[i] = 0;
    }
  k = 0;
  templong = *note;
  count = (templong>>12)-1;
  countstop = (templong>>12)-1;
  nownote = 0;
}

std::string CksmPlayer::getinstrument(unsigned int n)
{
  if(trchan[n])
    return std::string(instname[trinst[n]]);
  else
    return std::string();
}

/*** private methods *************************************/

void CksmPlayer::loadinsts(binistream *f)
{
  int i, j;

  for(i = 0; i < 256; i++) {
    f->readString(instname[i], 20);
    for(j = 0; j < 11; j++) inst[i][j] = f->readInt(1);
    f->ignore(2);
  }
}

void CksmPlayer::setinst(int chan,
			 unsigned char v0,unsigned char v1,unsigned char v2,
			 unsigned char v3,unsigned char v4,unsigned char v5,
			 unsigned char v6,unsigned char v7,unsigned char v8,
			 unsigned char v9,unsigned char v10)
{
  int offs;

  opl->write(0xa0+chan,0);
  opl->write(0xb0+chan,0);
  opl->write(0xc0+chan,v10);
  offs = op_table[chan];
  opl->write(0x20+offs,v5);
  opl->write(0x40+offs,v6);
  opl->write(0x60+offs,v7);
  opl->write(0x80+offs,v8);
  opl->write(0xe0+offs,v9);
  offs+=3;
  opl->write(0x20+offs,v0);
  opl->write(0x40+offs,v1);
  opl->write(0x60+offs,v2);
  opl->write(0x80+offs,v3);
  opl->write(0xe0+offs,v4);
}
