Mercurial > audlegacy
view Plugins/Input/console/Gym_Emu.cpp @ 137:b8d4c1faa6d7 trunk
[svn] Import WMA decoder into SVN.
author | nenolod |
---|---|
date | Thu, 10 Nov 2005 14:56:35 -0800 |
parents | 252843aac42f |
children | 84aabc053b6e |
line wrap: on
line source
// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ #include "Gym_Emu.h" #include "ym2612.h" #include <string.h> /* Copyright (C) 2003-2005 Shay Green. This module 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 module 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 module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include BLARGG_SOURCE_BEGIN const long base_clock = 53700300; const long clock_rate = base_clock / 15; Gym_Emu::Gym_Emu() { data = NULL; pos = NULL; mem = NULL; pairs_per_frame = 0; } Gym_Emu::~Gym_Emu() { unload(); } void Gym_Emu::unload() { delete [] mem; mem = NULL; data = NULL; pos = NULL; track_ended_ = false; } blargg_err_t Gym_Emu::init( long sample_rate, double gain, double oversample_ ) { require( oversample_ <= 4.0 ); blip_eq_t eq( -32, 8000, sample_rate ); apu.treble_eq( eq ); apu.volume( 0.27 * gain ); dac_synth.treble_eq( eq ); dac_synth.volume( 0.25 * gain ); oversample = resampler.time_ratio( oversample_, 0.990, gain ); pairs_per_frame = sample_rate / 60; oversamples_per_frame = int (pairs_per_frame * oversample) * 2 + 2; clocks_per_sample = (double) clock_rate / sample_rate; BLARGG_RETURN_ERR( resampler.buffer_size( oversamples_per_frame + 256 ) ); BLARGG_RETURN_ERR( blip_buf.sample_rate( sample_rate, 1000 / 30 ) ); BLARGG_RETURN_ERR( fm.set_rate( sample_rate * oversample, base_clock / 7 ) ); blip_buf.clock_rate( clock_rate ); return blargg_success; } void Gym_Emu::mute_voices( int mask ) { fm.mute_voices( mask ); dac_disabled = mask & 0x40; apu.output( (mask & 0x80) ? NULL : &blip_buf ); } const char** Gym_Emu::voice_names() const { static const char* names [] = { "FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "SN76489" }; return names; } static blargg_err_t check_header( const Gym_Emu::header_t& h, int* data_offset = NULL ) { if ( memcmp( h.tag, "GYMX", 4 ) == 0 ) { if ( memcmp( h.packed, "\0\0\0\0", 4 ) != 0 ) return "Packed GYM file not supported"; if ( data_offset ) *data_offset = sizeof h; } else if ( h.tag [0] != 0 && h.tag [0] != 1 ) { // not a headerless GYM // to do: more thorough check, or just require a damn header return "Not a GYM file"; } return blargg_success; } blargg_err_t Gym_Emu::load_( const void* file, long data_offset, long file_size ) { require( pairs_per_frame ); data = (const byte*) file + data_offset; data_end = (const byte*) file + file_size; loop_begin = NULL; loop_offset = 0; if ( data_offset ) { const header_t& h = *(header_t*) file; loop_offset = h.loop [3] * 0x1000000L + h.loop [2] * 0x10000L + h.loop [1] * 0x100L + h.loop [0]; } track_count_ = 1; voice_count_ = 8; mute_voices( 0 ); return blargg_success; } blargg_err_t Gym_Emu::load( const void* file, long file_size ) { unload(); if ( file_size < sizeof (header_t) ) return "Not a GYM file"; int data_offset = 0; BLARGG_RETURN_ERR( check_header( *(header_t*) file, &data_offset ) ); return load_( file, data_offset, file_size ); } blargg_err_t Gym_Emu::load( const header_t& h, Emu_Reader& in ) { unload(); int data_offset = 0; BLARGG_RETURN_ERR( check_header( h, &data_offset ) ); long file_size = sizeof h + in.remain(); mem = new byte [file_size]; if ( !mem ) return "Out of memory"; memcpy( mem, &h, sizeof h ); BLARGG_RETURN_ERR( in.read( mem + sizeof h, file_size - sizeof h ) ); return load_( mem, data_offset, file_size ); } int Gym_Emu::track_length() const { if ( loop_offset || loop_begin ) return 0; long time = 0; // 1/60 sec frames const byte* p = data; while ( p < data_end ) { switch ( *p++ ) { case 0: time++; break; case 1: case 2: ++p; case 3: ++p; break; default: dprintf( "Bad command: %02X\n", (int) p [-1] ); break; } } return (time + 30 + 59) / 60; } blargg_err_t Gym_Emu::start_track( int ) { require( data ); pos = &data [0]; extra_pos = 0; loop_remain = loop_offset; prev_dac_count = 0; dac_enabled = false; last_dac = -1; fm.reset(); apu.reset(); blip_buf.clear( false ); resampler.clear(); track_ended_ = false; return blargg_success; } void Gym_Emu::play_frame( sample_t* out ) { parse_frame(); // run SMS APU and buffer blip_time_t clock_count = (pairs_per_frame + 1 - blip_buf.samples_avail()) * clocks_per_sample; apu.end_frame( clock_count ); blip_buf.end_frame( clock_count ); assert( unsigned (blip_buf.samples_avail() - pairs_per_frame) <= 4 ); // run fm const int sample_count = oversamples_per_frame - resampler.written(); sample_t* buf = resampler.buffer(); memset( buf, 0, sample_count * sizeof *buf ); fm.run( buf, sample_count ); resampler.write( sample_count ); int count = resampler.read( sample_buf, pairs_per_frame * 2 ); assert( count <= sample_buf_size ); assert( unsigned (count - pairs_per_frame * 2) < 32 ); // mix outputs mix_samples( out ); blip_buf.remove_samples( pairs_per_frame ); } blargg_err_t Gym_Emu::play( long count, sample_t* out ) { require( pos ); const int samples_per_frame = pairs_per_frame * 2; // empty extra buffer if ( extra_pos ) { int n = samples_per_frame - extra_pos; if ( n > count ) n = count; memcpy( out, sample_buf + extra_pos, n * sizeof *out ); out += n; count -= n; extra_pos = (extra_pos + n) % samples_per_frame; } // entire frames while ( count >= samples_per_frame ) { play_frame( out ); out += samples_per_frame; count -= samples_per_frame; } // extra if ( count ) { play_frame( sample_buf ); extra_pos = count; memcpy( out, sample_buf, count * sizeof *out ); out += count; } return blargg_success; } blargg_err_t Gym_Emu::skip( long count ) { // to do: figure out why total muting generated access violation on MorphOS const int buf_size = 1024; sample_t buf [buf_size]; while ( count ) { int n = buf_size; if ( n > count ) n = count; count -= n; BLARGG_RETURN_ERR( play( n, buf ) ); } return blargg_success; } void Gym_Emu::run_dac( int dac_count ) { if ( !dac_disabled ) { // Guess beginning and end of sample and adjust rate and buffer position accordingly. // count dac samples in next frame int next_dac_count = 0; const byte* p = this->pos; int cmd; while ( (cmd = *p++) != 0 ) { int data = *p++; if ( cmd <= 2 ) ++p; if ( cmd == 1 && data == 0x2A ) next_dac_count++; } // adjust int rate_count = dac_count; int start = 0; if ( !prev_dac_count && next_dac_count && dac_count < next_dac_count ) { rate_count = next_dac_count; start = next_dac_count - dac_count; } else if ( prev_dac_count && !next_dac_count && dac_count < prev_dac_count ) { rate_count = prev_dac_count; } // Evenly space samples within buffer section being used Blip_Buffer::resampled_time_t period = blip_buf.resampled_duration( clock_rate / 60 ) / rate_count; Blip_Buffer::resampled_time_t time = blip_buf.resampled_time( 0 ) + period * start + (period >> 1); int last_dac = this->last_dac; if ( last_dac < 0 ) last_dac = dac_buf [0]; for ( int i = 0; i < dac_count; i++ ) { int diff = dac_buf [i] - last_dac; last_dac += diff; dac_synth.offset_resampled( time, diff, &blip_buf ); time += period; } this->last_dac = last_dac; } int const step = 6 * oversample; int remain = pairs_per_frame * oversample; while ( remain ) { int n = step; if ( n > remain ) n = remain; remain -= n; fm.run_timer( n ); } } void Gym_Emu::parse_frame() { if ( track_ended_ ) return; int dac_count = 0; const byte* pos = this->pos; if ( loop_remain && !--loop_remain ) loop_begin = pos; // find loop on first time through sequence int cmd; while ( (cmd = *pos++) != 0 ) { int data = *pos++; if ( cmd == 1 ) { int data2 = *pos++; if ( data == 0x2A ) { if ( dac_count < sizeof dac_buf ) { dac_buf [dac_count] = data2; dac_count += dac_enabled; } } else { if ( data == 0x2B ) dac_enabled = (data2 & 0x80) != 0; fm.write( 0, data ); fm.write( 1, data2 ); } } else if ( cmd == 2 ) { fm.write( 2, data ); fm.write( 3, *pos++ ); } else if ( cmd == 3 ) { apu.write_data( 0, data ); } else { dprintf( "Bad command: %02X\n", (int) cmd ); --pos; // put data back } } // loop if ( pos >= data_end ) { if ( loop_begin ) pos = loop_begin; else track_ended_ = true; } this->pos = pos; // dac if ( dac_count ) run_dac( dac_count ); prev_dac_count = dac_count; } #include BLARGG_ENABLE_OPTIMIZER void Gym_Emu::mix_samples( sample_t* out ) { // Mix one frame of Blip_Buffer (SMS APU and PCM) and resampled YM audio Blip_Reader sn; int bass = sn.begin( blip_buf ); const sample_t* ym = sample_buf; for ( int n = pairs_per_frame; n--; ) { int s = sn.read(); long l = ym [0] * 2 + s; sn.next( bass ); if ( (BOOST::int16_t) l != l ) l = 0x7FFF - (l >> 24); long r = ym [1] * 2 + s; ym += 2; out [0] = l; out [1] = r; out += 2; if ( (BOOST::int16_t) r != r ) out [-1] = 0x7FFF - (r >> 24); } sn.end( blip_buf ); }