/*
 * Adplug - Replayer for many OPL2/OPL3 audio file formats.
 * Copyright (C) 1999 - 2003 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
 *
 * [xad] RAT player, by Riven the Mage <riven@ok.ru>
 */

/*
    - discovery -

  file(s) : PINA.EXE
     type : Experimental Connection BBStro tune
     tune : by (?)Ratt/GRIF
   player : by (?)Ratt/GRIF
  comment : there are bug in original replayer's adlib_init(): wrong frequency registers.
*/

#include <string.h>

#include "rat.h"
#include "debug.h"

const unsigned char
  CxadratPlayer::rat_adlib_bases[18] = {
  0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12,
  0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15
};

const unsigned short
  CxadratPlayer::rat_notes[16] = {
  0x157, 0x16B, 0x181, 0x198, 0x1B0, 0x1CA, 0x1E5, 0x202, 0x220, 0x241, 0x263,
    0x287,
  0x000, 0x000, 0x000, 0x000    // by riven
};

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

bool
CxadratPlayer::xadplayer_load ()
{
  if (xad.fmt != RAT)
    return false;

  // load header
  memcpy (&rat.hdr, &tune[0], sizeof (rat_header));

  // is 'RAT'-signed ?
  if (strncmp (rat.hdr.id, "RAT", 3))
    return false;

  // is version 1.0 ?
  if (rat.hdr.version != 0x10)
    return false;

  // load order
  rat.order = &tune[0x40];

  // load instruments
  rat.inst = (rat_instrument *) & tune[0x140];

  // load pattern data
  unsigned short patseg = (rat.hdr.patseg[1] << 8) + rat.hdr.patseg[0];
  unsigned char *event_ptr = &tune[patseg << 4];

  for (int i = 0; i < rat.hdr.numpat; i++)
    for (int j = 0; j < 64; j++)
      for (int k = 0; k < rat.hdr.numchan; k++)
      {
        memcpy (&rat.tracks[i][j][k], event_ptr, sizeof (rat_event));

        event_ptr += sizeof (rat_event);
      }

  return true;
}

void
CxadratPlayer::xadplayer_rewind (int subsong)
{
  int i;

  rat.order_pos = rat.hdr.order_start;
  rat.pattern_pos = 0;
  rat.volume = rat.hdr.volume;

  plr.speed = rat.hdr.speed;

  // clear channel data
  memset (&rat.channel, 0, sizeof (rat.channel[0]) * 9);

  // init OPL
  opl_write (0x01, 0x20);
  opl_write (0x08, 0x00);
  opl_write (0xBD, 0x00);

  // set default frequencies
  for (i = 0; i < 9; i++)
  {
    opl_write (0xA0 + i, 0x00);
    opl_write (0xA3 + i, 0x00);
    opl_write (0xB0 + i, 0x00);
    opl_write (0xB3 + i, 0x00);
  }

  // set default volumes
  for (i = 0; i < 0x1F; i++)
    opl_write (0x40 + i, 0x3F);
}

