/*
 * 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
 *
 * u6m.cpp - Ultima 6 Music Player by Marc Winterrowd.
 * This code extends the Adlib Winamp plug-in by Simon Peter <dn.tlp@gmx.net>
 */

#include "u6m.h"

// Makes security checks on output buffer before writing
#define SAVE_OUTPUT_ROOT(c, d, p) if(!output_root(c, d, p)) return false;

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

bool Cu6mPlayer::load(const std::string &filename, const CFileProvider &fp)
{
  // file validation section
  // this section only checks a few *necessary* conditions
  size_t filesize, decompressed_filesize;
  binistream *f;

  f = fp.open(filename); if(!f) return false;
  filesize = fp.filesize(f);

  if (filesize >= 6)
    {
      // check if the file has a valid pseudo-header
      unsigned char pseudo_header[6];
      f->readString((char *)pseudo_header, 6);
      decompressed_filesize = pseudo_header[0] + (pseudo_header[1] << 8);

      if (!( (pseudo_header[2]==0) && (pseudo_header[3]==0) &&
	     (pseudo_header[4] + ((pseudo_header[5] & 0x1)<<8) == 0x100) &&
	     (decompressed_filesize > (filesize-4)) ))
        {
	  fp.close(f);
	  return(false);
        }
    }
  else
    {
      fp.close(f);
      return(false);
    }

  // load section
  delete[] song_data; song_size = 0;
  song_data = new unsigned char[decompressed_filesize];
  unsigned char* compressed_song_data = new unsigned char[filesize-3];

  f->seek(4);
  f->readString((char *)compressed_song_data, filesize - 4);
  fp.close(f);

  // attempt to decompress the song data
  data_block source, destination;
  source.size = filesize-4;
  source.data = compressed_song_data;
  destination.size = decompressed_filesize;
  destination.data = song_data;
	
  if (!lzw_decompress(source,destination))
    {
      delete[] compressed_song_data;
      return(false);
    }

  // deallocation section
  delete[] compressed_song_data;

  song_size = decompressed_filesize;
  rewind(0);
  return (true);
}


bool Cu6mPlayer::update()
{
  if (!driver_active)
    {
      driver_active = true;
      dec_clip(read_delay);
      if (read_delay == 0)
        {
	  command_loop();
        }

      // on all Adlib channels: freq slide/vibrato, mute factor slide
      for (int i = 0; i < 9; i++)
        {
	  if (channel_freq_signed_delta[i]!=0)
            // frequency slide + mute factor slide
            {
	      // freq slide
	      freq_slide(i);

	      // mute factor slide
	      if (carrier_mf_signed_delta[i]!=0)
                {
		  mf_slide(i);
                }
            }
	  else
            // vibrato + mute factor slide
            {
	      // vibrato
	      if ((vb_multiplier[i]!=0) && ((channel_freq[i].hi & 0x20)==0x20))
                {
		  vibrato(i);
                }

	      // mute factor slide
	      if (carrier_mf_signed_delta[i]!=0)
                {
		  mf_slide(i);
                }
            }
        }

      driver_active = false;
    }

  return !songend;
}


void Cu6mPlayer::rewind(int subsong)
{
  songend = false;

  // set the driver's internal variables
  byte_pair freq_word = {0,0};

  driver_active = false;
  song_pos = 0;
  loop_position = 0;   // position of the loop point
  read_delay = 0;      // delay (in timer ticks) before further song data is read
 
  for (int i = 0; i < 9; i++)
    {
      // frequency
      channel_freq_signed_delta[i] = 0;
      channel_freq[i] = freq_word;  // Adlib freq settings for each channel

      // vibrato ("vb")
      vb_current_value[i] = 0;
      vb_double_amplitude[i] = 0;
      vb_multiplier[i] = 0;
      vb_direction_flag[i] = 0;

      // mute factor ("mf") == ~(volume)
      carrier_mf[i] = 0;
      carrier_mf_signed_delta[i] = 0;
      carrier_mf_mod_delay_backup[i] = 0;
      carrier_mf_mod_delay[i] = 0;
    }

  while (!subsong_stack.empty())		// empty subsong stack
    subsong_stack.pop();

  opl->init();
  out_adlib(1,32);	// go to OPL2 mode
}


float Cu6mPlayer::getrefresh()
{
  return ((float)60);   // the Ultima 6 music driver expects to be called at 60 Hz
}


// ============================================================================================
//
//
//    Functions called by load()
//
//
// ============================================================================================


