view src/adplug/core/rol.cxx @ 955:4709ce4e209e trunk

[svn] Run indent -ts4 -nut -bli0 -cdw on this messy lot. Upstream is not consistent with whitespace anyway, no loss there.
author chainsaw
date Sat, 14 Apr 2007 15:23:50 -0700
parents 028350cb60b1
children 88ba14f18587
line wrap: on
line source

/*
 * 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
 *
 * rol.h - ROL Player by OPLx <oplx@yahoo.com>
 *
 * Visit:  http://tenacity.hispeed.com/aomit/oplx/
 */
#include <algorithm>

#include "rol.h"
#include "debug.h"

int const
  CrolPlayer::kSizeofDataRecord = 30;
int const
  CrolPlayer::kMaxTickBeat = 60;
int const
  CrolPlayer::kSilenceNote = -12;
int const
  CrolPlayer::kNumMelodicVoices = 9;
int const
  CrolPlayer::kNumPercussiveVoices = 11;
int const
  CrolPlayer::kBassDrumChannel = 6;
int const
  CrolPlayer::kSnareDrumChannel = 7;
int const
  CrolPlayer::kTomtomChannel = 8;
int const
  CrolPlayer::kTomtomFreq = 2;  //4;
int const
  CrolPlayer::kSnareDrumFreq = 2;   //kTomtomFreq + 7;
float const
  CrolPlayer::kDefaultUpdateTme = 18.2f;
float const
  CrolPlayer::kPitchFactor = 400.0f;

static const unsigned char
drum_table[4] = { 0x14, 0x12, 0x15, 0x11 };

CrolPlayer::uint16 const
  CrolPlayer::kNoteTable[12] = {
  340,                          // C
  363,                          // C#
  385,                          // D
  408,                          // D#
  432,                          // E
  458,                          // F
  485,                          // F#
  514,                          // G
  544,                          // G#
  577,                          // A
  611,                          // A#
  647                           // B
};

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

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

//---------------------------------------------------------
CrolPlayer::CrolPlayer (Copl * newopl):CPlayer (newopl), rol_header (NULL), mNextTempoEvent (0), mCurrTick (0),
mTimeOfLastNote (0), mRefresh (kDefaultUpdateTme),
bdRegister (0)
{
  int
    n;

  memset (bxRegister, 0, sizeof (bxRegister));
  memset (volumeCache, 0, sizeof (volumeCache));
  memset (freqCache, 0, sizeof (freqCache));

  for (n = 0; n < 11; n++)
    pitchCache[n] = 1.0f;
}

//---------------------------------------------------------
CrolPlayer::~CrolPlayer ()
{
  if (rol_header)
  {
    delete rol_header;
    rol_header = 0;
  }
}

