Mercurial > audlegacy-plugins
diff src/Input/adplug/core/rol.cxx @ 0:13389e613d67 trunk
[svn] - initial import of audacious-plugins tree (lots to do)
author | nenolod |
---|---|
date | Mon, 18 Sep 2006 01:11:49 -0700 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Input/adplug/core/rol.cxx Mon Sep 18 01:11:49 2006 -0700 @@ -0,0 +1,723 @@ +/* + * 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 + * + * 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 != NULL ) + { + delete rol_header; + rol_header=NULL; + } +} +//--------------------------------------------------------- +bool CrolPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + + char *fn = new char[filename.length()+9]; + 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; + 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; + binistream *bnk_file = fp.open( bnk_filename.c_str() ); + + 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); + + 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); +}