// decompress from memory to memory
bool Cu6mPlayer::lzw_decompress(Cu6mPlayer::data_block source, Cu6mPlayer::data_block dest)
{
  bool end_marker_reached = false;
  int codeword_size = 9;
  unsigned long bits_read = 0;
  int next_free_codeword = 0x102;
  int dictionary_size = 0x200;
  MyDict dictionary = MyDict();
  std::stack<unsigned char> root_stack;

  size_t bytes_written = 0;

  int cW;
  int pW=0;
  unsigned char C;

  while (!end_marker_reached)
    {
      cW = get_next_codeword(bits_read, source, codeword_size);
      switch (cW)
        {
	  // re-init the dictionary
	case 0x100:
	  codeword_size = 9;
	  next_free_codeword = 0x102;
	  dictionary_size = 0x200;
	  dictionary.reset();
	  cW = get_next_codeword(bits_read, source, codeword_size);
	  SAVE_OUTPUT_ROOT((unsigned char)cW, dest, bytes_written);
	  break;
	  // end of compressed file has been reached
	case 0x101:
	  end_marker_reached = true;
	  break;
          // no next code word available, i.e., truncated or invalid data
        case -1:
          return false;
	  // (cW <> 0x100) && (cW <> 0x101)
	default:
	  if (cW < next_free_codeword)  // codeword is already in the dictionary
	    {
	      // create the string associated with cW (on the stack)
	      get_string(cW,dictionary,root_stack);
	      C = root_stack.top();
	      // output the string represented by cW
	      while (!root_stack.empty())
		{
		  SAVE_OUTPUT_ROOT(root_stack.top(), dest, bytes_written);
		  root_stack.pop();
		}
	      // add pW+C to the dictionary
	      dictionary.add(C,pW);

	      next_free_codeword++;
	      if (next_free_codeword >= dictionary_size)
		{
		  if (codeword_size < max_codeword_length)
		    {
		      codeword_size += 1;
		      dictionary_size *= 2;
		    }
		}
	    }
	  else  // codeword is not yet defined
	    {
	      // create the string associated with pW (on the stack)
	      get_string(pW,dictionary,root_stack);
	      C = root_stack.top();
	      // output the string represented by pW
	      while (!root_stack.empty())
		{
		  SAVE_OUTPUT_ROOT(root_stack.top(), dest, bytes_written);
		  root_stack.pop();
		}
	      // output the char C
	      SAVE_OUTPUT_ROOT(C, dest, bytes_written);

	      // the new dictionary entry must correspond to cW
	      // if it doesn't, something is wrong with the lzw-compressed data.
	      if (cW != next_free_codeword)
		{
		  /*                        printf("cW != next_free_codeword!\n");
					    exit(-1); */
		  return false;
		}
	      // add pW+C to the dictionary
	      dictionary.add(C,pW);
 
	      next_free_codeword++;
	      if (next_free_codeword >= dictionary_size)
		{
		  if (codeword_size < max_codeword_length)
		    {
		      codeword_size += 1;
		      dictionary_size *= 2;
		    }
		}
	    };
	  break;
        }
      // shift roles - the current cW becomes the new pW
      pW = cW;
    }

  return(true);   // indicate successful decompression
}


// --------------------
// Additional functions
// --------------------


// Read the next code word from the source buffer
int Cu6mPlayer::get_next_codeword (unsigned long& bits_read, data_block& source,
                                   int codeword_size)
{
  unsigned char b0,b1,b2;
  int codeword;

  if (source.size - bits_read / 8 < 2 + (bits_read % 8 + codeword_size > 16))
    {
      return -1; // source exhausted
    }

  b0 = source.data[bits_read/8];
  b1 = source.data[bits_read/8+1];
  b2 = bits_read % 8 + codeword_size > 16 ? source.data[bits_read/8+2] : 0;

  codeword = ((b2 << 16) + (b1 << 8) + b0);
  codeword = codeword >> (bits_read % 8);
  switch (codeword_size)
    {
    case 0x9:
      codeword = codeword & 0x1ff;
      break;
    case 0xa:
      codeword = codeword & 0x3ff;
      break;
    case 0xb:
      codeword = codeword & 0x7ff;
      break;
    case 0xc:
      codeword = codeword & 0xfff;
      break;
    default:
      codeword = -1;   // indicates that an error has occurred
      break;
    }

  bits_read += codeword_size;
  return (codeword);
}


