Mercurial > audlegacy
diff Plugins/Input/console/Gym_Emu.cpp @ 90:252843aac42f trunk
[svn] Import the initial sources for console music support.
author | nenolod |
---|---|
date | Tue, 01 Nov 2005 19:57:26 -0800 |
parents | |
children | 84aabc053b6e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gym_Emu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,441 @@ + +// 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 ); +} +