Mercurial > audlegacy-plugins
view src/adplug/core/rol.cxx @ 1086:cd854e8ced20 trunk
[svn] - vtx: convert to plugin API v2
author | nenolod |
---|---|
date | Thu, 24 May 2007 23:09:03 -0700 |
parents | 88ba14f18587 |
children | fa9f85cebade |
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 ¬eEvent = 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 ¬e_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 { 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); }