Added code to handle the music mode data fetchers in the DPC chip so that

all of the Pitfall II music is played now that the TIA sound routines can
handle it.


git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@146 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
bwmott 2002-11-19 04:29:21 +00:00
parent ca438890ff
commit c1043e2dc9
2 changed files with 200 additions and 42 deletions

View File

@ -13,23 +13,15 @@
// See the file "license" for information on usage and redistribution of // See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES. // this file, and for a DISCLAIMER OF ALL WARRANTIES.
// //
// $Id: CartDPC.cxx,v 1.6 2002-05-14 15:22:28 stephena Exp $ // $Id: CartDPC.cxx,v 1.7 2002-11-19 04:29:21 bwmott Exp $
//============================================================================ //============================================================================
#include <assert.h> #include <assert.h>
#include <iostream>
#include "CartDPC.hxx" #include "CartDPC.hxx"
#include "System.hxx" #include "System.hxx"
#include "Serializer.hxx" #include "Serializer.hxx"
#include "Deserializer.hxx" #include "Deserializer.hxx"
#include <iostream>
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// This class does not emulate the music mode data fetchers of the DPC. A
// draft version of this class which does support the music mode data fetchers
// has been developed, however, it hasn't been checked in because additional
// work to the Stella sound system is needed for the sound to work correctly.
// Full support for the DPC will be added in the 1.3 release of Stella.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartridgeDPC::CartridgeDPC(const uInt8* image, uInt32 size) CartridgeDPC::CartridgeDPC(const uInt8* image, uInt32 size)
@ -51,11 +43,18 @@ CartridgeDPC::CartridgeDPC(const uInt8* image, uInt32 size)
// Initialize the DPC data fetcher registers // Initialize the DPC data fetcher registers
for(uInt16 i = 0; i < 8; ++i) for(uInt16 i = 0; i < 8; ++i)
{ {
myBottoms[i] = myCounters[i] = myFlags[i] = myTops[i] = 0; myTops[i] = myBottoms[i] = myCounters[i] = myFlags[i] = 0;
} }
// None of the data fetchers are in music mode
myMusicMode[0] = myMusicMode[1] = myMusicMode[2] = false;
// Initialize the DPC's random number generator register (must be non-zero) // Initialize the DPC's random number generator register (must be non-zero)
myRandomNumber = 1; myRandomNumber = 1;
// Initialize the system cycles counter & fractional clock values
mySystemCycles = 0;
myFractionalClocks = 0.0;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -72,10 +71,24 @@ const char* CartridgeDPC::name() const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartridgeDPC::reset() void CartridgeDPC::reset()
{ {
// Update cycles to the current system cycles
mySystemCycles = mySystem->cycles();
myFractionalClocks = 0.0;
// Upon reset we switch to bank 1 // Upon reset we switch to bank 1
bank(1); bank(1);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartridgeDPC::systemCyclesReset()
{
// Get the current system cycle
uInt32 cycles = mySystem->cycles();
// Adjust the cycle counter so that it reflects the new value
mySystemCycles -= cycles;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartridgeDPC::install(System& system) void CartridgeDPC::install(System& system)
{ {
@ -114,7 +127,7 @@ inline void CartridgeDPC::clockRandomNumberGenerator()
{ {
// Table for computing the input bit of the random number generator's // Table for computing the input bit of the random number generator's
// shift register (it's the NOT of the EOR of four bits) // shift register (it's the NOT of the EOR of four bits)
static uInt8 f[16] = { static const uInt8 f[16] = {
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1
}; };
@ -127,6 +140,60 @@ inline void CartridgeDPC::clockRandomNumberGenerator()
myRandomNumber = (myRandomNumber << 1) | bit; myRandomNumber = (myRandomNumber << 1) | bit;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
inline void CartridgeDPC::updateMusicModeDataFetchers()
{
// Calculate the number of cycles since the last update
Int32 cycles = mySystem->cycles() - mySystemCycles;
mySystemCycles = mySystem->cycles();
// Calculate the number of DPC OSC clocks since the last update
double clocks = ((15750.0 * cycles) / 1193182.0) + myFractionalClocks;
Int32 wholeClocks = (Int32)clocks;
myFractionalClocks = clocks - (double)wholeClocks;
if(wholeClocks <= 0)
{
return;
}
// Let's update counters and flags of the music mode data fetchers
for(int x = 5; x <= 7; ++x)
{
// Update only if the data fetcher is in music mode
if(myMusicMode[x - 5])
{
Int32 top = myTops[x] + 1;
Int32 newLow = (Int32)(myCounters[x] & 0x00ff);
if(myTops[x] != 0)
{
newLow -= (wholeClocks % top);
if(newLow < 0)
{
newLow += top;
}
}
else
{
newLow = 0;
}
// Update flag register for this data fetcher
if(newLow <= myBottoms[x])
{
myFlags[x] = 0x00;
}
else if(newLow <= myTops[x])
{
myFlags[x] = 0xff;
}
myCounters[x] = (myCounters[x] & 0x0700) | (uInt16)newLow;
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 CartridgeDPC::peek(uInt16 address) uInt8 CartridgeDPC::peek(uInt16 address)
{ {
@ -139,11 +206,13 @@ uInt8 CartridgeDPC::peek(uInt16 address)
if(address < 0x0040) if(address < 0x0040)
{ {
uInt8 result = 0;
// Get the index of the data fetcher that's being accessed // Get the index of the data fetcher that's being accessed
uInt32 index = address & 0x07; uInt32 index = address & 0x07;
uInt32 function = (address >> 3) & 0x07; uInt32 function = (address >> 3) & 0x07;
// Update flag register for this data fetcher // Update flag register for selected data fetcher
if((myCounters[index] & 0x00ff) == myTops[index]) if((myCounters[index] & 0x00ff) == myTops[index])
{ {
myFlags[index] = 0xff; myFlags[index] = 0xff;
@ -160,44 +229,71 @@ uInt8 CartridgeDPC::peek(uInt16 address)
// Is this a random number read // Is this a random number read
if(index < 4) if(index < 4)
{ {
return myRandomNumber; result = myRandomNumber;
} }
// No, it's a music read
else else
{ {
// TODO: Replace with sound read code static const uInt8 musicAmplitudes[8] = {
return 0; 0x00, 0x04, 0x05, 0x09, 0x06, 0x0a, 0x0b, 0x0f
};
// Update the music data fetchers (counter & flag)
updateMusicModeDataFetchers();
uInt8 i = 0;
if(myMusicMode[0] && myFlags[5])
{
i |= 0x01;
}
if(myMusicMode[1] && myFlags[6])
{
i |= 0x02;
}
if(myMusicMode[2] && myFlags[7])
{
i |= 0x04;
}
result = musicAmplitudes[i];
} }
break;
} }
// DFx display data read // DFx display data read
case 0x01: case 0x01:
{ {
uInt8 r = myDisplayImage[2047 - myCounters[index]]; result = myDisplayImage[2047 - myCounters[index]];
myCounters[index] = (myCounters[index] - 1) & 0x07ff; break;
return r;
} }
// DFx display data read AND'd w/flag // DFx display data read AND'd w/flag
case 0x02: case 0x02:
{ {
uInt8 r = myDisplayImage[2047 - myCounters[index]] & myFlags[index]; result = myDisplayImage[2047 - myCounters[index]] & myFlags[index];
myCounters[index] = (myCounters[index] - 1) & 0x07ff; break;
return r;
} }
// DFx flag // DFx flag
case 0x07: case 0x07:
{ {
uInt8 r = myFlags[index]; result = myFlags[index];
myCounters[index] = (myCounters[index] - 1) & 0x07ff; break;
return r;
} }
default: default:
{ {
return 0; result = 0;
} }
} }
// Clock the selected data fetcher's counter if needed
if((index < 5) || ((index >= 5) && (!myMusicMode[index - 5])))
{
myCounters[index] = (myCounters[index] - 1) & 0x07ff;
}
return result;
} }
else else
{ {
@ -219,7 +315,6 @@ uInt8 CartridgeDPC::peek(uInt16 address)
} }
return myProgramImage[myCurrentBank * 4096 + address]; return myProgramImage[myCurrentBank * 4096 + address];
} }
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -244,6 +339,7 @@ void CartridgeDPC::poke(uInt16 address, uInt8 value)
case 0x00: case 0x00:
{ {
myTops[index] = value; myTops[index] = value;
myFlags[index] = 0x00;
break; break;
} }
@ -257,17 +353,38 @@ void CartridgeDPC::poke(uInt16 address, uInt8 value)
// DFx counter low // DFx counter low
case 0x02: case 0x02:
{ {
myCounters[index] = (myCounters[index] & 0x0700) | (uInt16)value; if((index >= 5) && myMusicMode[index - 5])
{
// Data fecther is in music mode so its low counter value
// should be loaded from the top register not the poked value
myCounters[index] = (myCounters[index] & 0x0700) |
(uInt16)myTops[index];
}
else
{
// Data fecther is either not a music mode data fecther or it
// isn't in music mode so it's low counter value should be loaded
// with the poked value
myCounters[index] = (myCounters[index] & 0x0700) | (uInt16)value;
}
break; break;
} }
// DFx counter high // DFx counter high
case 0x03: case 0x03:
{ {
myFlags[index] = 0x00;
myCounters[index] = (((uInt16)value & 0x07) << 8) | myCounters[index] = (((uInt16)value & 0x07) << 8) |
(myCounters[index] & 0x00ff); (myCounters[index] & 0x00ff);
// TODO: Handle music data fetchers
// Execute special code for music mode data fetchers
if(index >= 5)
{
myMusicMode[index - 5] = (value & 0x10);
// 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; break;
} }
@ -342,6 +459,11 @@ bool CartridgeDPC::save(Serializer& out)
// Indicates which bank is currently active // Indicates which bank is currently active
out.putLong(myCurrentBank); out.putLong(myCurrentBank);
// The top registers for the data fetchers
out.putLong(8);
for(i = 0; i < 8; ++i)
out.putLong(myTops[i]);
// The bottom registers for the data fetchers // The bottom registers for the data fetchers
out.putLong(8); out.putLong(8);
for(i = 0; i < 8; ++i) for(i = 0; i < 8; ++i)
@ -357,13 +479,16 @@ bool CartridgeDPC::save(Serializer& out)
for(i = 0; i < 8; ++i) for(i = 0; i < 8; ++i)
out.putLong(myFlags[i]); out.putLong(myFlags[i]);
// The music mode flags for the data fetchers
out.putLong(3);
for(i = 0; i < 3; ++i)
out.putBool(myMusicMode[i]);
// The random number generator register // The random number generator register
out.putLong(myRandomNumber); out.putLong(myRandomNumber);
// The top registers for the data fetchers out.putLong(mySystemCycles);
out.putLong(8); out.putLong((uInt32)(myFractionalClocks * 100000000.0));
for(i = 0; i < 8; ++i)
out.putLong(myTops[i]);
} }
catch(char *msg) catch(char *msg)
{ {
@ -394,6 +519,11 @@ bool CartridgeDPC::load(Deserializer& in)
// Indicates which bank is currently active // Indicates which bank is currently active
myCurrentBank = (uInt16) in.getLong(); myCurrentBank = (uInt16) in.getLong();
// The top registers for the data fetchers
limit = (uInt32) in.getLong();
for(i = 0; i < limit; ++i)
myTops[i] = (uInt8) in.getLong();
// The bottom registers for the data fetchers // The bottom registers for the data fetchers
limit = (uInt32) in.getLong(); limit = (uInt32) in.getLong();
for(i = 0; i < limit; ++i) for(i = 0; i < limit; ++i)
@ -409,13 +539,17 @@ bool CartridgeDPC::load(Deserializer& in)
for(i = 0; i < limit; ++i) for(i = 0; i < limit; ++i)
myFlags[i] = (uInt8) in.getLong(); myFlags[i] = (uInt8) in.getLong();
// The music mode flags for the data fetchers
limit = (uInt32) in.getLong();
for(i = 0; i < limit; ++i)
myMusicMode[i] = in.getBool();
// The random number generator register // The random number generator register
myRandomNumber = (uInt8) in.getLong(); myRandomNumber = (uInt8) in.getLong();
// The top registers for the data fetchers // Get system cycles and fractional clocks
limit = (uInt32) in.getLong(); mySystemCycles = in.getLong();
for(i = 0; i < limit; ++i) myFractionalClocks = (double)in.getLong() / 100000000.0;
myTops[i] = (uInt8) in.getLong();
} }
catch(char *msg) catch(char *msg)
{ {
@ -433,3 +567,4 @@ bool CartridgeDPC::load(Deserializer& in)
return true; return true;
} }

View File

@ -13,7 +13,7 @@
// See the file "license" for information on usage and redistribution of // See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES. // this file, and for a DISCLAIMER OF ALL WARRANTIES.
// //
// $Id: CartDPC.hxx,v 1.2 2002-05-13 19:17:32 stephena Exp $ // $Id: CartDPC.hxx,v 1.3 2002-11-19 04:29:21 bwmott Exp $
//============================================================================ //============================================================================
#ifndef CARTRIDGEDCP_HXX #ifndef CARTRIDGEDCP_HXX
@ -32,7 +32,7 @@ class Deserializer;
see David P. Crane's United States Patent Number 4,644,495. see David P. Crane's United States Patent Number 4,644,495.
@author Bradford W. Mott @author Bradford W. Mott
@version $Id: CartDPC.hxx,v 1.2 2002-05-13 19:17:32 stephena Exp $ @version $Id: CartDPC.hxx,v 1.3 2002-11-19 04:29:21 bwmott Exp $
*/ */
class CartridgeDPC : public Cartridge class CartridgeDPC : public Cartridge
{ {
@ -62,6 +62,13 @@ class CartridgeDPC : public Cartridge
*/ */
virtual void reset(); virtual void reset();
/**
Notification method invoked by the system right before the
system resets its cycle counter to zero. It may be necessary
to override this method for devices that remember cycle counts.
*/
virtual void systemCyclesReset();
/** /**
Install cartridge in the specified system. Invoked by the system Install cartridge in the specified system. Invoked by the system
when the cartridge is attached to it. when the cartridge is attached to it.
@ -115,6 +122,12 @@ class CartridgeDPC : public Cartridge
*/ */
void clockRandomNumberGenerator(); void clockRandomNumberGenerator();
/**
Updates any data fetchers in music mode based on the number of
CPU cycles which have passed since the last update.
*/
void updateMusicModeDataFetchers();
private: private:
// Indicates which bank is currently active // Indicates which bank is currently active
uInt16 myCurrentBank; uInt16 myCurrentBank;
@ -125,6 +138,9 @@ class CartridgeDPC : public Cartridge
// The 2K display ROM image of the cartridge // The 2K display ROM image of the cartridge
uInt8 myDisplayImage[2048]; uInt8 myDisplayImage[2048];
// The top registers for the data fetchers
uInt8 myTops[8];
// The bottom registers for the data fetchers // The bottom registers for the data fetchers
uInt8 myBottoms[8]; uInt8 myBottoms[8];
@ -134,10 +150,17 @@ class CartridgeDPC : public Cartridge
// The flag registers for the data fetchers // The flag registers for the data fetchers
uInt8 myFlags[8]; uInt8 myFlags[8];
// The music mode DF5, DF6, & DF7 enabled flags
bool myMusicMode[3];
// The random number generator register // The random number generator register
uInt8 myRandomNumber; uInt8 myRandomNumber;
// The top registers for the data fetchers // System cycle count when the last update to music data fetchers occurred
uInt8 myTops[8]; Int32 mySystemCycles;
// Fractional DPC music OSC clocks unused during the last update
double myFractionalClocks;
}; };
#endif #endif