diff src/Input/console/Vgm_Emu.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/console/Vgm_Emu.cxx	Mon Sep 18 01:11:49 2006 -0700
@@ -0,0 +1,298 @@
+
+// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/
+
+#include "Vgm_Emu.h"
+
+#include <math.h>
+#include <string.h>
+#include "blargg_endian.h"
+
+/* Copyright (C) 2003-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
+
+#include BLARGG_SOURCE_BEGIN
+
+double const gain = 3.0; // FM emulators are internally quieter to avoid 16-bit overflow
+double const rolloff = 0.990;
+double const oversample_factor = 1.5;
+
+Vgm_Emu::Vgm_Emu( bool os, double tempo )
+{
+	oversample = os;
+	pos = NULL;
+	data = NULL;
+	uses_fm = false;
+	vgm_rate = (long) (header_t::time_rate * tempo + 0.5);
+	
+	static equalizer_t const eq = { -14.0, 80 };
+	set_equalizer( eq );
+	psg.volume( 1.0 );
+}
+
+Vgm_Emu::~Vgm_Emu()
+{
+	unload();
+}
+
+void Vgm_Emu::unload()
+{
+	data = NULL;
+	pos = NULL;
+	set_track_ended( false );
+	mem.clear();
+}
+
+blargg_err_t Vgm_Emu::set_sample_rate( long sample_rate )
+{
+	BLARGG_RETURN_ERR( blip_buf.set_sample_rate( sample_rate, 1000 / 30 ) );
+	return Classic_Emu::set_sample_rate( sample_rate );
+}
+
+BOOST::uint8_t const* Vgm_Emu::gd3_data( int* size ) const
+{
+	if ( size )
+		*size = 0;
+	
+	long gd3_offset = get_le32( header_.gd3_offset );
+	if ( !gd3_offset )
+		return NULL;
+	
+	gd3_offset -= 0x40 - offsetof (header_t,gd3_offset);
+	if ( gd3_offset < 0 )
+		return NULL;
+	
+	byte const* gd3 = data + gd3_offset;
+	if ( data_end - gd3 < 16 || 0 != memcmp( gd3, "Gd3 ", 4 ) || get_le32( gd3 + 4 ) >= 0x200 )
+		return NULL;
+	
+	long gd3_size = get_le32( gd3 + 8 );
+	if ( data_end - gd3 < gd3_size - 12 )
+		return NULL;
+	
+	if ( size )
+		*size = data_end - gd3;
+	return gd3;
+}
+
+void Vgm_Emu::update_eq( blip_eq_t const& eq )
+{
+	psg.treble_eq( eq );
+	dac_synth.treble_eq( eq );
+}
+
+void Vgm_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
+{
+	if ( i < psg.osc_count )
+		psg.osc_output( i, c, l, r );
+}
+
+const char** Vgm_Emu::voice_names() const
+{
+	static const char* fm_names [] = {
+		"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG"
+	};
+	if ( uses_fm )
+		return fm_names;
+	
+	static const char* psg_names [] = { "Square 1", "Square 2", "Square 3", "Noise" };
+	return psg_names;
+}
+
+void Vgm_Emu::mute_voices( int mask )
+{
+	Classic_Emu::mute_voices( mask );
+	dac_synth.output( &blip_buf );
+	if ( uses_fm )
+	{
+		psg.output( (mask & 0x80) ? 0 : &blip_buf );
+		if ( ym2612.enabled() )
+		{
+			dac_synth.volume( (mask & 0x40) ? 0.0 : 0.1115 / 256 * gain );
+			ym2612.mute_voices( mask );
+		}
+		
+		if ( ym2413.enabled() )
+		{
+			int m = mask & 0x3f;
+			if ( mask & 0x20 )
+				m |= 0x01e0; // channels 5-8
+			if ( mask & 0x40 )
+				m |= 0x3e00;
+			ym2413.mute_voices( m );
+		}
+	}
+}
+
+blargg_err_t Vgm_Emu::load_( const header_t& h, void const* new_data, long new_size )
+{
+	header_ = h;
+	
+	// compatibility
+	if ( 0 != memcmp( header_.tag, "Vgm ", 4 ) )
+		return "Not a VGM file";
+	check( get_le32( header_.version ) <= 0x150 );
+	
+	// psg rate
+	long psg_rate = get_le32( header_.psg_rate );
+	if ( !psg_rate )
+		psg_rate = 3579545;
+	blip_time_factor = (long) floor( (double) (1L << blip_time_bits) / vgm_rate * psg_rate + 0.5 );
+	blip_buf.clock_rate( psg_rate );
+	
+	data = (byte*) new_data;
+	data_end = data + new_size;
+	
+	// get loop
+	loop_begin = data_end;
+	if ( get_le32( header_.loop_offset ) )
+		loop_begin = &data [get_le32( header_.loop_offset ) + offsetof (header_t,loop_offset) - 0x40];
+	
+	set_voice_count( psg.osc_count );
+	set_track_count( 1 );
+	
+	BLARGG_RETURN_ERR( setup_fm() );
+	
+	// do after FM in case output buffer is changed
+	BLARGG_RETURN_ERR( Classic_Emu::setup_buffer( psg_rate ) );
+	
+	return blargg_success;
+}
+
+blargg_err_t Vgm_Emu::setup_fm()
+{
+	long ym2612_rate = get_le32( header_.ym2612_rate );
+	long ym2413_rate = get_le32( header_.ym2413_rate );
+	if ( ym2413_rate && get_le32( header_.version ) < 0x110 )
+		update_fm_rates( &ym2413_rate, &ym2612_rate );
+	
+	uses_fm = false;
+	
+	double fm_rate = blip_buf.sample_rate() * oversample_factor;
+	
+	if ( ym2612_rate )
+	{
+		uses_fm = true;
+		if ( !oversample )
+			fm_rate = ym2612_rate / 144.0;
+		Dual_Resampler::setup( fm_rate / blip_buf.sample_rate(), rolloff, gain );
+		BLARGG_RETURN_ERR( ym2612.set_rate( fm_rate, ym2612_rate ) );
+		ym2612.enable( true );
+		set_voice_count( 8 );
+	}
+	
+	if ( !uses_fm && ym2413_rate )
+	{
+		uses_fm = true;
+		if ( !oversample )
+			fm_rate = ym2413_rate / 72.0;
+		Dual_Resampler::setup( fm_rate / blip_buf.sample_rate(), rolloff, gain );
+		int result = ym2413.set_rate( fm_rate, ym2413_rate );
+		if ( result == 2 )
+			return "YM2413 FM sound isn't supported";
+		BLARGG_CHECK_ALLOC( !result );
+		ym2413.enable( true );
+		set_voice_count( 8 );
+	}
+	
+	if ( uses_fm )
+	{
+		//dprintf( "fm_rate: %f\n", fm_rate );
+		fm_time_factor = 2 + (long) floor( fm_rate * (1L << fm_time_bits) / vgm_rate + 0.5 );
+		BLARGG_RETURN_ERR( Dual_Resampler::resize( blip_buf.length() * blip_buf.sample_rate() / 1000 ) );
+		psg.volume( 0.135 * gain );
+	}
+	else
+	{
+		ym2612.enable( false );
+		ym2413.enable( false );
+		psg.volume( 1.0 );
+	}
+	
+	return blargg_success;
+}
+
+blargg_err_t Vgm_Emu::load( Data_Reader& reader )
+{
+	header_t h;
+	BLARGG_RETURN_ERR( reader.read( &h, sizeof h ) );
+	return load( h, reader );
+}
+
+blargg_err_t Vgm_Emu::load( const header_t& h, Data_Reader& reader )
+{
+	unload();
+	
+	// allocate and read data
+	long data_size = reader.remain();
+	int const padding = 8;
+	BLARGG_RETURN_ERR( mem.resize( data_size + padding ) );
+	blargg_err_t err = reader.read( mem.begin(), data_size );
+	if ( err ) {
+		unload();
+		return err;
+	}
+	memset( &mem [data_size], 0x66, padding ); // pad with end command
+	
+	return load_( h, mem.begin(), data_size );
+}
+
+void Vgm_Emu::start_track( int track )
+{
+	require( data ); // file must have been loaded
+	
+	Classic_Emu::start_track( track );
+	psg.reset();
+	
+	dac_disabled = -1;
+	pcm_data = data;
+	pcm_pos = data;
+	dac_amp = -1;
+	vgm_time = 0;
+	pos = data;
+	if ( get_le32( header_.version ) >= 0x150 )
+	{
+		long data_offset = get_le32( header_.data_offset );
+		check( data_offset );
+		if ( data_offset )
+			pos += data_offset + offsetof (header_t,data_offset) - 0x40;
+	}
+	
+	if ( uses_fm )
+	{
+		if ( ym2413.enabled() )
+			ym2413.reset();
+		
+		if ( ym2612.enabled() )
+			ym2612.reset();
+		
+		fm_time_offset = 0;
+		blip_buf.clear();
+		Dual_Resampler::clear();
+	}
+}
+
+long Vgm_Emu::run( int msec, bool* added_stereo )
+{
+	blip_time_t psg_end = run_commands( msec * vgm_rate / 1000 );
+	*added_stereo = psg.end_frame( psg_end );
+	return psg_end;
+}
+
+void Vgm_Emu::play( long count, sample_t* out )
+{
+	require( pos ); // track must have been started
+	
+	if ( uses_fm )
+		Dual_Resampler::play( count, out, blip_buf );
+	else
+		Classic_Emu::play( count, out );
+}
+