/*
 * 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());
    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;
					}
					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);
}