//---------------------------------------------------------
bool
CrolPlayer::load (VFSFile * fd, const CFileProvider & fp)
{
  binistream *f = fp.open (fd);
  if (!f)
    return false;
  std::string filename (fd->uri);

  char *fn = new char[filename.length () + 12];
  int i;
  std::string bnk_filename;

  AdPlug_LogWrite ("*** CrolPlayer::load(f, \"%s\") ***\n",
                   filename.c_str ());
  strcpy (fn, filename.data ());
  for (i = strlen (fn) - 1; i >= 0; i--)
    if (fn[i] == '/' || fn[i] == '\\')
      break;
  strcpy (fn + i + 1, "standard.bnk");
  bnk_filename = fn;
  delete[]fn;
  fn = 0;
  AdPlug_LogWrite ("bnk_filename = \"%s\"\n", bnk_filename.c_str ());

  rol_header = new SRolHeader;
  memset (rol_header, 0, sizeof (SRolHeader));

  rol_header->version_major = f->readInt (2);
  rol_header->version_minor = f->readInt (2);

  // Version check
  if (rol_header->version_major != 0 || rol_header->version_minor != 4)
  {
    AdPlug_LogWrite ("Unsupported file version %d.%d or not a ROL file!\n",
                     rol_header->version_major, rol_header->version_minor);
    AdPlug_LogWrite ("--- CrolPlayer::load ---\n");
    fp.close (f);
    return false;
  }

  f->seek (40, binio::Add);

  rol_header->ticks_per_beat = f->readInt (2);
  rol_header->beats_per_measure = f->readInt (2);
  rol_header->edit_scale_y = f->readInt (2);
  rol_header->edit_scale_x = f->readInt (2);

  f->seek (1, binio::Add);

  rol_header->mode = f->readInt (1);

  f->seek (90 + 38 + 15, binio::Add);

  rol_header->basic_tempo = f->readFloat (binio::Single);

  load_tempo_events (f);

  mTimeOfLastNote = 0;

  if (load_voice_data (f, bnk_filename, fp) != true)
  {
    AdPlug_LogWrite ("CrolPlayer::load_voice_data(f) failed!\n");
    AdPlug_LogWrite ("--- CrolPlayer::load ---\n");

    fp.close (f);
    return false;
  }

  fp.close (f);

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

//---------------------------------------------------------
bool
CrolPlayer::update ()
{
  if (mNextTempoEvent < mTempoEvents.size () &&
      mTempoEvents[mNextTempoEvent].time == mCurrTick)
  {
    SetRefresh (mTempoEvents[mNextTempoEvent].multiplier);
    ++mNextTempoEvent;
  }

  TVoiceData::iterator curr = voice_data.begin ();
  TVoiceData::iterator end = voice_data.end ();
  int voice = 0;

  while (curr != end)
  {
    UpdateVoice (voice, *curr);
    ++curr;
    ++voice;
  }

  ++mCurrTick;

  if (mCurrTick > mTimeOfLastNote)
  {
    return false;
  }

  return true;
  //return ( mCurrTick > mTimeOfLastNote ) ? false : true;
}

//---------------------------------------------------------
void
CrolPlayer::rewind (int subsong)
{
  TVoiceData::iterator curr = voice_data.begin ();
  TVoiceData::iterator end = voice_data.end ();

  while (curr != end)
  {
    CVoiceData & voice = *curr;

    voice.Reset ();
    ++curr;
  }

  memset (bxRegister, 0, sizeof (bxRegister));
  memset (volumeCache, 0, sizeof (volumeCache));

  bdRegister = 0;

  opl->init ();                 // initialize to melodic by default
  opl->write (1, 0x20);         // Enable waveform select (bit 5)

  if (rol_header->mode == 0)
  {
    opl->write (0xbd, 0x20);    // select rhythm mode (bit 5)
    bdRegister = 0x20;

    SetFreq (kTomtomChannel, 24);
    SetFreq (kSnareDrumChannel, 31);
  }

  mNextTempoEvent = 0;
  mCurrTick = 0;

  SetRefresh (1.0f);
}

//---------------------------------------------------------
inline float
fmin (int const a, int const b)
{
  return static_cast < float >(a < b ? a : b);
}

//---------------------------------------------------------
void
CrolPlayer::SetRefresh (float const multiplier)
{
  float const tickBeat = fmin (kMaxTickBeat, rol_header->ticks_per_beat);

  mRefresh = (tickBeat * rol_header->basic_tempo * multiplier) / 60.0f;
}

//---------------------------------------------------------
float
CrolPlayer::getrefresh ()
{
  return mRefresh;
}

//---------------------------------------------------------
void
CrolPlayer::UpdateVoice (int const voice, CVoiceData & voiceData)
{
  TNoteEvents const &nEvents = voiceData.note_events;

  if (nEvents.empty () || voiceData.mEventStatus & CVoiceData::kES_NoteEnd)
  {
    return;                     // no note data to process, don't bother doing anything.
  }

  TInstrumentEvents & iEvents = voiceData.instrument_events;
  TVolumeEvents & vEvents = voiceData.volume_events;
  TPitchEvents & pEvents = voiceData.pitch_events;

  if (!(voiceData.mEventStatus & CVoiceData::kES_InstrEnd) &&
      iEvents[voiceData.next_instrument_event].time == mCurrTick)
  {
    if (voiceData.next_instrument_event < iEvents.size ())
    {
      send_ins_data_to_chip (voice,
                             iEvents[voiceData.next_instrument_event].
                             ins_index);
      ++voiceData.next_instrument_event;
    }
    else
    {
      voiceData.mEventStatus |= CVoiceData::kES_InstrEnd;
    }
  }

  if (!(voiceData.mEventStatus & CVoiceData::kES_VolumeEnd) &&
      vEvents[voiceData.next_volume_event].time == mCurrTick)
  {
    SVolumeEvent const &volumeEvent = vEvents[voiceData.next_volume_event];

    if (voiceData.next_volume_event < vEvents.size ())
    {
      int const volume = (int) (63.0f * (1.0f - volumeEvent.multiplier));

      SetVolume (voice, volume);

      ++voiceData.next_volume_event;    // move to next volume event
    }
    else
    {
      voiceData.mEventStatus |= CVoiceData::kES_VolumeEnd;
    }
  }

  if (voiceData.mForceNote
      || voiceData.current_note_duration > voiceData.mNoteDuration - 1)
  {
    if (mCurrTick != 0)
    {
      ++voiceData.current_note;
    }

    if (voiceData.current_note < nEvents.size ())
    {
      SNoteEvent const &noteEvent = nEvents[voiceData.current_note];

      SetNote (voice, noteEvent.number);
      voiceData.current_note_duration = 0;
      voiceData.mNoteDuration = noteEvent.duration;
      voiceData.mForceNote = false;
    }
    else
    {
      SetNote (voice, kSilenceNote);
      voiceData.mEventStatus |= CVoiceData::kES_NoteEnd;
      return;
    }
  }

  if (!(voiceData.mEventStatus & CVoiceData::kES_PitchEnd) &&
      pEvents[voiceData.next_pitch_event].time == mCurrTick)
  {
    if (voiceData.next_pitch_event < pEvents.size ())
    {
      SetPitch (voice, pEvents[voiceData.next_pitch_event].variation);
      ++voiceData.next_pitch_event;
    }
    else
    {
      voiceData.mEventStatus |= CVoiceData::kES_PitchEnd;
    }
  }

  ++voiceData.current_note_duration;
}

//---------------------------------------------------------
void
CrolPlayer::SetNote (int const voice, int const note)
{
  if (voice < kBassDrumChannel || rol_header->mode)
  {
    SetNoteMelodic (voice, note);
  }
  else
  {
    SetNotePercussive (voice, note);
  }
}

//---------------------------------------------------------
void
CrolPlayer::SetNotePercussive (int const voice, int const note)
{
  int const bit_pos = 4 - voice + kBassDrumChannel;

  bdRegister &= ~(1 << bit_pos);
  opl->write (0xbd, bdRegister);

  if (note != kSilenceNote)
  {
    switch (voice)
    {
    case kTomtomChannel:
      SetFreq (kSnareDrumChannel, note + 7);
    case kBassDrumChannel:
      SetFreq (voice, note);
      break;
    }

    bdRegister |= 1 << bit_pos;
    opl->write (0xbd, bdRegister);
  }
}

//---------------------------------------------------------
void
CrolPlayer::SetNoteMelodic (int const voice, int const note)
{
  opl->write (0xb0 + voice, bxRegister[voice] & ~0x20);

  if (note != kSilenceNote)
  {
    SetFreq (voice, note, true);
  }
}

//---------------------------------------------------------
void
CrolPlayer::SetPitch (int const voice, real32 const variation)
{
  pitchCache[voice] = variation;
  freqCache[voice] +=
    (uint16) ((((float) freqCache[voice]) * (variation - 1.0f)) /
              kPitchFactor);

  opl->write (0xa0 + voice, freqCache[voice] & 0xff);
}

//---------------------------------------------------------
void
CrolPlayer::SetFreq (int const voice, int const note, bool const keyOn)
{
  uint16 freq = kNoteTable[note % 12] + ((note / 12) << 10);
  freq +=
    (uint16) ((((float) freq) * (pitchCache[voice] - 1.0f)) / kPitchFactor);

  freqCache[voice] = freq;
  bxRegister[voice] = ((freq >> 8) & 0x1f);

  opl->write (0xa0 + voice, freq & 0xff);
  opl->write (0xb0 + voice, bxRegister[voice] | (keyOn ? 0x20 : 0x0));
}

//---------------------------------------------------------
void
CrolPlayer::SetVolume (int const voice, int const volume)
{
  volumeCache[voice] = (volumeCache[voice] & 0xc0) | volume;

  int const op_offset = (voice < kSnareDrumChannel || rol_header->mode) ?
    op_table[voice] + 3 : drum_table[voice - kSnareDrumChannel];

  opl->write (0x40 + op_offset, volumeCache[voice]);
}

//---------------------------------------------------------
void
CrolPlayer::send_ins_data_to_chip (int const voice, int const ins_index)
{
  SRolInstrument & instrument = ins_list[ins_index].instrument;

  send_operator (voice, instrument.modulator, instrument.carrier);
}

//---------------------------------------------------------
void
CrolPlayer::send_operator (int const voice, SOPL2Op const &modulator,
                           SOPL2Op const &carrier)
{
  if (voice < kSnareDrumChannel || rol_header->mode)
  {
    int const op_offset = op_table[voice];

    opl->write (0x20 + op_offset, modulator.ammulti);
    opl->write (0x40 + op_offset, modulator.ksltl);
    opl->write (0x60 + op_offset, modulator.ardr);
    opl->write (0x80 + op_offset, modulator.slrr);
    opl->write (0xc0 + voice, modulator.fbc);
    opl->write (0xe0 + op_offset, modulator.waveform);

    volumeCache[voice] = (carrier.ksltl & 0xc0) | volumeCache[voice] & 0x3f;

    opl->write (0x23 + op_offset, carrier.ammulti);
    opl->write (0x43 + op_offset, volumeCache[voice]);
    opl->write (0x63 + op_offset, carrier.ardr);
    opl->write (0x83 + op_offset, carrier.slrr);
//        opl->write( 0xc3+voice    , carrier.fbc      ); <- don't bother writing this.
    opl->write (0xe3 + op_offset, carrier.waveform);
  }
  else
  {
    int const op_offset = drum_table[voice - kSnareDrumChannel];

    volumeCache[voice] = (modulator.ksltl & 0xc0) | volumeCache[voice] & 0x3f;

    opl->write (0x20 + op_offset, modulator.ammulti);
    opl->write (0x40 + op_offset, volumeCache[voice]);
    opl->write (0x60 + op_offset, modulator.ardr);
    opl->write (0x80 + op_offset, modulator.slrr);
    opl->write (0xc0 + voice, modulator.fbc);
    opl->write (0xe0 + op_offset, modulator.waveform);
  }
}

//---------------------------------------------------------
void
CrolPlayer::load_tempo_events (binistream * f)
{
  int16 const num_tempo_events = f->readInt (2);

  mTempoEvents.reserve (num_tempo_events);

  for (int i = 0; i < num_tempo_events; ++i)
  {
    STempoEvent event;

    event.time = f->readInt (2);
    event.multiplier = f->readFloat (binio::Single);
    mTempoEvents.push_back (event);
  }
}

//---------------------------------------------------------
bool
CrolPlayer::load_voice_data (binistream * f, std::string const &bnk_filename,
                             const CFileProvider & fp)
{
  SBnkHeader bnk_header;
  VFSFile *fd = vfs_fopen (bnk_filename.c_str (), "rb");
  binistream *bnk_file = fp.open (fd);

  if (bnk_file)
  {
    load_bnk_info (bnk_file, bnk_header);

    int const numVoices =
      rol_header->mode ? kNumMelodicVoices : kNumPercussiveVoices;

    voice_data.reserve (numVoices);
    for (int i = 0; i < numVoices; ++i)
    {
      CVoiceData voice;

      load_note_events (f, voice);
      load_instrument_events (f, voice, bnk_file, bnk_header);
      load_volume_events (f, voice);
      load_pitch_events (f, voice);

      voice_data.push_back (voice);
    }

    fp.close (bnk_file);
    vfs_fclose (fd);

    return true;
  }

  return false;
}

//---------------------------------------------------------
void
CrolPlayer::load_note_events (binistream * f, CVoiceData & voice)
{
  f->seek (15, binio::Add);

  int16 const time_of_last_note = f->readInt (2);

  if (time_of_last_note != 0)
  {
    TNoteEvents & note_events = voice.note_events;
    int16 total_duration = 0;

    do
    {
      SNoteEvent event;

      event.number = f->readInt (2);
      event.duration = f->readInt (2);

      event.number += kSilenceNote; // adding -12

      note_events.push_back (event);

      total_duration += event.duration;
    } while (total_duration < time_of_last_note);

    if (time_of_last_note > mTimeOfLastNote)
    {
      mTimeOfLastNote = time_of_last_note;
    }
  }

  f->seek (15, binio::Add);
}

//---------------------------------------------------------
void
CrolPlayer::load_instrument_events (binistream * f, CVoiceData & voice,
                                    binistream * bnk_file,
                                    SBnkHeader const &bnk_header)
{
  int16 const number_of_instrument_events = f->readInt (2);

  TInstrumentEvents & instrument_events = voice.instrument_events;

  instrument_events.reserve (number_of_instrument_events);

  for (int i = 0; i < number_of_instrument_events; ++i)
  {
    SInstrumentEvent event;
    event.time = f->readInt (2);
    f->readString (event.name, 9);

    std::string event_name = event.name;
    event.ins_index = load_rol_instrument (bnk_file, bnk_header, event_name);

    instrument_events.push_back (event);

    f->seek (1 + 2, binio::Add);
  }

  f->seek (15, binio::Add);
}

//---------------------------------------------------------
void
CrolPlayer::load_volume_events (binistream * f, CVoiceData & voice)
{
  int16 const number_of_volume_events = f->readInt (2);

  TVolumeEvents & volume_events = voice.volume_events;

  volume_events.reserve (number_of_volume_events);

  for (int i = 0; i < number_of_volume_events; ++i)
  {
    SVolumeEvent event;
    event.time = f->readInt (2);
    event.multiplier = f->readFloat (binio::Single);

    volume_events.push_back (event);
  }

  f->seek (15, binio::Add);
}

//---------------------------------------------------------
void
CrolPlayer::load_pitch_events (binistream * f, CVoiceData & voice)
{
  int16 const number_of_pitch_events = f->readInt (2);

  TPitchEvents & pitch_events = voice.pitch_events;

  pitch_events.reserve (number_of_pitch_events);

  for (int i = 0; i < number_of_pitch_events; ++i)
  {
    SPitchEvent event;
    event.time = f->readInt (2);
    event.variation = f->readFloat (binio::Single);

    pitch_events.push_back (event);
  }
}

//---------------------------------------------------------
bool
CrolPlayer::load_bnk_info (binistream * f, SBnkHeader & header)
{
  header.version_major = f->readInt (1);
  header.version_minor = f->readInt (1);
  f->readString (header.signature, 6);

  header.number_of_list_entries_used = f->readInt (2);
  header.total_number_of_list_entries = f->readInt (2);

  header.abs_offset_of_name_list = f->readInt (4);
  header.abs_offset_of_data = f->readInt (4);

  f->seek (header.abs_offset_of_name_list, binio::Set);

  TInstrumentNames & ins_name_list = header.ins_name_list;
  ins_name_list.reserve (header.number_of_list_entries_used);

  for (int i = 0; i < header.number_of_list_entries_used; ++i)
  {
    SInstrumentName instrument;

    instrument.index = f->readInt (2);
    instrument.record_used = f->readInt (1);
    f->readString (instrument.name, 9);

    // printf("%s = #%d\n", instrument.name, i );

    ins_name_list.push_back (instrument);
  }

  //std::sort( ins_name_list.begin(), ins_name_list.end(), StringCompare() );

  return true;
}

//---------------------------------------------------------
int
CrolPlayer::load_rol_instrument (binistream * f, SBnkHeader const &header,
                                 std::string & name)
{
  TInstrumentNames const &ins_name_list = header.ins_name_list;

  int const ins_index = get_ins_index (name);

  if (ins_index != -1)
  {
    return ins_index;
  }

  typedef TInstrumentNames::const_iterator TInsIter;
  typedef std::pair < TInsIter, TInsIter > TInsIterPair;

  TInsIterPair range = std::equal_range (ins_name_list.begin (),
                                         ins_name_list.end (),
                                         name,
                                         StringCompare ());

  if (range.first != range.second)
  {
    int const seekOffs =
      header.abs_offset_of_data + (range.first->index * kSizeofDataRecord);
    f->seek (seekOffs, binio::Set);
  }

  SUsedList usedIns;
  usedIns.name = name;

  if (range.first != range.second)
  {
    read_rol_instrument (f, usedIns.instrument);
  }
  else
  {
    // set up default instrument data here
    memset (&usedIns.instrument, 0, kSizeofDataRecord);
  }
  ins_list.push_back (usedIns);

  return ins_list.size () - 1;
}

//---------------------------------------------------------
int
CrolPlayer::get_ins_index (std::string const &name) const const
{
  for (unsigned int i = 0; i < ins_list.size (); ++i)
  {
    if (stricmp (ins_list[i].name.c_str (), name.c_str ()) == 0)
    {
      return i;
    }
  }

  return -1;
}

//---------------------------------------------------------
void
CrolPlayer::read_rol_instrument (binistream * f, SRolInstrument & ins)
{
  ins.mode = f->readInt (1);
  ins.voice_number = f->readInt (1);

  read_fm_operator (f, ins.modulator);
  read_fm_operator (f, ins.carrier);

  ins.modulator.waveform = f->readInt (1);
  ins.carrier.waveform = f->readInt (1);
}

//---------------------------------------------------------
void
CrolPlayer::read_fm_operator (binistream * f, SOPL2Op & opl2_op)
{
  SFMOperator fm_op;

  fm_op.key_scale_level = f->readInt (1);
  fm_op.freq_multiplier = f->readInt (1);
  fm_op.feed_back = f->readInt (1);
  fm_op.attack_rate = f->readInt (1);
  fm_op.sustain_level = f->readInt (1);
  fm_op.sustaining_sound = f->readInt (1);
  fm_op.decay_rate = f->readInt (1);
  fm_op.release_rate = f->readInt (1);
  fm_op.output_level = f->readInt (1);
  fm_op.amplitude_vibrato = f->readInt (1);
  fm_op.frequency_vibrato = f->readInt (1);
  fm_op.envelope_scaling = f->readInt (1);
  fm_op.fm_type = f->readInt (1);

  opl2_op.ammulti =
    fm_op.amplitude_vibrato << 7 | fm_op.frequency_vibrato << 6 | fm_op.
    sustaining_sound << 5 | fm_op.envelope_scaling << 4 | fm_op.
    freq_multiplier;
  opl2_op.ksltl = fm_op.key_scale_level << 6 | fm_op.output_level;
  opl2_op.ardr = fm_op.attack_rate << 4 | fm_op.decay_rate;
  opl2_op.slrr = fm_op.sustain_level << 4 | fm_op.release_rate;
  opl2_op.fbc = fm_op.feed_back << 1 | (fm_op.fm_type ^ 1);
}