// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/

#include "Nsf_Emu.h"

#if !NSF_EMU_APU_ONLY
	#include "Nes_Namco_Apu.h"
	#include "Nes_Vrc6_Apu.h"
	#include "Nes_Fme7_Apu.h"
	#include "Nes_Fds_Apu.h"
	#include "Nes_Mmc5_Apu.h"
	#include "Nes_Vrc7_Apu.h"
#endif

/* Copyright (C) 2003-2008 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.h"

Nsf_Emu::equalizer_t const Nsf_Emu::nes_eq     = {  -1.0, 80 };
Nsf_Emu::equalizer_t const Nsf_Emu::famicom_eq = { -15.0, 80 };

Nsf_Emu::Nsf_Emu()
{
	set_type( gme_nsf_type );
	set_silence_lookahead( 6 );
	set_gain( 1.4 );
	set_equalizer( nes_eq );
}

Nsf_Emu::~Nsf_Emu()
{
	unload();
}

void Nsf_Emu::unload()
{
	core_.unload();
	Music_Emu::unload();
}

// Track info

static void copy_nsf_fields( Nsf_Emu::header_t const& h, track_info_t* out )
{
	GME_COPY_FIELD( h, out, game );
	GME_COPY_FIELD( h, out, author );
	GME_COPY_FIELD( h, out, copyright );
	if ( h.chip_flags )
		Music_Emu::copy_field_( out->system, "Famicom" );
}

blargg_err_t Nsf_Emu::track_info_( track_info_t* out, int ) const
{
	copy_nsf_fields( header(), out );
	return blargg_ok;
}

static blargg_err_t check_nsf_header( Nsf_Emu::header_t const& h )
{
	if ( !h.valid_tag() )
		return blargg_err_file_type;
	return blargg_ok;
}

struct Nsf_File : Gme_Info_
{
	Nsf_Emu::header_t h;
	
	Nsf_File() { set_type( gme_nsf_type ); }
	
	blargg_err_t load_( Data_Reader& in )
	{
		blargg_err_t err = in.read( &h, h.size );
		if ( err )
			return (blargg_is_err_type( err, blargg_err_file_eof ) ? blargg_err_file_type : err);
		
		if ( h.vers != 1 )
			set_warning( "Unknown file version" );
		
		int unsupported_chips = ~Nsf_Core::chips_mask;
		#if NSF_EMU_NO_VRC7
			unsupported_chips |= Nsf_Emu::header_t::vrc7_mask;
		#endif
		if ( h.chip_flags & unsupported_chips )
			set_warning( "Uses unsupported audio expansion hardware" );
		
		set_track_count( h.track_count );
		return check_nsf_header( h );
	}
	
	blargg_err_t track_info_( track_info_t* out, int ) const
	{
		copy_nsf_fields( h, out );
		return blargg_ok;
	}
};

static Music_Emu* new_nsf_emu () { return BLARGG_NEW Nsf_Emu ; }
static Music_Emu* new_nsf_file() { return BLARGG_NEW Nsf_File; }

gme_type_t_ const gme_nsf_type [1] = {{ "Nintendo NES", 0, &new_nsf_emu, &new_nsf_file, "NSF", 1 }};

// Setup

void Nsf_Emu::set_tempo_( double t )
{
	core_.set_tempo( t );
}

void Nsf_Emu::append_voices( const char* const names [], int const types [], int count )
{
	assert( voice_count_ + count < max_voices );
	for ( int i = 0; i < count; i++ )
	{
		voice_names_ [voice_count_ + i] = names [i];
		voice_types_ [voice_count_ + i] = types [i];
	}
	voice_count_ += count;
	set_voice_count( voice_count_ );
	set_voice_types( voice_types_ );
}

blargg_err_t Nsf_Emu::init_sound()
{
	voice_count_ = 0;
	set_voice_names( voice_names_ );
	
	{
		int const count = Nes_Apu::osc_count;
		static const char* const names [Nes_Apu::osc_count] = {
			"Square 1", "Square 2", "Triangle", "Noise", "DMC"
		};
		static int const types [count] = {
			wave_type+1, wave_type+2, mixed_type+1, noise_type+0, mixed_type+1
		};
		append_voices( names, types, count );
	}
	
	// Make adjusted_gain * 0.75 = 1.0 so usual APU and one sound chip uses 1.0
	double adjusted_gain = 1.0 / 0.75 * gain();
	
#if !NSF_EMU_APU_ONLY
	// TODO: order of chips here must match that in set_voice()
	
	if ( core_.vrc6_apu() )
	{
		int const count = Nes_Vrc6_Apu::osc_count;
		static const char* const names [count] = {
			"Square 3", "Square 4", "Saw Wave"
		};
		static int const types [count] = {
			wave_type+3, wave_type+4, wave_type+5,
		};
		append_voices( names, types, count );
		adjusted_gain *= 0.75;
	}
	
	if ( core_.fme7_apu() )
	{
		int const count = Nes_Fme7_Apu::osc_count;
		static const char* const names [count] = {
			"Square 3", "Square 4", "Square 5"
		};
		static int const types [count] = {
			wave_type+3, wave_type+4, wave_type+5,
		};
		append_voices( names, types, count );
		adjusted_gain *= 0.75;
	}
	
	if ( core_.mmc5_apu() )
	{
		int const count = Nes_Mmc5_Apu::osc_count;
		static const char* const names [count] = {
			"Square 3", "Square 4", "PCM"
		};
		static int const types [count] = {
			wave_type+3, wave_type+4, mixed_type+2
		};
		append_voices( names, types, count );
		adjusted_gain *= 0.75;
	}
	
	if ( core_.fds_apu() )
	{
		int const count = Nes_Fds_Apu::osc_count;
		static const char* const names [count] = {
			"FM"
		};
		static int const types [count] = {
			wave_type+0
		};
		append_voices( names, types, count );
		adjusted_gain *= 0.75;
	}
	
	if ( core_.namco_apu() )
	{
		int const count = Nes_Namco_Apu::osc_count;
		static const char* const names [count] = {
			"Wave 1", "Wave 2", "Wave 3", "Wave 4",
			"Wave 5", "Wave 6", "Wave 7", "Wave 8"
		};
		static int const types [count] = {
			wave_type+3, wave_type+4, wave_type+5, wave_type+ 6,
			wave_type+7, wave_type+8, wave_type+9, wave_type+10,
		};
		append_voices( names, types, count );
		adjusted_gain *= 0.75;
	}
	
	if ( core_.vrc7_apu() )
	{
		int const count = Nes_Vrc7_Apu::osc_count;
		static const char* const names [count] = {
			"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"
		};
		static int const types [count] = {
			wave_type+3, wave_type+4, wave_type+5, wave_type+6,
			wave_type+7, wave_type+8
		};
		append_voices( names, types, count );
		adjusted_gain *= 0.75;
	}
	
	if ( core_.vrc7_apu()  ) core_.vrc7_apu() ->volume( adjusted_gain );
	if ( core_.namco_apu() ) core_.namco_apu()->volume( adjusted_gain );
	if ( core_.vrc6_apu()  ) core_.vrc6_apu() ->volume( adjusted_gain );
	if ( core_.fme7_apu()  ) core_.fme7_apu() ->volume( adjusted_gain );
	if ( core_.mmc5_apu()  ) core_.mmc5_apu() ->volume( adjusted_gain );
	if ( core_.fds_apu()   ) core_.fds_apu()  ->volume( adjusted_gain );
#endif
	
	if ( adjusted_gain > gain() )
		adjusted_gain = gain(); // only occurs if no other sound chips
	
	core_.nes_apu()->volume( adjusted_gain );
	
	return blargg_ok;
}

blargg_err_t Nsf_Emu::load_( Data_Reader& in )
{
	RETURN_ERR( core_.load( in ) );
	set_track_count( header().track_count );
	RETURN_ERR( check_nsf_header( header() ) );
	set_warning( core_.warning() );
	RETURN_ERR( init_sound() );
	set_tempo( tempo() );
	return setup_buffer( (int) (header().clock_rate() + 0.5) );
}

void Nsf_Emu::update_eq( blip_eq_t const& eq )
{
	core_.nes_apu()->treble_eq( eq );
	
	#if !NSF_EMU_APU_ONLY
	{
		if ( core_.namco_apu() ) core_.namco_apu()->treble_eq( eq );
		if ( core_.vrc6_apu()  ) core_.vrc6_apu() ->treble_eq( eq );
		if ( core_.fme7_apu()  ) core_.fme7_apu() ->treble_eq( eq );
		if ( core_.mmc5_apu()  ) core_.mmc5_apu() ->treble_eq( eq );
		if ( core_.fds_apu()   ) core_.fds_apu()  ->treble_eq( eq );
		if ( core_.vrc7_apu()  ) core_.vrc7_apu() ->treble_eq( eq );
	}
	#endif
}

void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* )
{
	#define HANDLE_CHIP( chip ) \
		if ( chip && (i -= chip->osc_count) < 0 )\
		{\
			chip->set_output( i + chip->osc_count, buf );\
			return;\
		}\
	
	HANDLE_CHIP( core_.nes_apu() );
	
	#if !NSF_EMU_APU_ONLY
	{
		// TODO: order of chips here must match that in init_sound()
		HANDLE_CHIP( core_.vrc6_apu()  );
		HANDLE_CHIP( core_.fme7_apu()  );
		HANDLE_CHIP( core_.mmc5_apu()  );
		HANDLE_CHIP( core_.fds_apu()   );
		HANDLE_CHIP( core_.namco_apu() );
		HANDLE_CHIP( core_.vrc7_apu()  );
	}
	#endif
}

blargg_err_t Nsf_Emu::start_track_( int track )
{
	RETURN_ERR( Classic_Emu::start_track_( track ) );
	return core_.start_track( track );
}

blargg_err_t Nsf_Emu::run_clocks( blip_time_t& duration, int )
{
	core_.end_frame( duration );
	const char* w = core_.warning();
	if ( w )
		set_warning( w );
	return blargg_ok;
}