// output a root to memory
bool Cu6mPlayer::output_root(unsigned char root, data_block& destination,
                             size_t& position)
{
  if (position >= destination.size)
    {
      return false;
    }

  destination.data[position] = root;
  position++;
  return true;
}


// output the string represented by a codeword
void Cu6mPlayer::get_string(int codeword, Cu6mPlayer::MyDict& dictionary, std::stack<unsigned char>& root_stack)
{
  unsigned char root;
  int current_codeword;

  current_codeword = codeword;

  while (current_codeword > 0xff)
    {
      root = dictionary.get_root(current_codeword);
      current_codeword = dictionary.get_codeword(current_codeword);
      root_stack.push(root);
    }

  // push the root at the leaf
  root_stack.push((unsigned char)current_codeword);
}


// ============================================================================================
//
//
//    Functions called by update()
//
//
// ============================================================================================


// This function reads the song data and executes the embedded commands.
void Cu6mPlayer::command_loop()
{
  int command_byte;             // current command byte
  int command_nibble_hi;        // command byte, bits 4-7
  int command_nibble_lo;        // command byte, bite 0-3
  bool repeat_loop = true;      //

  do
    {
      // extract low and high command nibbles
      command_byte = read_song_byte();   // implicitly increments song_pos
      if (command_byte < 0)              // handle invalid position
        {
          songend = true;
          break;
        }
      command_nibble_hi = command_byte >> 4;
      command_nibble_lo = command_byte & 0xf;
 
      switch (command_nibble_hi)
        {
	case 0x0: command_0(command_nibble_lo); break;
	case 0x1: command_1(command_nibble_lo); break;
	case 0x2: command_2(command_nibble_lo); break;
	case 0x3: command_3(command_nibble_lo); break;
	case 0x4: command_4(command_nibble_lo); break;
	case 0x5: command_5(command_nibble_lo); break;
	case 0x6: command_6(command_nibble_lo); break;
	case 0x7: command_7(command_nibble_lo); break;
	case 0x8:
	  switch (command_nibble_lo)
	    {
	    case 1: command_81(); break;
	    case 2: command_82(); repeat_loop = false; break;
	    case 3: command_83(); break;
	    case 5: command_85(); break;
	    case 6: command_86(); break;
	    default: break; // maybe generate an error?
	    }
	  break;
	case 0xE: command_E(); break;
	case 0xF: command_F(); break;
	default: break; // maybe generate an error?
        }

    } while (repeat_loop);
}


// --------------------------------------------------------
//    The commands supported by the U6 music file format
// --------------------------------------------------------

// ----------------------------------------
// Set octave and frequency, note off
// Format: 0c nn
// c = channel, nn = packed Adlib frequency
// ----------------------------------------
void Cu6mPlayer::command_0(int channel)
{
  unsigned char freq_byte;
  byte_pair freq_word;

  freq_byte = read_song_byte();
  freq_word = expand_freq_byte(freq_byte);
  if (channel < 9)
    {
      set_adlib_freq(channel, freq_word);
    }
}


// ---------------------------------------------------
// Set octave and frequency, old note off, new note on
// Format: 1c nn
// c = channel, nn = packed Adlib frequency
// ---------------------------------------------------
void Cu6mPlayer::command_1(int channel)
{
  unsigned char freq_byte = read_song_byte();
  byte_pair freq_word = expand_freq_byte(freq_byte);

  if (channel < 9)
    {
      vb_direction_flag[channel] = 0;
      vb_current_value[channel] = 0;
 
      set_adlib_freq(channel, freq_word);

      freq_word.hi = freq_word.hi | 0x20; // note on
      set_adlib_freq(channel, freq_word);
    }
}


// ----------------------------------------
// Set octave and frequency, note on
// Format: 2c nn
// c = channel, nn = packed Adlib frequency
// ----------------------------------------
void Cu6mPlayer::command_2(int channel)
{
  unsigned char freq_byte;
  byte_pair freq_word;
 
  freq_byte = read_song_byte();
  freq_word = expand_freq_byte(freq_byte);
  freq_word.hi = freq_word.hi | 0x20; // note on
  if (channel < 9)
    {
      set_adlib_freq(channel, freq_word);
    }
}


// --------------------------------------
// Set "carrier mute factor"==not(volume)
// Format: 3c nn
// c = channel, nn = mute factor
// --------------------------------------
void Cu6mPlayer::command_3(int channel)
{
  unsigned char mf_byte = read_song_byte();

  if (channel < 9)
    {
      carrier_mf_signed_delta[channel] = 0;
      set_carrier_mf(channel, mf_byte); // apply mask?
    }
}


