PageRenderTime 53ms CodeModel.GetById 17ms app.highlight 31ms RepoModel.GetById 1ms app.codeStats 0ms

/gme/Sap_Apu.cpp

http://game-music-emu.googlecode.com/
C++ | 334 lines | 261 code | 36 blank | 37 comment | 50 complexity | 61307410c727187b449840d47655ce67 MD5 | raw file
  1// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/
  2
  3#include "Sap_Apu.h"
  4
  5#include <string.h>
  6
  7/* Copyright (C) 2006 Shay Green. This module is free software; you
  8can redistribute it and/or modify it under the terms of the GNU Lesser
  9General Public License as published by the Free Software Foundation; either
 10version 2.1 of the License, or (at your option) any later version. This
 11module is distributed in the hope that it will be useful, but WITHOUT ANY
 12WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 13FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 14details. You should have received a copy of the GNU Lesser General Public
 15License along with this module; if not, write to the Free Software Foundation,
 16Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
 17
 18#include "blargg_source.h"
 19
 20int const max_frequency = 12000; // pure waves above this frequency are silenced
 21
 22static void gen_poly( blargg_ulong mask, int count, byte* out )
 23{
 24	blargg_ulong n = 1;
 25	do
 26	{
 27		int bits = 0;
 28		int b = 0;
 29		do
 30		{
 31			// implemented using "Galios configuration"
 32			bits |= (n & 1) << b;
 33			n = (n >> 1) ^ (mask & -(n & 1));
 34		}
 35		while ( b++ < 7 );
 36		*out++ = bits;
 37	}
 38	while ( --count );
 39}
 40
 41// poly5
 42int const poly5_len = (1 <<  5) - 1;
 43blargg_ulong const poly5_mask = (1UL << poly5_len) - 1;
 44blargg_ulong const poly5 = 0x167C6EA1;
 45
 46inline blargg_ulong run_poly5( blargg_ulong in, int shift )
 47{
 48	return (in << shift & poly5_mask) | (in >> (poly5_len - shift));
 49}
 50
 51#define POLY_MASK( width, tap1, tap2 ) \
 52	((1UL << (width - 1 - tap1)) | (1UL << (width - 1 - tap2)))
 53
 54Sap_Apu_Impl::Sap_Apu_Impl()
 55{
 56	gen_poly( POLY_MASK(  4, 1, 0 ), sizeof poly4,  poly4  );
 57	gen_poly( POLY_MASK(  9, 5, 0 ), sizeof poly9,  poly9  );
 58	gen_poly( POLY_MASK( 17, 5, 0 ), sizeof poly17, poly17 );
 59	
 60	if ( 0 ) // comment out to recauculate poly5 constant
 61	{
 62		byte poly5 [4];
 63		gen_poly( POLY_MASK(  5, 2, 0 ), sizeof poly5,  poly5  );
 64		blargg_ulong n = poly5 [3] * 0x1000000L + poly5 [2] * 0x10000L + 
 65				poly5 [1] * 0x100L + poly5 [0];
 66		blargg_ulong rev = n & 1;
 67		for ( int i = 1; i < poly5_len; i++ )
 68			rev |= (n >> i & 1) << (poly5_len - i);
 69		debug_printf( "poly5: 0x%08lX\n", rev );
 70	}
 71}
 72
 73Sap_Apu::Sap_Apu()
 74{
 75	impl = 0;
 76	for ( int i = 0; i < osc_count; i++ )
 77		osc_output( i, 0 );
 78}
 79
 80void Sap_Apu::reset( Sap_Apu_Impl* new_impl )
 81{
 82	impl      = new_impl;
 83	last_time = 0;
 84	poly5_pos = 0;
 85	poly4_pos = 0;
 86	polym_pos = 0;
 87	control   = 0;
 88	
 89	for ( int i = 0; i < osc_count; i++ )
 90		memset( &oscs [i], 0, offsetof (osc_t,output) );
 91}
 92
 93inline void Sap_Apu::calc_periods()
 94{
 95	 // 15/64 kHz clock
 96	int divider = 28;
 97	if ( this->control & 1 )
 98		divider = 114;
 99	
100	for ( int i = 0; i < osc_count; i++ )
101	{
102		osc_t* const osc = &oscs [i];
103		
104		int const osc_reload = osc->regs [0]; // cache
105		blargg_long period = (osc_reload + 1) * divider;
106		static byte const fast_bits [osc_count] = { 1 << 6, 1 << 4, 1 << 5, 1 << 3 };
107		if ( this->control & fast_bits [i] )
108		{
109			period = osc_reload + 4;
110			if ( i & 1 )
111			{
112				period = osc_reload * 0x100L + osc [-1].regs [0] + 7;
113				if ( !(this->control & fast_bits [i - 1]) )
114					period = (period - 6) * divider;
115				
116				if ( (osc [-1].regs [1] & 0x1F) > 0x10 )
117					debug_printf( "Use of slave channel in 16-bit mode not supported\n" );
118			}
119		}
120		osc->period = period;
121	}
122}
123
124void Sap_Apu::run_until( blip_time_t end_time )
125{
126	calc_periods();
127	Sap_Apu_Impl* const impl = this->impl; // cache
128	
129	// 17/9-bit poly selection
130	byte const* polym = impl->poly17;
131	int polym_len = poly17_len;
132	if ( this->control & 0x80 )
133	{
134		polym_len = poly9_len;
135		polym = impl->poly9;
136	}
137	polym_pos %= polym_len;
138	
139	for ( int i = 0; i < osc_count; i++ )
140	{
141		osc_t* const osc = &oscs [i];
142		blip_time_t time = last_time + osc->delay;
143		blip_time_t const period = osc->period;
144		
145		// output
146		Blip_Buffer* output = osc->output;
147		if ( output )
148		{
149			output->set_modified();
150			
151			int const osc_control = osc->regs [1]; // cache
152			int volume = (osc_control & 0x0F) * 2;
153			if ( !volume || osc_control & 0x10 || // silent, DAC mode, or inaudible frequency
154					((osc_control & 0xA0) == 0xA0 && period < 1789773 / 2 / max_frequency) )
155			{
156				if ( !(osc_control & 0x10) )
157					volume >>= 1; // inaudible frequency = half volume
158				
159				int delta = volume - osc->last_amp;
160				if ( delta )
161				{
162					osc->last_amp = volume;
163					impl->synth.offset( last_time, delta, output );
164				}
165				
166				// TODO: doesn't maintain high pass flip-flop (very minor issue)
167			}
168			else
169			{
170				// high pass
171				static byte const hipass_bits [osc_count] = { 1 << 2, 1 << 1, 0, 0 };
172				blip_time_t period2 = 0; // unused if no high pass
173				blip_time_t time2 = end_time;
174				if ( this->control & hipass_bits [i] )
175				{
176					period2 = osc [2].period;
177					time2 = last_time + osc [2].delay;
178					if ( osc->invert )
179					{
180						// trick inner wave loop into inverting output
181						osc->last_amp -= volume;
182						volume = -volume;
183					}
184				}
185				
186				if ( time < end_time || time2 < end_time )
187				{
188					// poly source
189					static byte const poly1 [] = { 0x55, 0x55 }; // square wave
190					byte const* poly = poly1;
191					int poly_len = 8 * sizeof poly1; // can be just 2 bits, but this is faster
192					int poly_pos = osc->phase & 1;
193					int poly_inc = 1;
194					if ( !(osc_control & 0x20) )
195					{
196						poly     = polym;
197						poly_len = polym_len;
198						poly_pos = polym_pos;
199						if ( osc_control & 0x40 )
200						{
201							poly     = impl->poly4;
202							poly_len = poly4_len;
203							poly_pos = poly4_pos;
204						}
205						poly_inc = period % poly_len;
206						poly_pos = (poly_pos + osc->delay) % poly_len;
207					}
208					poly_inc -= poly_len; // allows more optimized inner loop below
209					
210					// square/poly5 wave
211					blargg_ulong wave = poly5;
212					check( poly5 & 1 ); // low bit is set for pure wave
213					int poly5_inc = 0;
214					if ( !(osc_control & 0x80) )
215					{
216						wave = run_poly5( wave, (osc->delay + poly5_pos) % poly5_len );
217						poly5_inc = period % poly5_len;
218					}
219					
220					// Run wave and high pass interleved with each catching up to the other.
221					// Disabled high pass has no performance effect since inner wave loop
222					// makes no compromise for high pass, and only runs once in that case.
223					int osc_last_amp = osc->last_amp;
224					do
225					{
226						// run high pass
227						if ( time2 < time )
228						{
229							int delta = -osc_last_amp;
230							if ( volume < 0 )
231								delta += volume;
232							if ( delta )
233							{
234								osc_last_amp += delta - volume;
235								volume = -volume;
236								impl->synth.offset( time2, delta, output );
237							}
238						}
239						while ( time2 <= time ) // must advance *past* time to avoid hang
240							time2 += period2;
241						
242						// run wave
243						blip_time_t end = end_time;
244						if ( end > time2 )
245							end = time2;
246						while ( time < end )
247						{
248							if ( wave & 1 )
249							{
250								int amp = volume & -(poly [poly_pos >> 3] >> (poly_pos & 7) & 1);
251								if ( (poly_pos += poly_inc) < 0 )
252									poly_pos += poly_len;
253								int delta = amp - osc_last_amp;
254								if ( delta )
255								{
256									osc_last_amp = amp;
257									impl->synth.offset( time, delta, output );
258								}
259							}
260							wave = run_poly5( wave, poly5_inc );
261							time += period;
262						}
263					}
264					while ( time < end_time || time2 < end_time );
265					
266					osc->phase = poly_pos;
267					osc->last_amp = osc_last_amp;
268				}
269				
270				osc->invert = 0;
271				if ( volume < 0 )
272				{
273					// undo inversion trickery
274					osc->last_amp -= volume;
275					osc->invert = 1;
276				}
277			}
278		}
279		
280		// maintain divider
281		blip_time_t remain = end_time - time;
282		if ( remain > 0 )
283		{
284			blargg_long count = (remain + period - 1) / period;
285			osc->phase ^= count;
286			time += count * period;
287		}
288		osc->delay = time - end_time;
289	}
290	
291	// advance polies
292	blip_time_t duration = end_time - last_time;
293	last_time = end_time;
294	poly4_pos = (poly4_pos + duration) % poly4_len;
295	poly5_pos = (poly5_pos + duration) % poly5_len;
296	polym_pos += duration; // will get %'d on next call
297}
298
299void Sap_Apu::write_data( blip_time_t time, unsigned addr, int data )
300{
301	run_until( time );
302	int i = (addr ^ 0xD200) >> 1;
303	if ( i < osc_count )
304	{
305		oscs [i].regs [addr & 1] = data;
306	}
307	else if ( addr == 0xD208 )
308	{
309		control = data;
310	}
311	else if ( addr == 0xD209 )
312	{
313		oscs [0].delay = 0;
314		oscs [1].delay = 0;
315		oscs [2].delay = 0;
316		oscs [3].delay = 0;
317	}
318	/*
319	// TODO: are polynomials reset in this case?
320	else if ( addr == 0xD20F )
321	{
322		if ( (data & 3) == 0 )
323			polym_pos = 0;
324	}
325	*/
326}
327
328void Sap_Apu::end_frame( blip_time_t end_time )
329{
330	if ( end_time > last_time )
331		run_until( end_time );
332	
333	last_time -= end_time;
334}