diff src/Input/console/Gb_Apu.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/Gb_Apu.cxx	Mon Sep 18 01:11:49 2006 -0700
@@ -0,0 +1,311 @@
+
+// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/
+
+#include "Gb_Apu.h"
+
+#include <string.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
+
+const unsigned vol_reg    = 0xFF24;
+const unsigned status_reg = 0xFF26;
+
+Gb_Apu::Gb_Apu()
+{
+	square1.synth = &square_synth;
+	square2.synth = &square_synth;
+	wave.synth  = &other_synth;
+	noise.synth = &other_synth;
+	
+	oscs [0] = &square1;
+	oscs [1] = &square2;
+	oscs [2] = &wave;
+	oscs [3] = &noise;
+	
+	for ( int i = 0; i < osc_count; i++ )
+	{
+		Gb_Osc& osc = *oscs [i];
+		osc.regs = &regs [i * 5];
+		osc.output = NULL;
+		osc.outputs [0] = NULL;
+		osc.outputs [1] = NULL;
+		osc.outputs [2] = NULL;
+		osc.outputs [3] = NULL;
+	}
+	
+	volume( 1.0 );
+	reset();
+}
+
+Gb_Apu::~Gb_Apu()
+{
+}
+
+void Gb_Apu::treble_eq( const blip_eq_t& eq )
+{
+	square_synth.treble_eq( eq );
+	other_synth.treble_eq( eq );
+}
+
+void Gb_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
+{
+	require( (unsigned) index < osc_count );
+	require( (center && left && right) || (!center && !left && !right) );
+	Gb_Osc& osc = *oscs [index];
+	osc.outputs [1] = right;
+	osc.outputs [2] = left;
+	osc.outputs [3] = center;
+	osc.output = osc.outputs [osc.output_select];
+}
+
+void Gb_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
+{
+	for ( int i = 0; i < osc_count; i++ )
+		osc_output( i, center, left, right );
+}
+
+void Gb_Apu::update_volume()
+{
+	// to do: doesn't handle differing left/right global volume
+	int data = regs [vol_reg - start_addr];
+	double vol = (max( data & 7, data >> 4 & 7 ) + 1) * volume_unit;
+	square_synth.volume( vol );
+	other_synth.volume( vol );
+}
+
+static unsigned char const powerup_regs [0x30] = {
+	0x80,0x3F,0x00,0xFF,0xBF, // square 1
+	0xFF,0x3F,0x00,0xFF,0xBF, // square 2
+	0x7F,0xFF,0x9F,0xFF,0xBF, // wave
+	0xFF,0xFF,0x00,0x00,0xBF, // noise
+	0x00, // left/right enables
+	0x77, // master volume
+	0x80, // power
+	0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
+	0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C, // wave table
+	0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA
+};
+
+void Gb_Apu::reset()
+{
+	next_frame_time = 0;
+	last_time = 0;
+	frame_count = 0;
+	stereo_found = false;
+	
+	square1.reset();
+	square2.reset();
+	wave.reset();
+	noise.reset();
+	noise.bits = 1;
+	wave.wave_pos = 0;
+	
+	// avoid click at beginning
+	regs [vol_reg - start_addr] = 0x77;
+	update_volume();
+	
+	regs [status_reg - start_addr] = 0x01; // force power
+	write_register( 0, status_reg, 0x00 );
+}
+
+// to do: remove
+static unsigned long abs_time;
+
+void Gb_Apu::run_until( gb_time_t end_time )
+{
+	require( end_time >= last_time ); // end_time must not be before previous time
+	if ( end_time == last_time )
+		return;
+	
+	while ( true )
+	{
+		gb_time_t time = next_frame_time;
+		if ( time > end_time )
+			time = end_time;
+		
+		// run oscillators
+		for ( int i = 0; i < osc_count; ++i )
+		{
+			Gb_Osc& osc = *oscs [i];
+			if ( osc.output )
+			{
+				int playing = false;
+				if ( osc.enabled && osc.volume &&
+						(!(osc.regs [4] & osc.len_enabled_mask) || osc.length) )
+					playing = -1;
+				if ( osc.output != osc.outputs [3] )
+					stereo_found = true;
+				switch ( i )
+				{
+				case 0: square1.run( last_time, time, playing ); break;
+				case 1: square2.run( last_time, time, playing ); break;
+				case 2: wave   .run( last_time, time, playing ); break;
+				case 3: noise  .run( last_time, time, playing ); break;
+				}
+			}
+		}
+		last_time = time;
+		
+		if ( time == end_time )
+			break;
+		
+		next_frame_time += 4194304 / 256; // 256 Hz
+		
+		// 256 Hz actions
+		square1.clock_length();
+		square2.clock_length();
+		wave.clock_length();
+		noise.clock_length();
+		
+		frame_count = (frame_count + 1) & 3;
+		if ( frame_count == 0 )
+		{
+			// 64 Hz actions
+			square1.clock_envelope();
+			square2.clock_envelope();
+			noise.clock_envelope();
+		}
+		
+		if ( frame_count & 1 )
+			square1.clock_sweep(); // 128 Hz action
+	}
+}
+
+bool Gb_Apu::end_frame( gb_time_t end_time )
+{
+	if ( end_time > last_time )
+		run_until( end_time );
+	
+	abs_time += end_time;
+	
+	assert( next_frame_time >= end_time );
+	next_frame_time -= end_time;
+	
+	assert( last_time >= end_time );
+	last_time -= end_time;
+	
+	bool result = stereo_found;
+	stereo_found = false;
+	return result;
+}
+
+void Gb_Apu::write_register( gb_time_t time, gb_addr_t addr, int data )
+{
+	require( (unsigned) data < 0x100 );
+	
+	int reg = addr - start_addr;
+	if ( (unsigned) reg >= register_count )
+		return;
+	
+	run_until( time );
+	
+	int old_reg = regs [reg];
+	regs [reg] = data;
+	
+	if ( addr < vol_reg )
+	{
+		write_osc( reg / 5, reg, data );
+	}
+	else if ( addr == vol_reg && data != old_reg ) // global volume
+	{
+		// return all oscs to 0
+		for ( int i = 0; i < osc_count; i++ )
+		{
+			Gb_Osc& osc = *oscs [i];
+			int amp = osc.last_amp;
+			osc.last_amp = 0;
+			if ( amp && osc.enabled && osc.output )
+				other_synth.offset( time, -amp, osc.output );
+		}
+		
+		if ( wave.outputs [3] )
+			other_synth.offset( time, 30, wave.outputs [3] );
+		
+		update_volume();
+		
+		if ( wave.outputs [3] )
+			other_synth.offset( time, -30, wave.outputs [3] );
+		
+		// oscs will update with new amplitude when next run
+	}
+	else if ( addr == 0xFF25 || addr == status_reg )
+	{
+		int mask = (regs [status_reg - start_addr] & 0x80) ? ~0 : 0;
+		int flags = regs [0xFF25 - start_addr] & mask;
+		
+		// left/right assignments
+		for ( int i = 0; i < osc_count; i++ )
+		{
+			Gb_Osc& osc = *oscs [i];
+			osc.enabled &= mask;
+			int bits = flags >> i;
+			Blip_Buffer* old_output = osc.output;
+			osc.output_select = (bits >> 3 & 2) | (bits & 1);
+			osc.output = osc.outputs [osc.output_select];
+			if ( osc.output != old_output )
+			{
+				int amp = osc.last_amp;
+				osc.last_amp = 0;
+				if ( amp && old_output )
+					other_synth.offset( time, -amp, old_output );
+			}
+		}
+		
+		if ( addr == status_reg && data != old_reg )
+		{
+			if ( !(data & 0x80) )
+			{
+				for ( int i = 0; i < (int) sizeof powerup_regs; i++ )
+				{
+					if ( i != status_reg - start_addr )
+						write_register( time, i + start_addr, powerup_regs [i] );
+				}
+			}
+			else
+			{
+				//dprintf( "APU powered on\n" );
+			}
+		}
+	}
+	else if ( addr >= 0xFF30 )
+	{
+		
+		int index = (addr & 0x0F) * 2;
+		wave.wave [index] = data >> 4;
+		wave.wave [index + 1] = data & 0x0F;
+	}
+}
+
+int Gb_Apu::read_register( gb_time_t time, gb_addr_t addr )
+{
+	run_until( time );
+	
+	int index = addr - start_addr;
+	require( (unsigned) index < register_count );
+	int data = regs [index];
+	
+	if ( addr == status_reg )
+	{
+		data = (data & 0x80) | 0x70;
+		for ( int i = 0; i < osc_count; i++ )
+		{
+			const Gb_Osc& osc = *oscs [i];
+			if ( osc.enabled && (osc.length || !(osc.regs [4] & osc.len_enabled_mask)) )
+				data |= 1 << i;
+		}
+	}
+	
+	return data;
+}
+