// ----------------------------------------
// set "modulator mute factor"==not(volume)
// Format: 4c nn
// c = channel, nn = mute factor
// ----------------------------------------
void Cu6mPlayer::command_4(int channel)
{
  unsigned char mf_byte = read_song_byte();

  if (channel < 9)
    {
      set_modulator_mf(channel, mf_byte); // apply mask?
    }
}


// --------------------------------------------
// Set portamento (pitch slide)
// Format: 5c nn
// c = channel, nn = signed channel pitch delta
// --------------------------------------------
void Cu6mPlayer::command_5(int channel)
{
  signed char delta = read_signed_song_byte();

  if (channel < 9)
    {
      channel_freq_signed_delta[channel] = delta;
    }
}


// --------------------------------------------
// Set vibrato parameters
// Format: 6c mn
// c = channel
// m = vibrato double amplitude
// n = vibrato multiplier
// --------------------------------------------
void Cu6mPlayer::command_6(int channel)
{
  unsigned char vb_parameters = read_song_byte();

  if (channel < 9)
    {
      vb_double_amplitude[channel] = vb_parameters >> 4; // high nibble
      vb_multiplier[channel] = vb_parameters & 0xF; // low nibble
    }
}


// ----------------------------------------
// Assign Adlib instrument to Adlib channel
// Format: 7c nn
// c = channel, nn = instrument number
// ----------------------------------------
void Cu6mPlayer::command_7(int channel)
{
  unsigned char instrument_number = read_song_byte();

  if (channel < 9 && instrument_number < 9)
    {
      size_t instrument_offset = instrument_offsets[instrument_number];
      out_adlib_opcell(channel, false, 0x20, song_data[instrument_offset+0]);
      out_adlib_opcell(channel, false, 0x40, song_data[instrument_offset+1]);
      out_adlib_opcell(channel, false, 0x60, song_data[instrument_offset+2]);
      out_adlib_opcell(channel, false, 0x80, song_data[instrument_offset+3]);
      out_adlib_opcell(channel, false, 0xE0, song_data[instrument_offset+4]);
      out_adlib_opcell(channel, true, 0x20, song_data[instrument_offset+5]);
      out_adlib_opcell(channel, true, 0x40, song_data[instrument_offset+6]);
      out_adlib_opcell(channel, true, 0x60, song_data[instrument_offset+7]);
      out_adlib_opcell(channel, true, 0x80, song_data[instrument_offset+8]);
      out_adlib_opcell(channel, true, 0xE0, song_data[instrument_offset+9]);
      out_adlib(0xC0+channel, song_data[instrument_offset+10]);
    }
}


// -------------------------------------------
// Branch to a new subsong
// Format: 81 nn aa bb
// nn == number of times to repeat the subsong
// aa == subsong offset (low byte)
// bb == subsong offset (high byte)
// -------------------------------------------
void Cu6mPlayer::command_81()
{
  subsong_info new_ss_info;
 
  new_ss_info.subsong_repetitions = read_song_byte();
  new_ss_info.subsong_start = read_song_byte();
  new_ss_info.subsong_start += read_song_byte() << 8;
  new_ss_info.continue_pos = song_pos;

  // Since the new position is not validated, this could jump after the
  // end of song_data. Handled by the usual checks.
  subsong_stack.push(new_ss_info);
  song_pos = new_ss_info.subsong_start;
}


// ------------------------------------------------------------
// Stop interpreting commands for this timer tick
// Format: 82 nn
// nn == delay (in timer ticks) until further data will be read
// ------------------------------------------------------------
void Cu6mPlayer::command_82()
{
  read_delay = read_song_byte();
}


// -----------------------------
// Adlib instrument data follows
// Format: 83 nn <11 bytes>
// nn == instrument number
// -----------------------------
void Cu6mPlayer::command_83()
{
  unsigned char instrument_number = read_song_byte();

  if (instrument_number < 9 && song_size > 11 && song_pos < song_size - 11)
    {
      instrument_offsets[instrument_number] = song_pos;
      song_pos += 11;
    }
}


// ----------------------------------------------
// Set -1 mute factor slide (upward volume slide)
// Format: 85 cn
// c == channel
// n == slide delay
// ----------------------------------------------
void Cu6mPlayer::command_85()
{
  unsigned char data_byte = read_song_byte();
  int channel = data_byte >> 4; // high nibble
  unsigned char slide_delay = data_byte & 0xF; // low nibble

  if (channel < 9)
    {
      carrier_mf_signed_delta[channel] = +1;
      carrier_mf_mod_delay[channel] = slide_delay + 1;
      carrier_mf_mod_delay_backup[channel] = slide_delay + 1;
    }
}