void
CxadratPlayer::xadplayer_update ()
{
  int i;

  rat_event event;

  // process events
  for (i = 0; i < rat.hdr.numchan; i++)
  {
    memcpy (&event, &rat.tracks[rat.order[rat.order_pos]][rat.pattern_pos][i],
            sizeof (rat_event));
#ifdef DEBUG
    AdPlug_LogWrite
      ("order %02X, pattern %02X, row %02X, channel %02X, event %02X %02X %02X %02X %02X:\n",
       rat.order_pos, rat.order[rat.order_pos], rat.pattern_pos, i,
       event.note, event.instrument, event.volume, event.fx, event.fxp);
#endif

    // is instrument ?
    if (event.instrument != 0xFF)
    {
      rat.channel[i].instrument = event.instrument - 1;
      rat.channel[i].volume = rat.inst[event.instrument - 1].volume;
    }

    // is volume ?
    if (event.volume != 0xFF)
      rat.channel[i].volume = event.volume;

    // is note ?
    if (event.note != 0xFF)
    {
      // mute channel
      opl_write (0xB0 + i, 0x00);
      opl_write (0xA0 + i, 0x00);

      // if note != 0xFE then play
      if (event.note != 0xFE)
      {
        unsigned char ins = rat.channel[i].instrument;

        // synthesis/feedback
        opl_write (0xC0 + i, rat.inst[ins].connect);

        // controls
        opl_write (0x20 + rat_adlib_bases[i], rat.inst[ins].mod_ctrl);
        opl_write (0x20 + rat_adlib_bases[i + 9], rat.inst[ins].car_ctrl);

        // volumes
        opl_write (0x40 + rat_adlib_bases[i],
                   __rat_calc_volume (rat.inst[ins].mod_volume,
                                      rat.channel[i].volume, rat.volume));
        opl_write (0x40 + rat_adlib_bases[i + 9],
                   __rat_calc_volume (rat.inst[ins].car_volume,
                                      rat.channel[i].volume, rat.volume));

        // attack/decay
        opl_write (0x60 + rat_adlib_bases[i], rat.inst[ins].mod_AD);
        opl_write (0x60 + rat_adlib_bases[i + 9], rat.inst[ins].car_AD);

        // sustain/release
        opl_write (0x80 + rat_adlib_bases[i], rat.inst[ins].mod_SR);
        opl_write (0x80 + rat_adlib_bases[i + 9], rat.inst[ins].car_SR);

        // waveforms
        opl_write (0xE0 + rat_adlib_bases[i], rat.inst[ins].mod_wave);
        opl_write (0xE0 + rat_adlib_bases[i + 9], rat.inst[ins].car_wave);

        // octave/frequency
        unsigned short insfreq =
          (rat.inst[ins].freq[1] << 8) + rat.inst[ins].freq[0];
        unsigned short freq = insfreq * rat_notes[event.note & 0x0F] / 0x20AB;

        opl_write (0xA0 + i, freq & 0xFF);
        opl_write (0xB0 + i, (freq >> 8) | ((event.note & 0xF0) >> 2) | 0x20);
      }
    }

    // is effect ?
    if (event.fx != 0xFF)
    {
      rat.channel[i].fx = event.fx;
      rat.channel[i].fxp = event.fxp;
    }
  }

  // next row
  rat.pattern_pos++;

  // process effects
  for (i = 0; i < rat.hdr.numchan; i++)
  {
    unsigned char old_order_pos = rat.order_pos;

    switch (rat.channel[i].fx)
    {
    case 0x01:                 // 0x01: Set Speed
      plr.speed = rat.channel[i].fxp;
      break;
    case 0x02:                 // 0x02: Position Jump
      if (rat.channel[i].fxp < rat.hdr.order_end)
        rat.order_pos = rat.channel[i].fxp;
      else
        rat.order_pos = 0;

      // jumpback ?
      if (rat.order_pos <= old_order_pos)
        plr.looping = 1;

      rat.pattern_pos = 0;
      break;
    case 0x03:                 // 0x03: Pattern Break (?)
      rat.pattern_pos = 0x40;
      break;
    }

    rat.channel[i].fx = 0;
  }

  // end of pattern ?
  if (rat.pattern_pos >= 0x40)
  {
    rat.pattern_pos = 0;

    rat.order_pos++;

    // end of module ?
    if (rat.order_pos == rat.hdr.order_end)
    {
      rat.order_pos = rat.hdr.order_loop;

      plr.looping = 1;
    }
  }
}

float
CxadratPlayer::xadplayer_getrefresh ()
{
  return 60.0f;
}

std::string CxadratPlayer::xadplayer_gettype ()
{
  return (std::string ("xad: rat player"));
}

std::string CxadratPlayer::xadplayer_gettitle ()
{
  return (std::string (rat.hdr.title, 32));
}

unsigned int
CxadratPlayer::xadplayer_getinstruments ()
{
  return rat.hdr.numinst;
}

/* -------- Internal Functions ---------------------------- */

unsigned char
CxadratPlayer::__rat_calc_volume (unsigned char ivol, unsigned char cvol,
                                  unsigned char gvol)
{
#ifdef DEBUG
  AdPlug_LogWrite ("volumes: instrument %02X, channel %02X, global %02X:\n",
                   ivol, cvol, gvol);
#endif
  unsigned short vol;

  vol = ivol;
  vol &= 0x3F;
  vol ^= 0x3F;
  vol *= cvol;
  vol >>= 6;
  vol *= gvol;
  vol >>= 6;
  vol ^= 0x3F;

  vol |= ivol & 0xC0;

  return vol;
}
