2014-04-04 19:46:41 +00:00
using System ;
2014-04-12 04:11:52 +00:00
using System.Linq ;
using BizHawk.Common ;
2014-04-04 19:46:41 +00:00
namespace BizHawk.Emulation.Cores.Atari.Atari2600
{
/ * *
Cartridge class used for DPC + . There are six 4 K program banks , a 4 K
display bank , 1 K frequency table and the DPC chip . For complete details on
the DPC chip see David P . Crane ' s United States Patent Number 4 , 644 , 495.
* /
internal class mDPCPlus : MapperBase
{
2014-05-23 00:29:31 +00:00
// TODO: PokeMem, and everything else
2014-04-04 19:46:41 +00:00
public mDPCPlus ( )
{
throw new NotImplementedException ( ) ;
}
2014-04-12 04:11:52 +00:00
private IntBuffer _counters = new IntBuffer ( 8 ) ;
private ByteBuffer _tops = new ByteBuffer ( 8 ) ;
private ByteBuffer _flags = new ByteBuffer ( 8 ) ;
private ByteBuffer _bottoms = new ByteBuffer ( 8 ) ;
private bool [ ] _musicModes = new bool [ 3 ] ;
private int _bank4K ;
private byte _currentRandomVal ;
private int _elapsedCycles = 85 ; // 85 compensates for a slight timing issue when ClockCpu is first run, 85 puts BizHawk back on track with Stella on elapsed timing values
private float _fractionalClocks ; // Fractional DPC music OSC clocks unused during the last update
private byte [ ] _dspData ;
2017-04-24 15:09:17 +00:00
public byte [ ] DspData = > _dspData ? ? ( _dspData = Core . Rom . Skip ( 8192 ) . Take ( 2048 ) . ToArray ( ) ) ;
2014-04-12 04:11:52 +00:00
// Table for computing the input bit of the random number generator's
// shift register (it's the NOT of the EOR of four bits)
private readonly byte [ ] _randomInputBits = { 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 } ;
public override void Dispose ( )
{
base . Dispose ( ) ;
_counters . Dispose ( ) ;
_tops . Dispose ( ) ;
_flags . Dispose ( ) ;
_bottoms . Dispose ( ) ;
}
public override void SyncState ( Serializer ser )
{
base . SyncState ( ser ) ;
ser . Sync ( "counters" , ref _counters ) ;
ser . Sync ( "tops" , ref _tops ) ;
ser . Sync ( "flags" , ref _flags ) ;
ser . Sync ( "bottoms" , ref _bottoms ) ;
ser . Sync ( "musicMode0" , ref _musicModes [ 0 ] ) ; // Silly, but I didn't want to support bool[] in Serializer just for this one variable
ser . Sync ( "musicMode1" , ref _musicModes [ 1 ] ) ;
ser . Sync ( "musicMode2" , ref _musicModes [ 2 ] ) ;
ser . Sync ( "bank_4k" , ref _bank4K ) ;
ser . Sync ( "currentRandomVal" , ref _currentRandomVal ) ;
ser . Sync ( "elapsedCycles" , ref _elapsedCycles ) ;
ser . Sync ( "fractionalClocks" , ref _fractionalClocks ) ;
}
public override void HardReset ( )
{
_counters = new IntBuffer ( 8 ) ;
_tops = new ByteBuffer ( 8 ) ;
_flags = new ByteBuffer ( 8 ) ;
_bottoms = new ByteBuffer ( 8 ) ;
_musicModes = new bool [ 3 ] ;
_bank4K = 0 ;
_currentRandomVal = 0 ;
_elapsedCycles = 85 ;
_fractionalClocks = 0 ;
base . HardReset ( ) ;
}
public override void ClockCpu ( )
{
_elapsedCycles + + ;
}
private byte ReadMem ( ushort addr , bool peek )
{
if ( addr < 0x1000 )
{
return base . ReadMemory ( addr ) ;
}
if ( ! peek )
{
Address ( addr ) ;
ClockRandomNumberGenerator ( ) ;
}
if ( addr < 0x1040 )
{
byte result ;
// Get the index of the data fetcher that's being accessed
var index = addr & 0x07 ;
var function = ( addr > > 3 ) & 0x07 ;
// Update flag register for selected data fetcher
if ( ( _counters [ index ] & 0x00ff ) = = _tops [ index ] )
{
_flags [ index ] = 0xff ;
}
else if ( ( _counters [ index ] & 0x00ff ) = = _bottoms [ index ] )
{
_flags [ index ] = 0x00 ;
}
switch ( function )
{
case 0x00 :
if ( index < 4 )
{
result = _currentRandomVal ;
}
else // No, it's a music read
{
var musicAmplitudes = new byte [ ] {
0x00 , 0x04 , 0x05 , 0x09 , 0x06 , 0x0a , 0x0b , 0x0f
} ;
// Update the music data fetchers (counter & flag)
UpdateMusicModeDataFetchers ( ) ;
byte i = 0 ;
if ( _musicModes [ 0 ] & & _flags [ 5 ] > 0 )
{
i | = 0x01 ;
}
if ( _musicModes [ 1 ] & & _flags [ 6 ] > 0 )
{
i | = 0x02 ;
}
if ( _musicModes [ 2 ] & & _flags [ 7 ] > 0 )
{
i | = 0x04 ;
}
result = musicAmplitudes [ i ] ;
}
break ;
// DFx display data read
case 0x01 :
result = DspData [ 2047 - _counters [ index ] ] ;
break ;
// DFx display data read AND'd w/flag
case 0x02 :
result = ( byte ) ( DspData [ 2047 - _counters [ index ] ] & _flags [ index ] ) ;
break ;
// DFx flag
case 0x07 :
result = _flags [ index ] ;
break ;
default :
result = 0 ;
break ;
}
// Clock the selected data fetcher's counter if needed
if ( ( index < 5 ) | | ( ( index > = 5 ) & & ( ! _musicModes [ index - 5 ] ) ) )
{
_counters [ index ] = ( _counters [ index ] - 1 ) & 0x07ff ;
}
return result ;
}
return Core . Rom [ ( _bank4K < < 12 ) + ( addr & 0xFFF ) ] ;
}
public override byte ReadMemory ( ushort addr )
{
return ReadMem ( addr , false ) ;
}
public override byte PeekMemory ( ushort addr )
{
return ReadMem ( addr , true ) ;
}
public override void WriteMemory ( ushort addr , byte value )
{
if ( addr < 0x1000 )
{
base . WriteMemory ( addr , value ) ;
return ;
}
Address ( addr ) ;
ClockRandomNumberGenerator ( ) ;
if ( addr > = 0x1040 & & addr < 0x1080 )
{
var index = addr & 0x07 ;
var function = ( addr > > 3 ) & 0x07 ;
switch ( function )
{
// DFx top count
case 0x00 :
_tops [ index ] = value ;
_flags [ index ] = 0x00 ;
break ;
// DFx bottom count
case 0x01 :
_bottoms [ index ] = value ;
break ;
// DFx counter low
case 0x02 :
if ( ( index > = 5 ) & & _musicModes [ index - 5 ] )
{
// Data fetcher is in music mode so its low counter value
// should be loaded from the top register not the poked value
_counters [ index ] = ( _counters [ index ] & 0x0700 ) |
_tops [ index ] ;
}
else
{
// Data fetcher is either not a music mode data fetcher or it
// isn't in music mode so it's low counter value should be loaded
// with the poked value
_counters [ index ] = ( _counters [ index ] & 0x0700 ) | value ;
}
break ;
// DFx counter high
case 0x03 :
_counters [ index ] = ( ushort ) ( ( ( value & 0x07 ) < < 8 ) |
( _counters [ index ] & 0x00ff ) ) ;
// Execute special code for music mode data fetchers
if ( index > = 5 )
{
_musicModes [ index - 5 ] = ( value & 0x10 ) > 0 ;
// NOTE: We are not handling the clock source input for
// the music mode data fetchers. We're going to assume
// they always use the OSC input.
}
break ;
// Random Number Generator Reset
case 0x06 :
_currentRandomVal = 1 ;
break ;
}
}
}
private void Address ( ushort addr )
{
if ( addr = = 0x1FF6 )
{
_bank4K = 0 ;
}
else if ( addr = = 0x1FF7 )
{
_bank4K = 1 ;
}
else if ( addr = = 0x1FF8 )
{
_bank4K = 2 ;
}
else if ( addr = = 0x1FF9 )
{
_bank4K = 3 ;
}
else if ( addr = = 0x1FFA )
{
_bank4K = 4 ;
}
else if ( addr = = 0x1FFB )
{
_bank4K = 5 ;
}
}
private void ClockRandomNumberGenerator ( )
{
// Using bits 7, 5, 4, & 3 of the shift register compute the input
// bit for the shift register
var bit = _randomInputBits [ ( ( _currentRandomVal > > 3 ) & 0x07 ) |
( ( ( _currentRandomVal & 0x80 ) > 0 ) ? 0x08 : 0x00 ) ] ;
// Update the shift register
_currentRandomVal = ( byte ) ( ( _currentRandomVal < < 1 ) | bit ) ;
}
private void UpdateMusicModeDataFetchers ( )
{
// Calculate the number of cycles since the last update
var cycles = _elapsedCycles ;
_elapsedCycles = 0 ;
// Calculate the number of DPC OSC clocks since the last update
var clocks = ( ( 20000.0 * cycles ) / 1193191.66666667 ) + _fractionalClocks ;
var wholeClocks = ( int ) clocks ;
_fractionalClocks = ( float ) ( clocks - wholeClocks ) ;
if ( wholeClocks < = 0 )
{
return ;
}
// Let's update counters and flags of the music mode data fetchers
for ( var x = 5 ; x < = 7 ; + + x )
{
// Update only if the data fetcher is in music mode
if ( _musicModes [ x - 5 ] )
{
var top = _tops [ x ] + 1 ;
var newLow = _counters [ x ] & 0x00ff ;
if ( _tops [ x ] ! = 0 )
{
newLow - = wholeClocks % top ;
if ( newLow < 0 )
{
newLow + = top ;
}
}
else
{
newLow = 0 ;
}
// Update flag register for this data fetcher
if ( newLow < = _bottoms [ x ] )
{
_flags [ x ] = 0x00 ;
}
else if ( newLow < = _tops [ x ] )
{
_flags [ x ] = 0xff ;
}
_counters [ x ] = ( _counters [ x ] & 0x0700 ) | ( ushort ) newLow ;
}
}
}
2014-04-04 19:46:41 +00:00
}
}