// ------------------------------------------------
// Set +1 mute factor slide (downward volume slide)
// Format: 86 cn
// c == channel
// n == slide speed
// ------------------------------------------------
void Cu6mPlayer::command_86()
{
  unsigned char data_byte = read_song_byte();
  int channel = data_byte >> 4; // high nibble
  unsigned char slide_delay = data_byte & 0xF; // low nibble

  if (channel < 9)
    {
      carrier_mf_signed_delta[channel] = -1;
      carrier_mf_mod_delay[channel] = slide_delay + 1;
      carrier_mf_mod_delay_backup[channel] = slide_delay + 1;
    }
}


// --------------
// Set loop point
// Format: E?
// --------------
void Cu6mPlayer::command_E()
{
  loop_position = song_pos;
}


// ---------------------------
// Return from current subsong
// Format: F?
// ---------------------------
void Cu6mPlayer::command_F()
{
  if (!subsong_stack.empty())
    {
      subsong_info temp = subsong_stack.top();
      subsong_stack.pop();
      temp.subsong_repetitions--;
      if (temp.subsong_repetitions==0)
        {
	  song_pos = temp.continue_pos;
        }
      else
        {
	  song_pos = temp.subsong_start;
	  subsong_stack.push(temp);
        }
    }
  else
    {
      song_pos = loop_position;
      songend = true;
    }
}


// --------------------
// Additional functions
// --------------------

// This function decrements its argument, without allowing it to become negative.
void Cu6mPlayer::dec_clip(int& param)
{
  param--;
  if (param < 0) { param = 0; }
}


// Returns the byte at the current song position.
// Side effect: increments song_pos.
int Cu6mPlayer::read_song_byte()
{
  if (song_pos < song_size)
    {
      return song_data[song_pos++];
    }
  else
    {
      return -1;
    }
}


// Same as read_song_byte(), except that it returns a signed byte
signed char Cu6mPlayer::read_signed_song_byte()
{
  int song_byte = read_song_byte();

  if (song_byte >= 0x80)
    {
      song_byte -= 0x100;
    }
  return song_byte;
}


Cu6mPlayer::byte_pair Cu6mPlayer::expand_freq_byte(unsigned char freq_byte)
{
  const byte_pair freq_table[24] =
    {
      {0x00,0x00}, {0x58,0x01}, {0x82,0x01}, {0xB0,0x01},
      {0xCC,0x01}, {0x03,0x02}, {0x41,0x02}, {0x86,0x02},
      {0x00,0x00}, {0x6A,0x01}, {0x96,0x01}, {0xC7,0x01},
      {0xE4,0x01}, {0x1E,0x02}, {0x5F,0x02}, {0xA8,0x02},
      {0x00,0x00}, {0x47,0x01}, {0x6E,0x01}, {0x9A,0x01},
      {0xB5,0x01}, {0xE9,0x01}, {0x24,0x02}, {0x66,0x02}
    };

  int packed_freq;
  int octave;
  byte_pair freq_word;

  packed_freq = freq_byte & 0x1F;
  octave = freq_byte >> 5;

  // range check (not present in the original U6 music driver)
  if (packed_freq >= 24) { packed_freq = 0; }

  freq_word.hi = freq_table[packed_freq].hi + (octave << 2);
  freq_word.lo = freq_table[packed_freq].lo;

  return(freq_word);
}


void Cu6mPlayer::set_adlib_freq(int channel,Cu6mPlayer::byte_pair freq_word)
{
  out_adlib(0xA0+channel,freq_word.lo);
  out_adlib(0xB0+channel,freq_word.hi);
  // update the Adlib register backups
  channel_freq[channel] = freq_word;
}


// this function sets the Adlib frequency, but does not update the register backups
void Cu6mPlayer::set_adlib_freq_no_update(int channel,Cu6mPlayer::byte_pair freq_word)
{
  out_adlib(0xA0+channel,freq_word.lo);
  out_adlib(0xB0+channel,freq_word.hi);
}


void Cu6mPlayer::set_carrier_mf(int channel,unsigned char mute_factor)
{
  out_adlib_opcell(channel,true,0x40,mute_factor);
  carrier_mf[channel] = mute_factor;
}


void Cu6mPlayer::set_modulator_mf(int channel,unsigned char mute_factor)
{
  out_adlib_opcell(channel,false,0x40,mute_factor);
}


void Cu6mPlayer::freq_slide(int channel)
{
  byte_pair freq = channel_freq[channel];

  long freq_word = freq.lo + (freq.hi << 8) + channel_freq_signed_delta[channel];
  if (freq_word < 0) { freq_word += 0x10000; }
  if (freq_word > 0xFFFF) { freq_word -= 0x10000; }

  freq.lo = freq_word & 0xFF;
  freq.hi = (freq_word >> 8) & 0xFF;
  set_adlib_freq(channel,freq);
}


void Cu6mPlayer::vibrato(int channel)
{
  byte_pair freq;

  if (vb_current_value[channel] >= vb_double_amplitude[channel])
    { vb_direction_flag[channel] = 1; }
  else if (vb_current_value[channel] <= 0)
    { vb_direction_flag[channel] = 0; }

  if (vb_direction_flag[channel]==0)
    { vb_current_value[channel]++; }
  else
    { vb_current_value[channel]--; }

  long freq_word = channel_freq[channel].lo + (channel_freq[channel].hi << 8);
  freq_word += (vb_current_value[channel] - (vb_double_amplitude[channel] >> 1))
    * vb_multiplier[channel];
  if (freq_word < 0) { freq_word += 0x10000; }
  if (freq_word > 0xFFFF) { freq_word -= 0x10000; }

  freq.lo = freq_word & 0xFF;
  freq.hi = (freq_word >> 8) & 0xFF;
  set_adlib_freq_no_update(channel,freq);
}


void Cu6mPlayer::mf_slide(int channel)
{
  carrier_mf_mod_delay[channel]--;
  if (carrier_mf_mod_delay[channel]==0)
    {
      carrier_mf_mod_delay[channel] = carrier_mf_mod_delay_backup[channel];
      int current_mf = carrier_mf[channel] + carrier_mf_signed_delta[channel];
      if (current_mf > 0x3F)
        {
	  current_mf = 0x3F;
	  carrier_mf_signed_delta[channel] = 0;
        }
      else if (current_mf < 0)
	{
	  current_mf = 0;
	  carrier_mf_signed_delta[channel] = 0;
	}

      set_carrier_mf(channel,(unsigned char)current_mf);
    }
}


void Cu6mPlayer::out_adlib(unsigned char adlib_register, unsigned char adlib_data)
{
  opl->write(adlib_register,adlib_data);
}


void Cu6mPlayer::out_adlib_opcell(int channel, bool carrier, unsigned char adlib_register, unsigned char out_byte)
{
  const unsigned char adlib_channel_to_carrier_offset[9] =
    {0x03,0x04,0x05,0x0B,0x0C,0x0D,0x13,0x14,0x15};
  const unsigned char adlib_channel_to_modulator_offset[9] =
    {0x00,0x01,0x02,0x08,0x09,0x0A,0x10,0x11,0x12};

  if (carrier)
    {
      out_adlib(adlib_register+adlib_channel_to_carrier_offset[channel],out_byte);
    }
  else
    {
      out_adlib(adlib_register+adlib_channel_to_modulator_offset[channel],out_byte);
    }
}


// ============================================================================================
//
//
//    The Dictionary
//
//
// ============================================================================================


Cu6mPlayer::MyDict::MyDict()
{
  dict_size = default_dict_size;
  dictionary = new dict_entry[dict_size-0x100]; // don't allocate space for the roots
  contains = 0x102;
}


Cu6mPlayer::MyDict::MyDict(int max_size)
{
  dict_size = max_size;
  dictionary = new dict_entry[dict_size-0x100]; // don't allocate space for the roots
  contains = 0x102;
}


Cu6mPlayer::MyDict::~MyDict()
{
  delete [] dictionary;
}

// re-initializes the dictionary
void Cu6mPlayer::MyDict::reset()
{
  contains = 0x102;
}


// Note: If the dictionary is already full, this function does nothing.
void Cu6mPlayer::MyDict::add(unsigned char root, int codeword)
{
  if (contains < dict_size)
    {
      dictionary[contains-0x100].root = root;
      dictionary[contains-0x100].codeword = codeword;
      contains++;
    }
}


unsigned char Cu6mPlayer::MyDict::get_root(int codeword)
{
  return (dictionary[codeword-0x100].root);
}


int Cu6mPlayer::MyDict::get_codeword(int codeword)
{
  return (dictionary[codeword-0x100].codeword);
}
