- implemented Vavoom's vertex height things (1505, 1506). These are not tested yet!

- added Updaterevision tool to print proper revision information in the console.
- removed support for specialty file formats from ModPlug loader. Having the 4 basic
  module formats (MOD, XM, S3M and IT) plus UMX should be enough.
- added new brightmaps by phi108 (Player and Baron attack)
- fixed a few brightmap definition errors
- fixed: DFraggleThinker::Destroy still treated the SpawnedThings array as DActorPointers 
  although that helper class has been removed.
- merged the GC branch into the trunk
- updated to ZDoom r796

git-svn-id: http://mancubus.net/svn/hosted/gzdoom/trunk@54 b0f79afe-0144-0410-b225-9a4edf0717df
This commit is contained in:
Christoph Oelckers 2008-03-12 15:21:17 +00:00
parent e8e77c8122
commit 0c2560bb36
262 changed files with 11637 additions and 16759 deletions

View file

@ -1,3 +1,160 @@
March 12, 2008 (Changes by Graf Zahl)
- Bumped the minimum savegame version because the current version crashes
each time an old savegame is loaded.
March 11, 2008
- Removed lots of spc_* cvars that are no longer meaningful and changed
spc_amp from a x.4 fixed point number to a normal float.
- Switched SPC playback from the external SNESAPU.DLL to Blargg's LGPL
snes_spc library. I've compiled it with the fast DSP rather than the
highly accurate one, since I didn't notice a meaningful difference between
the two in my limited testing. In short: SPC playback is now built in to
ZDoom. You don't need to download anything extra to make it work, and it
also works on Linux as well as Windows (though building with Linux is
currently untested).
- Fixed: Stereo separation was calculated very wrongly when in 2D sound mode.
March 10, 2008
- EAX is a pain in the butt to get it to work reliably. Why? I can get the
reverb to work just fine with software, but with hardware, I hear nothing
special at all.
- Fixed: Sounds apparently don't default to location 0,0,0 so I need to set
that explicitly for 2D sounds in 3D mode.
- Fixed: I had forgotten to actually set the head relative flag for 2D sounds
played in 3D.
- Fixed: Reverb was applied to digital music in software 3D mode. I tried
turning off reverb for 2D sounds too, but that turned it off for _all_
subsequent sounds and not that specific channel.
March 9, 2008 (Changes by Graf Zahl)
- fixed: StreamSong::SetPosition required a NULL pointer check.
- fixed: The release build still linked to the old FMOD version.
- fixed: SPCSong only works for Win32 so its definition must be excluded for Linux.
March 8, 2008
- Fixed: If you wanted to make cleandep with MinGW, you had to specifically
specify Makefile.mingw as the makefile to use.
- Added a normalizer to the OPL synth. It helped bring up the volume a little,
but not nearly as much as I would have liked.
- Removed MIDI Mapper references. It doesn't work with the stream API, and
it doesn't really exist on NT kernels, either.
- Reworked music volume: Except for MIDI, all music volume is controlled
through GSnd and not at the individual song level.
- Removed the mididevice global variable.
- Added back support for custom streams.
March 7, 2008
- I think the new FMOD Ex code should support everything the old FMOD 3 code
did, sans custom streaming sounds.
- Removed snd_midivolume. Now that all music uses a linear volume scale,
there's no need for two separate music volume controls.
- Increased snd_samplerate default up to 48000.
- Added snd_format, defaulting to "PCM-16".
- Added snd_speakermode, defaulting to "Auto".
- Replaced snd_fpu with snd_resampler, defaulting to "Linear".
- Bumped the snd_channels default up from a pitiful 12 to 32.
- Changed snd_3d default to true. The new cvar snd_hw3d determines if
hardware 3D support is used and default to false.
- Removed the libFLAC source, since FMOD Ex has native FLAC support.
- Removed the altsound code, since it was terribly gimped in comparison to
the FMOD code. Its original purpose was to have been as a springboard for
writing a non-FMOD sound system for Unix-y systems, but that never
happened.
- Finished preliminary FMOD Ex support.
March 6, 2008
- Fixed: If P_BounceWall() can't find a wall when it does its trace, but it
was entered because a line blocked the projectile, then it should still use
that blocking line for the bounce.
- The full master volume SysEx is now always sent to the MIDI device, even if
it seems to have a working volume control.
- Renamed music_midi_stream.cpp to music_midi_base.cpp.
- Moved the WinMM MIDI code into a new container class.
- Fixed: StatusBar and screen need to be barriered, because they are roots
but can also change at runtime.
- Fixed: Objects that are forcibly deleted outside the GC need to be removed
from the gray list too.
March 5, 2008
- Fixed: Thinkers needed write barriers when they were removed from their
lists.
- Fixed: DLevelScript::Link() and Unlink() needed write barriers.
March 4, 2008
- Moved the identical code between the MUS and MIDI streamers into a new base
class so that all the low-level details of MIDI streaming are kept in
one place.
- Converted the SMF MIDI playback to use the same MIDI streams as MUS
playback.
- Moved MUS playback back into its own thread so that it can continue
uninterrupted if the main thread is too busy to service it in a timely
manner.
- Fixed: The MEVT_* values are not defined shifted into their spot for a
MIDIEVENT, so I need to do it myself.
- Fixed: Pausing a MUS and then changing snd_midivolume caused the paused
notes to become audible.
March 3, 2008
- Changed MUS playback to use MIDI streams, like it did during the early days
of ZDoom, except now the entire song isn't prebuffered in large chunks, so
I can insert MIDI events into the playback with fairly low latency. This
should offer more precise timing than the combination of low-level MIDI and
WaitForMultipleObjects timeouts that it replaces.
- Fixed: PTR_BounceTraverse only checked for projectiles that were too
high to pass through two-sided lines, but not ones that were too low.
- Fixed: SBARINFO couldn't detect the extreme death damage type for the
player face animation.
March 1, 2008 (Changes by Graf Zahl)
- fixed: A_CountdownArg used 0 based indices although all uses of it assumed
it is 1-based.
- added MF5_DONTRIP flag.
- added CheckActorFloorTexture, CheckActorCeilingTexture and
GetActorLightLevel ACS functions.
- added IF_ADDITIVETIME flag to create powerups that add their duration
to the one of the currently active item of the same type.
- fixed: bouncecount wasn't decreased when bouncing on walls.
- Added MF5_ALWAYSRESPAWN and MF5_NEVERRESPAWN flags that selectively
enable or disable monster respawning regardless of skill setting.
- Prettified deprecated flag handling.
- Fixed: When starting a level while the music has been paused S_Start has
to stop the old music so the new one always starts at the beginning.
- Fixed:: AActor::master was not serialized.
- Fixed: Sound targets pointing to dead players should be cleared before
respawning the player.
- Fixed: When the DONTMOVE flag is set A_Chase must not call P_NewChaseDir.
- Changed PowerupGiver initialization so that the actual powerup class is looked
up during postprocessing.
- Gave Strife's instant death sector type its own damage type.
February 29, 2008
- Changed the default GC parameters to make it run more aggressively.
- Added NULL checks to the GC::WriteBarrier() wrappers.
- Fixed: The DObject destructor set GC::SweepPos incorrectly if it pointed
at that object being deleted.
- Added "soft roots" to the garbage collector. These are similar in
purpose to setting the OF_Fixed bit, but they behave as full-fledged roots
as far as the collector is concerned, so any objects reachable through
them will not be collected.
- Added support for the OF_Fixed bit. This is ONLY for simple objects which
do not contain pointers to other objects, because all it does is prevent
an object that is otherwise dead from being freed. It does not include
the object in the propagate stage if it is unreachable, so any objects
that are only reachable through it will still be freed unless they are
also fixed.
- Fixed: R_SetupAddClampCol() checked add4cols' memory instead of
adclamp4cols' memory when deciding if it should skip modification.
February 28, 2008
- Fixed: The colormap changes by column, so the assembly rt_* routines need
to be setup for every column group, not once per image.
- Fixed: rt_Translate1col() had a couple of bugs.
February 27, 2008
- Added assembly versions of rt_add4cols and rt_addclamp4cols.
- Added a read barrier to FArchive::SerializeObject() to make sure that
objects that are being euthanized are not stored in the archive.
February 26, 2008 February 26, 2008
- Added an assembly version of rt_shaded4cols, since that's the main decal - Added an assembly version of rt_shaded4cols, since that's the main decal
drawing function. The most improvement came from being able to turn some drawing function. The most improvement came from being able to turn some
@ -11,12 +168,54 @@ February 26, 2008
the translation in one step and the drawing in another. This lets me call the translation in one step and the drawing in another. This lets me call
the untranslated drawer to do the real drawing instead of mostly duplicating the untranslated drawer to do the real drawing instead of mostly duplicating
them. Performance wise, there is practically no difference from before. them. Performance wise, there is practically no difference from before.
- Fixed: Using +stat from the command line caused a crash.
- More write barriers and pointer declarations. Here are rules I've come up
with so far for when write barriers can be ommitted:
* Initializing pointers for a newly created object: A new object can never
black, so none of its pointers that get set by the constructor (or code
just following its creation) need to be behind write barriers.
* Creating a new thinker and storing it in a pointer: The thinker
constructor already puts it behind a write barrier when it links it into
the thinker list, so you don't need to do it again.
* As a corollary of the above: No pointers to thinkers should need to be
behind write barriers, because it is incorrect to have a thinker that is
live but not in a thinker list. I realized this while I was going through
the long list of actor target references, so there are some write barriers
for those in place already, since they don't hurt to have them around.
As a consequence of the last point, I think I'm done placing write barriers
in all their necessary places without even touching most of the code. I'm
going to let it sit like this for a few days to see if I can think of a
counter-example where a live thinker wouldn't be in a thinker list, but it
feels correct to me right now.
February 25, 2008 (Changes by Graf Zahl) February 25, 2008 (Changes by Graf Zahl)
- Fixed: The DECORATE expression evaluator's random function could produce - Fixed: The DECORATE expression evaluator's random function could produce
incorrect results for ranges > 255. Changed so that FRandom's default incorrect results for ranges > 255. Changed so that FRandom's default
implementation is used instead. implementation is used instead.
February 24, 2008
- Fixed: DDecalFader::Tick() still referenced TheDecal even after destroying
it.
- Added write barriers around the thinker list insertions in dthinker.cpp.
February 23, 2008 (Changes by Graf Zahl)
- Fixed: AWeaponPiece didn't declare its FullWeapon pointer as such.
- Fixed: Hexen's random player class skill menu was missing the '$' indicating
a string table identifier.
- Fixed: DCorpsePointer::Serialize didn't call the super method.
February 22, 2008
- Moved DSeqNode's and its children's destructor code into their Destroy()
methods.
- Moved DSectorEffect's destructor code into a Destroy() method.
- Changed the myoffsetof definition to match DECLARE_POINTER's because GCC
started complaining about its use in p_setup.cpp, though I'm not sure why.
- Added a pointer list to DACSThinker so that the collector can find scripts
instead of thinking they're all dead.
- Added read barriers to sector_t's pointers.
- Used the HAS_OBJECT_POINTERS macros to locate places to place TObjPtrs to
form read barriers.
February 21, 2008 February 21, 2008
- Fixed: DThinker::SerializeAll() did not serialize any thinkers in the - Fixed: DThinker::SerializeAll() did not serialize any thinkers in the
FreshThinkers lists. These thinkers would still be saved in the savegame if FreshThinkers lists. These thinkers would still be saved in the savegame if
@ -34,10 +233,30 @@ February 21, 2008
work for GCC (and presumably VC++, though I never ran into that case with work for GCC (and presumably VC++, though I never ran into that case with
it) because it tried to stringify something that wasn't a macro argument. it) because it tried to stringify something that wasn't a macro argument.
February 20, 2008
- Changed the DHUDMessage deletions into destroys.
- Added read barriers to the pointers inside AActor.
- Split the AActor::LastLook union into its constituent parts.
February 20, 2008 (Changes by Graf Zahl) February 20, 2008 (Changes by Graf Zahl)
- Added a modified version of Karate Chris's submission for killing specific - Added a modified version of Karate Chris's submission for killing specific
monsters with the 'Kill' ccmd. monsters with the 'Kill' ccmd.
February 19, 2008
- Added a rough initial garbage collector, based largely on Lua's. There are
no write or read barriers in place yet, so it's not a very close
approximation of the pointer cleaning code that was in place before, but it
does collect stuff. But guess what! Nuts.wad ran at 1-2 FPS before. Now
it's way, way more and easily stays above 35 FPS. The performance hit when
the GC is running is pretty minimal compared to the old way, which was in
many ways like a full garbage collection every tic.
- Changed the status bars into DObjects.
- Fixed: FBaseStatusBar::Tick() deleted hud messages instead of destroying
them.
- Changed the global Args object to a pointer to one.
- Changed several DArgs deletions into destroys.
- Removed DBoundingBox from the DObject hierarchy.
February 18, 2008 February 18, 2008
- Added vid_refreshrate cvar to override Windows' automatic refresh rate - Added vid_refreshrate cvar to override Windows' automatic refresh rate
selection. selection.
@ -632,7 +851,7 @@ January 1, 2008
- Fixed a D3D memory leak on every frame in windowed mode and the same thing - Fixed a D3D memory leak on every frame in windowed mode and the same thing
for the screen wipes. Note to self: If it's an interface, be sure to for the screen wipes. Note to self: If it's an interface, be sure to
Release it, because it will be AddRef'ed before being returned to you. Release it, because it will be AddRef'ed before being returned to you.
- Moved the BlendView() call out of FBaseStatusBar::Draw() so that it can be - Moved the BlendView() call out of DBaseStatusBar::Draw() so that it can be
applied before copying the 3D scene to the screen underneath the 2D parts. applied before copying the 3D scene to the screen underneath the 2D parts.
- Restored the console's darkening level to its old table-based amount. - Restored the console's darkening level to its old table-based amount.
- Fixed D3DFB::SetColorOverlay()'s incorrect calculations. - Fixed D3DFB::SetColorOverlay()'s incorrect calculations.
@ -4330,7 +4549,7 @@ April 18, 2006 (Changes by Graf Zahl)
fighting the programmer. fighting the programmer.
April 18, 2006 April 18, 2006
- Fixed: FBaseStatusBar::DrBNumber() should behave like Doom's - Fixed: DBaseStatusBar::DrBNumber() should behave like Doom's
STlib_drawNum(), and FDoomStatusBarTexture::DrawToBar() should add STlib_drawNum(), and FDoomStatusBarTexture::DrawToBar() should add
the textures left offset to the x coordinate before drawing. the textures left offset to the x coordinate before drawing.
These fix Twice Risen's status bar. These fix Twice Risen's status bar.
@ -5143,7 +5362,7 @@ February 11, 2005
February 10, 2005 February 10, 2005
- Fixed: The screen fading ACS commands did not work when viewing through a camera - Fixed: The screen fading ACS commands did not work when viewing through a camera
because FBaseStatusBar::BlendView() did not think there was a player to get those because DBaseStatusBar::BlendView() did not think there was a player to get those
values from. Now, the blend comes from either the player viewing from or the values from. Now, the blend comes from either the player viewing from or the
console player if the current view is not from a player. console player if the current view is not from a player.
- Fixed: Returning to a previously visited level in a hub with fewer players than you - Fixed: Returning to a previously visited level in a hub with fewer players than you
@ -5318,7 +5537,7 @@ January 26, 2005
Unfortunately, I didn't bump the demo version between 2.0.63a and 2.0.90, so Unfortunately, I didn't bump the demo version between 2.0.63a and 2.0.90, so
anything pre-2.0.97 won't play with 2.0.97, even though there's probably anything pre-2.0.97 won't play with 2.0.97, even though there's probably
no changes that would cause desynching between 96 and 97. Oh well. no changes that would cause desynching between 96 and 97. Oh well.
- Fixed FBaseStatusBar::DrBNumber(Outer) to work when some of the big number - Fixed DBaseStatusBar::DrBNumber(Outer) to work when some of the big number
graphics are missing. graphics are missing.
January 15, 2005 January 15, 2005
@ -5494,7 +5713,7 @@ December 18, 2004
on top of the next digit. on top of the next digit.
- Fixed: The Doom HUD drew the selected inventory item on top of the secondary - Fixed: The Doom HUD drew the selected inventory item on top of the secondary
ammo if both were present. ammo if both were present.
- Moved the DrawPowerups() call out of FBaseStatusBar::Draw() and into - Moved the DrawPowerups() call out of DBaseStatusBar::Draw() and into
DrawTopStuff(), so the Doom HUD key display won't cover them up DrawTopStuff(), so the Doom HUD key display won't cover them up
- Fixed: If you started flying and then pressed the land key, you wouldn't be - Fixed: If you started flying and then pressed the land key, you wouldn't be
able to fly again until the Wings of Wrath wore off. This was done by changing able to fly again until the Wings of Wrath wore off. This was done by changing
@ -6350,7 +6569,7 @@ July 15, 2004
- Updated to FMOD 3.73. - Updated to FMOD 3.73.
July 14, 2004 July 14, 2004
- Moved the full-screen HUD coordinate logic out of FBaseStatusBar and into - Moved the full-screen HUD coordinate logic out of DBaseStatusBar and into
DCanvas::DrawTexture. This is so that powerups can draw their status icons DCanvas::DrawTexture. This is so that powerups can draw their status icons
themselves without needing to hook into the status bar. themselves without needing to hook into the status bar.
- Reimplemented the Tome of Power. - Reimplemented the Tome of Power.
@ -6919,7 +7138,7 @@ December 12, 2003
up okay in Doom.) up okay in Doom.)
- Added a fix to the blockmap tracers where all the blocks along the trace are - Added a fix to the blockmap tracers where all the blocks along the trace are
crossed on their corners. crossed on their corners.
- Fixed a potential crash in FBaseStatusBar::DrawMessages() when a HUD message - Fixed a potential crash in DBaseStatusBar::DrawMessages() when a HUD message
removes itself as part of its drawing process. removes itself as part of its drawing process.
- Fixed: A few of the Strife weapons were erroneously defined with WIF_NOALERT. - Fixed: A few of the Strife weapons were erroneously defined with WIF_NOALERT.
- Fixed: Sky textures defaulted to 0 if a map had a MAPINFO that did not - Fixed: Sky textures defaulted to 0 if a map had a MAPINFO that did not
@ -7067,7 +7286,7 @@ November 23, 2003
things spawned afterward. things spawned afterward.
- Added calls to SetWindowLongPtr to take away the window's border in - Added calls to SetWindowLongPtr to take away the window's border in
fullscreen mode and put it back in windowed mode. fullscreen mode and put it back in windowed mode.
- Fixed FBaseStatusBar::RepositionCoords() so that LilWhiteMouse's Chosen - Fixed DBaseStatusBar::RepositionCoords() so that LilWhiteMouse's Chosen
ammo icons appear on the fullscreen HUD again. ammo icons appear on the fullscreen HUD again.
November 21, 2003 November 21, 2003
@ -7799,7 +8018,7 @@ July 30, 2003
- Added nonexistant texture check to the warping texture setup. - Added nonexistant texture check to the warping texture setup.
July 28, 2003 July 28, 2003
- Removed the ScaleCopy canvas from FBaseStatusBar, because everything is now - Removed the ScaleCopy canvas from DBaseStatusBar, because everything is now
drawn to the screen directly even when the status bar is scaled. drawn to the screen directly even when the status bar is scaled.
- Changed the pitch calculations in the DSimpleCanvas constructor to provide - Changed the pitch calculations in the DSimpleCanvas constructor to provide
better wall drawing performance at all resolutions. better wall drawing performance at all resolutions.
@ -10941,7 +11160,7 @@ October 23, 2001
torch lighting up the entire sky, so skies should ignore the setting of torch lighting up the entire sky, so skies should ignore the setting of
fixedlightlev. fixedlightlev.
- Fixed the scaled status bar. I was simply forgetting to add the image - Fixed the scaled status bar. I was simply forgetting to add the image
offsets in FBaseStatusBar::DrawImage() before calling CopyToScreen(). offsets in DBaseStatusBar::DrawImage() before calling CopyToScreen().
- Added code to let decals move with sliding and rotating polyobjects. - Added code to let decals move with sliding and rotating polyobjects.
- Fixed: The wait console command waited one tic too few. - Fixed: The wait console command waited one tic too few.
- Fixed a resizing problem with playing movies windowed. - Fixed a resizing problem with playing movies windowed.
@ -11761,7 +11980,7 @@ April 7, 2001
big ammo count when switching to a weapon that does not use ammo. big ammo count when switching to a weapon that does not use ammo.
- Fixed: At certain screen heights, scaled status bars would not touch the - Fixed: At certain screen heights, scaled status bars would not touch the
bottom of the screen because of inaccuracy in the calculation of ::ST_Y bottom of the screen because of inaccuracy in the calculation of ::ST_Y
in FBaseStatusBar::SetScaled(). in DBaseStatusBar::SetScaled().
- Added separate pickup sounds for health, armor, and ammo items. - Added separate pickup sounds for health, armor, and ammo items.
- Improved sound link resolution in S_StartSound() so that regular sounds - Improved sound link resolution in S_StartSound() so that regular sounds
can alias player sounds. can alias player sounds.

View file

@ -11,8 +11,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zdoom", "gzdoom.vcproj", "{
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "zlib\zlib.vcproj", "{F9D9E7D4-E1A2-4866-9E85-B1B14137EE63}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "zlib\zlib.vcproj", "{F9D9E7D4-E1A2-4866-9E85-B1B14137EE63}"
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FLAC", "FLAC\FLAC.vcproj", "{873F2EEA-24DF-454C-B245-CB9738BA993E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lemon", "tools\lemon\lemon.vcproj", "{0F80ACBF-460E-44F0-B28E-B3272D1774A7}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lemon", "tools\lemon\lemon.vcproj", "{0F80ACBF-460E-44F0-B28E-B3272D1774A7}"
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "re2c", "tools\re2c\re2c.vcproj", "{667D2EE7-C357-49E2-9BAB-0A4A45F0F76E}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "re2c", "tools\re2c\re2c.vcproj", "{667D2EE7-C357-49E2-9BAB-0A4A45F0F76E}"
@ -37,10 +35,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dehsupp", "tools\dehsupp\de
{0F80ACBF-460E-44F0-B28E-B3272D1774A7} = {0F80ACBF-460E-44F0-B28E-B3272D1774A7} {0F80ACBF-460E-44F0-B28E-B3272D1774A7} = {0F80ACBF-460E-44F0-B28E-B3272D1774A7}
EndProjectSection EndProjectSection
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "updaterevision", "tools\updaterevision\updaterevision.vcproj", "{6077B7D6-349F-4077-B552-3BC302EF5859}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jpeg-6b", "jpeg-6b\jpeg-6b.vcproj", "{AC3F5340-40CB-4C3A-8AA7-CB7158DB4466}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jpeg-6b", "jpeg-6b\jpeg-6b.vcproj", "{AC3F5340-40CB-4C3A-8AA7-CB7158DB4466}"
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fixrtext", "tools\fixrtext\fixrtext.vcproj", "{DA47396F-60C1-4BDE-A977-7F7DE461CF77}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fixrtext", "tools\fixrtext\fixrtext.vcproj", "{DA47396F-60C1-4BDE-A977-7F7DE461CF77}"
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "snes_spc", "snes_spc\snes_spc.vcproj", "{E83FD370-2E72-4D4C-9427-FF9D9DED1E88}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "brightmaps", "wadsrc_bm\brightmaps.vcproj", "{087B206F-F49E-4EFB-92CB-E1F6E32D1278}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "brightmaps", "wadsrc_bm\brightmaps.vcproj", "{087B206F-F49E-4EFB-92CB-E1F6E32D1278}"
ProjectSection(ProjectDependencies) = postProject ProjectSection(ProjectDependencies) = postProject
{24A19C02-F041-4AB0-A1A1-02E1E88EDBD3} = {24A19C02-F041-4AB0-A1A1-02E1E88EDBD3} {24A19C02-F041-4AB0-A1A1-02E1E88EDBD3} = {24A19C02-F041-4AB0-A1A1-02E1E88EDBD3}

View file

@ -28,6 +28,8 @@
> >
<Tool <Tool
Name="VCPreBuildEventTool" Name="VCPreBuildEventTool"
Description="Checking svnrevision.h..."
CommandLine="&quot;$(OutDir)\updaterevision.exe&quot; src src/svnrevision_gz.h"
/> />
<Tool <Tool
Name="VCCustomBuildTool" Name="VCCustomBuildTool"
@ -54,7 +56,7 @@
EnableIntrinsicFunctions="true" EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1" FavorSizeOrSpeed="1"
OmitFramePointers="true" OmitFramePointers="true"
AdditionalIncludeDirectories="src\win32,src\sound,src,zlib,src\g_shared,src\g_doom,src\g_raven,src\g_heretic,src\g_hexen,src\g_strife;flac;jpeg-6b" AdditionalIncludeDirectories="src\win32,src\sound,src,zlib,src\g_shared,src\g_doom,src\g_raven,src\g_heretic,src\g_hexen,src\g_strife;flac;jpeg-6b;snes_spc\snes_spc"
PreprocessorDefinitions="NDEBUG,WIN32,_WIN32,_WINDOWS,USEASM,HAVE_STRUPR,HAVE_FILELENGTH,SILENT_INSTANT_FLOORS" PreprocessorDefinitions="NDEBUG,WIN32,_WIN32,_WINDOWS,USEASM,HAVE_STRUPR,HAVE_FILELENGTH,SILENT_INSTANT_FLOORS"
StringPooling="true" StringPooling="true"
RuntimeLibrary="0" RuntimeLibrary="0"
@ -146,6 +148,8 @@
> >
<Tool <Tool
Name="VCPreBuildEventTool" Name="VCPreBuildEventTool"
Description="Checking svnrevision.h..."
CommandLine="$(OutDir)\updaterevision.exe src src/svnrevision_gz.h"
/> />
<Tool <Tool
Name="VCCustomBuildTool" Name="VCCustomBuildTool"
@ -168,7 +172,7 @@
Name="VCCLCompilerTool" Name="VCCLCompilerTool"
AdditionalOptions="&quot; /I /fmod/api/inc&quot; &quot; /I /fmod/api/inc&quot; &quot; /I /fmod/api/inc&quot; &quot; /I /fmod/api/inc&quot; " AdditionalOptions="&quot; /I /fmod/api/inc&quot; &quot; /I /fmod/api/inc&quot; &quot; /I /fmod/api/inc&quot; &quot; /I /fmod/api/inc&quot; "
Optimization="0" Optimization="0"
AdditionalIncludeDirectories="src\win32;src\sound;src;zlib;src\g_shared;src\g_doom;src\g_raven;src\g_heretic;src\g_hexen;src\g_strife;flac;jpeg-6b" AdditionalIncludeDirectories="src\win32;src\sound;src;zlib;src\g_shared;src\g_doom;src\g_raven;src\g_heretic;src\g_hexen;src\g_strife;flac;jpeg-6b;snes_spc\snes_spc"
PreprocessorDefinitions="WIN32,_DEBUG,_WIN32,_WINDOWS,USEASM,_CRTDBG_MAP_ALLOC,HAVE_STRUPR,HAVE_FILELENGTH" PreprocessorDefinitions="WIN32,_DEBUG,_WIN32,_WINDOWS,USEASM,_CRTDBG_MAP_ALLOC,HAVE_STRUPR,HAVE_FILELENGTH"
MinimalRebuild="true" MinimalRebuild="true"
RuntimeLibrary="1" RuntimeLibrary="1"
@ -1401,6 +1405,10 @@
/> />
</FileConfiguration> </FileConfiguration>
</File> </File>
<File
RelativePath=".\src\dobjgc.cpp"
>
</File>
<File <File
RelativePath=".\src\dobjtype.cpp" RelativePath=".\src\dobjtype.cpp"
> >

52
snes_spc/Makefile Normal file
View file

@ -0,0 +1,52 @@
# Makefile for snes_spc, derived from zlib/Makefile.mgw.
ifeq (Windows_NT,$(OS))
CMD=1
endif
ifeq ($(findstring msys,$(shell sh --version 2>nul)),msys)
CMD=0
endif
STATICLIB = libsnes_spc.a
CCDV = @../ccdv
CC = gcc
CFLAGS = $(LOC) -O3 -Wall -fomit-frame-pointer
LD = $(CC)
LDFLAGS = $(LOC) -s
AR = ar
ARFLAGS = rcs
OBJS = snes_spc/dsp.o snes_spc/SNES_SPC.o snes_spc/SNES_SPC_misc.o snes_spc/SNES_SPC_state.o \
snes_spc/spc.o snes_spc/SPC_DSP.o snes_spc/SPC_Filter.o
all: $(STATICLIB)
.cpp.o:
$(CCDV) $(CC) $(CFLAGS) -c -o $@ $<
$(STATICLIB): $(OBJS)
$(CCDV) $(AR) $(ARFLAGS) $@ $(OBJS)
.PHONY: clean
clean:
ifeq (0,$(CMD))
rm -f $(STATICLIB)
rm -f snes_spc/*.o
else
-del /q /f $(STATICLIB) 2>nul
-del /q /f snes_spc\*.o 2>nul
endif
dsp.o: snes_spc/dsp.cpp snes_spc/dsp.h snes_spc/SPC_DSP.h
SNES_SPC.o: snes_spc/SNES_SPC.cpp snes_spc/SNES_SPC.h snes_spc/SPC_DSP.h
SNES_SPC_misc.o: snes_spc/SNES_SPC_misc.cpp snes_spc/SNES_SPC.h snes_spc/SPC_DSP.h
SNES_SPC_state.o: snes_spc/SNES_SPC_state.cpp snes_spc/SNES_SPC.h snes_spc/SPC_DSP.h
spc.o: snes_spc/spc.cpp snes_spc/spc.h snes_spc/SNES_SPC.h snes_spc/SPC_DSP.h snes_spc/SPC_Filter.h
SPC_DSP.o: snes_spc/SPC_DSP.cpp snes_spc/SPC_DSP.h
SPC_Filter.o: snes_spc/SPC_Filter.cpp snes_spc/SPC_Filter.h

107
snes_spc/changes.txt Normal file
View file

@ -0,0 +1,107 @@
snes_spc Change Log
-------------------
snes_spc 0.9.0
--------------
- Improved documentation
- SPC: Added spc_skip() function for quickly seeking in an SPC music
file. Runs 3-4x faster than normal playback using the fast DSP (or about
43-60X real-time on my 400 MHz Mac).
- SPC: Added spc_set_tempo() to change tempo of SPC music playback.
- SPC: Sample generation is now corrected to generate exactly one pair
of samples every 32 clocks without exception. Before it could generate a
few samples more or less depending on how far ahead or behind DSP was at
the moment.
- SPC: Changed spc_reset() and spc_soft_reset() to also reset output
buffer (see spc.h).
- SPC: Fixed minor timer counting bug.
- SPC: Stack pointer wrap-around is now emulated (and without any
noticeable performance hit).
- SPC: Runs about 5% faster due to various optimizations.
- SPC: Found way to make fast DSP register accesses cycle-accurate in
most cases, without reducing performance. Allows fast DSP to pass most
of my validation tests.
- DSP: Added surround disable support to fast DSP again.
- DSP: Improved voice un-muting to take effect immediately on fast DSP.
- DSP: Noise shift register now starts at 0x4000 instead of 0x4001 as it
incorrectly did before.
- Converted library to C++ code internally. A C interface is still
included in spc.h and dsp.h. Note that these are different than the
previous interface, so your code will require minor changes:
Old SPC code New SPC code
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#include "spc/spc.h" #include "snes_spc/spc.h"
snes_spc_t* spc; SNES_SPC* spc;
spc = malloc( sizeof (snes_spc_t) ); spc = spc_new();
spc_init( spc );
spc_end_frame( time ); spc_end_frame( spc, time );
/* etc. */
/* done using SPC */ spc_delete( spc );
Old DSP code New DSP code
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#include "spc/spc_dsp.h" #include "snes_spc/dsp.h"
spc_dsp_init( ram ); SPC_DSP* dsp;
dsp = spc_dsp_new();
spc_dsp_init( dsp, ram );
spc_dsp_run( count ); spc_dsp_run( dsp, count );
/* etc. */
/* done using DSP */ spc_dsp_delete( dsp );
snes_spc 0.8.0
--------------
- Added several demos
- Added high-pass/low-pass filter to better match SNES sound
- Added save state functionality for SPC and accurate DSP (but not fast
DSP)
- Added emulation of reset switch on NES (soft reset)
- Made source more compatible with pre-C99 compilers by eliminating
mid-block declarations
- SPC: Many S-SMP accuracy improvements, mostly in memory access times
- SPC: S-SMP speed improvements
- SPC: Added SPC load/save functions and KON checking to help trim
silence from beginning
- SPC: Changed spc_init() to have you allocate most of the memory used
by the library so you have more control over it
- DSP: New highly accurate DSP and faster version derived from same code
- DSP: Changed prefix from dsp_ to spc_dsp_. Your DSP code will require
changes.
- DSP: Removed surround disable and gain. Gain can now be done with the
dsp_filter module, and surround disable will probably only be
implemented in the fast DSP at some point.
- DSP: Changed interface to work in clocks rather than samples,
necessary for the new accurate DSP. Sample output is now done with
separate functions. Your DSP code will require changes.

58
snes_spc/demo/benchmark.c Normal file
View file

@ -0,0 +1,58 @@
/* Measures performance of SPC emulator. Takes about 4 seconds.
NOTE: This assumes that the program is getting all processor time; you might need to
arrange for this or else the performance will be reported lower than it really is.
Usage: benchmark [test.spc]
*/
#include "snes_spc/spc.h"
#include "demo_util.h" /* error(), load_file() */
#include <time.h>
clock_t start_timing( int seconds );
int main( int argc, char** argv )
{
/* Load SPC */
long spc_size;
void* spc = load_file( (argc > 1) ? argv [1] : "test.spc", &spc_size );
SNES_SPC* snes_spc = spc_new();
if ( !snes_spc ) error( "Out of memory" );
spc_load_spc( snes_spc, spc, spc_size );
free( spc );
{
/* Repeatedly fill buffer for 4 seconds */
int const seconds = 4;
#define BUF_SIZE 4096
clock_t end = start_timing( seconds );
int count = 0;
while ( clock() < end )
{
static short buf [BUF_SIZE];
error( spc_play( snes_spc, BUF_SIZE, buf ) );
count++;
}
/* Report performance based on how many buffer fills were possible */
{
double rate = (double) count * BUF_SIZE / (spc_sample_rate * 2 * seconds);
printf( "Performance: %.3fx real-time, or %.0f%% CPU for normal rate\n",
rate, 100.0 / rate );
}
}
return 0;
}
/* Synchronizes with host clock and returns clock() time that is duration seconds from now */
clock_t start_timing( int duration )
{
clock_t clock_dur = duration * CLOCKS_PER_SEC;
clock_t time = clock();
while ( clock() == time ) { }
if ( clock() - time > clock_dur )
error( "Insufficient clock() time resolution" );
return clock() + clock_dur;
}

70
snes_spc/demo/comm.c Normal file
View file

@ -0,0 +1,70 @@
/* Communicates with SPC the way the SNES would.
Note: You'll need an "spc_rom.h" file that contains the 64-byte IPL ROM contents */
#include "snes_spc/spc.h"
#include "demo_util.h"
#include <string.h>
#include <stdio.h>
static SNES_SPC* snes_spc;
/* Make port access more convenient. Fakes time by simply incrementing it each call. */
static spc_time_t stime;
static int pread ( int port ) { return spc_read_port( snes_spc, stime++, port ); }
static void pwrite( int port, int data ) { spc_write_port( snes_spc, stime++, port, data ); }
static unsigned char const spc_rom [spc_rom_size] = {
/* ROM data not provided with emulator */
#include "spc_rom.h"
};
int main()
{
int i;
/* Data to upload */
static unsigned char const data [4] = "\xFA\xDE\xD1";
unsigned const data_addr = 0xF5; /* second I/O port */
snes_spc = spc_new();
if ( !snes_spc ) error( "Out of memory" );
spc_init_rom( snes_spc, spc_rom );
spc_reset( snes_spc );
/* Simulate reads and writes that SNES code would do */
/* Wait for SPC to be ready */
while ( pread( 0 ) != 0xAA || pread( 1 ) != 0xBB ) { }
/* Start address */
pwrite( 2, data_addr & 0xFF );
pwrite( 3, data_addr >> 8 );
/* Tell SPC to start transfer and wait for acknowledgement */
pwrite( 0, 0xCC );
pwrite( 1, 0x01 );
while ( pread( 0 ) != 0xCC ) { }
/* Send each byte and wait for acknowledgement */
for ( i = 0; i < 4; i++ )
{
printf( "%02X ", data [i] );
pwrite( 1, data [i] );
pwrite( 0, i );
while ( pread( 0 ) != i ) { }
}
printf( "\n" );
/* Verify that data was transferred properly */
for ( i = 0; i < 3; i++ )
printf( "%02X ", pread( i + 1 ) );
printf( "\n" );
printf( "Cycles: %ld\n", (long) stime );
spc_delete( snes_spc );
return 0;
}

57
snes_spc/demo/demo_util.c Normal file
View file

@ -0,0 +1,57 @@
#include "demo_util.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/* Copyright (C) 2007 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 */
unsigned char* load_file( const char* path, long* size_out )
{
size_t size;
unsigned char* data;
FILE* in = fopen( path, "rb" );
if ( !in ) error( "Couldn't open file" );
fseek( in, 0, SEEK_END );
size = ftell( in );
if ( size_out )
*size_out = size;
rewind( in );
data = (unsigned char*) malloc( size );
if ( !data ) error( "Out of memory" );
if ( fread( data, 1, size, in ) < size ) error( "Couldn't read file" );
fclose( in );
return data;
}
void write_file( const char* path, void const* in, long size )
{
FILE* out = fopen( path, "wb" );
if ( !out ) error( "Couldn't create file" );
if ( (long) fwrite( in, 1, size, out ) < size ) error( "Couldn't write file" );
fclose( out );
}
void error( const char* str )
{
if ( str )
{
fprintf( stderr, "Error: %s\n", str );
exit( EXIT_FAILURE );
}
}

31
snes_spc/demo/demo_util.h Normal file
View file

@ -0,0 +1,31 @@
/* General-purpose utilities used by demos */
/* snes_spc 0.9.0 */
#ifndef DEMO_UTIL_H
#define DEMO_UTIL_H
/* commonly used headers */
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
/* If str is not NULL, prints it and exits program, otherwise returns */
void error( const char* str );
/* Loads file and returns pointer to data in memory, allocated with malloc().
If size_out != NULL, sets *size_out to size of data. */
unsigned char* load_file( const char* path, long* size_out );
/* Writes data to file */
void write_file( const char* path, void const* in, long size );
#ifdef __cplusplus
}
#endif
#endif

57
snes_spc/demo/play_spc.c Normal file
View file

@ -0,0 +1,57 @@
/* Records SPC into wave file. Uses dsp_filter to give more authentic sound.
Usage: play_spc [test.spc]
*/
#include "snes_spc/spc.h"
#include "wave_writer.h"
#include "demo_util.h" /* error(), load_file() */
int main( int argc, char** argv )
{
/* Create emulator and filter */
SNES_SPC* snes_spc = spc_new();
SPC_Filter* filter = spc_filter_new();
if ( !snes_spc || !filter ) error( "Out of memory" );
/* Load SPC */
{
/* Load file into memory */
long spc_size;
void* spc = load_file( (argc > 1) ? argv [1] : "test.spc", &spc_size );
/* Load SPC data into emulator */
error( spc_load_spc( snes_spc, spc, spc_size ) );
free( spc ); /* emulator makes copy of data */
/* Most SPC files have garbage data in the echo buffer, so clear that */
spc_clear_echo( snes_spc );
/* Clear filter before playing */
spc_filter_clear( filter );
}
/* Record 20 seconds to wave file */
wave_open( spc_sample_rate, "out.wav" );
wave_enable_stereo();
while ( wave_sample_count() < 20 * spc_sample_rate * 2 )
{
/* Play into buffer */
#define BUF_SIZE 2048
short buf [BUF_SIZE];
error( spc_play( snes_spc, BUF_SIZE, buf ) );
/* Filter samples */
spc_filter_run( filter, buf, BUF_SIZE );
wave_write( buf, BUF_SIZE );
}
/* Cleanup */
spc_filter_delete( filter );
spc_delete( snes_spc );
wave_close();
return 0;
}

107
snes_spc/demo/save_state.c Normal file
View file

@ -0,0 +1,107 @@
/* Loads "test.spc", skips 5 seconds, saves exact emulator state to "state.bin",
hen records 5 seconds to "first.wav". When run again, loads this state back into
emulator and records 5 seconds to "second.wav". These two wave files should
be identical.
Usage: save_state [test.spc]
*/
#include "snes_spc/spc.h"
#include "wave_writer.h"
#include "demo_util.h" /* error(), load_file() */
static SNES_SPC* snes_spc;
void record_wav( const char* path, int secs )
{
/* Start writing wave file */
wave_open( spc_sample_rate, path );
wave_enable_stereo();
while ( wave_sample_count() < secs * spc_sample_rate * 2 )
{
/* Play into buffer */
#define BUF_SIZE 2048
short buf [BUF_SIZE];
error( spc_play( snes_spc, BUF_SIZE, buf ) );
wave_write( buf, BUF_SIZE );
}
wave_close();
}
void state_save( unsigned char** out, void* in, size_t size )
{
memcpy( *out, in, size );
*out += size;
}
void make_save_state( const char* path )
{
/* Load SPC */
long spc_size;
void* spc = load_file( path, &spc_size );
error( spc_load_spc( snes_spc, spc, spc_size ) );
free( spc );
spc_clear_echo( snes_spc );
/* Skip several seconds */
error( spc_play( snes_spc, 5 * spc_sample_rate * 2, 0 ) );
/* Save state to file */
{
static unsigned char state [spc_state_size]; /* buffer large enough for data */
unsigned char* out = state;
spc_copy_state( snes_spc, &out, state_save );
write_file( "state.bin", state, out - state );
}
record_wav( "first.wav", 5 );
}
void state_load( unsigned char** in, void* out, size_t size )
{
memcpy( out, *in, size );
*in += size;
}
void use_save_state()
{
/* Load state into memory */
long state_size;
unsigned char* state = load_file( "state.bin", &state_size );
/* Load into emulator */
unsigned char* in = state;
spc_copy_state( snes_spc, &in, state_load );
assert( in - state <= state_size ); /* be sure it didn't read past end */
record_wav( "second.wav", 5 );
}
int file_exists( const char* path )
{
FILE* file = fopen( path, "rb" );
if ( !file )
return 0;
fclose( file );
return 1;
}
int main( int argc, char** argv )
{
snes_spc = spc_new();
if ( !snes_spc ) error( "Out of memory" );
/* Make new state if it doesn't exist, otherwise load it and
record to wave file */
if ( !file_exists( "state.bin" ) )
make_save_state( (argc > 1) ? argv [1] : "test.spc" );
else
use_save_state();
spc_delete( snes_spc );
return 0;
}

83
snes_spc/demo/trim_spc.c Normal file
View file

@ -0,0 +1,83 @@
/* Trims silence off beginning of SPC file.
Requires the accurate DSP; won't compile with the fast DSP.
Please note that the algorithm could be improved; this is just
a simple example showing the idea.
Usage: trim_spc [test.spc [trimmed.spc]]
*/
#include "snes_spc/spc.h"
#include "demo_util.h" /* error(), load_file() */
/* Change to 1 to have it trim to next key on event rather than trim silence */
enum { use_kon_check = 0 };
/* True if all count samples from in are silent (or very close to it) */
int is_silent( short const* in, int count );
int main( int argc, char** argv )
{
/* Load file into memory */
long spc_size;
void* spc = load_file( (argc > 1) ? argv [1] : "test.spc", &spc_size );
/* Load into emulator */
SNES_SPC* snes_spc = spc_new();
if ( !snes_spc ) error( "Out of memory" );
error( spc_load_spc( snes_spc, spc, spc_size ) );
/* Expand SPC data so there's enough room for emulator to save to.
We simply overwrite the emulator data in the old SPC file rather
than creating new SPC data. This preserves the text tags from
the old file. */
if ( spc_size < spc_file_size )
{
spc_size = spc_file_size;
spc = realloc( spc, spc_size ); /* leaks memory if allocation fails */
if ( !spc ) error( "Out of memory" );
}
/* Keep saving SPC, then playing a little more. Once SPC becomes non-silent,
write the SPC data saved just before this. */
{
long samples_trimmed = 0;
while ( 1 )
{
#define BUF_SIZE 1024
short buf [BUF_SIZE];
if ( samples_trimmed > 10 * spc_sample_rate * 2 )
error( "Excess silence found" );
spc_clear_echo( snes_spc );
spc_save_spc( snes_spc, spc );
/* Fill buffer */
error( spc_play( snes_spc, BUF_SIZE, buf ) );
samples_trimmed += BUF_SIZE;
/* See if SPC became non-silent */
if ( use_kon_check ? spc_check_kon( snes_spc ) : !is_silent( buf, BUF_SIZE ) )
break;
}
printf( "Trimmed %.1f seconds\n", (double) samples_trimmed / spc_sample_rate / 2 );
}
spc_delete( snes_spc );
write_file( (argc > 2) ? argv [2] : "trimmed.spc", spc, spc_size );
return 0;
}
int is_silent( short const* in, int count )
{
unsigned const threshold = 0x10;
while ( count-- )
{
if ( (unsigned) (*in++ + threshold / 2) > threshold )
return 0;
}
return 1;
}

153
snes_spc/demo/wave_writer.c Normal file
View file

@ -0,0 +1,153 @@
/* snes_spc 0.9.0. http://www.slack.net/~ant/ */
#include "wave_writer.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
/* Copyright (C) 2003-2007 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 */
enum { buf_size = 32768 * 2 };
enum { header_size = 0x2C };
typedef short sample_t;
static unsigned char* buf;
static FILE* file;
static long sample_count_;
static long sample_rate_;
static long buf_pos;
static int chan_count;
static void exit_with_error( const char* str )
{
printf( "Error: %s\n", str ); getchar();
exit( EXIT_FAILURE );
}
void wave_open( long sample_rate, const char* filename )
{
sample_count_ = 0;
sample_rate_ = sample_rate;
buf_pos = header_size;
chan_count = 1;
buf = (unsigned char*) malloc( buf_size * sizeof *buf );
if ( !buf )
exit_with_error( "Out of memory" );
file = fopen( filename, "wb" );
if ( !file )
exit_with_error( "Couldn't open WAVE file for writing" );
setvbuf( file, 0, _IOFBF, 32 * 1024L );
}
void wave_enable_stereo( void )
{
chan_count = 2;
}
static void flush_()
{
if ( buf_pos && !fwrite( buf, buf_pos, 1, file ) )
exit_with_error( "Couldn't write WAVE data" );
buf_pos = 0;
}
void wave_write( short const* in, long remain )
{
sample_count_ += remain;
while ( remain )
{
if ( buf_pos >= buf_size )
flush_();
{
unsigned char* p = &buf [buf_pos];
long n = (buf_size - buf_pos) / sizeof (sample_t);
if ( n > remain )
n = remain;
remain -= n;
/* convert to LSB first format */
while ( n-- )
{
int s = *in++;
*p++ = (unsigned char) s;
*p++ = (unsigned char) (s >> 8);
}
buf_pos = p - buf;
assert( buf_pos <= buf_size );
}
}
}
long wave_sample_count( void )
{
return sample_count_;
}
static void set_le32( void* p, unsigned long n )
{
((unsigned char*) p) [0] = (unsigned char) n;
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
((unsigned char*) p) [2] = (unsigned char) (n >> 16);
((unsigned char*) p) [3] = (unsigned char) (n >> 24);
}
void wave_close( void )
{
if ( file )
{
/* generate header */
unsigned char h [header_size] =
{
'R','I','F','F',
0,0,0,0, /* length of rest of file */
'W','A','V','E',
'f','m','t',' ',
0x10,0,0,0, /* size of fmt chunk */
1,0, /* uncompressed format */
0,0, /* channel count */
0,0,0,0, /* sample rate */
0,0,0,0, /* bytes per second */
0,0, /* bytes per sample frame */
16,0, /* bits per sample */
'd','a','t','a',
0,0,0,0, /* size of sample data */
/* ... */ /* sample data */
};
long ds = sample_count_ * sizeof (sample_t);
int frame_size = chan_count * sizeof (sample_t);
set_le32( h + 0x04, header_size - 8 + ds );
h [0x16] = chan_count;
set_le32( h + 0x18, sample_rate_ );
set_le32( h + 0x1C, sample_rate_ * frame_size );
h [0x20] = frame_size;
set_le32( h + 0x28, ds );
flush_();
/* write header */
fseek( file, 0, SEEK_SET );
fwrite( h, sizeof h, 1, file );
fclose( file );
file = 0;
free( buf );
buf = 0;
}
}

View file

@ -0,0 +1,20 @@
/* WAVE sound file writer for recording 16-bit output during program development */
#ifndef WAVE_WRITER_H
#define WAVE_WRITER_H
#ifdef __cplusplus
extern "C" {
#endif
void wave_open( long sample_rate, const char* filename );
void wave_enable_stereo( void );
void wave_write( short const* in, long count );
long wave_sample_count( void );
void wave_close( void );
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,703 @@
// snes_spc 0.9.0. http://www.slack.net/~ant/
#include "SPC_DSP.h"
#include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2007 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"
#ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER
#endif
#if INT_MAX < 0x7FFFFFFF
#error "Requires that int type have at least 32 bits"
#endif
// TODO: add to blargg_endian.h
#define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr ))
#define GET_LE16A( addr ) GET_LE16( addr )
#define SET_LE16A( addr, data ) SET_LE16( addr, data )
static BOOST::uint8_t const initial_regs [SPC_DSP::register_count] =
{
0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80,
0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF,
0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A,
0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF,
0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67,
0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF,
0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F,
0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF
};
// if ( io < -32768 ) io = -32768;
// if ( io > 32767 ) io = 32767;
#define CLAMP16( io )\
{\
if ( (int16_t) io != io )\
io = (io >> 31) ^ 0x7FFF;\
}
// Access global DSP register
#define REG(n) m.regs [r_##n]
// Access voice DSP register
#define VREG(r,n) r [v_##n]
#define WRITE_SAMPLES( l, r, out ) \
{\
out [0] = l;\
out [1] = r;\
out += 2;\
if ( out >= m.out_end )\
{\
check( out == m.out_end );\
check( m.out_end != &m.extra [extra_size] || \
(m.extra <= m.out_begin && m.extra < &m.extra [extra_size]) );\
out = m.extra;\
m.out_end = &m.extra [extra_size];\
}\
}\
void SPC_DSP::set_output( sample_t* out, int size )
{
require( (size & 1) == 0 ); // must be even
if ( !out )
{
out = m.extra;
size = extra_size;
}
m.out_begin = out;
m.out = out;
m.out_end = out + size;
}
// Volume registers and efb are signed! Easy to forget int8_t cast.
// Prefixes are to avoid accidental use of locals with same names.
// Interleved gauss table (to improve cache coherency)
// interleved_gauss [i] = gauss [(i & 1) * 256 + 255 - (i >> 1 & 0xFF)]
static short const interleved_gauss [512] =
{
370,1305, 366,1305, 362,1304, 358,1304, 354,1304, 351,1304, 347,1304, 343,1303,
339,1303, 336,1303, 332,1302, 328,1302, 325,1301, 321,1300, 318,1300, 314,1299,
311,1298, 307,1297, 304,1297, 300,1296, 297,1295, 293,1294, 290,1293, 286,1292,
283,1291, 280,1290, 276,1288, 273,1287, 270,1286, 267,1284, 263,1283, 260,1282,
257,1280, 254,1279, 251,1277, 248,1275, 245,1274, 242,1272, 239,1270, 236,1269,
233,1267, 230,1265, 227,1263, 224,1261, 221,1259, 218,1257, 215,1255, 212,1253,
210,1251, 207,1248, 204,1246, 201,1244, 199,1241, 196,1239, 193,1237, 191,1234,
188,1232, 186,1229, 183,1227, 180,1224, 178,1221, 175,1219, 173,1216, 171,1213,
168,1210, 166,1207, 163,1205, 161,1202, 159,1199, 156,1196, 154,1193, 152,1190,
150,1186, 147,1183, 145,1180, 143,1177, 141,1174, 139,1170, 137,1167, 134,1164,
132,1160, 130,1157, 128,1153, 126,1150, 124,1146, 122,1143, 120,1139, 118,1136,
117,1132, 115,1128, 113,1125, 111,1121, 109,1117, 107,1113, 106,1109, 104,1106,
102,1102, 100,1098, 99,1094, 97,1090, 95,1086, 94,1082, 92,1078, 90,1074,
89,1070, 87,1066, 86,1061, 84,1057, 83,1053, 81,1049, 80,1045, 78,1040,
77,1036, 76,1032, 74,1027, 73,1023, 71,1019, 70,1014, 69,1010, 67,1005,
66,1001, 65, 997, 64, 992, 62, 988, 61, 983, 60, 978, 59, 974, 58, 969,
56, 965, 55, 960, 54, 955, 53, 951, 52, 946, 51, 941, 50, 937, 49, 932,
48, 927, 47, 923, 46, 918, 45, 913, 44, 908, 43, 904, 42, 899, 41, 894,
40, 889, 39, 884, 38, 880, 37, 875, 36, 870, 36, 865, 35, 860, 34, 855,
33, 851, 32, 846, 32, 841, 31, 836, 30, 831, 29, 826, 29, 821, 28, 816,
27, 811, 27, 806, 26, 802, 25, 797, 24, 792, 24, 787, 23, 782, 23, 777,
22, 772, 21, 767, 21, 762, 20, 757, 20, 752, 19, 747, 19, 742, 18, 737,
17, 732, 17, 728, 16, 723, 16, 718, 15, 713, 15, 708, 15, 703, 14, 698,
14, 693, 13, 688, 13, 683, 12, 678, 12, 674, 11, 669, 11, 664, 11, 659,
10, 654, 10, 649, 10, 644, 9, 640, 9, 635, 9, 630, 8, 625, 8, 620,
8, 615, 7, 611, 7, 606, 7, 601, 6, 596, 6, 592, 6, 587, 6, 582,
5, 577, 5, 573, 5, 568, 5, 563, 4, 559, 4, 554, 4, 550, 4, 545,
4, 540, 3, 536, 3, 531, 3, 527, 3, 522, 3, 517, 2, 513, 2, 508,
2, 504, 2, 499, 2, 495, 2, 491, 2, 486, 1, 482, 1, 477, 1, 473,
1, 469, 1, 464, 1, 460, 1, 456, 1, 451, 1, 447, 1, 443, 1, 439,
0, 434, 0, 430, 0, 426, 0, 422, 0, 418, 0, 414, 0, 410, 0, 405,
0, 401, 0, 397, 0, 393, 0, 389, 0, 385, 0, 381, 0, 378, 0, 374,
};
//// Counters
#define RATE( rate, div )\
(rate >= div ? rate / div * 8 - 1 : rate - 1)
static unsigned const counter_mask [32] =
{
RATE( 2,2), RATE(2048,4), RATE(1536,3),
RATE(1280,5), RATE(1024,4), RATE( 768,3),
RATE( 640,5), RATE( 512,4), RATE( 384,3),
RATE( 320,5), RATE( 256,4), RATE( 192,3),
RATE( 160,5), RATE( 128,4), RATE( 96,3),
RATE( 80,5), RATE( 64,4), RATE( 48,3),
RATE( 40,5), RATE( 32,4), RATE( 24,3),
RATE( 20,5), RATE( 16,4), RATE( 12,3),
RATE( 10,5), RATE( 8,4), RATE( 6,3),
RATE( 5,5), RATE( 4,4), RATE( 3,3),
RATE( 2,4),
RATE( 1,4)
};
#undef RATE
inline void SPC_DSP::init_counter()
{
// counters start out with this synchronization
m.counters [0] = 1;
m.counters [1] = 0;
m.counters [2] = -0x20u;
m.counters [3] = 0x0B;
int n = 2;
for ( int i = 1; i < 32; i++ )
{
m.counter_select [i] = &m.counters [n];
if ( !--n )
n = 3;
}
m.counter_select [ 0] = &m.counters [0];
m.counter_select [30] = &m.counters [2];
}
inline void SPC_DSP::run_counter( int i )
{
int n = m.counters [i];
if ( !(n-- & 7) )
n -= 6 - i;
m.counters [i] = n;
}
#define READ_COUNTER( rate )\
(*m.counter_select [rate] & counter_mask [rate])
//// Emulation
void SPC_DSP::run( int clock_count )
{
int new_phase = m.phase + clock_count;
int count = new_phase >> 5;
m.phase = new_phase & 31;
if ( !count )
return;
uint8_t* const ram = m.ram;
uint8_t const* const dir = &ram [REG(dir) * 0x100];
int const slow_gaussian = (REG(pmon) >> 1) | REG(non);
int const noise_rate = REG(flg) & 0x1F;
// Global volume
int mvoll = (int8_t) REG(mvoll);
int mvolr = (int8_t) REG(mvolr);
if ( mvoll * mvolr < m.surround_threshold )
mvoll = -mvoll; // eliminate surround
do
{
// KON/KOFF reading
if ( (m.every_other_sample ^= 1) != 0 )
{
m.new_kon &= ~m.kon;
m.kon = m.new_kon;
m.t_koff = REG(koff);
}
run_counter( 1 );
run_counter( 2 );
run_counter( 3 );
// Noise
if ( !READ_COUNTER( noise_rate ) )
{
int feedback = (m.noise << 13) ^ (m.noise << 14);
m.noise = (feedback & 0x4000) ^ (m.noise >> 1);
}
// Voices
int pmon_input = 0;
int main_out_l = 0;
int main_out_r = 0;
int echo_out_l = 0;
int echo_out_r = 0;
voice_t* v = m.voices;
uint8_t* v_regs = m.regs;
int vbit = 1;
do
{
#define SAMPLE_PTR(i) GET_LE16A( &dir [VREG(v_regs,srcn) * 4 + i * 2] )
int brr_header = ram [v->brr_addr];
int kon_delay = v->kon_delay;
// Pitch
int pitch = GET_LE16A( &VREG(v_regs,pitchl) ) & 0x3FFF;
if ( REG(pmon) & vbit )
pitch += ((pmon_input >> 5) * pitch) >> 10;
// KON phases
if ( --kon_delay >= 0 )
{
v->kon_delay = kon_delay;
// Get ready to start BRR decoding on next sample
if ( kon_delay == 4 )
{
v->brr_addr = SAMPLE_PTR( 0 );
v->brr_offset = 1;
v->buf_pos = v->buf;
brr_header = 0; // header is ignored on this sample
}
// Envelope is never run during KON
v->env = 0;
v->hidden_env = 0;
// Disable BRR decoding until last three samples
v->interp_pos = (kon_delay & 3 ? 0x4000 : 0);
// Pitch is never added during KON
pitch = 0;
}
int env = v->env;
// Gaussian interpolation
{
int output = 0;
VREG(v_regs,envx) = (uint8_t) (env >> 4);
if ( env )
{
// Make pointers into gaussian based on fractional position between samples
int offset = (unsigned) v->interp_pos >> 3 & 0x1FE;
short const* fwd = interleved_gauss + offset;
short const* rev = interleved_gauss + 510 - offset; // mirror left half of gaussian
int const* in = &v->buf_pos [(unsigned) v->interp_pos >> 12];
if ( !(slow_gaussian & vbit) ) // 99%
{
// Faster approximation when exact sample value isn't necessary for pitch mod
output = (fwd [0] * in [0] +
fwd [1] * in [1] +
rev [1] * in [2] +
rev [0] * in [3]) >> 11;
output = (output * env) >> 11;
}
else
{
output = (int16_t) (m.noise * 2);
if ( !(REG(non) & vbit) )
{
output = (fwd [0] * in [0]) >> 11;
output += (fwd [1] * in [1]) >> 11;
output += (rev [1] * in [2]) >> 11;
output = (int16_t) output;
output += (rev [0] * in [3]) >> 11;
CLAMP16( output );
output &= ~1;
}
output = (output * env) >> 11 & ~1;
}
// Output
int l = output * v->volume [0];
int r = output * v->volume [1];
main_out_l += l;
main_out_r += r;
if ( REG(eon) & vbit )
{
echo_out_l += l;
echo_out_r += r;
}
}
pmon_input = output;
VREG(v_regs,outx) = (uint8_t) (output >> 8);
}
// Soft reset or end of sample
if ( REG(flg) & 0x80 || (brr_header & 3) == 1 )
{
v->env_mode = env_release;
env = 0;
}
if ( m.every_other_sample )
{
// KOFF
if ( m.t_koff & vbit )
v->env_mode = env_release;
// KON
if ( m.kon & vbit )
{
v->kon_delay = 5;
v->env_mode = env_attack;
REG(endx) &= ~vbit;
}
}
// Envelope
if ( !v->kon_delay )
{
if ( v->env_mode == env_release ) // 97%
{
env -= 0x8;
v->env = env;
if ( env <= 0 )
{
v->env = 0;
goto skip_brr; // no BRR decoding for you!
}
}
else // 3%
{
int rate;
int const adsr0 = VREG(v_regs,adsr0);
int env_data = VREG(v_regs,adsr1);
if ( adsr0 >= 0x80 ) // 97% ADSR
{
if ( v->env_mode > env_decay ) // 89%
{
env--;
env -= env >> 8;
rate = env_data & 0x1F;
// optimized handling
v->hidden_env = env;
if ( READ_COUNTER( rate ) )
goto exit_env;
v->env = env;
goto exit_env;
}
else if ( v->env_mode == env_decay )
{
env--;
env -= env >> 8;
rate = (adsr0 >> 3 & 0x0E) + 0x10;
}
else // env_attack
{
rate = (adsr0 & 0x0F) * 2 + 1;
env += rate < 31 ? 0x20 : 0x400;
}
}
else // GAIN
{
int mode;
env_data = VREG(v_regs,gain);
mode = env_data >> 5;
if ( mode < 4 ) // direct
{
env = env_data * 0x10;
rate = 31;
}
else
{
rate = env_data & 0x1F;
if ( mode == 4 ) // 4: linear decrease
{
env -= 0x20;
}
else if ( mode < 6 ) // 5: exponential decrease
{
env--;
env -= env >> 8;
}
else // 6,7: linear increase
{
env += 0x20;
if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 )
env += 0x8 - 0x20; // 7: two-slope linear increase
}
}
}
// Sustain level
if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay )
v->env_mode = env_sustain;
v->hidden_env = env;
// unsigned cast because linear decrease going negative also triggers this
if ( (unsigned) env > 0x7FF )
{
env = (env < 0 ? 0 : 0x7FF);
if ( v->env_mode == env_attack )
v->env_mode = env_decay;
}
if ( !READ_COUNTER( rate ) )
v->env = env; // nothing else is controlled by the counter
}
}
exit_env:
{
// Apply pitch
int old_pos = v->interp_pos;
int interp_pos = (old_pos & 0x3FFF) + pitch;
if ( interp_pos > 0x7FFF )
interp_pos = 0x7FFF;
v->interp_pos = interp_pos;
// BRR decode if necessary
if ( old_pos >= 0x4000 )
{
// Arrange the four input nybbles in 0xABCD order for easy decoding
int nybbles = ram [(v->brr_addr + v->brr_offset) & 0xFFFF] * 0x100 +
ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF];
// Advance read position
int const brr_block_size = 9;
int brr_offset = v->brr_offset;
if ( (brr_offset += 2) >= brr_block_size )
{
// Next BRR block
int brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF;
assert( brr_offset == brr_block_size );
if ( brr_header & 1 )
{
brr_addr = SAMPLE_PTR( 1 );
if ( !v->kon_delay )
REG(endx) |= vbit;
}
v->brr_addr = brr_addr;
brr_offset = 1;
}
v->brr_offset = brr_offset;
// Decode
// 0: >>1 1: <<0 2: <<1 ... 12: <<11 13-15: >>4 <<11
static unsigned char const shifts [16 * 2] = {
13,12,12,12,12,12,12,12,12,12,12, 12, 12, 16, 16, 16,
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 11, 11
};
int const scale = brr_header >> 4;
int const right_shift = shifts [scale];
int const left_shift = shifts [scale + 16];
// Write to next four samples in circular buffer
int* pos = v->buf_pos;
int* end;
// Decode four samples
for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 )
{
// Extract upper nybble and scale appropriately
int s = ((int16_t) nybbles >> right_shift) << left_shift;
// Apply IIR filter (8 is the most commonly used)
int const filter = brr_header & 0x0C;
int const p1 = pos [brr_buf_size - 1];
int const p2 = pos [brr_buf_size - 2] >> 1;
if ( filter >= 8 )
{
s += p1;
s -= p2;
if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875
{
s += p2 >> 4;
s += (p1 * -3) >> 6;
}
else // s += p1 * 0.8984375 - p2 * 0.40625
{
s += (p1 * -13) >> 7;
s += (p2 * 3) >> 4;
}
}
else if ( filter ) // s += p1 * 0.46875
{
s += p1 >> 1;
s += (-p1) >> 5;
}
// Adjust and write sample
CLAMP16( s );
s = (int16_t) (s * 2);
pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around
}
if ( pos >= &v->buf [brr_buf_size] )
pos = v->buf;
v->buf_pos = pos;
}
}
skip_brr:
// Next voice
vbit <<= 1;
v_regs += 0x10;
v++;
}
while ( vbit < 0x100 );
// Echo position
int echo_offset = m.echo_offset;
uint8_t* const echo_ptr = &ram [(REG(esa) * 0x100 + echo_offset) & 0xFFFF];
if ( !echo_offset )
m.echo_length = (REG(edl) & 0x0F) * 0x800;
echo_offset += 4;
if ( echo_offset >= m.echo_length )
echo_offset = 0;
m.echo_offset = echo_offset;
// FIR
int echo_in_l = GET_LE16SA( echo_ptr + 0 );
int echo_in_r = GET_LE16SA( echo_ptr + 2 );
int (*echo_hist_pos) [2] = m.echo_hist_pos;
if ( ++echo_hist_pos >= &m.echo_hist [echo_hist_size] )
echo_hist_pos = m.echo_hist;
m.echo_hist_pos = echo_hist_pos;
echo_hist_pos [0] [0] = echo_hist_pos [8] [0] = echo_in_l;
echo_hist_pos [0] [1] = echo_hist_pos [8] [1] = echo_in_r;
#define CALC_FIR_( i, in ) ((in) * (int8_t) REG(fir + i * 0x10))
echo_in_l = CALC_FIR_( 7, echo_in_l );
echo_in_r = CALC_FIR_( 7, echo_in_r );
#define CALC_FIR( i, ch ) CALC_FIR_( i, echo_hist_pos [i + 1] [ch] )
#define DO_FIR( i )\
echo_in_l += CALC_FIR( i, 0 );\
echo_in_r += CALC_FIR( i, 1 );
DO_FIR( 0 );
DO_FIR( 1 );
DO_FIR( 2 );
#if defined (__MWERKS__) && __MWERKS__ < 0x3200
__eieio(); // keeps compiler from stupidly "caching" things in memory
#endif
DO_FIR( 3 );
DO_FIR( 4 );
DO_FIR( 5 );
DO_FIR( 6 );
// Echo out
if ( !(REG(flg) & 0x20) )
{
int l = (echo_out_l >> 7) + ((echo_in_l * (int8_t) REG(efb)) >> 14);
int r = (echo_out_r >> 7) + ((echo_in_r * (int8_t) REG(efb)) >> 14);
// just to help pass more validation tests
#if SPC_MORE_ACCURACY
l &= ~1;
r &= ~1;
#endif
CLAMP16( l );
CLAMP16( r );
SET_LE16A( echo_ptr + 0, l );
SET_LE16A( echo_ptr + 2, r );
}
// Sound out
int l = (main_out_l * mvoll + echo_in_l * (int8_t) REG(evoll)) >> 14;
int r = (main_out_r * mvolr + echo_in_r * (int8_t) REG(evolr)) >> 14;
CLAMP16( l );
CLAMP16( r );
if ( (REG(flg) & 0x40) )
{
l = 0;
r = 0;
}
sample_t* out = m.out;
WRITE_SAMPLES( l, r, out );
m.out = out;
}
while ( --count );
}
//// Setup
void SPC_DSP::mute_voices( int mask )
{
m.mute_mask = mask;
for ( int i = 0; i < voice_count; i++ )
{
m.voices [i].enabled = (mask >> i & 1) - 1;
update_voice_vol( i * 0x10 );
}
}
void SPC_DSP::init( void* ram_64k )
{
m.ram = (uint8_t*) ram_64k;
mute_voices( 0 );
disable_surround( false );
set_output( 0, 0 );
reset();
#ifndef NDEBUG
// be sure this sign-extends
assert( (int16_t) 0x8000 == -0x8000 );
// be sure right shift preserves sign
assert( (-1 >> 1) == -1 );
// check clamp macro
int i;
i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF );
i = -0x8001; CLAMP16( i ); assert( i == -0x8000 );
blargg_verify_byte_order();
#endif
}
void SPC_DSP::soft_reset_common()
{
require( m.ram ); // init() must have been called already
m.noise = 0x4000;
m.echo_hist_pos = m.echo_hist;
m.every_other_sample = 1;
m.echo_offset = 0;
m.phase = 0;
init_counter();
}
void SPC_DSP::soft_reset()
{
REG(flg) = 0xE0;
soft_reset_common();
}
void SPC_DSP::load( uint8_t const regs [register_count] )
{
memcpy( m.regs, regs, sizeof m.regs );
memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count );
// Internal state
int i;
for ( i = voice_count; --i >= 0; )
{
voice_t& v = m.voices [i];
v.brr_offset = 1;
v.buf_pos = v.buf;
}
m.new_kon = REG(kon);
mute_voices( m.mute_mask );
soft_reset_common();
}
void SPC_DSP::reset() { load( initial_regs ); }

212
snes_spc/fast_dsp/SPC_DSP.h Normal file
View file

@ -0,0 +1,212 @@
// Fast SNES SPC-700 DSP emulator (about 3x speed of accurate one)
// snes_spc 0.9.0
#ifndef SPC_DSP_H
#define SPC_DSP_H
#include "blargg_common.h"
struct SPC_DSP {
public:
typedef BOOST::uint8_t uint8_t;
// Setup
// Initializes DSP and has it use the 64K RAM provided
void init( void* ram_64k );
// Sets destination for output samples. If out is NULL or out_size is 0,
// doesn't generate any.
typedef short sample_t;
void set_output( sample_t* out, int out_size );
// Number of samples written to output since it was last set, always
// a multiple of 2. Undefined if more samples were generated than
// output buffer could hold.
int sample_count() const;
// Emulation
// Resets DSP to power-on state
void reset();
// Emulates pressing reset switch on SNES
void soft_reset();
// Reads/writes DSP registers. For accuracy, you must first call spc_run_dsp()
// to catch the DSP up to present.
int read ( int addr ) const;
void write( int addr, int data );
// Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks
// a pair of samples is be generated.
void run( int clock_count );
// Sound control
// Mutes voices corresponding to non-zero bits in mask (overrides VxVOL with 0).
// Reduces emulation accuracy.
enum { voice_count = 8 };
void mute_voices( int mask );
// If true, prevents channels and global volumes from being phase-negated
void disable_surround( bool disable = true );
// State
// Resets DSP and uses supplied values to initialize registers
enum { register_count = 128 };
void load( uint8_t const regs [register_count] );
// DSP register addresses
// Global registers
enum {
r_mvoll = 0x0C, r_mvolr = 0x1C,
r_evoll = 0x2C, r_evolr = 0x3C,
r_kon = 0x4C, r_koff = 0x5C,
r_flg = 0x6C, r_endx = 0x7C,
r_efb = 0x0D, r_pmon = 0x2D,
r_non = 0x3D, r_eon = 0x4D,
r_dir = 0x5D, r_esa = 0x6D,
r_edl = 0x7D,
r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F
};
// Voice registers
enum {
v_voll = 0x00, v_volr = 0x01,
v_pitchl = 0x02, v_pitchh = 0x03,
v_srcn = 0x04, v_adsr0 = 0x05,
v_adsr1 = 0x06, v_gain = 0x07,
v_envx = 0x08, v_outx = 0x09
};
public:
enum { extra_size = 16 };
sample_t* extra() { return m.extra; }
sample_t const* out_pos() const { return m.out; }
public:
BLARGG_DISABLE_NOTHROW
typedef BOOST::int8_t int8_t;
typedef BOOST::int16_t int16_t;
enum { echo_hist_size = 8 };
enum env_mode_t { env_release, env_attack, env_decay, env_sustain };
enum { brr_buf_size = 12 };
struct voice_t
{
int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling)
int* buf_pos; // place in buffer where next samples will be decoded
int interp_pos; // relative fractional position in sample (0x1000 = 1.0)
int brr_addr; // address of current BRR block
int brr_offset; // current decoding offset in BRR block
int kon_delay; // KON delay/current setup phase
env_mode_t env_mode;
int env; // current envelope level
int hidden_env; // used by GAIN mode 7, very obscure quirk
int volume [2]; // copy of volume from DSP registers, with surround disabled
int enabled; // -1 if enabled, 0 if muted
};
private:
struct state_t
{
uint8_t regs [register_count];
// Echo history keeps most recent 8 samples (twice the size to simplify wrap handling)
int echo_hist [echo_hist_size * 2] [2];
int (*echo_hist_pos) [2]; // &echo_hist [0 to 7]
int every_other_sample; // toggles every sample
int kon; // KON value when last checked
int noise;
int echo_offset; // offset from ESA in echo buffer
int echo_length; // number of bytes that echo_offset will stop at
int phase; // next clock cycle to run (0-31)
unsigned counters [4];
int new_kon;
int t_koff;
voice_t voices [voice_count];
unsigned* counter_select [32];
// non-emulation state
uint8_t* ram; // 64K shared RAM between DSP and SMP
int mute_mask;
int surround_threshold;
sample_t* out;
sample_t* out_end;
sample_t* out_begin;
sample_t extra [extra_size];
};
state_t m;
void init_counter();
void run_counter( int );
void soft_reset_common();
void write_outline( int addr, int data );
void update_voice_vol( int addr );
};
#include <assert.h>
inline int SPC_DSP::sample_count() const { return int(m.out - m.out_begin); }
inline int SPC_DSP::read( int addr ) const
{
assert( (unsigned) addr < register_count );
return m.regs [addr];
}
inline void SPC_DSP::update_voice_vol( int addr )
{
int l = (int8_t) m.regs [addr + v_voll];
int r = (int8_t) m.regs [addr + v_volr];
if ( l * r < m.surround_threshold )
{
// signs differ, so negate those that are negative
l ^= l >> 7;
r ^= r >> 7;
}
voice_t& v = m.voices [addr >> 4];
int enabled = v.enabled;
v.volume [0] = l & enabled;
v.volume [1] = r & enabled;
}
inline void SPC_DSP::write( int addr, int data )
{
assert( (unsigned) addr < register_count );
m.regs [addr] = (uint8_t) data;
int low = addr & 0x0F;
if ( low < 0x2 ) // voice volumes
{
update_voice_vol( low ^ addr );
}
else if ( low == 0xC )
{
if ( addr == r_kon )
m.new_kon = (uint8_t) data;
if ( addr == r_endx ) // always cleared, regardless of data written
m.regs [r_endx] = 0;
}
}
inline void SPC_DSP::disable_surround( bool disable )
{
m.surround_threshold = disable ? 0 : -0x4000;
}
#define SPC_NO_COPY_STATE_FUNCS 1
#define SPC_LESS_ACCURATE 1
#endif

504
snes_spc/license.txt Normal file
View file

@ -0,0 +1,504 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library 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 library 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 library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

86
snes_spc/readme.txt Normal file
View file

@ -0,0 +1,86 @@
snes_spc 0.9.0: SNES SPC-700 APU Emulator
-----------------------------------------
This library includes a full SPC emulator and an S-DSP emulator that can
be used on its own. Two S-DSP emulators are available: a highly accurate
one for use in a SNES emulator, and a 3x faster one for use in an SPC
music player or a resource-limited SNES emulator.
* Can be used from C and C++ code
* Full SPC-700 APU emulator with cycle accuracy in most cases
* Loads, plays, and saves SPC music files
* Can save and load exact full emulator state
* DSP voice muting, surround sound disable, and song tempo adjustment
* Uses 7% CPU average on 400 MHz Mac to play an SPC using fast DSP
The accurate DSP emulator is based on past research by others and
hundreds of hours of recent research by me. It passes over a hundred
strenuous timing and behavior validation tests that were also run on the
SNES. As far as I know, it's the first DSP emulator with cycle accuracy,
properly emulating every DSP register and memory access at the exact SPC
cycle it occurs at, whereas previous DSP emulators emulated these only
to the nearest sample (which occurs every 32 clocks).
Author : Shay Green <gblargg@gmail.com>
Website: http://www.slack.net/~ant/
Forum : http://groups.google.com/group/blargg-sound-libs
License: GNU Lesser General Public License (LGPL)
Getting Started
---------------
Build a program consisting of demo/play_spc.c, demo/demo_util.c,
demo/wave_writer.c, and all source files in snes_spc/. Put an SPC music
file in the same directory and name it "test.spc". Running the program
should generate the recording "out.wav".
Read snes_spc.txt for more information. Post to the discussion forum for
assistance.
Files
-----
snes_spc.txt Documentation
changes.txt Change log
license.txt GNU LGPL license
demo/
play_spc.c Records SPC file to wave sound file
benchmark.c Finds how fast emulator runs on your computer
trim_spc.c Trims silence off beginning of an SPC file
save_state.c Saves/loads exact emulator state to/from file
comm.c Communicates with SPC how SNES would
demo_util.h General utility functions used by demos
demo_util.c
wave_writer.h WAVE sound file writer used for demo output
wave_writer.c
fast_dsp/ Optional standalone fast DSP emulator
SPC_DSP.h To use with full SPC emulator, move into
SPC_DSP.cpp snes_spc/ and replace original files
snes_spc/ Library sources
blargg_config.h Configuration (modify as necessary)
spc.h C interface to SPC emulator and sound filter
spc.cpp
SPC_Filter.h Optional filter to make sound more authentic
SPC_Filter.cpp
SNES_SPC.h Full SPC emulator
SNES_SPC.cpp
SNES_SPC_misc.cpp
SNES_SPC_state.cpp
SPC_CPU.h
dsp.h C interface to DSP emulator
dsp.cpp
SPC_DSP.h Standalone accurate DSP emulator
SPC_DSP.cpp
blargg_common.h
blargg_endian.h
blargg_source.h
--
Shay Green <gblargg@gmail.com>

File diff suppressed because it is too large Load diff

304
snes_spc/slow_dsp/SPC_DSP.h Normal file
View file

@ -0,0 +1,304 @@
// Highly accurate SNES SPC-700 DSP emulator
// snes_spc 0.9.0
#ifndef SPC_DSP_H
#define SPC_DSP_H
#include "blargg_common.h"
extern "C" { typedef void (*dsp_copy_func_t)( unsigned char** io, void* state, size_t ); }
struct SPC_DSP {
public:
typedef BOOST::uint8_t uint8_t;
// Setup
// Initializes DSP and has it use the 64K RAM provided
void init( void* ram_64k );
// Sets destination for output samples. If out is NULL or out_size is 0,
// doesn't generate any.
typedef short sample_t;
void set_output( sample_t* out, int out_size );
// Number of samples written to output since it was last set, always
// a multiple of 2. Undefined if more samples were generated than
// output buffer could hold.
int sample_count() const;
// Emulation
// Resets DSP to power-on state
void reset();
// Emulates pressing reset switch on SNES
void soft_reset();
// Reads/writes DSP registers. For accuracy, you must first call run()
// to catch the DSP up to present.
int read ( int addr ) const;
void write( int addr, int data );
// Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks
// a pair of samples is be generated.
void run( int clock_count );
// Sound control
// Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events).
// Reduces emulation accuracy.
enum { voice_count = 8 };
void mute_voices( int mask );
// State
// Resets DSP and uses supplied values to initialize registers
enum { register_count = 128 };
void load( uint8_t const regs [register_count] );
// Saves/loads exact emulator state
enum { state_size = 640 }; // maximum space needed when saving
typedef dsp_copy_func_t copy_func_t;
void copy_state( unsigned char** io, copy_func_t );
// Returns non-zero if new key-on events occurred since last call
bool check_kon();
// DSP register addresses
// Global registers
enum {
r_mvoll = 0x0C, r_mvolr = 0x1C,
r_evoll = 0x2C, r_evolr = 0x3C,
r_kon = 0x4C, r_koff = 0x5C,
r_flg = 0x6C, r_endx = 0x7C,
r_efb = 0x0D, r_pmon = 0x2D,
r_non = 0x3D, r_eon = 0x4D,
r_dir = 0x5D, r_esa = 0x6D,
r_edl = 0x7D,
r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F
};
// Voice registers
enum {
v_voll = 0x00, v_volr = 0x01,
v_pitchl = 0x02, v_pitchh = 0x03,
v_srcn = 0x04, v_adsr0 = 0x05,
v_adsr1 = 0x06, v_gain = 0x07,
v_envx = 0x08, v_outx = 0x09
};
public:
enum { extra_size = 16 };
sample_t* extra() { return m.extra; }
sample_t const* out_pos() const { return m.out; }
void disable_surround( bool ) { } // not supported
public:
BLARGG_DISABLE_NOTHROW
typedef BOOST::int8_t int8_t;
typedef BOOST::int16_t int16_t;
enum { echo_hist_size = 8 };
enum env_mode_t { env_release, env_attack, env_decay, env_sustain };
enum { brr_buf_size = 12 };
struct voice_t
{
int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling)
int buf_pos; // place in buffer where next samples will be decoded
int interp_pos; // relative fractional position in sample (0x1000 = 1.0)
int brr_addr; // address of current BRR block
int brr_offset; // current decoding offset in BRR block
uint8_t* regs; // pointer to voice's DSP registers
int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc.
int kon_delay; // KON delay/current setup phase
env_mode_t env_mode;
int env; // current envelope level
int hidden_env; // used by GAIN mode 7, very obscure quirk
uint8_t t_envx_out;
};
private:
enum { brr_block_size = 9 };
struct state_t
{
uint8_t regs [register_count];
// Echo history keeps most recent 8 samples (twice the size to simplify wrap handling)
int echo_hist [echo_hist_size * 2] [2];
int (*echo_hist_pos) [2]; // &echo_hist [0 to 7]
int every_other_sample; // toggles every sample
int kon; // KON value when last checked
int noise;
int counter;
int echo_offset; // offset from ESA in echo buffer
int echo_length; // number of bytes that echo_offset will stop at
int phase; // next clock cycle to run (0-31)
bool kon_check; // set when a new KON occurs
// Hidden registers also written to when main register is written to
int new_kon;
uint8_t endx_buf;
uint8_t envx_buf;
uint8_t outx_buf;
// Temporary state between clocks
// read once per sample
int t_pmon;
int t_non;
int t_eon;
int t_dir;
int t_koff;
// read a few clocks ahead then used
int t_brr_next_addr;
int t_adsr0;
int t_brr_header;
int t_brr_byte;
int t_srcn;
int t_esa;
int t_echo_enabled;
// internal state that is recalculated every sample
int t_dir_addr;
int t_pitch;
int t_output;
int t_looped;
int t_echo_ptr;
// left/right sums
int t_main_out [2];
int t_echo_out [2];
int t_echo_in [2];
voice_t voices [voice_count];
// non-emulation state
uint8_t* ram; // 64K shared RAM between DSP and SMP
int mute_mask;
sample_t* out;
sample_t* out_end;
sample_t* out_begin;
sample_t extra [extra_size];
};
state_t m;
void init_counter();
void run_counters();
unsigned read_counter( int rate );
int interpolate( voice_t const* v );
void run_envelope( voice_t* const v );
void decode_brr( voice_t* v );
void misc_27();
void misc_28();
void misc_29();
void misc_30();
void voice_output( voice_t const* v, int ch );
void voice_V1( voice_t* const );
void voice_V2( voice_t* const );
void voice_V3( voice_t* const );
void voice_V3a( voice_t* const );
void voice_V3b( voice_t* const );
void voice_V3c( voice_t* const );
void voice_V4( voice_t* const );
void voice_V5( voice_t* const );
void voice_V6( voice_t* const );
void voice_V7( voice_t* const );
void voice_V8( voice_t* const );
void voice_V9( voice_t* const );
void voice_V7_V4_V1( voice_t* const );
void voice_V8_V5_V2( voice_t* const );
void voice_V9_V6_V3( voice_t* const );
void echo_read( int ch );
int echo_output( int ch );
void echo_write( int ch );
void echo_22();
void echo_23();
void echo_24();
void echo_25();
void echo_26();
void echo_27();
void echo_28();
void echo_29();
void echo_30();
void soft_reset_common();
};
#include <assert.h>
inline int SPC_DSP::sample_count() const { return int(m.out - m.out_begin); }
inline int SPC_DSP::read( int addr ) const
{
assert( (unsigned) addr < register_count );
return m.regs [addr];
}
inline void SPC_DSP::write( int addr, int data )
{
assert( (unsigned) addr < register_count );
m.regs [addr] = (uint8_t) data;
switch ( addr & 0x0F )
{
case v_envx:
m.envx_buf = (uint8_t) data;
break;
case v_outx:
m.outx_buf = (uint8_t) data;
break;
case 0x0C:
if ( addr == r_kon )
m.new_kon = (uint8_t) data;
if ( addr == r_endx ) // always cleared, regardless of data written
{
m.endx_buf = 0;
m.regs [r_endx] = 0;
}
break;
}
}
inline void SPC_DSP::mute_voices( int mask ) { m.mute_mask = mask; }
inline bool SPC_DSP::check_kon()
{
bool old = m.kon_check;
m.kon_check = 0;
return old;
}
#if !SPC_NO_COPY_STATE_FUNCS
class SPC_State_Copier {
SPC_DSP::copy_func_t func;
unsigned char** buf;
public:
SPC_State_Copier( unsigned char** p, SPC_DSP::copy_func_t f ) { func = f; buf = p; }
void copy( void* state, size_t size );
int copy_int( int state, int size );
void skip( int count );
void extra();
};
#define SPC_COPY( type, state )\
{\
state = (BOOST::type) copier.copy_int( state, sizeof (BOOST::type) );\
assert( (BOOST::type) state == state );\
}
#endif
#endif

318
snes_spc/snes_spc.txt Normal file
View file

@ -0,0 +1,318 @@
snes_spc 0.9.0: SNES SPC-700 APU Emulator
-----------------------------------------
Author : Shay Green <gblargg@gmail.com>
Website: http://www.slack.net/~ant/
Forum : http://groups.google.com/group/blargg-sound-libs
License: GNU Lesser General Public License (LGPL)
Contents
--------
* C and C++ Interfaces
* Overview
* Full SPC Emulation
* DSP Emulation
* SPC Music Playback
* State Copying
* Library Compilation
* Error handling
* Solving Problems
* Accurate S-DSP Limitations
* Fast S-DSP Limitations
* S-SMP Limitations
* To Do
* Thanks
C and C++ Interfaces
--------------------
The library includes a C interface in spc.h and dsp.h, which can be used
from C and C++. This C interface is referred to in the following
documentation. If you're building this as a shared library (rather than
linking statically), you should use the C interface since it will change
less in future versions.
The native C++ interface is in the header files SNES_SPC.h, SPC_DSP.h,
and SPC_Filter.h, and the two interfaces can be freely mixed in C++
code. Conversion between the two interfaces generally follows a pattern:
C interface C++ interface
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SNES_SPC* spc; SNES_SPC* spc;
spc = spc_new(); spc = new SNES_SPC;
spc_play( spc, count, buf ); spc->play( count, buf );
spc_sample_rate SNES_SPC::sample_rate
spc_delete( spc ); delete spc;
Overview
--------
There are three main roles for this library:
* Full SPC emulation in a SNES emulator
* DSP emulation in a SNES emulator (where you emulate the SMP CPU)
* SPC playback in an SPC music file player
Each of these uses are described separately below.
Full SPC Emulation
------------------
See spc.h for full function reference (SNES_SPC.h if using C++).
* Create SPC emulator with spc_new() and check for NULL.
* Call spc_init_rom() with a pointer to the 64-byte IPL ROM dump (not
included with library).
* When your emulated SNES is powered on, call spc_reset(). When the
reset switch is pressed, call spc_soft_reset().
* Call spc_set_output() with your output buffer, then do emulation.
* When the SNES CPU accesses APU ports, call spc_read_port() and
spc_write_port().
* When your emulator's timebase is going back to 0, call
spc_end_frame(), usually at the end of a video frame or scanline.
* Periodically play samples from your buffer. Use spc_sample_count() to
find out how many samples have been written, then spc_set_output() after
you've made more space in your buffer.
* Save/load full emulator state with spc_copy_state().
* You can save as an SPC music file with spc_save_spc().
* When done, use spc_delete() to free memory.
DSP Emulation
-------------
See dsp.h for full function reference (SPC_DSP.h if using C++).
* Create DSP emulator with spc_dsp_new() and check for NULL.
* Let the DSP know where your 64K RAM is with spc_dsp_init().
* When your emulated SNES is powered on, call spc_dsp_reset(). When the
reset switch is pressed, call spc_dsp_soft_reset().
* Call spc_dsp_set_output() with your output buffer, then do emulation.
* Use spc_dsp_run() to run DSP for specified number of clocks (1024000
per second). Every 32 clocks a pair of samples is added to your output
buffer.
* Use spc_dsp_read() and spc_dsp_write() to handle DSP reads/writes from
the S-SMP. Before calling these always catch the DSP up to present time
with spc_dsp_run().
* Periodically play samples from your buffer. Use spc_dsp_sample_count()
to find out how many samples have been written, then
spc_dsp_set_output() after you've made more space in your buffer.
* Use spc_dsp_copy_state() to save/load full DSP state.
* When done, use spc_delete() to free memory.
SPC Music Playback
------------------
See spc.h for full function reference (SNES_SPC.h if using C++).
* Create SPC emulator with spc_new() and check for NULL.
* Load SPC with spc_load_spc() and check for error.
* Optionally cear echo buffer with spc_clear_echo(). Many SPCs have
garbage in echo buffer, which causes noise at the beginning.
* Generate samples as needed with spc_play().
* When done, use spc_delete() to free memory.
* For a more complete game music playback library, use Game_Music_Emu
State Copying
-------------
The SPC and DSP modules include state save/load functions. They take a
pointer to a pointer to a buffer to store state, and a copy function.
This copy function can either copy data to the buffer or from it,
allowing state save and restore with the same library function. The
internal save state format allows for future expansion without making
previous save states unusable.
The SPC save state format puts the most important parts first to make it
easier to manually examine. It's organized as follows:
Offset Size Data
- - - - - - - - - - - - - - - - - -
0 $10000 SPC RAM
$10000 $10 SMP $F0-$FF registers
$10010 4 SMP $F4-$F8 output registers
$10014 2 PC
$10016 1 A
$10017 1 X
$10018 1 Y
$10019 1 PSW
$1001A 1 SP
$1001B 5 internal
$10020 $80 DSP registers
$100A0 ... internal
Library Compilation
-------------------
While this library is in C++, it has been written to easily link in a C
program *without* needing the standard C++ library. It doesn't use
exception handling or run-time type information (RTTI), so you can
disable these in your C++ compiler to increase efficiency.
If you're building a shared library (DLL), I recommend only exporting
the C interfaces in spc.h and dsp.h, as the C++ interfaces expose
implementation details that will break link compatibility across
versions.
If you're using C and compiling with GCC, I recommend the following
command-line options when compiling the library source, otherwise GCC
will insert calls to the standard C++ library and require that it be
linked in:
-fno-rtti -fno-exceptions
For maximum optimization, see the NDEBUG and BLARGG_NONPORTABLE options
in blargg_config. If using GCC, you can enable these by adding the
following command-line options when you invoke gcc. If you encounter
problems, try without -DBLARGG_NONPORTABLE; if that works, contact me so
I can figure out why BLARGG_NONPORTABLE was causing it to fail.
-O3 -DNDEBUG -DBLARGG_NONPORTABLE -fno-rtti -fno-exceptions
Error handling
--------------
Functions which can fail have a return type of spc_err_t (blargg_err_t
in the C++ interfaces), which is a pointer to an error string (const
char*). If a function is successful it returns NULL. Errors that you can
easily avoid are checked with debug assertions; spc_err_t return values
are only used for genuine run-time errors that can't be easily predicted
in advance (out of memory, I/O errors, incompatible file data). Your
code should check all error values.
To improve usability for C programmers, C++ programmers unfamiliar with
exceptions, and compatibility with older C++ compilers, the library does
*not* throw any C++ exceptions and uses malloc() instead of the standard
operator new. This means that you *must* check for NULL when creating a
library object with the new operator.
Solving Problems
----------------
If you're having problems, try the following:
* If you're getting garbled sound, try this simple siren generator in
place of your call to play(). This will quickly tell whether the problem
is in the library or in your code.
static void play_siren( long count, short* out )
{
static double a, a2;
while ( count-- )
*out++ = 0x2000 * sin( a += .1 + .05*sin( a2+=.00005 ) );
}
* Enable debugging support in your environment. This enables assertions
and other run-time checks.
* Turn the compiler's optimizer is off. Sometimes an optimizer generates
bad code.
* If multiple threads are being used, ensure that only one at a time is
accessing a given set of objects from the library. This library is not
in general thread-safe, though independent objects can be used in
separate threads.
* If all else fails, see if the demos work.
Accurate S-DSP Limitations
--------------------------
* Power-up and soft reset behavior might have slight inaccuracies.
* Muting (FLG bit 6) behavior when toggling bit very rapidly is not
emulated properly.
* No other known inaccuracies. Has passed 100+ strenuous tests.
Fast S-DSP Limitations
----------------------
* Uses faster sample calculations except in cases where exact value is
actually important (BRR decoding, and gaussian interpolation combined
with pitch modulation).
* Stops decoding BRR data when a voice's envelope has released to
silence.
* Emulates 32 clocks at a time, so DSP register and memory accesses are
all done in a bunch rather than spread out. Even though, some clever
code makes register accesses separated by 40 or so clocks occur with
cycle-accurate timing.
S-SMP Limitations
-----------------
* Opcode fetches and indirect pointers are always read directly from
memory, even for the $F0-$FF region, and the DSP is not caught up for
these fetches.
* Attempts to perversely execute data in registers or an area being
modified by echo will not be emulated properly.
* Has not been thoroughly tested.
* Test register ($F0) is not implemented.
* Echo buffer can overwrite IPL ROM area, and does not correctly update
extra RAM there.
To Do
-----
* I'd like feedback on the interface and any ways to improve it. In
particular, the differing features between the accurate and fast DSP
emulators might make it harder to cleanly switch between them without
modifying source code.
* Finish thorough tests on SMP memory access times.
* Finish thorough tests on SMP instruction behavior (flags, registers).
* Finish thorough tests on SMP timers.
* Finish power-up and reset behavior testing.
* Come up with best starting conditions to play an SPC and implement in
hardware SNES SPC player for verification.
Thanks
------
Thanks to Anti-Resonance's SPC2ROM and help getting SPCs playing on my
SNES in the first place, then Brad Martin's openspc and Chris Moeller's
openspc++ C++ adaptation, giving me a good SPC emulator to start with
several years ago. Thanks to Richard Bannister, Mahendra Tallur, Shazz,
nenolod, theHobbit, Johan Samuelsson, nes6502, and Micket for helping
test my Game_Music_Emu library. Thanks to hcs for help in converting to
C for the Rockbox port. Thanks to byuu (bsnes author) and pagefault and
Nach (zsnes team) for testing and using my new rewritten DSP in their
emulators. Thanks to anomie for his good SNES documentation and
discussions with me to keep it up to date with my latest findings.
--
Shay Green <gblargg@gmail.com>

397
snes_spc/snes_spc.vcproj Normal file
View file

@ -0,0 +1,397 @@
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="8.00"
Name="snes_spc"
ProjectGUID="{E83FD370-2E72-4D4C-9427-FF9D9DED1E88}"
RootNamespace="snes_spc"
Keyword="Win32Proj"
>
<Platforms>
<Platform
Name="Win32"
/>
</Platforms>
<ToolFiles>
</ToolFiles>
<Configurations>
<Configuration
Name="Debug|Win32"
OutputDirectory="$(SolutionDir)$(ConfigurationName)"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="4"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
PreprocessorDefinitions="WIN32;_DEBUG;_LIB"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="4"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLibrarianTool"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="$(SolutionDir)$(ConfigurationName)"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="4"
CharacterSet="2"
WholeProgramOptimization="1"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1"
OmitFramePointers="true"
WholeProgramOptimization="false"
PreprocessorDefinitions="WIN32;NDEBUG;_LIB"
StringPooling="true"
ExceptionHandling="0"
RuntimeLibrary="0"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="3"
CallingConvention="1"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLibrarianTool"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release DLL|Win32"
OutputDirectory="$(SolutionDir)$(ConfigurationName)"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="2"
CharacterSet="2"
WholeProgramOptimization="0"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1"
OmitFramePointers="true"
WholeProgramOptimization="false"
PreprocessorDefinitions="WIN32;NDEBUG;_LIB"
StringPooling="true"
ExceptionHandling="0"
RuntimeLibrary="0"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="3"
CallingConvention="1"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCWebDeploymentTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Debug DLL|Win32"
OutputDirectory="$(SolutionDir)$(ConfigurationName)"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="2"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
PreprocessorDefinitions="WIN32;_DEBUG;_LIB"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="4"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
OptimizeReferences="1"
EnableCOMDATFolding="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCWebDeploymentTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<Filter
Name="Source Files"
Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
>
<File
RelativePath=".\snes_spc\dsp.cpp"
>
</File>
<File
RelativePath=".\snes_spc\SNES_SPC.cpp"
>
</File>
<File
RelativePath=".\snes_spc\SNES_SPC_misc.cpp"
>
</File>
<File
RelativePath=".\snes_spc\SNES_SPC_state.cpp"
>
</File>
<File
RelativePath=".\snes_spc\spc.cpp"
>
</File>
<File
RelativePath=".\snes_spc\SPC_DSP.cpp"
>
</File>
<File
RelativePath=".\snes_spc\SPC_Filter.cpp"
>
</File>
</Filter>
<Filter
Name="Header Files"
Filter="h;hpp;hxx;hm;inl;inc;xsd"
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
>
<File
RelativePath=".\snes_spc\blargg_common.h"
>
</File>
<File
RelativePath=".\snes_spc\blargg_config.h"
>
</File>
<File
RelativePath=".\snes_spc\blargg_endian.h"
>
</File>
<File
RelativePath=".\snes_spc\blargg_source.h"
>
</File>
<File
RelativePath=".\snes_spc\dsp.h"
>
</File>
<File
RelativePath=".\snes_spc\SNES_SPC.h"
>
</File>
<File
RelativePath=".\snes_spc\spc.h"
>
</File>
<File
RelativePath=".\snes_spc\SPC_CPU.h"
>
</File>
<File
RelativePath=".\snes_spc\SPC_DSP.h"
>
</File>
<File
RelativePath=".\snes_spc\SPC_Filter.h"
>
</File>
</Filter>
<Filter
Name="Resource Files"
Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
>
</Filter>
<File
RelativePath=".\readme.txt"
>
</File>
<File
RelativePath=".\snes_spc.txt"
>
</File>
</Files>
<Globals>
</Globals>
</VisualStudioProject>

View file

@ -0,0 +1,564 @@
// Core SPC emulation: CPU, timers, SMP registers, memory
// snes_spc 0.9.0. http://www.slack.net/~ant/
#include "SNES_SPC.h"
#include <string.h>
/* Copyright (C) 2004-2007 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"
#define RAM (m.ram.ram)
#define REGS (m.smp_regs [0])
#define REGS_IN (m.smp_regs [1])
// (n ? n : 256)
#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
// Note: SPC_MORE_ACCURACY exists mainly so I can run my validation tests, which
// do crazy echo buffer accesses.
#ifndef SPC_MORE_ACCURACY
#define SPC_MORE_ACCURACY 0
#endif
#ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER
#endif
//// Timers
#if SPC_DISABLE_TEMPO
#define TIMER_DIV( t, n ) ((n) >> t->prescaler)
#define TIMER_MUL( t, n ) ((n) << t->prescaler)
#else
#define TIMER_DIV( t, n ) ((n) / t->prescaler)
#define TIMER_MUL( t, n ) ((n) * t->prescaler)
#endif
SNES_SPC::Timer* SNES_SPC::run_timer_( Timer* t, rel_time_t time )
{
int elapsed = TIMER_DIV( t, time - t->next_time ) + 1;
t->next_time += TIMER_MUL( t, elapsed );
if ( t->enabled )
{
int remain = IF_0_THEN_256( t->period - t->divider );
int divider = t->divider + elapsed;
int over = elapsed - remain;
if ( over >= 0 )
{
int n = over / t->period;
t->counter = (t->counter + 1 + n) & 0x0F;
divider = over - n * t->period;
}
t->divider = (uint8_t) divider;
}
return t;
}
inline SNES_SPC::Timer* SNES_SPC::run_timer( Timer* t, rel_time_t time )
{
if ( time >= t->next_time )
t = run_timer_( t, time );
return t;
}
//// ROM
void SNES_SPC::enable_rom( int enable )
{
if ( m.rom_enabled != enable )
{
m.rom_enabled = enable;
if ( enable )
memcpy( m.hi_ram, &RAM [rom_addr], sizeof m.hi_ram );
memcpy( &RAM [rom_addr], (enable ? m.rom : m.hi_ram), rom_size );
// TODO: ROM can still get overwritten when DSP writes to echo buffer
}
}
//// DSP
#if SPC_LESS_ACCURATE
int const max_reg_time = 29;
signed char const SNES_SPC::reg_times_ [256] =
{
-1, 0,-11,-10,-15,-11, -2, -2, 4, 3, 14, 14, 26, 26, 14, 22,
2, 3, 0, 1,-12, 0, 1, 1, 7, 6, 14, 14, 27, 14, 14, 23,
5, 6, 3, 4, -1, 3, 4, 4, 10, 9, 14, 14, 26, -5, 14, 23,
8, 9, 6, 7, 2, 6, 7, 7, 13, 12, 14, 14, 27, -4, 14, 24,
11, 12, 9, 10, 5, 9, 10, 10, 16, 15, 14, 14, -2, -4, 14, 24,
14, 15, 12, 13, 8, 12, 13, 13, 19, 18, 14, 14, -2,-36, 14, 24,
17, 18, 15, 16, 11, 15, 16, 16, 22, 21, 14, 14, 28, -3, 14, 25,
20, 21, 18, 19, 14, 18, 19, 19, 25, 24, 14, 14, 14, 29, 14, 25,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
};
#define RUN_DSP( time, offset ) \
int count = (time) - (offset) - m.dsp_time;\
if ( count >= 0 )\
{\
int clock_count = (count & ~(clocks_per_sample - 1)) + clocks_per_sample;\
m.dsp_time += clock_count;\
dsp.run( clock_count );\
}
#else
#define RUN_DSP( time, offset ) \
{\
int count = (time) - m.dsp_time;\
if ( !SPC_MORE_ACCURACY || count )\
{\
assert( count > 0 );\
m.dsp_time = (time);\
dsp.run( count );\
}\
}
#endif
int SNES_SPC::dsp_read( rel_time_t time )
{
RUN_DSP( time, reg_times [REGS [r_dspaddr] & 0x7F] );
int result = dsp.read( REGS [r_dspaddr] & 0x7F );
#ifdef SPC_DSP_READ_HOOK
SPC_DSP_READ_HOOK( spc_time + time, (REGS [r_dspaddr] & 0x7F), result );
#endif
return result;
}
inline void SNES_SPC::dsp_write( int data, rel_time_t time )
{
RUN_DSP( time, reg_times [REGS [r_dspaddr]] )
#if SPC_LESS_ACCURATE
else if ( m.dsp_time == skipping_time )
{
int r = REGS [r_dspaddr];
if ( r == SPC_DSP::r_kon )
m.skipped_kon |= data & ~dsp.read( SPC_DSP::r_koff );
if ( r == SPC_DSP::r_koff )
{
m.skipped_koff |= data;
m.skipped_kon &= ~data;
}
}
#endif
#ifdef SPC_DSP_WRITE_HOOK
SPC_DSP_WRITE_HOOK( m.spc_time + time, REGS [r_dspaddr], (uint8_t) data );
#endif
if ( REGS [r_dspaddr] <= 0x7F )
dsp.write( REGS [r_dspaddr], data );
else if ( !SPC_MORE_ACCURACY )
dprintf( "SPC wrote to DSP register > $7F\n" );
}
//// Memory access extras
#if SPC_MORE_ACCURACY
#define MEM_ACCESS( time, addr ) \
{\
if ( time >= m.dsp_time )\
{\
RUN_DSP( time, max_reg_time );\
}\
}
#elif !defined (NDEBUG)
// Debug-only check for read/write within echo buffer, since this might result in
// inaccurate emulation due to the DSP not being caught up to the present.
bool SNES_SPC::check_echo_access( int addr )
{
if ( !(dsp.read( SPC_DSP::r_flg ) & 0x20) )
{
int start = 0x100 * dsp.read( SPC_DSP::r_esa );
int size = 0x800 * (dsp.read( SPC_DSP::r_edl ) & 0x0F);
int end = start + (size ? size : 4);
if ( start <= addr && addr < end )
{
if ( !m.echo_accessed )
{
m.echo_accessed = 1;
return true;
}
}
}
return false;
}
#define MEM_ACCESS( time, addr ) check( !check_echo_access( (uint16_t) addr ) );
#else
#define MEM_ACCESS( time, addr )
#endif
//// CPU write
#if SPC_MORE_ACCURACY
static unsigned char const glitch_probs [3] [256] =
{
0xC3,0x92,0x5B,0x1C,0xD1,0x92,0x5B,0x1C,0xDB,0x9C,0x72,0x18,0xCD,0x5C,0x38,0x0B,
0xE1,0x9C,0x74,0x17,0xCF,0x75,0x45,0x0C,0xCF,0x6E,0x4A,0x0D,0xA3,0x3A,0x1D,0x08,
0xDB,0xA0,0x82,0x19,0xD9,0x73,0x3C,0x0E,0xCB,0x76,0x52,0x0B,0xA5,0x46,0x1D,0x09,
0xDA,0x74,0x55,0x0F,0xA2,0x3F,0x21,0x05,0x9A,0x40,0x20,0x07,0x63,0x1E,0x10,0x01,
0xDF,0xA9,0x85,0x1D,0xD3,0x84,0x4B,0x0E,0xCF,0x6F,0x49,0x0F,0xB3,0x48,0x1E,0x05,
0xD8,0x77,0x52,0x12,0xB7,0x49,0x23,0x06,0xAA,0x45,0x28,0x07,0x7D,0x28,0x0F,0x07,
0xCC,0x7B,0x4A,0x0E,0xB2,0x4F,0x24,0x07,0xAD,0x43,0x2C,0x06,0x86,0x29,0x11,0x07,
0xAE,0x48,0x1F,0x0A,0x76,0x21,0x19,0x05,0x76,0x21,0x14,0x05,0x44,0x11,0x0B,0x01,
0xE7,0xAD,0x96,0x23,0xDC,0x86,0x59,0x0E,0xDC,0x7C,0x5F,0x15,0xBB,0x53,0x2E,0x09,
0xD6,0x7C,0x4A,0x16,0xBB,0x4A,0x25,0x08,0xB3,0x4F,0x28,0x0B,0x8E,0x23,0x15,0x08,
0xCF,0x7F,0x57,0x11,0xB5,0x4A,0x23,0x0A,0xAA,0x42,0x28,0x05,0x7D,0x22,0x12,0x03,
0xA6,0x49,0x28,0x09,0x82,0x2B,0x0D,0x04,0x7A,0x20,0x0F,0x04,0x3D,0x0F,0x09,0x03,
0xD1,0x7C,0x4C,0x0F,0xAF,0x4E,0x21,0x09,0xA8,0x46,0x2A,0x07,0x85,0x1F,0x0E,0x07,
0xA6,0x3F,0x26,0x07,0x7C,0x24,0x14,0x07,0x78,0x22,0x16,0x04,0x46,0x12,0x0A,0x02,
0xA6,0x41,0x2C,0x0A,0x7E,0x28,0x11,0x05,0x73,0x1B,0x14,0x05,0x3D,0x11,0x0A,0x02,
0x70,0x22,0x17,0x05,0x48,0x13,0x08,0x03,0x3C,0x07,0x0D,0x07,0x26,0x07,0x06,0x01,
0xE0,0x9F,0xDA,0x7C,0x4F,0x18,0x28,0x0D,0xE9,0x9F,0xDA,0x7C,0x4F,0x18,0x1F,0x07,
0xE6,0x97,0xD8,0x72,0x64,0x13,0x26,0x09,0xDC,0x67,0xA9,0x38,0x21,0x07,0x15,0x06,
0xE9,0x91,0xD2,0x6B,0x63,0x14,0x2B,0x0E,0xD6,0x61,0xB7,0x41,0x2B,0x0E,0x10,0x09,
0xCF,0x59,0xB0,0x2F,0x35,0x08,0x0F,0x07,0xB6,0x30,0x7A,0x21,0x17,0x07,0x09,0x03,
0xE7,0xA3,0xE5,0x6B,0x65,0x1F,0x34,0x09,0xD8,0x6B,0xBE,0x45,0x27,0x07,0x10,0x07,
0xDA,0x54,0xB1,0x39,0x2E,0x0E,0x17,0x08,0xA9,0x3C,0x86,0x22,0x16,0x06,0x07,0x03,
0xD4,0x51,0xBC,0x3D,0x38,0x0A,0x13,0x06,0xB2,0x37,0x79,0x1C,0x17,0x05,0x0E,0x06,
0xA7,0x31,0x74,0x1C,0x11,0x06,0x0C,0x02,0x6D,0x1A,0x38,0x10,0x0B,0x05,0x06,0x03,
0xEB,0x9A,0xE1,0x7A,0x6F,0x13,0x34,0x0E,0xE6,0x75,0xC5,0x45,0x3E,0x0B,0x1A,0x05,
0xD8,0x63,0xC1,0x40,0x3C,0x1B,0x19,0x06,0xB3,0x42,0x83,0x29,0x18,0x0A,0x08,0x04,
0xD4,0x58,0xBA,0x43,0x3F,0x0A,0x1F,0x09,0xB1,0x33,0x8A,0x1F,0x1F,0x06,0x0D,0x05,
0xAF,0x3C,0x7A,0x1F,0x16,0x08,0x0A,0x01,0x72,0x1B,0x52,0x0D,0x0B,0x09,0x06,0x01,
0xCF,0x63,0xB7,0x47,0x40,0x10,0x14,0x06,0xC0,0x41,0x96,0x20,0x1C,0x09,0x10,0x05,
0xA6,0x35,0x82,0x1A,0x20,0x0C,0x0E,0x04,0x80,0x1F,0x53,0x0F,0x0B,0x02,0x06,0x01,
0xA6,0x31,0x81,0x1B,0x1D,0x01,0x08,0x08,0x7B,0x20,0x4D,0x19,0x0E,0x05,0x07,0x03,
0x6B,0x17,0x49,0x07,0x0E,0x03,0x0A,0x05,0x37,0x0B,0x1F,0x06,0x04,0x02,0x07,0x01,
0xF0,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x47,0x1E,0x6E,0x1B,0x32,0x0A,
0xF0,0xD6,0xEA,0xA4,0xED,0xC4,0xDE,0x82,0x98,0x1F,0x50,0x13,0x52,0x15,0x2A,0x0A,
0xF1,0xD1,0xEB,0xA2,0xEB,0xB7,0xD8,0x69,0xA2,0x1F,0x5B,0x18,0x55,0x18,0x2C,0x0A,
0xED,0xB5,0xDE,0x7E,0xE6,0x85,0xD3,0x59,0x59,0x0F,0x2C,0x09,0x24,0x07,0x15,0x09,
0xF1,0xD6,0xEA,0xA0,0xEC,0xBB,0xDA,0x77,0xA9,0x23,0x58,0x14,0x5D,0x12,0x2F,0x09,
0xF1,0xC1,0xE3,0x86,0xE4,0x87,0xD2,0x4E,0x68,0x15,0x26,0x0B,0x27,0x09,0x15,0x02,
0xEE,0xA6,0xE0,0x5C,0xE0,0x77,0xC3,0x41,0x67,0x1B,0x3C,0x07,0x2A,0x06,0x19,0x07,
0xE4,0x75,0xC6,0x43,0xCC,0x50,0x95,0x23,0x35,0x09,0x14,0x04,0x15,0x05,0x0B,0x04,
0xEE,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x56,0x14,0x5A,0x12,0x26,0x0A,
0xEE,0xBB,0xE7,0x7E,0xE9,0x8D,0xCB,0x49,0x67,0x11,0x34,0x07,0x2B,0x0B,0x14,0x07,
0xED,0xA7,0xE5,0x76,0xE3,0x7E,0xC4,0x4B,0x77,0x14,0x34,0x08,0x27,0x07,0x14,0x04,
0xE7,0x8B,0xD2,0x4C,0xCA,0x56,0x9E,0x31,0x36,0x0C,0x11,0x07,0x14,0x04,0x0A,0x02,
0xF0,0x9B,0xEA,0x6F,0xE5,0x81,0xC4,0x43,0x74,0x10,0x30,0x0B,0x2D,0x08,0x1B,0x06,
0xE6,0x83,0xCA,0x48,0xD9,0x56,0xA7,0x23,0x3B,0x09,0x12,0x09,0x15,0x07,0x0A,0x03,
0xE5,0x5F,0xCB,0x3C,0xCF,0x48,0x91,0x22,0x31,0x0A,0x17,0x08,0x15,0x04,0x0D,0x02,
0xD1,0x43,0x91,0x20,0xA9,0x2D,0x54,0x12,0x17,0x07,0x09,0x02,0x0C,0x04,0x05,0x03,
};
#endif
// divided into multiple functions to keep rarely-used functionality separate
// so often-used functionality can be optimized better by compiler
// If write isn't preceded by read, data has this added to it
int const no_read_before_write = 0x2000;
void SNES_SPC::cpu_write_smp_reg_( int data, rel_time_t time, int addr )
{
switch ( addr )
{
case r_t0target:
case r_t1target:
case r_t2target: {
Timer* t = &m.timers [addr - r_t0target];
int period = IF_0_THEN_256( data );
if ( t->period != period )
{
t = run_timer( t, time );
#if SPC_MORE_ACCURACY
// Insane behavior when target is written just after counter is
// clocked and counter matches new period and new period isn't 1, 2, 4, or 8
if ( t->divider == (period & 0xFF) &&
t->next_time == time + TIMER_MUL( t, 1 ) &&
((period - 1) | ~0x0F) & period )
{
//dprintf( "SPC pathological timer target write\n" );
// If the period is 3, 5, or 9, there's a probability this behavior won't occur,
// based on the previous period
int prob = 0xFF;
int old_period = t->period & 0xFF;
if ( period == 3 ) prob = glitch_probs [0] [old_period];
if ( period == 5 ) prob = glitch_probs [1] [old_period];
if ( period == 9 ) prob = glitch_probs [2] [old_period];
// The glitch suppresses incrementing of one of the counter bits, based on
// the lowest set bit in the new period
int b = 1;
while ( !(period & b) )
b <<= 1;
if ( (rand() >> 4 & 0xFF) <= prob )
t->divider = (t->divider - b) & 0xFF;
}
#endif
t->period = period;
}
break;
}
case r_t0out:
case r_t1out:
case r_t2out:
if ( !SPC_MORE_ACCURACY )
dprintf( "SPC wrote to counter %d\n", (int) addr - r_t0out );
if ( data < no_read_before_write / 2 )
run_timer( &m.timers [addr - r_t0out], time - 1 )->counter = 0;
break;
// Registers that act like RAM
case 0x8:
case 0x9:
REGS_IN [addr] = (uint8_t) data;
break;
case r_test:
if ( (uint8_t) data != 0x0A )
dprintf( "SPC wrote to test register\n" );
break;
case r_control:
// port clears
if ( data & 0x10 )
{
REGS_IN [r_cpuio0] = 0;
REGS_IN [r_cpuio1] = 0;
}
if ( data & 0x20 )
{
REGS_IN [r_cpuio2] = 0;
REGS_IN [r_cpuio3] = 0;
}
// timers
{
for ( int i = 0; i < timer_count; i++ )
{
Timer* t = &m.timers [i];
int enabled = data >> i & 1;
if ( t->enabled != enabled )
{
t = run_timer( t, time );
t->enabled = enabled;
if ( enabled )
{
t->divider = 0;
t->counter = 0;
}
}
}
}
enable_rom( data & 0x80 );
break;
}
}
void SNES_SPC::cpu_write_smp_reg( int data, rel_time_t time, int addr )
{
if ( addr == r_dspdata ) // 99%
dsp_write( data, time );
else
cpu_write_smp_reg_( data, time, addr );
}
void SNES_SPC::cpu_write_high( int data, int i, rel_time_t time )
{
if ( i < rom_size )
{
m.hi_ram [i] = (uint8_t) data;
if ( m.rom_enabled )
RAM [i + rom_addr] = m.rom [i]; // restore overwritten ROM
}
else
{
assert( RAM [i + rom_addr] == (uint8_t) data );
RAM [i + rom_addr] = cpu_pad_fill; // restore overwritten padding
cpu_write( data, i + rom_addr - 0x10000, time );
}
}
int const bits_in_int = CHAR_BIT * sizeof (int);
void SNES_SPC::cpu_write( int data, int addr, rel_time_t time )
{
MEM_ACCESS( time, addr )
// RAM
RAM [addr] = (uint8_t) data;
int reg = addr - 0xF0;
if ( reg >= 0 ) // 64%
{
// $F0-$FF
if ( reg < reg_count ) // 87%
{
REGS [reg] = (uint8_t) data;
// Ports
#ifdef SPC_PORT_WRITE_HOOK
if ( (unsigned) (reg - r_cpuio0) < port_count )
SPC_PORT_WRITE_HOOK( m.spc_time + time, (reg - r_cpuio0),
(uint8_t) data, &REGS [r_cpuio0] );
#endif
// Registers other than $F2 and $F4-$F7
//if ( reg != 2 && reg != 4 && reg != 5 && reg != 6 && reg != 7 )
// TODO: this is a bit on the fragile side
if ( ((~0x2F00 << (bits_in_int - 16)) << reg) < 0 ) // 36%
cpu_write_smp_reg( data, time, reg );
}
// High mem/address wrap-around
else
{
reg -= rom_addr - 0xF0;
if ( reg >= 0 ) // 1% in IPL ROM area or address wrapped around
cpu_write_high( data, reg, time );
}
}
}
//// CPU read
inline int SNES_SPC::cpu_read_smp_reg( int reg, rel_time_t time )
{
int result = REGS_IN [reg];
reg -= r_dspaddr;
// DSP addr and data
if ( (unsigned) reg <= 1 ) // 4% 0xF2 and 0xF3
{
result = REGS [r_dspaddr];
if ( (unsigned) reg == 1 )
result = dsp_read( time ); // 0xF3
}
return result;
}
int SNES_SPC::cpu_read( int addr, rel_time_t time )
{
MEM_ACCESS( time, addr )
// RAM
int result = RAM [addr];
int reg = addr - 0xF0;
if ( reg >= 0 ) // 40%
{
reg -= 0x10;
if ( (unsigned) reg >= 0xFF00 ) // 21%
{
reg += 0x10 - r_t0out;
// Timers
if ( (unsigned) reg < timer_count ) // 90%
{
Timer* t = &m.timers [reg];
if ( time >= t->next_time )
t = run_timer_( t, time );
result = t->counter;
t->counter = 0;
}
// Other registers
else if ( reg < 0 ) // 10%
{
result = cpu_read_smp_reg( reg + r_t0out, time );
}
else // 1%
{
assert( reg + (r_t0out + 0xF0 - 0x10000) < 0x100 );
result = cpu_read( reg + (r_t0out + 0xF0 - 0x10000), time );
}
}
}
return result;
}
//// Run
// Prefix and suffix for CPU emulator function
#define SPC_CPU_RUN_FUNC \
BOOST::uint8_t* SNES_SPC::run_until_( time_t end_time )\
{\
rel_time_t rel_time = m.spc_time - end_time;\
assert( rel_time <= 0 );\
m.spc_time = end_time;\
m.dsp_time += rel_time;\
m.timers [0].next_time += rel_time;\
m.timers [1].next_time += rel_time;\
m.timers [2].next_time += rel_time;
#define SPC_CPU_RUN_FUNC_END \
m.spc_time += rel_time;\
m.dsp_time -= rel_time;\
m.timers [0].next_time -= rel_time;\
m.timers [1].next_time -= rel_time;\
m.timers [2].next_time -= rel_time;\
assert( m.spc_time <= end_time );\
return &REGS [r_cpuio0];\
}
int const cpu_lag_max = 12 - 1; // DIV YA,X takes 12 clocks
void SNES_SPC::end_frame( time_t end_time )
{
// Catch CPU up to as close to end as possible. If final instruction
// would exceed end, does NOT execute it and leaves m.spc_time < end.
if ( end_time > m.spc_time )
run_until_( end_time );
m.spc_time -= end_time;
m.extra_clocks += end_time;
// Greatest number of clocks early that emulation can stop early due to
// not being able to execute current instruction without going over
// allowed time.
assert( -cpu_lag_max <= m.spc_time && m.spc_time <= 0 );
// Catch timers up to CPU
for ( int i = 0; i < timer_count; i++ )
run_timer( &m.timers [i], 0 );
// Catch DSP up to CPU
if ( m.dsp_time < 0 )
{
RUN_DSP( 0, max_reg_time );
}
// Save any extra samples beyond what should be generated
if ( m.buf_begin )
save_extra();
}
// Inclusion here allows static memory access functions and better optimization
#include "SPC_CPU.h"

View file

@ -0,0 +1,279 @@
// SNES SPC-700 APU emulator
// snes_spc 0.9.0
#ifndef SNES_SPC_H
#define SNES_SPC_H
#include "SPC_DSP.h"
#include "blargg_endian.h"
struct SNES_SPC {
public:
typedef BOOST::uint8_t uint8_t;
// Must be called once before using
blargg_err_t init();
// Sample pairs generated per second
enum { sample_rate = 32000 };
// Emulator use
// Sets IPL ROM data. Library does not include ROM data. Most SPC music files
// don't need ROM, but a full emulator must provide this.
enum { rom_size = 0x40 };
void init_rom( uint8_t const rom [rom_size] );
// Sets destination for output samples
typedef short sample_t;
void set_output( sample_t* out, int out_size );
// Number of samples written to output since last set
int sample_count() const;
// Resets SPC to power-on state. This resets your output buffer, so you must
// call set_output() after this.
void reset();
// Emulates pressing reset switch on SNES. This resets your output buffer, so
// you must call set_output() after this.
void soft_reset();
// 1024000 SPC clocks per second, sample pair every 32 clocks
typedef int time_t;
enum { clock_rate = 1024000 };
enum { clocks_per_sample = 32 };
// Emulated port read/write at specified time
enum { port_count = 4 };
int read_port ( time_t, int port );
void write_port( time_t, int port, int data );
// Runs SPC to end_time and starts a new time frame at 0
void end_frame( time_t end_time );
// Sound control
// Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events).
// Reduces emulation accuracy.
enum { voice_count = 8 };
void mute_voices( int mask );
// If true, prevents channels and global volumes from being phase-negated.
// Only supported by fast DSP.
void disable_surround( bool disable = true );
// Sets tempo, where tempo_unit = normal, tempo_unit / 2 = half speed, etc.
enum { tempo_unit = 0x100 };
void set_tempo( int );
// SPC music files
// Loads SPC data into emulator
enum { spc_min_file_size = 0x10180 };
enum { spc_file_size = 0x10200 };
blargg_err_t load_spc( void const* in, long size );
// Clears echo region. Useful after loading an SPC as many have garbage in echo.
void clear_echo();
// Plays for count samples and write samples to out. Discards samples if out
// is NULL. Count must be a multiple of 2 since output is stereo.
blargg_err_t play( int count, sample_t* out );
// Skips count samples. Several times faster than play() when using fast DSP.
blargg_err_t skip( int count );
// State save/load (only available with accurate DSP)
#if !SPC_NO_COPY_STATE_FUNCS
// Saves/loads state
enum { state_size = 67 * 1024L }; // maximum space needed when saving
typedef SPC_DSP::copy_func_t copy_func_t;
void copy_state( unsigned char** io, copy_func_t );
// Writes minimal header to spc_out
static void init_header( void* spc_out );
// Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out.
// Does not set up SPC header; use init_header() for that.
void save_spc( void* spc_out );
// Returns true if new key-on events occurred since last check. Useful for
// trimming silence while saving an SPC.
bool check_kon();
#endif
public:
BLARGG_DISABLE_NOTHROW
typedef BOOST::uint16_t uint16_t;
// Time relative to m_spc_time. Speeds up code a bit by eliminating need to
// constantly add m_spc_time to time from CPU. CPU uses time that ends at
// 0 to eliminate reloading end time every instruction. It pays off.
typedef int rel_time_t;
struct Timer
{
rel_time_t next_time; // time of next event
int prescaler;
int period;
int divider;
int enabled;
int counter;
};
enum { reg_count = 0x10 };
enum { timer_count = 3 };
enum { extra_size = SPC_DSP::extra_size };
enum { signature_size = 35 };
private:
SPC_DSP dsp;
#if SPC_LESS_ACCURATE
static signed char const reg_times_ [256];
signed char reg_times [256];
#endif
struct state_t
{
Timer timers [timer_count];
uint8_t smp_regs [2] [reg_count];
struct
{
int pc;
int a;
int x;
int y;
int psw;
int sp;
} cpu_regs;
rel_time_t dsp_time;
time_t spc_time;
bool echo_accessed;
int tempo;
int skipped_kon;
int skipped_koff;
const char* cpu_error;
int extra_clocks;
sample_t* buf_begin;
sample_t const* buf_end;
sample_t* extra_pos;
sample_t extra_buf [extra_size];
int rom_enabled;
uint8_t rom [rom_size];
uint8_t hi_ram [rom_size];
unsigned char cycle_table [256];
struct
{
// padding to neutralize address overflow
union {
uint8_t padding1 [0x100];
uint16_t align; // makes compiler align data for 16-bit access
} padding1 [1];
uint8_t ram [0x10000];
uint8_t padding2 [0x100];
} ram;
};
state_t m;
enum { rom_addr = 0xFFC0 };
enum { skipping_time = 127 };
// Value that padding should be filled with
enum { cpu_pad_fill = 0xFF };
enum {
r_test = 0x0, r_control = 0x1,
r_dspaddr = 0x2, r_dspdata = 0x3,
r_cpuio0 = 0x4, r_cpuio1 = 0x5,
r_cpuio2 = 0x6, r_cpuio3 = 0x7,
r_f8 = 0x8, r_f9 = 0x9,
r_t0target = 0xA, r_t1target = 0xB, r_t2target = 0xC,
r_t0out = 0xD, r_t1out = 0xE, r_t2out = 0xF
};
void timers_loaded();
void enable_rom( int enable );
void reset_buf();
void save_extra();
void load_regs( uint8_t const in [reg_count] );
void ram_loaded();
void regs_loaded();
void reset_time_regs();
void reset_common( int timer_counter_init );
Timer* run_timer_ ( Timer* t, rel_time_t );
Timer* run_timer ( Timer* t, rel_time_t );
int dsp_read ( rel_time_t );
void dsp_write ( int data, rel_time_t );
void cpu_write_smp_reg_( int data, rel_time_t, int addr );
void cpu_write_smp_reg ( int data, rel_time_t, int addr );
void cpu_write_high ( int data, int i, rel_time_t );
void cpu_write ( int data, int addr, rel_time_t );
int cpu_read_smp_reg ( int i, rel_time_t );
int cpu_read ( int addr, rel_time_t );
unsigned CPU_mem_bit ( uint8_t const* pc, rel_time_t );
bool check_echo_access ( int addr );
uint8_t* run_until_( time_t end_time );
struct spc_file_t
{
char signature [signature_size];
uint8_t has_id666;
uint8_t version;
uint8_t pcl, pch;
uint8_t a;
uint8_t x;
uint8_t y;
uint8_t psw;
uint8_t sp;
char text [212];
uint8_t ram [0x10000];
uint8_t dsp [128];
uint8_t unused [0x40];
uint8_t ipl_rom [0x40];
};
static char const signature [signature_size + 1];
void save_regs( uint8_t out [reg_count] );
};
#include <assert.h>
inline int SNES_SPC::sample_count() const { return (m.extra_clocks >> 5) * 2; }
inline int SNES_SPC::read_port( time_t t, int port )
{
assert( (unsigned) port < port_count );
return run_until_( t ) [port];
}
inline void SNES_SPC::write_port( time_t t, int port, int data )
{
assert( (unsigned) port < port_count );
run_until_( t ) [0x10 + port] = data;
}
inline void SNES_SPC::mute_voices( int mask ) { dsp.mute_voices( mask ); }
inline void SNES_SPC::disable_surround( bool disable ) { dsp.disable_surround( disable ); }
#if !SPC_NO_COPY_STATE_FUNCS
inline bool SNES_SPC::check_kon() { return dsp.check_kon(); }
#endif
#endif

View file

@ -0,0 +1,380 @@
// SPC emulation support: init, sample buffering, reset, SPC loading
// snes_spc 0.9.0. http://www.slack.net/~ant/
#include "SNES_SPC.h"
#include <string.h>
/* Copyright (C) 2004-2007 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"
#define RAM (m.ram.ram)
#define REGS (m.smp_regs [0])
#define REGS_IN (m.smp_regs [1])
// (n ? n : 256)
#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
//// Init
blargg_err_t SNES_SPC::init()
{
memset( &m, 0, sizeof m );
dsp.init( RAM );
m.tempo = tempo_unit;
// Most SPC music doesn't need ROM, and almost all the rest only rely
// on these two bytes
m.rom [0x3E] = 0xFF;
m.rom [0x3F] = 0xC0;
static unsigned char const cycle_table [128] =
{// 01 23 45 67 89 AB CD EF
0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x68, // 0
0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x46, // 1
0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x74, // 2
0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x38, // 3
0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x66, // 4
0x48,0x47,0x45,0x56,0x55,0x45,0x22,0x43, // 5
0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x75, // 6
0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x36, // 7
0x28,0x47,0x34,0x36,0x26,0x54,0x52,0x45, // 8
0x48,0x47,0x45,0x56,0x55,0x55,0x22,0xC5, // 9
0x38,0x47,0x34,0x36,0x26,0x44,0x52,0x44, // A
0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x34, // B
0x38,0x47,0x45,0x47,0x25,0x64,0x52,0x49, // C
0x48,0x47,0x56,0x67,0x45,0x55,0x22,0x83, // D
0x28,0x47,0x34,0x36,0x24,0x53,0x43,0x40, // E
0x48,0x47,0x45,0x56,0x34,0x54,0x22,0x60, // F
};
// unpack cycle table
for ( int i = 0; i < 128; i++ )
{
int n = cycle_table [i];
m.cycle_table [i * 2 + 0] = n >> 4;
m.cycle_table [i * 2 + 1] = n & 0x0F;
}
#if SPC_LESS_ACCURATE
memcpy( reg_times, reg_times_, sizeof reg_times );
#endif
reset();
return 0;
}
void SNES_SPC::init_rom( uint8_t const in [rom_size] )
{
memcpy( m.rom, in, sizeof m.rom );
}
void SNES_SPC::set_tempo( int t )
{
m.tempo = t;
int const timer2_shift = 4; // 64 kHz
int const other_shift = 3; // 8 kHz
#if SPC_DISABLE_TEMPO
m.timers [2].prescaler = timer2_shift;
m.timers [1].prescaler = timer2_shift + other_shift;
m.timers [0].prescaler = timer2_shift + other_shift;
#else
if ( !t )
t = 1;
int const timer2_rate = 1 << timer2_shift;
int rate = (timer2_rate * tempo_unit + (t >> 1)) / t;
if ( rate < timer2_rate / 4 )
rate = timer2_rate / 4; // max 4x tempo
m.timers [2].prescaler = rate;
m.timers [1].prescaler = rate << other_shift;
m.timers [0].prescaler = rate << other_shift;
#endif
}
// Timer registers have been loaded. Applies these to the timers. Does not
// reset timer prescalers or dividers.
void SNES_SPC::timers_loaded()
{
int i;
for ( i = 0; i < timer_count; i++ )
{
Timer* t = &m.timers [i];
t->period = IF_0_THEN_256( REGS [r_t0target + i] );
t->enabled = REGS [r_control] >> i & 1;
t->counter = REGS_IN [r_t0out + i] & 0x0F;
}
set_tempo( m.tempo );
}
// Loads registers from unified 16-byte format
void SNES_SPC::load_regs( uint8_t const in [reg_count] )
{
memcpy( REGS, in, reg_count );
memcpy( REGS_IN, REGS, reg_count );
// These always read back as 0
REGS_IN [r_test ] = 0;
REGS_IN [r_control ] = 0;
REGS_IN [r_t0target] = 0;
REGS_IN [r_t1target] = 0;
REGS_IN [r_t2target] = 0;
}
// RAM was just loaded from SPC, with $F0-$FF containing SMP registers
// and timer counts. Copies these to proper registers.
void SNES_SPC::ram_loaded()
{
m.rom_enabled = 0;
load_regs( &RAM [0xF0] );
// Put STOP instruction around memory to catch PC underflow/overflow
memset( m.ram.padding1, cpu_pad_fill, sizeof m.ram.padding1 );
memset( m.ram.padding2, cpu_pad_fill, sizeof m.ram.padding2 );
}
// Registers were just loaded. Applies these new values.
void SNES_SPC::regs_loaded()
{
enable_rom( REGS [r_control] & 0x80 );
timers_loaded();
}
void SNES_SPC::reset_time_regs()
{
m.cpu_error = 0;
m.echo_accessed = 0;
m.spc_time = 0;
m.dsp_time = 0;
#if SPC_LESS_ACCURATE
m.dsp_time = clocks_per_sample + 1;
#endif
for ( int i = 0; i < timer_count; i++ )
{
Timer* t = &m.timers [i];
t->next_time = 1;
t->divider = 0;
}
regs_loaded();
m.extra_clocks = 0;
reset_buf();
}
void SNES_SPC::reset_common( int timer_counter_init )
{
int i;
for ( i = 0; i < timer_count; i++ )
REGS_IN [r_t0out + i] = timer_counter_init;
// Run IPL ROM
memset( &m.cpu_regs, 0, sizeof m.cpu_regs );
m.cpu_regs.pc = rom_addr;
REGS [r_test ] = 0x0A;
REGS [r_control] = 0xB0; // ROM enabled, clear ports
for ( i = 0; i < port_count; i++ )
REGS_IN [r_cpuio0 + i] = 0;
reset_time_regs();
}
void SNES_SPC::soft_reset()
{
reset_common( 0 );
dsp.soft_reset();
}
void SNES_SPC::reset()
{
memset( RAM, 0xFF, 0x10000 );
ram_loaded();
reset_common( 0x0F );
dsp.reset();
}
char const SNES_SPC::signature [signature_size + 1] =
"SNES-SPC700 Sound File Data v0.30\x1A\x1A";
blargg_err_t SNES_SPC::load_spc( void const* data, long size )
{
spc_file_t const* const spc = (spc_file_t const*) data;
// be sure compiler didn't insert any padding into fle_t
assert( sizeof (spc_file_t) == spc_min_file_size + 0x80 );
// Check signature and file size
if ( size < signature_size || memcmp( spc, signature, 27 ) )
return "Not an SPC file";
if ( size < spc_min_file_size )
return "Corrupt SPC file";
// CPU registers
m.cpu_regs.pc = spc->pch * 0x100 + spc->pcl;
m.cpu_regs.a = spc->a;
m.cpu_regs.x = spc->x;
m.cpu_regs.y = spc->y;
m.cpu_regs.psw = spc->psw;
m.cpu_regs.sp = spc->sp;
// RAM and registers
memcpy( RAM, spc->ram, 0x10000 );
ram_loaded();
// DSP registers
dsp.load( spc->dsp );
reset_time_regs();
return 0;
}
void SNES_SPC::clear_echo()
{
if ( !(dsp.read( SPC_DSP::r_flg ) & 0x20) )
{
int addr = 0x100 * dsp.read( SPC_DSP::r_esa );
int end = addr + 0x800 * (dsp.read( SPC_DSP::r_edl ) & 0x0F);
if ( end > 0x10000 )
end = 0x10000;
memset( &RAM [addr], 0xFF, end - addr );
}
}
//// Sample output
void SNES_SPC::reset_buf()
{
// Start with half extra buffer of silence
sample_t* out = m.extra_buf;
while ( out < &m.extra_buf [extra_size / 2] )
*out++ = 0;
m.extra_pos = out;
m.buf_begin = 0;
dsp.set_output( 0, 0 );
}
void SNES_SPC::set_output( sample_t* out, int size )
{
require( (size & 1) == 0 ); // size must be even
m.extra_clocks &= clocks_per_sample - 1;
if ( out )
{
sample_t const* out_end = out + size;
m.buf_begin = out;
m.buf_end = out_end;
// Copy extra to output
sample_t const* in = m.extra_buf;
while ( in < m.extra_pos && out < out_end )
*out++ = *in++;
// Handle output being full already
if ( out >= out_end )
{
// Have DSP write to remaining extra space
out = dsp.extra();
out_end = &dsp.extra() [extra_size];
// Copy any remaining extra samples as if DSP wrote them
while ( in < m.extra_pos )
*out++ = *in++;
assert( out <= out_end );
}
dsp.set_output( out, int(out_end - out) );
}
else
{
reset_buf();
}
}
void SNES_SPC::save_extra()
{
// Get end pointers
sample_t const* main_end = m.buf_end; // end of data written to buf
sample_t const* dsp_end = dsp.out_pos(); // end of data written to dsp.extra()
if ( m.buf_begin <= dsp_end && dsp_end <= main_end )
{
main_end = dsp_end;
dsp_end = dsp.extra(); // nothing in DSP's extra
}
// Copy any extra samples at these ends into extra_buf
sample_t* out = m.extra_buf;
sample_t const* in;
for ( in = m.buf_begin + sample_count(); in < main_end; in++ )
*out++ = *in;
for ( in = dsp.extra(); in < dsp_end ; in++ )
*out++ = *in;
m.extra_pos = out;
assert( out <= &m.extra_buf [extra_size] );
}
blargg_err_t SNES_SPC::play( int count, sample_t* out )
{
require( (count & 1) == 0 ); // must be even
if ( count )
{
set_output( out, count );
end_frame( count * (clocks_per_sample / 2) );
}
const char* err = m.cpu_error;
m.cpu_error = 0;
return err;
}
blargg_err_t SNES_SPC::skip( int count )
{
#if SPC_LESS_ACCURATE
if ( count > 2 * sample_rate * 2 )
{
set_output( 0, 0 );
// Skip a multiple of 4 samples
time_t end = count;
count = (count & 3) + 1 * sample_rate * 2;
end = (end - count) * (clocks_per_sample / 2);
m.skipped_kon = 0;
m.skipped_koff = 0;
// Preserve DSP and timer synchronization
// TODO: verify that this really preserves it
int old_dsp_time = m.dsp_time + m.spc_time;
m.dsp_time = end - m.spc_time + skipping_time;
end_frame( end );
m.dsp_time = m.dsp_time - skipping_time + old_dsp_time;
dsp.write( SPC_DSP::r_koff, m.skipped_koff & ~m.skipped_kon );
dsp.write( SPC_DSP::r_kon , m.skipped_kon );
clear_echo();
}
#endif
return play( count, 0 );
}

View file

@ -0,0 +1,129 @@
// SPC emulation state save/load: copy_state(), save_spc()
// Separate file to avoid linking in unless needed
// snes_spc 0.9.0. http://www.slack.net/~ant/
#include "SNES_SPC.h"
#if !SPC_NO_COPY_STATE_FUNCS
#include <string.h>
/* Copyright (C) 2004-2007 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"
#define RAM (m.ram.ram)
#define REGS (m.smp_regs [0])
#define REGS_IN (m.smp_regs [1])
void SNES_SPC::save_regs( uint8_t out [reg_count] )
{
// Use current timer counter values
for ( int i = 0; i < timer_count; i++ )
out [r_t0out + i] = m.timers [i].counter;
// Last written values
memcpy( out, REGS, r_t0out );
}
void SNES_SPC::init_header( void* spc_out )
{
spc_file_t* const spc = (spc_file_t*) spc_out;
spc->has_id666 = 26; // has none
spc->version = 30;
memcpy( spc, signature, sizeof spc->signature );
memset( spc->text, 0, sizeof spc->text );
}
void SNES_SPC::save_spc( void* spc_out )
{
spc_file_t* const spc = (spc_file_t*) spc_out;
// CPU
spc->pcl = (uint8_t) (m.cpu_regs.pc >> 0);
spc->pch = (uint8_t) (m.cpu_regs.pc >> 8);
spc->a = m.cpu_regs.a;
spc->x = m.cpu_regs.x;
spc->y = m.cpu_regs.y;
spc->psw = m.cpu_regs.psw;
spc->sp = m.cpu_regs.sp;
// RAM, ROM
memcpy( spc->ram, RAM, sizeof spc->ram );
if ( m.rom_enabled )
memcpy( spc->ram + rom_addr, m.hi_ram, sizeof m.hi_ram );
memset( spc->unused, 0, sizeof spc->unused );
memcpy( spc->ipl_rom, m.rom, sizeof spc->ipl_rom );
// SMP registers
save_regs( &spc->ram [0xF0] );
int i;
for ( i = 0; i < port_count; i++ )
spc->ram [0xF0 + r_cpuio0 + i] = REGS_IN [r_cpuio0 + i];
// DSP registers
for ( i = 0; i < SPC_DSP::register_count; i++ )
spc->dsp [i] = dsp.read( i );
}
void SNES_SPC::copy_state( unsigned char** io, copy_func_t copy )
{
SPC_State_Copier copier( io, copy );
// Make state data more readable by putting 64K RAM, 16 SMP registers,
// then DSP (with its 128 registers) first
// RAM
enable_rom( 0 ); // will get re-enabled if necessary in regs_loaded() below
copier.copy( RAM, 0x10000 );
{
// SMP registers
uint8_t out_ports [port_count];
uint8_t regs [reg_count];
memcpy( out_ports, &REGS [r_cpuio0], sizeof out_ports );
save_regs( regs );
copier.copy( regs, sizeof regs );
copier.copy( out_ports, sizeof out_ports );
load_regs( regs );
regs_loaded();
memcpy( &REGS [r_cpuio0], out_ports, sizeof out_ports );
}
// CPU registers
SPC_COPY( uint16_t, m.cpu_regs.pc );
SPC_COPY( uint8_t, m.cpu_regs.a );
SPC_COPY( uint8_t, m.cpu_regs.x );
SPC_COPY( uint8_t, m.cpu_regs.y );
SPC_COPY( uint8_t, m.cpu_regs.psw );
SPC_COPY( uint8_t, m.cpu_regs.sp );
copier.extra();
SPC_COPY( int16_t, m.spc_time );
SPC_COPY( int16_t, m.dsp_time );
// DSP
dsp.copy_state( io, copy );
// Timers
for ( int i = 0; i < timer_count; i++ )
{
Timer* t = &m.timers [i];
SPC_COPY( int16_t, t->next_time );
SPC_COPY( uint8_t, t->divider );
copier.extra();
}
copier.extra();
}
#endif

1220
snes_spc/snes_spc/SPC_CPU.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,703 @@
// snes_spc 0.9.0. http://www.slack.net/~ant/
#include "SPC_DSP.h"
#include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2007 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"
#ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER
#endif
#if INT_MAX < 0x7FFFFFFF
#error "Requires that int type have at least 32 bits"
#endif
// TODO: add to blargg_endian.h
#define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr ))
#define GET_LE16A( addr ) GET_LE16( addr )
#define SET_LE16A( addr, data ) SET_LE16( addr, data )
static BOOST::uint8_t const initial_regs [SPC_DSP::register_count] =
{
0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80,
0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF,
0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A,
0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF,
0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67,
0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF,
0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F,
0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF
};
// if ( io < -32768 ) io = -32768;
// if ( io > 32767 ) io = 32767;
#define CLAMP16( io )\
{\
if ( (int16_t) io != io )\
io = (io >> 31) ^ 0x7FFF;\
}
// Access global DSP register
#define REG(n) m.regs [r_##n]
// Access voice DSP register
#define VREG(r,n) r [v_##n]
#define WRITE_SAMPLES( l, r, out ) \
{\
out [0] = l;\
out [1] = r;\
out += 2;\
if ( out >= m.out_end )\
{\
check( out == m.out_end );\
check( m.out_end != &m.extra [extra_size] || \
(m.extra <= m.out_begin && m.extra < &m.extra [extra_size]) );\
out = m.extra;\
m.out_end = &m.extra [extra_size];\
}\
}\
void SPC_DSP::set_output( sample_t* out, int size )
{
require( (size & 1) == 0 ); // must be even
if ( !out )
{
out = m.extra;
size = extra_size;
}
m.out_begin = out;
m.out = out;
m.out_end = out + size;
}
// Volume registers and efb are signed! Easy to forget int8_t cast.
// Prefixes are to avoid accidental use of locals with same names.
// Interleved gauss table (to improve cache coherency)
// interleved_gauss [i] = gauss [(i & 1) * 256 + 255 - (i >> 1 & 0xFF)]
static short const interleved_gauss [512] =
{
370,1305, 366,1305, 362,1304, 358,1304, 354,1304, 351,1304, 347,1304, 343,1303,
339,1303, 336,1303, 332,1302, 328,1302, 325,1301, 321,1300, 318,1300, 314,1299,
311,1298, 307,1297, 304,1297, 300,1296, 297,1295, 293,1294, 290,1293, 286,1292,
283,1291, 280,1290, 276,1288, 273,1287, 270,1286, 267,1284, 263,1283, 260,1282,
257,1280, 254,1279, 251,1277, 248,1275, 245,1274, 242,1272, 239,1270, 236,1269,
233,1267, 230,1265, 227,1263, 224,1261, 221,1259, 218,1257, 215,1255, 212,1253,
210,1251, 207,1248, 204,1246, 201,1244, 199,1241, 196,1239, 193,1237, 191,1234,
188,1232, 186,1229, 183,1227, 180,1224, 178,1221, 175,1219, 173,1216, 171,1213,
168,1210, 166,1207, 163,1205, 161,1202, 159,1199, 156,1196, 154,1193, 152,1190,
150,1186, 147,1183, 145,1180, 143,1177, 141,1174, 139,1170, 137,1167, 134,1164,
132,1160, 130,1157, 128,1153, 126,1150, 124,1146, 122,1143, 120,1139, 118,1136,
117,1132, 115,1128, 113,1125, 111,1121, 109,1117, 107,1113, 106,1109, 104,1106,
102,1102, 100,1098, 99,1094, 97,1090, 95,1086, 94,1082, 92,1078, 90,1074,
89,1070, 87,1066, 86,1061, 84,1057, 83,1053, 81,1049, 80,1045, 78,1040,
77,1036, 76,1032, 74,1027, 73,1023, 71,1019, 70,1014, 69,1010, 67,1005,
66,1001, 65, 997, 64, 992, 62, 988, 61, 983, 60, 978, 59, 974, 58, 969,
56, 965, 55, 960, 54, 955, 53, 951, 52, 946, 51, 941, 50, 937, 49, 932,
48, 927, 47, 923, 46, 918, 45, 913, 44, 908, 43, 904, 42, 899, 41, 894,
40, 889, 39, 884, 38, 880, 37, 875, 36, 870, 36, 865, 35, 860, 34, 855,
33, 851, 32, 846, 32, 841, 31, 836, 30, 831, 29, 826, 29, 821, 28, 816,
27, 811, 27, 806, 26, 802, 25, 797, 24, 792, 24, 787, 23, 782, 23, 777,
22, 772, 21, 767, 21, 762, 20, 757, 20, 752, 19, 747, 19, 742, 18, 737,
17, 732, 17, 728, 16, 723, 16, 718, 15, 713, 15, 708, 15, 703, 14, 698,
14, 693, 13, 688, 13, 683, 12, 678, 12, 674, 11, 669, 11, 664, 11, 659,
10, 654, 10, 649, 10, 644, 9, 640, 9, 635, 9, 630, 8, 625, 8, 620,
8, 615, 7, 611, 7, 606, 7, 601, 6, 596, 6, 592, 6, 587, 6, 582,
5, 577, 5, 573, 5, 568, 5, 563, 4, 559, 4, 554, 4, 550, 4, 545,
4, 540, 3, 536, 3, 531, 3, 527, 3, 522, 3, 517, 2, 513, 2, 508,
2, 504, 2, 499, 2, 495, 2, 491, 2, 486, 1, 482, 1, 477, 1, 473,
1, 469, 1, 464, 1, 460, 1, 456, 1, 451, 1, 447, 1, 443, 1, 439,
0, 434, 0, 430, 0, 426, 0, 422, 0, 418, 0, 414, 0, 410, 0, 405,
0, 401, 0, 397, 0, 393, 0, 389, 0, 385, 0, 381, 0, 378, 0, 374,
};
//// Counters
#define RATE( rate, div )\
(rate >= div ? rate / div * 8 - 1 : rate - 1)
static unsigned const counter_mask [32] =
{
RATE( 2,2), RATE(2048,4), RATE(1536,3),
RATE(1280,5), RATE(1024,4), RATE( 768,3),
RATE( 640,5), RATE( 512,4), RATE( 384,3),
RATE( 320,5), RATE( 256,4), RATE( 192,3),
RATE( 160,5), RATE( 128,4), RATE( 96,3),
RATE( 80,5), RATE( 64,4), RATE( 48,3),
RATE( 40,5), RATE( 32,4), RATE( 24,3),
RATE( 20,5), RATE( 16,4), RATE( 12,3),
RATE( 10,5), RATE( 8,4), RATE( 6,3),
RATE( 5,5), RATE( 4,4), RATE( 3,3),
RATE( 2,4),
RATE( 1,4)
};
#undef RATE
inline void SPC_DSP::init_counter()
{
// counters start out with this synchronization
m.counters [0] = 1;
m.counters [1] = 0;
m.counters [2] = -0x20u;
m.counters [3] = 0x0B;
int n = 2;
for ( int i = 1; i < 32; i++ )
{
m.counter_select [i] = &m.counters [n];
if ( !--n )
n = 3;
}
m.counter_select [ 0] = &m.counters [0];
m.counter_select [30] = &m.counters [2];
}
inline void SPC_DSP::run_counter( int i )
{
int n = m.counters [i];
if ( !(n-- & 7) )
n -= 6 - i;
m.counters [i] = n;
}
#define READ_COUNTER( rate )\
(*m.counter_select [rate] & counter_mask [rate])
//// Emulation
void SPC_DSP::run( int clock_count )
{
int new_phase = m.phase + clock_count;
int count = new_phase >> 5;
m.phase = new_phase & 31;
if ( !count )
return;
uint8_t* const ram = m.ram;
uint8_t const* const dir = &ram [REG(dir) * 0x100];
int const slow_gaussian = (REG(pmon) >> 1) | REG(non);
int const noise_rate = REG(flg) & 0x1F;
// Global volume
int mvoll = (int8_t) REG(mvoll);
int mvolr = (int8_t) REG(mvolr);
if ( mvoll * mvolr < m.surround_threshold )
mvoll = -mvoll; // eliminate surround
do
{
// KON/KOFF reading
if ( (m.every_other_sample ^= 1) != 0 )
{
m.new_kon &= ~m.kon;
m.kon = m.new_kon;
m.t_koff = REG(koff);
}
run_counter( 1 );
run_counter( 2 );
run_counter( 3 );
// Noise
if ( !READ_COUNTER( noise_rate ) )
{
int feedback = (m.noise << 13) ^ (m.noise << 14);
m.noise = (feedback & 0x4000) ^ (m.noise >> 1);
}
// Voices
int pmon_input = 0;
int main_out_l = 0;
int main_out_r = 0;
int echo_out_l = 0;
int echo_out_r = 0;
voice_t* v = m.voices;
uint8_t* v_regs = m.regs;
int vbit = 1;
do
{
#define SAMPLE_PTR(i) GET_LE16A( &dir [VREG(v_regs,srcn) * 4 + i * 2] )
int brr_header = ram [v->brr_addr];
int kon_delay = v->kon_delay;
// Pitch
int pitch = GET_LE16A( &VREG(v_regs,pitchl) ) & 0x3FFF;
if ( REG(pmon) & vbit )
pitch += ((pmon_input >> 5) * pitch) >> 10;
// KON phases
if ( --kon_delay >= 0 )
{
v->kon_delay = kon_delay;
// Get ready to start BRR decoding on next sample
if ( kon_delay == 4 )
{
v->brr_addr = SAMPLE_PTR( 0 );
v->brr_offset = 1;
v->buf_pos = v->buf;
brr_header = 0; // header is ignored on this sample
}
// Envelope is never run during KON
v->env = 0;
v->hidden_env = 0;
// Disable BRR decoding until last three samples
v->interp_pos = (kon_delay & 3 ? 0x4000 : 0);
// Pitch is never added during KON
pitch = 0;
}
int env = v->env;
// Gaussian interpolation
{
int output = 0;
VREG(v_regs,envx) = (uint8_t) (env >> 4);
if ( env )
{
// Make pointers into gaussian based on fractional position between samples
int offset = (unsigned) v->interp_pos >> 3 & 0x1FE;
short const* fwd = interleved_gauss + offset;
short const* rev = interleved_gauss + 510 - offset; // mirror left half of gaussian
int const* in = &v->buf_pos [(unsigned) v->interp_pos >> 12];
if ( !(slow_gaussian & vbit) ) // 99%
{
// Faster approximation when exact sample value isn't necessary for pitch mod
output = (fwd [0] * in [0] +
fwd [1] * in [1] +
rev [1] * in [2] +
rev [0] * in [3]) >> 11;
output = (output * env) >> 11;
}
else
{
output = (int16_t) (m.noise * 2);
if ( !(REG(non) & vbit) )
{
output = (fwd [0] * in [0]) >> 11;
output += (fwd [1] * in [1]) >> 11;
output += (rev [1] * in [2]) >> 11;
output = (int16_t) output;
output += (rev [0] * in [3]) >> 11;
CLAMP16( output );
output &= ~1;
}
output = (output * env) >> 11 & ~1;
}
// Output
int l = output * v->volume [0];
int r = output * v->volume [1];
main_out_l += l;
main_out_r += r;
if ( REG(eon) & vbit )
{
echo_out_l += l;
echo_out_r += r;
}
}
pmon_input = output;
VREG(v_regs,outx) = (uint8_t) (output >> 8);
}
// Soft reset or end of sample
if ( REG(flg) & 0x80 || (brr_header & 3) == 1 )
{
v->env_mode = env_release;
env = 0;
}
if ( m.every_other_sample )
{
// KOFF
if ( m.t_koff & vbit )
v->env_mode = env_release;
// KON
if ( m.kon & vbit )
{
v->kon_delay = 5;
v->env_mode = env_attack;
REG(endx) &= ~vbit;
}
}
// Envelope
if ( !v->kon_delay )
{
if ( v->env_mode == env_release ) // 97%
{
env -= 0x8;
v->env = env;
if ( env <= 0 )
{
v->env = 0;
goto skip_brr; // no BRR decoding for you!
}
}
else // 3%
{
int rate;
int const adsr0 = VREG(v_regs,adsr0);
int env_data = VREG(v_regs,adsr1);
if ( adsr0 >= 0x80 ) // 97% ADSR
{
if ( v->env_mode > env_decay ) // 89%
{
env--;
env -= env >> 8;
rate = env_data & 0x1F;
// optimized handling
v->hidden_env = env;
if ( READ_COUNTER( rate ) )
goto exit_env;
v->env = env;
goto exit_env;
}
else if ( v->env_mode == env_decay )
{
env--;
env -= env >> 8;
rate = (adsr0 >> 3 & 0x0E) + 0x10;
}
else // env_attack
{
rate = (adsr0 & 0x0F) * 2 + 1;
env += rate < 31 ? 0x20 : 0x400;
}
}
else // GAIN
{
int mode;
env_data = VREG(v_regs,gain);
mode = env_data >> 5;
if ( mode < 4 ) // direct
{
env = env_data * 0x10;
rate = 31;
}
else
{
rate = env_data & 0x1F;
if ( mode == 4 ) // 4: linear decrease
{
env -= 0x20;
}
else if ( mode < 6 ) // 5: exponential decrease
{
env--;
env -= env >> 8;
}
else // 6,7: linear increase
{
env += 0x20;
if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 )
env += 0x8 - 0x20; // 7: two-slope linear increase
}
}
}
// Sustain level
if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay )
v->env_mode = env_sustain;
v->hidden_env = env;
// unsigned cast because linear decrease going negative also triggers this
if ( (unsigned) env > 0x7FF )
{
env = (env < 0 ? 0 : 0x7FF);
if ( v->env_mode == env_attack )
v->env_mode = env_decay;
}
if ( !READ_COUNTER( rate ) )
v->env = env; // nothing else is controlled by the counter
}
}
exit_env:
{
// Apply pitch
int old_pos = v->interp_pos;
int interp_pos = (old_pos & 0x3FFF) + pitch;
if ( interp_pos > 0x7FFF )
interp_pos = 0x7FFF;
v->interp_pos = interp_pos;
// BRR decode if necessary
if ( old_pos >= 0x4000 )
{
// Arrange the four input nybbles in 0xABCD order for easy decoding
int nybbles = ram [(v->brr_addr + v->brr_offset) & 0xFFFF] * 0x100 +
ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF];
// Advance read position
int const brr_block_size = 9;
int brr_offset = v->brr_offset;
if ( (brr_offset += 2) >= brr_block_size )
{
// Next BRR block
int brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF;
assert( brr_offset == brr_block_size );
if ( brr_header & 1 )
{
brr_addr = SAMPLE_PTR( 1 );
if ( !v->kon_delay )
REG(endx) |= vbit;
}
v->brr_addr = brr_addr;
brr_offset = 1;
}
v->brr_offset = brr_offset;
// Decode
// 0: >>1 1: <<0 2: <<1 ... 12: <<11 13-15: >>4 <<11
static unsigned char const shifts [16 * 2] = {
13,12,12,12,12,12,12,12,12,12,12, 12, 12, 16, 16, 16,
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 11, 11
};
int const scale = brr_header >> 4;
int const right_shift = shifts [scale];
int const left_shift = shifts [scale + 16];
// Write to next four samples in circular buffer
int* pos = v->buf_pos;
int* end;
// Decode four samples
for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 )
{
// Extract upper nybble and scale appropriately
int s = ((int16_t) nybbles >> right_shift) << left_shift;
// Apply IIR filter (8 is the most commonly used)
int const filter = brr_header & 0x0C;
int const p1 = pos [brr_buf_size - 1];
int const p2 = pos [brr_buf_size - 2] >> 1;
if ( filter >= 8 )
{
s += p1;
s -= p2;
if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875
{
s += p2 >> 4;
s += (p1 * -3) >> 6;
}
else // s += p1 * 0.8984375 - p2 * 0.40625
{
s += (p1 * -13) >> 7;
s += (p2 * 3) >> 4;
}
}
else if ( filter ) // s += p1 * 0.46875
{
s += p1 >> 1;
s += (-p1) >> 5;
}
// Adjust and write sample
CLAMP16( s );
s = (int16_t) (s * 2);
pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around
}
if ( pos >= &v->buf [brr_buf_size] )
pos = v->buf;
v->buf_pos = pos;
}
}
skip_brr:
// Next voice
vbit <<= 1;
v_regs += 0x10;
v++;
}
while ( vbit < 0x100 );
// Echo position
int echo_offset = m.echo_offset;
uint8_t* const echo_ptr = &ram [(REG(esa) * 0x100 + echo_offset) & 0xFFFF];
if ( !echo_offset )
m.echo_length = (REG(edl) & 0x0F) * 0x800;
echo_offset += 4;
if ( echo_offset >= m.echo_length )
echo_offset = 0;
m.echo_offset = echo_offset;
// FIR
int echo_in_l = GET_LE16SA( echo_ptr + 0 );
int echo_in_r = GET_LE16SA( echo_ptr + 2 );
int (*echo_hist_pos) [2] = m.echo_hist_pos;
if ( ++echo_hist_pos >= &m.echo_hist [echo_hist_size] )
echo_hist_pos = m.echo_hist;
m.echo_hist_pos = echo_hist_pos;
echo_hist_pos [0] [0] = echo_hist_pos [8] [0] = echo_in_l;
echo_hist_pos [0] [1] = echo_hist_pos [8] [1] = echo_in_r;
#define CALC_FIR_( i, in ) ((in) * (int8_t) REG(fir + i * 0x10))
echo_in_l = CALC_FIR_( 7, echo_in_l );
echo_in_r = CALC_FIR_( 7, echo_in_r );
#define CALC_FIR( i, ch ) CALC_FIR_( i, echo_hist_pos [i + 1] [ch] )
#define DO_FIR( i )\
echo_in_l += CALC_FIR( i, 0 );\
echo_in_r += CALC_FIR( i, 1 );
DO_FIR( 0 );
DO_FIR( 1 );
DO_FIR( 2 );
#if defined (__MWERKS__) && __MWERKS__ < 0x3200
__eieio(); // keeps compiler from stupidly "caching" things in memory
#endif
DO_FIR( 3 );
DO_FIR( 4 );
DO_FIR( 5 );
DO_FIR( 6 );
// Echo out
if ( !(REG(flg) & 0x20) )
{
int l = (echo_out_l >> 7) + ((echo_in_l * (int8_t) REG(efb)) >> 14);
int r = (echo_out_r >> 7) + ((echo_in_r * (int8_t) REG(efb)) >> 14);
// just to help pass more validation tests
#if SPC_MORE_ACCURACY
l &= ~1;
r &= ~1;
#endif
CLAMP16( l );
CLAMP16( r );
SET_LE16A( echo_ptr + 0, l );
SET_LE16A( echo_ptr + 2, r );
}
// Sound out
int l = (main_out_l * mvoll + echo_in_l * (int8_t) REG(evoll)) >> 14;
int r = (main_out_r * mvolr + echo_in_r * (int8_t) REG(evolr)) >> 14;
CLAMP16( l );
CLAMP16( r );
if ( (REG(flg) & 0x40) )
{
l = 0;
r = 0;
}
sample_t* out = m.out;
WRITE_SAMPLES( l, r, out );
m.out = out;
}
while ( --count );
}
//// Setup
void SPC_DSP::mute_voices( int mask )
{
m.mute_mask = mask;
for ( int i = 0; i < voice_count; i++ )
{
m.voices [i].enabled = (mask >> i & 1) - 1;
update_voice_vol( i * 0x10 );
}
}
void SPC_DSP::init( void* ram_64k )
{
m.ram = (uint8_t*) ram_64k;
mute_voices( 0 );
disable_surround( false );
set_output( 0, 0 );
reset();
#ifndef NDEBUG
// be sure this sign-extends
assert( (int16_t) 0x8000 == -0x8000 );
// be sure right shift preserves sign
assert( (-1 >> 1) == -1 );
// check clamp macro
int i;
i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF );
i = -0x8001; CLAMP16( i ); assert( i == -0x8000 );
blargg_verify_byte_order();
#endif
}
void SPC_DSP::soft_reset_common()
{
require( m.ram ); // init() must have been called already
m.noise = 0x4000;
m.echo_hist_pos = m.echo_hist;
m.every_other_sample = 1;
m.echo_offset = 0;
m.phase = 0;
init_counter();
}
void SPC_DSP::soft_reset()
{
REG(flg) = 0xE0;
soft_reset_common();
}
void SPC_DSP::load( uint8_t const regs [register_count] )
{
memcpy( m.regs, regs, sizeof m.regs );
memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count );
// Internal state
int i;
for ( i = voice_count; --i >= 0; )
{
voice_t& v = m.voices [i];
v.brr_offset = 1;
v.buf_pos = v.buf;
}
m.new_kon = REG(kon);
mute_voices( m.mute_mask );
soft_reset_common();
}
void SPC_DSP::reset() { load( initial_regs ); }

212
snes_spc/snes_spc/SPC_DSP.h Normal file
View file

@ -0,0 +1,212 @@
// Fast SNES SPC-700 DSP emulator (about 3x speed of accurate one)
// snes_spc 0.9.0
#ifndef SPC_DSP_H
#define SPC_DSP_H
#include "blargg_common.h"
struct SPC_DSP {
public:
typedef BOOST::uint8_t uint8_t;
// Setup
// Initializes DSP and has it use the 64K RAM provided
void init( void* ram_64k );
// Sets destination for output samples. If out is NULL or out_size is 0,
// doesn't generate any.
typedef short sample_t;
void set_output( sample_t* out, int out_size );
// Number of samples written to output since it was last set, always
// a multiple of 2. Undefined if more samples were generated than
// output buffer could hold.
int sample_count() const;
// Emulation
// Resets DSP to power-on state
void reset();
// Emulates pressing reset switch on SNES
void soft_reset();
// Reads/writes DSP registers. For accuracy, you must first call spc_run_dsp()
// to catch the DSP up to present.
int read ( int addr ) const;
void write( int addr, int data );
// Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks
// a pair of samples is be generated.
void run( int clock_count );
// Sound control
// Mutes voices corresponding to non-zero bits in mask (overrides VxVOL with 0).
// Reduces emulation accuracy.
enum { voice_count = 8 };
void mute_voices( int mask );
// If true, prevents channels and global volumes from being phase-negated
void disable_surround( bool disable = true );
// State
// Resets DSP and uses supplied values to initialize registers
enum { register_count = 128 };
void load( uint8_t const regs [register_count] );
// DSP register addresses
// Global registers
enum {
r_mvoll = 0x0C, r_mvolr = 0x1C,
r_evoll = 0x2C, r_evolr = 0x3C,
r_kon = 0x4C, r_koff = 0x5C,
r_flg = 0x6C, r_endx = 0x7C,
r_efb = 0x0D, r_pmon = 0x2D,
r_non = 0x3D, r_eon = 0x4D,
r_dir = 0x5D, r_esa = 0x6D,
r_edl = 0x7D,
r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F
};
// Voice registers
enum {
v_voll = 0x00, v_volr = 0x01,
v_pitchl = 0x02, v_pitchh = 0x03,
v_srcn = 0x04, v_adsr0 = 0x05,
v_adsr1 = 0x06, v_gain = 0x07,
v_envx = 0x08, v_outx = 0x09
};
public:
enum { extra_size = 16 };
sample_t* extra() { return m.extra; }
sample_t const* out_pos() const { return m.out; }
public:
BLARGG_DISABLE_NOTHROW
typedef BOOST::int8_t int8_t;
typedef BOOST::int16_t int16_t;
enum { echo_hist_size = 8 };
enum env_mode_t { env_release, env_attack, env_decay, env_sustain };
enum { brr_buf_size = 12 };
struct voice_t
{
int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling)
int* buf_pos; // place in buffer where next samples will be decoded
int interp_pos; // relative fractional position in sample (0x1000 = 1.0)
int brr_addr; // address of current BRR block
int brr_offset; // current decoding offset in BRR block
int kon_delay; // KON delay/current setup phase
env_mode_t env_mode;
int env; // current envelope level
int hidden_env; // used by GAIN mode 7, very obscure quirk
int volume [2]; // copy of volume from DSP registers, with surround disabled
int enabled; // -1 if enabled, 0 if muted
};
private:
struct state_t
{
uint8_t regs [register_count];
// Echo history keeps most recent 8 samples (twice the size to simplify wrap handling)
int echo_hist [echo_hist_size * 2] [2];
int (*echo_hist_pos) [2]; // &echo_hist [0 to 7]
int every_other_sample; // toggles every sample
int kon; // KON value when last checked
int noise;
int echo_offset; // offset from ESA in echo buffer
int echo_length; // number of bytes that echo_offset will stop at
int phase; // next clock cycle to run (0-31)
unsigned counters [4];
int new_kon;
int t_koff;
voice_t voices [voice_count];
unsigned* counter_select [32];
// non-emulation state
uint8_t* ram; // 64K shared RAM between DSP and SMP
int mute_mask;
int surround_threshold;
sample_t* out;
sample_t* out_end;
sample_t* out_begin;
sample_t extra [extra_size];
};
state_t m;
void init_counter();
void run_counter( int );
void soft_reset_common();
void write_outline( int addr, int data );
void update_voice_vol( int addr );
};
#include <assert.h>
inline int SPC_DSP::sample_count() const { return int(m.out - m.out_begin); }
inline int SPC_DSP::read( int addr ) const
{
assert( (unsigned) addr < register_count );
return m.regs [addr];
}
inline void SPC_DSP::update_voice_vol( int addr )
{
int l = (int8_t) m.regs [addr + v_voll];
int r = (int8_t) m.regs [addr + v_volr];
if ( l * r < m.surround_threshold )
{
// signs differ, so negate those that are negative
l ^= l >> 7;
r ^= r >> 7;
}
voice_t& v = m.voices [addr >> 4];
int enabled = v.enabled;
v.volume [0] = l & enabled;
v.volume [1] = r & enabled;
}
inline void SPC_DSP::write( int addr, int data )
{
assert( (unsigned) addr < register_count );
m.regs [addr] = (uint8_t) data;
int low = addr & 0x0F;
if ( low < 0x2 ) // voice volumes
{
update_voice_vol( low ^ addr );
}
else if ( low == 0xC )
{
if ( addr == r_kon )
m.new_kon = (uint8_t) data;
if ( addr == r_endx ) // always cleared, regardless of data written
m.regs [r_endx] = 0;
}
}
inline void SPC_DSP::disable_surround( bool disable )
{
m.surround_threshold = disable ? 0 : -0x4000;
}
#define SPC_NO_COPY_STATE_FUNCS 1
#define SPC_LESS_ACCURATE 1
#endif

View file

@ -0,0 +1,68 @@
// snes_spc 0.9.0. http://www.slack.net/~ant/
#include "SPC_Filter.h"
#include <string.h>
/* Copyright (C) 2007 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"
void SPC_Filter::clear() { memset( ch, 0, sizeof ch ); }
SPC_Filter::SPC_Filter()
{
gain = gain_unit;
bass = bass_norm;
clear();
}
void SPC_Filter::run( short* io, int count )
{
require( (count & 1) == 0 ); // must be even
int const gain = this->gain;
int const bass = this->bass;
chan_t* c = &ch [2];
do
{
// cache in registers
int sum = (--c)->sum;
int pp1 = c->pp1;
int p1 = c->p1;
for ( int i = 0; i < count; i += 2 )
{
// Low-pass filter (two point FIR with coeffs 0.25, 0.75)
int f = io [i] + p1;
p1 = io [i] * 3;
// High-pass filter ("leaky integrator")
int delta = f - pp1;
pp1 = f;
int s = sum >> (gain_bits + 2);
sum += (delta * gain) - (sum >> bass);
// Clamp to 16 bits
if ( (short) s != s )
s = (s >> 31) ^ 0x7FFF;
io [i] = (short) s;
}
c->p1 = p1;
c->pp1 = pp1;
c->sum = sum;
++io;
}
while ( c != ch );
}

View file

@ -0,0 +1,47 @@
// Simple low-pass and high-pass filter to better match sound output of a SNES
// snes_spc 0.9.0
#ifndef SPC_FILTER_H
#define SPC_FILTER_H
#include "blargg_common.h"
struct SPC_Filter {
public:
// Filters count samples of stereo sound in place. Count must be a multiple of 2.
typedef short sample_t;
void run( sample_t* io, int count );
// Optional features
// Clears filter to silence
void clear();
// Sets gain (volume), where gain_unit is normal. Gains greater than gain_unit
// are fine, since output is clamped to 16-bit sample range.
enum { gain_unit = 0x100 };
void set_gain( int gain );
// Sets amount of bass (logarithmic scale)
enum { bass_none = 0 };
enum { bass_norm = 8 }; // normal amount
enum { bass_max = 31 };
void set_bass( int bass );
public:
SPC_Filter();
BLARGG_DISABLE_NOTHROW
private:
enum { gain_bits = 8 };
int gain;
int bass;
struct chan_t { int p1, pp1, sum; };
chan_t ch [2];
};
inline void SPC_Filter::set_gain( int g ) { gain = g; }
inline void SPC_Filter::set_bass( int b ) { bass = b; }
#endif

View file

@ -0,0 +1,186 @@
// Sets up common environment for Shay Green's libraries.
// To change configuration options, modify blargg_config.h, not this file.
// snes_spc 0.9.0
#ifndef BLARGG_COMMON_H
#define BLARGG_COMMON_H
#include <stddef.h>
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
#undef BLARGG_COMMON_H
// allow blargg_config.h to #include blargg_common.h
#include "blargg_config.h"
#ifndef BLARGG_COMMON_H
#define BLARGG_COMMON_H
// BLARGG_RESTRICT: equivalent to restrict, where supported
#if defined (__GNUC__) || _MSC_VER >= 1100
#define BLARGG_RESTRICT __restrict
#else
#define BLARGG_RESTRICT
#endif
// STATIC_CAST(T,expr): Used in place of static_cast<T> (expr)
#ifndef STATIC_CAST
#define STATIC_CAST(T,expr) ((T) (expr))
#endif
// blargg_err_t (0 on success, otherwise error string)
#ifndef blargg_err_t
typedef const char* blargg_err_t;
#endif
// blargg_vector - very lightweight vector of POD types (no constructor/destructor)
template<class T>
class blargg_vector {
T* begin_;
size_t size_;
public:
blargg_vector() : begin_( 0 ), size_( 0 ) { }
~blargg_vector() { free( begin_ ); }
size_t size() const { return size_; }
T* begin() const { return begin_; }
T* end() const { return begin_ + size_; }
blargg_err_t resize( size_t n )
{
// TODO: blargg_common.cpp to hold this as an outline function, ugh
void* p = realloc( begin_, n * sizeof (T) );
if ( p )
begin_ = (T*) p;
else if ( n > size_ ) // realloc failure only a problem if expanding
return "Out of memory";
size_ = n;
return 0;
}
void clear() { void* p = begin_; begin_ = 0; size_ = 0; free( p ); }
T& operator [] ( size_t n ) const
{
assert( n <= size_ ); // <= to allow past-the-end value
return begin_ [n];
}
};
#ifndef BLARGG_DISABLE_NOTHROW
// throw spec mandatory in ISO C++ if operator new can return NULL
#if __cplusplus >= 199711 || defined (__GNUC__)
#define BLARGG_THROWS( spec ) throw spec
#else
#define BLARGG_THROWS( spec )
#endif
#define BLARGG_DISABLE_NOTHROW \
void* operator new ( size_t s ) BLARGG_THROWS(()) { return malloc( s ); }\
void operator delete ( void* p ) { free( p ); }
#define BLARGG_NEW new
#else
#include <new>
#define BLARGG_NEW new (std::nothrow)
#endif
// BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant)
#define BLARGG_4CHAR( a, b, c, d ) \
((a&0xFF)*0x1000000L + (b&0xFF)*0x10000L + (c&0xFF)*0x100L + (d&0xFF))
// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0.
#ifndef BOOST_STATIC_ASSERT
#ifdef _MSC_VER
// MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified
#define BOOST_STATIC_ASSERT( expr ) \
void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] )
#else
// Some other compilers fail when declaring same function multiple times in class,
// so differentiate them by line
#define BOOST_STATIC_ASSERT( expr ) \
void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] )
#endif
#endif
// BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compiler. If 1,
// compiler is assumed to support bool. If undefined, availability is determined.
#ifndef BLARGG_COMPILER_HAS_BOOL
#if defined (__MWERKS__)
#if !__option(bool)
#define BLARGG_COMPILER_HAS_BOOL 0
#endif
#elif defined (_MSC_VER)
#if _MSC_VER < 1100
#define BLARGG_COMPILER_HAS_BOOL 0
#endif
#elif defined (__GNUC__)
// supports bool
#elif __cplusplus < 199711
#define BLARGG_COMPILER_HAS_BOOL 0
#endif
#endif
#if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL
// If you get errors here, modify your blargg_config.h file
typedef int bool;
const bool true = 1;
const bool false = 0;
#endif
// blargg_long/blargg_ulong = at least 32 bits, int if it's big enough
#if INT_MAX < 0x7FFFFFFF || LONG_MAX == 0x7FFFFFFF
typedef long blargg_long;
#else
typedef int blargg_long;
#endif
#if UINT_MAX < 0xFFFFFFFF || ULONG_MAX == 0xFFFFFFFF
typedef unsigned long blargg_ulong;
#else
typedef unsigned blargg_ulong;
#endif
// BOOST::int8_t etc.
// HAVE_STDINT_H: If defined, use <stdint.h> for int8_t etc.
#if defined (HAVE_STDINT_H)
#include <stdint.h>
#define BOOST
// HAVE_INTTYPES_H: If defined, use <stdint.h> for int8_t etc.
#elif defined (HAVE_INTTYPES_H)
#include <inttypes.h>
#define BOOST
#else
struct BOOST
{
#if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F
typedef signed char int8_t;
typedef unsigned char uint8_t;
#else
// No suitable 8-bit type available
typedef struct see_blargg_common_h int8_t;
typedef struct see_blargg_common_h uint8_t;
#endif
#if USHRT_MAX == 0xFFFF
typedef short int16_t;
typedef unsigned short uint16_t;
#else
// No suitable 16-bit type available
typedef struct see_blargg_common_h int16_t;
typedef struct see_blargg_common_h uint16_t;
#endif
#if ULONG_MAX == 0xFFFFFFFF
typedef long int32_t;
typedef unsigned long uint32_t;
#elif UINT_MAX == 0xFFFFFFFF
typedef int int32_t;
typedef unsigned int uint32_t;
#else
// No suitable 32-bit type available
typedef struct see_blargg_common_h int32_t;
typedef struct see_blargg_common_h uint32_t;
#endif
};
#endif
#endif
#endif

View file

@ -0,0 +1,24 @@
// snes_spc 0.9.0 user configuration file. Don't replace when updating library.
// snes_spc 0.9.0
#ifndef BLARGG_CONFIG_H
#define BLARGG_CONFIG_H
// Uncomment to disable debugging checks
//#define NDEBUG 1
// Uncomment to enable platform-specific (and possibly non-portable) optimizations
//#define BLARGG_NONPORTABLE 1
// Uncomment if automatic byte-order determination doesn't work
//#define BLARGG_BIG_ENDIAN 1
// Uncomment if you get errors in the bool section of blargg_common.h
//#define BLARGG_COMPILER_HAS_BOOL 1
// Use standard config.h if present
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#endif

View file

@ -0,0 +1,185 @@
// CPU Byte Order Utilities
// snes_spc 0.9.0
#ifndef BLARGG_ENDIAN
#define BLARGG_ENDIAN
#include "blargg_common.h"
// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16)
#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \
defined (__x86_64__) || defined (__ia64__) || defined (__i386__)
#define BLARGG_CPU_X86 1
#define BLARGG_CPU_CISC 1
#endif
#if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc)
#define BLARGG_CPU_POWERPC 1
#define BLARGG_CPU_RISC 1
#endif
// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only
// one may be #defined to 1. Only needed if something actually depends on byte order.
#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN)
#ifdef __GLIBC__
// GCC handles this for us
#include <endian.h>
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define BLARGG_LITTLE_ENDIAN 1
#elif __BYTE_ORDER == __BIG_ENDIAN
#define BLARGG_BIG_ENDIAN 1
#endif
#else
#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \
(defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234)
#define BLARGG_LITTLE_ENDIAN 1
#endif
#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \
defined (__sparc__) || BLARGG_CPU_POWERPC || \
(defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321)
#define BLARGG_BIG_ENDIAN 1
#elif !defined (__mips__)
// No endian specified; assume little-endian, since it's most common
#define BLARGG_LITTLE_ENDIAN 1
#endif
#endif
#endif
#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN
#undef BLARGG_LITTLE_ENDIAN
#undef BLARGG_BIG_ENDIAN
#endif
inline void blargg_verify_byte_order()
{
#ifndef NDEBUG
#if BLARGG_BIG_ENDIAN
volatile int i = 1;
assert( *(volatile char*) &i == 0 );
#elif BLARGG_LITTLE_ENDIAN
volatile int i = 1;
assert( *(volatile char*) &i != 0 );
#endif
#endif
}
inline unsigned get_le16( void const* p )
{
return (unsigned) ((unsigned char const*) p) [1] << 8 |
(unsigned) ((unsigned char const*) p) [0];
}
inline unsigned get_be16( void const* p )
{
return (unsigned) ((unsigned char const*) p) [0] << 8 |
(unsigned) ((unsigned char const*) p) [1];
}
inline blargg_ulong get_le32( void const* p )
{
return (blargg_ulong) ((unsigned char const*) p) [3] << 24 |
(blargg_ulong) ((unsigned char const*) p) [2] << 16 |
(blargg_ulong) ((unsigned char const*) p) [1] << 8 |
(blargg_ulong) ((unsigned char const*) p) [0];
}
inline blargg_ulong get_be32( void const* p )
{
return (blargg_ulong) ((unsigned char const*) p) [0] << 24 |
(blargg_ulong) ((unsigned char const*) p) [1] << 16 |
(blargg_ulong) ((unsigned char const*) p) [2] << 8 |
(blargg_ulong) ((unsigned char const*) p) [3];
}
inline void set_le16( void* p, unsigned n )
{
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
((unsigned char*) p) [0] = (unsigned char) n;
}
inline void set_be16( void* p, unsigned n )
{
((unsigned char*) p) [0] = (unsigned char) (n >> 8);
((unsigned char*) p) [1] = (unsigned char) n;
}
inline void set_le32( void* p, blargg_ulong n )
{
((unsigned char*) p) [0] = (unsigned char) n;
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
((unsigned char*) p) [2] = (unsigned char) (n >> 16);
((unsigned char*) p) [3] = (unsigned char) (n >> 24);
}
inline void set_be32( void* p, blargg_ulong n )
{
((unsigned char*) p) [3] = (unsigned char) n;
((unsigned char*) p) [2] = (unsigned char) (n >> 8);
((unsigned char*) p) [1] = (unsigned char) (n >> 16);
((unsigned char*) p) [0] = (unsigned char) (n >> 24);
}
#if BLARGG_NONPORTABLE
// Optimized implementation if byte order is known
#if BLARGG_LITTLE_ENDIAN
#define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr))
#define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr))
#define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data))
#define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data))
#elif BLARGG_BIG_ENDIAN
#define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr))
#define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr))
#define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data))
#define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data))
#if BLARGG_CPU_POWERPC
// PowerPC has special byte-reversed instructions
#if defined (__MWERKS__)
#define GET_LE16( addr ) (__lhbrx( addr, 0 ))
#define GET_LE32( addr ) (__lwbrx( addr, 0 ))
#define SET_LE16( addr, in ) (__sthbrx( in, addr, 0 ))
#define SET_LE32( addr, in ) (__stwbrx( in, addr, 0 ))
#elif defined (__GNUC__)
#define GET_LE16( addr ) ({unsigned ppc_lhbrx_; asm( "lhbrx %0,0,%1" : "=r" (ppc_lhbrx_) : "r" (addr), "0" (ppc_lhbrx_) ); ppc_lhbrx_;})
#define GET_LE32( addr ) ({unsigned ppc_lwbrx_; asm( "lwbrx %0,0,%1" : "=r" (ppc_lwbrx_) : "r" (addr), "0" (ppc_lwbrx_) ); ppc_lwbrx_;})
#define SET_LE16( addr, in ) ({asm( "sthbrx %0,0,%1" : : "r" (in), "r" (addr) );})
#define SET_LE32( addr, in ) ({asm( "stwbrx %0,0,%1" : : "r" (in), "r" (addr) );})
#endif
#endif
#endif
#endif
#ifndef GET_LE16
#define GET_LE16( addr ) get_le16( addr )
#define SET_LE16( addr, data ) set_le16( addr, data )
#endif
#ifndef GET_LE32
#define GET_LE32( addr ) get_le32( addr )
#define SET_LE32( addr, data ) set_le32( addr, data )
#endif
#ifndef GET_BE16
#define GET_BE16( addr ) get_be16( addr )
#define SET_BE16( addr, data ) set_be16( addr, data )
#endif
#ifndef GET_BE32
#define GET_BE32( addr ) get_be32( addr )
#define SET_BE32( addr, data ) set_be32( addr, data )
#endif
// auto-selecting versions
inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); }
inline void set_le( BOOST::uint32_t* p, blargg_ulong n ) { SET_LE32( p, n ); }
inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); }
inline void set_be( BOOST::uint32_t* p, blargg_ulong n ) { SET_BE32( p, n ); }
inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); }
inline blargg_ulong get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); }
inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); }
inline blargg_ulong get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); }
#endif

View file

@ -0,0 +1,100 @@
/* Included at the beginning of library source files, after all other #include lines.
Sets up helpful macros and services used in my source code. They don't need
module an annoying module prefix on their names since they are defined after
all other #include lines. */
// snes_spc 0.9.0
#ifndef BLARGG_SOURCE_H
#define BLARGG_SOURCE_H
// If debugging is enabled, abort program if expr is false. Meant for checking
// internal state and consistency. A failed assertion indicates a bug in the module.
// void assert( bool expr );
#include <assert.h>
// If debugging is enabled and expr is false, abort program. Meant for checking
// caller-supplied parameters and operations that are outside the control of the
// module. A failed requirement indicates a bug outside the module.
// void require( bool expr );
#undef require
#define require( expr ) assert( expr )
// Like printf() except output goes to debug log file. Might be defined to do
// nothing (not even evaluate its arguments).
// void dprintf( const char* format, ... );
static inline void blargg_dprintf_( const char*, ... ) { }
#undef dprintf
#define dprintf (1) ? (void) 0 : blargg_dprintf_
// If enabled, evaluate expr and if false, make debug log entry with source file
// and line. Meant for finding situations that should be examined further, but that
// don't indicate a problem. In all cases, execution continues normally.
#undef check
#define check( expr ) ((void) 0)
// If expr yields error string, return it from current function, otherwise continue.
#undef RETURN_ERR
#define RETURN_ERR( expr ) do { \
blargg_err_t blargg_return_err_ = (expr); \
if ( blargg_return_err_ ) return blargg_return_err_; \
} while ( 0 )
// If ptr is 0, return out of memory error string.
#undef CHECK_ALLOC
#define CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 )
// Avoid any macros which evaluate their arguments multiple times
#undef min
#undef max
#define DEF_MIN_MAX( type ) \
static inline type min( type x, type y ) { if ( x < y ) return x; return y; }\
static inline type max( type x, type y ) { if ( y < x ) return x; return y; }
DEF_MIN_MAX( int )
DEF_MIN_MAX( unsigned )
DEF_MIN_MAX( long )
DEF_MIN_MAX( unsigned long )
DEF_MIN_MAX( float )
DEF_MIN_MAX( double )
#undef DEF_MIN_MAX
/*
// using const references generates crappy code, and I am currenly only using these
// for built-in types, so they take arguments by value
// TODO: remove
inline int min( int x, int y )
template<class T>
inline T min( T x, T y )
{
if ( x < y )
return x;
return y;
}
template<class T>
inline T max( T x, T y )
{
if ( x < y )
return y;
return x;
}
*/
// TODO: good idea? bad idea?
#undef byte
#define byte byte_
typedef unsigned char byte;
// deprecated
#define BLARGG_CHECK_ALLOC CHECK_ALLOC
#define BLARGG_RETURN_ERR RETURN_ERR
// BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf and check
#ifdef BLARGG_SOURCE_BEGIN
#include BLARGG_SOURCE_BEGIN
#endif
#endif

48
snes_spc/snes_spc/dsp.cpp Normal file
View file

@ -0,0 +1,48 @@
// snes_spc 0.9.0. http://www.slack.net/~ant/
#include "dsp.h"
#include "SPC_DSP.h"
/* Copyright (C) 2007 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"
SPC_DSP* spc_dsp_new( void )
{
// be sure constants match
assert( spc_dsp_voice_count == (int) SPC_DSP::voice_count );
assert( spc_dsp_register_count == (int) SPC_DSP::register_count );
#if !SPC_NO_COPY_STATE_FUNCS
assert( spc_dsp_state_size == (int) SPC_DSP::state_size );
#endif
return new SPC_DSP;
}
void spc_dsp_delete ( SPC_DSP* s ) { delete s; }
void spc_dsp_init ( SPC_DSP* s, void* ram_64k ) { s->init( ram_64k ); }
void spc_dsp_set_output ( SPC_DSP* s, spc_dsp_sample_t* p, int n ) { s->set_output( p, n ); }
int spc_dsp_sample_count( SPC_DSP const* s ) { return s->sample_count(); }
void spc_dsp_reset ( SPC_DSP* s ) { s->reset(); }
void spc_dsp_soft_reset ( SPC_DSP* s ) { s->soft_reset(); }
int spc_dsp_read ( SPC_DSP const* s, int addr ) { return s->read( addr ); }
void spc_dsp_write ( SPC_DSP* s, int addr, int data ) { s->write( addr, data ); }
void spc_dsp_run ( SPC_DSP* s, int clock_count ) { s->run( clock_count ); }
void spc_dsp_mute_voices ( SPC_DSP* s, int mask ) { s->mute_voices( mask ); }
void spc_dsp_disable_surround( SPC_DSP* s, int disable ) { s->disable_surround( !!disable ); }
void spc_dsp_load ( SPC_DSP* s, unsigned char const regs [spc_dsp_register_count] ) { s->load( regs ); }
#if !SPC_NO_COPY_STATE_FUNCS
void spc_dsp_copy_state ( SPC_DSP* s, unsigned char** p, spc_dsp_copy_func_t f ) { s->copy_state( p, f ); }
int spc_dsp_check_kon ( SPC_DSP* s ) { return s->check_kon(); }
#endif

83
snes_spc/snes_spc/dsp.h Normal file
View file

@ -0,0 +1,83 @@
/* SNES SPC-700 DSP emulator C interface (also usable from C++) */
/* snes_spc 0.9.0 */
#ifndef DSP_H
#define DSP_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct SPC_DSP SPC_DSP;
/* Creates new DSP emulator. NULL if out of memory. */
SPC_DSP* spc_dsp_new( void );
/* Frees DSP emulator */
void spc_dsp_delete( SPC_DSP* );
/* Initializes DSP and has it use the 64K RAM provided */
void spc_dsp_init( SPC_DSP*, void* ram_64k );
/* Sets destination for output samples. If out is NULL or out_size is 0,
doesn't generate any. */
typedef short spc_dsp_sample_t;
void spc_dsp_set_output( SPC_DSP*, spc_dsp_sample_t* out, int out_size );
/* Number of samples written to output since it was last set, always
a multiple of 2. Undefined if more samples were generated than
output buffer could hold. */
int spc_dsp_sample_count( SPC_DSP const* );
/**** Emulation *****/
/* Resets DSP to power-on state */
void spc_dsp_reset( SPC_DSP* );
/* Emulates pressing reset switch on SNES */
void spc_dsp_soft_reset( SPC_DSP* );
/* Reads/writes DSP registers. For accuracy, you must first call spc_dsp_run() */
/* to catch the DSP up to present. */
int spc_dsp_read ( SPC_DSP const*, int addr );
void spc_dsp_write( SPC_DSP*, int addr, int data );
/* Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks */
/* a pair of samples is be generated. */
void spc_dsp_run( SPC_DSP*, int clock_count );
/**** Sound control *****/
/* Mutes voices corresponding to non-zero bits in mask. Reduces emulation accuracy. */
enum { spc_dsp_voice_count = 8 };
void spc_dsp_mute_voices( SPC_DSP*, int mask );
/* If true, prevents channels and global volumes from being phase-negated.
Only supported by fast DSP; has no effect on accurate DSP. */
void spc_dsp_disable_surround( SPC_DSP*, int disable );
/**** State save/load *****/
/* Resets DSP and uses supplied values to initialize registers */
enum { spc_dsp_register_count = 128 };
void spc_dsp_load( SPC_DSP*, unsigned char const regs [spc_dsp_register_count] );
/* Saves/loads exact emulator state (accurate DSP only) */
enum { spc_dsp_state_size = 640 }; /* maximum space needed when saving */
typedef void (*spc_dsp_copy_func_t)( unsigned char** io, void* state, size_t );
void spc_dsp_copy_state( SPC_DSP*, unsigned char** io, spc_dsp_copy_func_t );
/* Returns non-zero if new key-on events occurred since last call (accurate DSP only) */
int spc_dsp_check_kon( SPC_DSP* );
#ifdef __cplusplus
}
#endif
#endif

73
snes_spc/snes_spc/spc.cpp Normal file
View file

@ -0,0 +1,73 @@
// snes_spc 0.9.0. http://www.slack.net/~ant/
#include "spc.h"
#include "SNES_SPC.h"
#include "SPC_Filter.h"
/* Copyright (C) 2004-2007 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"
SNES_SPC* spc_new( void )
{
// be sure constants match
assert( spc_sample_rate == (int) SNES_SPC::sample_rate );
assert( spc_rom_size == (int) SNES_SPC::rom_size );
assert( spc_clock_rate == (int) SNES_SPC::clock_rate );
assert( spc_clocks_per_sample == (int) SNES_SPC::clocks_per_sample );
assert( spc_port_count == (int) SNES_SPC::port_count );
assert( spc_voice_count == (int) SNES_SPC::voice_count );
assert( spc_tempo_unit == (int) SNES_SPC::tempo_unit );
assert( spc_file_size == (int) SNES_SPC::spc_file_size );
#if !SPC_NO_COPY_STATE_FUNCS
assert( spc_state_size == (int) SNES_SPC::state_size );
#endif
SNES_SPC* s = new SNES_SPC;
if ( s && s->init() )
{
delete s;
s = 0;
}
return s;
}
void spc_delete ( SNES_SPC* s ) { delete s; }
void spc_init_rom ( SNES_SPC* s, unsigned char const r [64] ) { s->init_rom( r ); }
void spc_set_output ( SNES_SPC* s, spc_sample_t* p, int n ) { s->set_output( p, n ); }
int spc_sample_count ( SNES_SPC const* s ) { return s->sample_count(); }
void spc_reset ( SNES_SPC* s ) { s->reset(); }
void spc_soft_reset ( SNES_SPC* s ) { s->soft_reset(); }
int spc_read_port ( SNES_SPC* s, spc_time_t t, int p ) { return s->read_port( t, p ); }
void spc_write_port ( SNES_SPC* s, spc_time_t t, int p, int d ) { s->write_port( t, p, d ); }
void spc_end_frame ( SNES_SPC* s, spc_time_t t ) { s->end_frame( t ); }
void spc_mute_voices ( SNES_SPC* s, int mask ) { s->mute_voices( mask ); }
void spc_disable_surround( SNES_SPC* s, int disable ) { s->disable_surround( !!disable ); }
void spc_set_tempo ( SNES_SPC* s, int tempo ) { s->set_tempo( tempo ); }
spc_err_t spc_load_spc ( SNES_SPC* s, void const* p, long n ) { return s->load_spc( p, n ); }
void spc_clear_echo ( SNES_SPC* s ) { s->clear_echo(); }
spc_err_t spc_play ( SNES_SPC* s, int count, short* out ) { return s->play( count, out ); }
spc_err_t spc_skip ( SNES_SPC* s, int count ) { return s->skip( count ); }
#if !SPC_NO_COPY_STATE_FUNCS
void spc_copy_state ( SNES_SPC* s, unsigned char** p, spc_copy_func_t f ) { s->copy_state( p, f ); }
void spc_init_header ( void* spc_out ) { SNES_SPC::init_header( spc_out ); }
void spc_save_spc ( SNES_SPC* s, void* spc_out ) { s->save_spc( spc_out ); }
int spc_check_kon ( SNES_SPC* s ) { return s->check_kon(); }
#endif
SPC_Filter* spc_filter_new( void ) { return new SPC_Filter; }
void spc_filter_delete( SPC_Filter* f ) { delete f; }
void spc_filter_run( SPC_Filter* f, spc_sample_t* p, int s ) { f->run( p, s ); }
void spc_filter_clear( SPC_Filter* f ) { f->clear(); }
void spc_filter_set_gain( SPC_Filter* f, int gain ) { f->set_gain( gain ); }
void spc_filter_set_bass( SPC_Filter* f, int bass ) { f->set_bass( bass ); }

147
snes_spc/snes_spc/spc.h Normal file
View file

@ -0,0 +1,147 @@
/* SNES SPC-700 APU emulator C interface (also usable from C++) */
/* snes_spc 0.9.0 */
#ifndef SPC_H
#define SPC_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Error string return. NULL if success, otherwise error message. */
typedef const char* spc_err_t;
typedef struct SNES_SPC SNES_SPC;
/* Creates new SPC emulator. NULL if out of memory. */
SNES_SPC* spc_new( void );
/* Frees SPC emulator */
void spc_delete( SNES_SPC* );
/* Sample pairs generated per second */
enum { spc_sample_rate = 32000 };
/**** Emulator use ****/
/* Sets IPL ROM data. Library does not include ROM data. Most SPC music files
don't need ROM, but a full emulator must provide this. */
enum { spc_rom_size = 0x40 };
void spc_init_rom( SNES_SPC*, unsigned char const rom [spc_rom_size] );
/* Sets destination for output samples */
typedef short spc_sample_t;
void spc_set_output( SNES_SPC*, spc_sample_t* out, int out_size );
/* Number of samples written to output since last set */
int spc_sample_count( SNES_SPC const* );
/* Resets SPC to power-on state. This resets your output buffer, so you must
call spc_set_output() after this. */
void spc_reset( SNES_SPC* );
/* Emulates pressing reset switch on SNES. This resets your output buffer, so
you must call spc_set_output() after this. */
void spc_soft_reset( SNES_SPC* );
/* 1024000 SPC clocks per second, sample pair every 32 clocks */
typedef int spc_time_t;
enum { spc_clock_rate = 1024000 };
enum { spc_clocks_per_sample = 32 };
/* Reads/writes port at specified time */
enum { spc_port_count = 4 };
int spc_read_port ( SNES_SPC*, spc_time_t, int port );
void spc_write_port( SNES_SPC*, spc_time_t, int port, int data );
/* Runs SPC to end_time and starts a new time frame at 0 */
void spc_end_frame( SNES_SPC*, spc_time_t end_time );
/**** Sound control ****/
/*Mutes voices corresponding to non-zero bits in mask. Reduces emulation accuracy. */
enum { spc_voice_count = 8 };
void spc_mute_voices( SNES_SPC*, int mask );
/* If true, prevents channels and global volumes from being phase-negated.
Only supported by fast DSP; has no effect on accurate DSP. */
void spc_disable_surround( SNES_SPC*, int disable );
/* Sets tempo, where spc_tempo_unit = normal, spc_tempo_unit / 2 = half speed, etc. */
enum { spc_tempo_unit = 0x100 };
void spc_set_tempo( SNES_SPC*, int );
/**** SPC music playback *****/
/* Loads SPC data into emulator. Returns NULL on success, otherwise error string. */
spc_err_t spc_load_spc( SNES_SPC*, void const* spc_in, long size );
/* Clears echo region. Useful after loading an SPC as many have garbage in echo. */
void spc_clear_echo( SNES_SPC* );
/* Plays for count samples and write samples to out. Discards samples if out
is NULL. Count must be a multiple of 2 since output is stereo. */
spc_err_t spc_play( SNES_SPC*, int count, short* out );
/* Skips count samples. Several times faster than spc_play(). */
spc_err_t spc_skip( SNES_SPC*, int count );
/**** State save/load (only available with accurate DSP) ****/
/* Saves/loads exact emulator state */
enum { spc_state_size = 67 * 1024L }; /* maximum space needed when saving */
typedef void (*spc_copy_func_t)( unsigned char** io, void* state, size_t );
void spc_copy_state( SNES_SPC*, unsigned char** io, spc_copy_func_t );
/* Writes minimal SPC file header to spc_out */
void spc_init_header( void* spc_out );
/* Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out.
Does not set up SPC header; use spc_init_header() for that. */
enum { spc_file_size = 0x10200 }; /* spc_out must have this many bytes allocated */
void spc_save_spc( SNES_SPC*, void* spc_out );
/* Returns non-zero if new key-on events occurred since last check. Useful for
trimming silence while saving an SPC. */
int spc_check_kon( SNES_SPC* );
/**** SPC_Filter ****/
typedef struct SPC_Filter SPC_Filter;
/* Creates new filter. NULL if out of memory. */
SPC_Filter* spc_filter_new( void );
/* Frees filter */
void spc_filter_delete( SPC_Filter* );
/* Filters count samples of stereo sound in place. Count must be a multiple of 2. */
void spc_filter_run( SPC_Filter*, spc_sample_t* io, int count );
/* Clears filter to silence */
void spc_filter_clear( SPC_Filter* );
/* Sets gain (volume), where spc_filter_gain_unit is normal. Gains greater than
spc_filter_gain_unit are fine, since output is clamped to 16-bit sample range. */
enum { spc_filter_gain_unit = 0x100 };
void spc_filter_set_gain( SPC_Filter*, int gain );
/* Sets amount of bass (logarithmic scale) */
enum { spc_filter_bass_none = 0 };
enum { spc_filter_bass_norm = 8 }; /* normal amount */
enum { spc_filter_bass_max = 31 };
void spc_filter_set_bass( SPC_Filter*, int bass );
#ifdef __cplusplus
}
#endif
#endif

View file

@ -545,9 +545,9 @@ public:
bool CheckLocalView (int playernum) const; bool CheckLocalView (int playernum) const;
// Finds the first item of a particular type. // Finds the first item of a particular type.
AInventory *FindInventory (const PClass *type) const; AInventory *FindInventory (const PClass *type);
AInventory *FindInventory (FName type) const; AInventory *FindInventory (FName type);
template<class T> T *FindInventory () const template<class T> T *FindInventory ()
{ {
return static_cast<T *> (FindInventory (RUNTIME_CLASS(T))); return static_cast<T *> (FindInventory (RUNTIME_CLASS(T)));
} }
@ -556,7 +556,7 @@ public:
AInventory *GiveInventoryType (const PClass *type); AInventory *GiveInventoryType (const PClass *type);
// Returns the first item held with IF_INVBAR set. // Returns the first item held with IF_INVBAR set.
AInventory *FirstInv () const; AInventory *FirstInv ();
// Tries to give the actor some ammo. // Tries to give the actor some ammo.
bool GiveAmmo (const PClass *type, int amount); bool GiveAmmo (const PClass *type, int amount);
@ -572,7 +572,7 @@ public:
void ConversationAnimation (int animnum); void ConversationAnimation (int animnum);
// Make this actor hate the same things as another actor // Make this actor hate the same things as another actor
void CopyFriendliness (const AActor *other, bool changeTarget); void CopyFriendliness (AActor *other, bool changeTarget);
// Moves the other actor's inventory to this one // Moves the other actor's inventory to this one
void ObtainInventory (AActor *other); void ObtainInventory (AActor *other);
@ -647,35 +647,32 @@ public:
BYTE movedir; // 0-7 BYTE movedir; // 0-7
SBYTE visdir; SBYTE visdir;
SWORD movecount; // when 0, select a new dir SWORD movecount; // when 0, select a new dir
AActor *target; // thing being chased/attacked (or NULL) TObjPtr<AActor> target; // thing being chased/attacked (or NULL)
// also the originator for missiles // also the originator for missiles
AActor *lastenemy; // Last known enemy -- killogh 2/15/98 TObjPtr<AActor> lastenemy; // Last known enemy -- killogh 2/15/98
AActor *LastHeard; // [RH] Last actor this one heard TObjPtr<AActor> LastHeard; // [RH] Last actor this one heard
SDWORD reactiontime; // if non 0, don't attack yet; used by SDWORD reactiontime; // if non 0, don't attack yet; used by
// player to freeze a bit after teleporting // player to freeze a bit after teleporting
SDWORD threshold; // if > 0, the target will be chased SDWORD threshold; // if > 0, the target will be chased
// no matter what (even if shot) // no matter what (even if shot)
player_s *player; // only valid if type of APlayerPawn player_s *player; // only valid if type of APlayerPawn
union TObjPtr<AActor> LastLookActor; // Actor last looked for (if TIDtoHate != 0)
{
AActor *Actor; // Actor last looked for (if TIDtoHate != 0)
SDWORD PlayerNumber; // Player number last looked for
} LastLook;
WORD SpawnPoint[3]; // For nightmare respawn WORD SpawnPoint[3]; // For nightmare respawn
WORD SpawnAngle; WORD SpawnAngle;
int skillrespawncount; int skillrespawncount;
AActor *tracer; // Thing being chased/attacked for tracers TObjPtr<AActor> tracer; // Thing being chased/attacked for tracers
AActor *master; // Thing which spawned this one (prevents mutual attacks) TObjPtr<AActor> master; // Thing which spawned this one (prevents mutual attacks)
fixed_t floorclip; // value to use for floor clipping fixed_t floorclip; // value to use for floor clipping
SWORD tid; // thing identifier SWORD tid; // thing identifier
BYTE special; // special BYTE special; // special
int args[5]; // special arguments int args[5]; // special arguments
AActor *inext, **iprev;// Links to other mobjs in same bucket AActor *inext, **iprev;// Links to other mobjs in same bucket
AActor *goal; // Monster's goal if not chasing anything TObjPtr<AActor> goal; // Monster's goal if not chasing anything
BYTE waterlevel; // 0=none, 1=feet, 2=waist, 3=eyes BYTE waterlevel; // 0=none, 1=feet, 2=waist, 3=eyes
BYTE boomwaterlevel; // splash information for non-swimmable water sectors BYTE boomwaterlevel; // splash information for non-swimmable water sectors
BYTE MinMissileChance;// [RH] If a random # is > than this, then missile attack. BYTE MinMissileChance;// [RH] If a random # is > than this, then missile attack.
SBYTE LastLookPlayerNumber;// Player number last looked for (if TIDtoHate == 0)
WORD SpawnFlags; WORD SpawnFlags;
fixed_t meleerange; // specifies how far a melee attack reaches. fixed_t meleerange; // specifies how far a melee attack reaches.
fixed_t meleethreshold; // Distance below which a monster doesn't try to shoot missiles anynore fixed_t meleethreshold; // Distance below which a monster doesn't try to shoot missiles anynore
@ -686,9 +683,10 @@ public:
int bouncecount; // Strife's grenades only bounce twice before exploding int bouncecount; // Strife's grenades only bounce twice before exploding
fixed_t gravity; // [GRB] Gravity factor fixed_t gravity; // [GRB] Gravity factor
int FastChaseStrafeCount; int FastChaseStrafeCount;
// [KS] These temporary-use properties are needed to allow A_LookEx to pass it's parameters to LookFor*InBlock in // [KS] These temporary-use properties are needed to allow A_LookEx to pass its parameters to
// P_BlockmapSearch so that friendly enemies and monsters that look for other monsters can find their targets properly. // LookFor*InBlock in P_BlockmapSearch so that friendly enemies and monsters that look for
// If there's a cleaner way of doing this, feel free to remove these and use that method instead. // other monsters can find their targets properly. If there's a cleaner way of doing this,
// feel free to remove these and use that method instead.
fixed_t LookExMinDist; // Minimum sight distance fixed_t LookExMinDist; // Minimum sight distance
fixed_t LookExMaxDist; // Maximum sight distance fixed_t LookExMaxDist; // Maximum sight distance
angle_t LookExFOV; // Field of Vision angle_t LookExFOV; // Field of Vision
@ -696,7 +694,7 @@ public:
// a linked list of sectors where this object appears // a linked list of sectors where this object appears
struct msecnode_s *touching_sectorlist; // phares 3/14/98 struct msecnode_s *touching_sectorlist; // phares 3/14/98
AInventory *Inventory; // [RH] This actor's inventory TObjPtr<AInventory> Inventory; // [RH] This actor's inventory
DWORD InventoryID; // A unique ID to keep track of inventory items DWORD InventoryID; // A unique ID to keep track of inventory items
//Added by MC: //Added by MC:
@ -769,10 +767,12 @@ public:
enum { S_NULL = 2, S_GENERICFREEZEDEATH = 3 }; enum { S_NULL = 2, S_GENERICFREEZEDEATH = 3 };
TArray<AActor*> dynamiclights; TArray<TObjPtr<AActor>> dynamiclights;
void * lightassociations; void * lightassociations;
bool hasmodel; bool hasmodel;
subsector_s * subsector; subsector_s * subsector;
size_t PropagateMark();
}; };
class FActorIterator class FActorIterator

View file

@ -117,7 +117,7 @@ public:
botinfo_t *botinfo; botinfo_t *botinfo;
int spawn_tries; int spawn_tries;
int wanted_botnum; int wanted_botnum;
AActor *firstthing; TObjPtr<AActor> firstthing;
bool m_Thinking; bool m_Thinking;
@ -141,8 +141,8 @@ protected:
bool ctf; bool ctf;
int loaded_bots; int loaded_bots;
int t_join; int t_join;
AActor *body1; TObjPtr<AActor> body1;
AActor *body2; TObjPtr<AActor> body2;
bool observer; //Consoleplayer is observer. bool observer; //Consoleplayer is observer.
}; };

View file

@ -453,16 +453,26 @@ void DCajunMaster::SetBodyAt (fixed_t x, fixed_t y, fixed_t z, int hostnum)
if (hostnum == 1) if (hostnum == 1)
{ {
if (body1) if (body1)
{
body1->SetOrigin (x, y, z); body1->SetOrigin (x, y, z);
}
else else
{
body1 = Spawn ("CajunBodyNode", x, y, z, NO_REPLACE); body1 = Spawn ("CajunBodyNode", x, y, z, NO_REPLACE);
GC::WriteBarrier(this, body1);
}
} }
else if (hostnum == 2) else if (hostnum == 2)
{ {
if (body2) if (body2)
{
body2->SetOrigin (x, y, z); body2->SetOrigin (x, y, z);
}
else else
{
body2 = Spawn ("CajunBodyNode", x, y, z, NO_REPLACE); body2 = Spawn ("CajunBodyNode", x, y, z, NO_REPLACE);
GC::WriteBarrier(this, body2);
}
} }
} }

View file

@ -94,9 +94,11 @@ DCajunMaster::~DCajunMaster()
ForgetBots(); ForgetBots();
if (getspawned != NULL) if (getspawned != NULL)
{ {
delete getspawned; getspawned->Destroy();
getspawned = NULL; getspawned = NULL;
} }
// FIXME: Make this object proper
ObjectFlags |= OF_Cleanup | OF_YesReallyDelete;
} }
//This function is called every tick (from g_game.c), //This function is called every tick (from g_game.c),

View file

@ -229,7 +229,7 @@ CUSTOM_CVAR (Int, msgmidcolor2, 4, CVAR_ARCHIVE)
static void maybedrawnow (bool tick, bool force) static void maybedrawnow (bool tick, bool force)
{ {
// FIXME: Does not work right with hw2d // FIXME: Does not work right with hw2d
if (ConsoleDrawing || !gotconback || screen->IsLocked () || screen->Accel2D) if (ConsoleDrawing || !gotconback || screen == NULL || screen->IsLocked () || screen->Accel2D)
{ {
return; return;
} }

View file

@ -1250,23 +1250,23 @@ CCMD (key)
// These all begin with '+' as opposed to '-'. // These all begin with '+' as opposed to '-'.
void C_ExecCmdLineParams () void C_ExecCmdLineParams ()
{ {
for (int currArg = 1; currArg < Args.NumArgs(); ) for (int currArg = 1; currArg < Args->NumArgs(); )
{ {
if (*Args.GetArg (currArg++) == '+') if (*Args->GetArg (currArg++) == '+')
{ {
FString cmdString; FString cmdString;
int cmdlen = 1; int cmdlen = 1;
int argstart = currArg - 1; int argstart = currArg - 1;
while (currArg < Args.NumArgs()) while (currArg < Args->NumArgs())
{ {
if (*Args.GetArg (currArg) == '-' || *Args.GetArg (currArg) == '+') if (*Args->GetArg (currArg) == '-' || *Args->GetArg (currArg) == '+')
break; break;
currArg++; currArg++;
cmdlen++; cmdlen++;
} }
cmdString = BuildString (cmdlen, Args.GetArgList (argstart)); cmdString = BuildString (cmdlen, Args->GetArgList (argstart));
if (!cmdString.IsEmpty()) if (!cmdString.IsEmpty())
{ {
C_DoCommand (&cmdString[1]); C_DoCommand (&cmdString[1]);

View file

@ -23,7 +23,7 @@
#include <stdarg.h> #include <stdarg.h>
// the dec offsetof macro doesnt work very well... // the dec offsetof macro doesnt work very well...
#define myoffsetof(type,identifier) ((size_t)&((type *)0)->identifier) #define myoffsetof(type,identifier) ((size_t)&((type *)1)->identifier - 1)
int Q_filelength (FILE *f); int Q_filelength (FILE *f);
bool FileExists (const char *filename); bool FileExists (const char *filename);

View file

@ -161,7 +161,7 @@ public:
void DoPickupSpecial (AActor *toucher); void DoPickupSpecial (AActor *toucher);
private: private:
const PClass *DetermineType (); const PClass *DetermineType ();
AInventory *RealPickup; TObjPtr<AInventory> RealPickup;
}; };
IMPLEMENT_POINTY_CLASS (ADehackedPickup) IMPLEMENT_POINTY_CLASS (ADehackedPickup)

View file

@ -745,7 +745,6 @@ void D_DoomLoop ()
if (singletics) if (singletics)
{ {
I_StartTic (); I_StartTic ();
DObject::BeginFrame ();
D_ProcessEvents (); D_ProcessEvents ();
G_BuildTiccmd (&netcmds[consoleplayer][maketic%BACKUPTICS]); G_BuildTiccmd (&netcmds[consoleplayer][maketic%BACKUPTICS]);
//Added by MC: For some of that bot stuff. The main bot function. //Added by MC: For some of that bot stuff. The main bot function.
@ -774,7 +773,7 @@ void D_DoomLoop ()
G_Ticker (); G_Ticker ();
gametic++; gametic++;
maketic++; maketic++;
DObject::EndFrame (); GC::CheckGC ();
Net_NewMakeTic (); Net_NewMakeTic ();
} }
else else
@ -1644,7 +1643,7 @@ static EIWADType IdentifyVersion (const char *zdoom_wad)
{ {
WadStuff wads[sizeof(IWADNames)/sizeof(char *)]; WadStuff wads[sizeof(IWADNames)/sizeof(char *)];
size_t foundwads[NUM_IWAD_TYPES] = { 0 }; size_t foundwads[NUM_IWAD_TYPES] = { 0 };
const char *iwadparm = Args.CheckValue ("-iwad"); const char *iwadparm = Args->CheckValue ("-iwad");
size_t numwads; size_t numwads;
int pickwad; int pickwad;
size_t i; size_t i;
@ -1914,7 +1913,7 @@ static const char *BaseFileSearch (const char *file, const char *ext, bool lookf
bool ConsiderPatches (const char *arg, const char *ext) bool ConsiderPatches (const char *arg, const char *ext)
{ {
bool noDef = false; bool noDef = false;
DArgs *files = Args.GatherFiles (arg, ext, false); DArgs *files = Args->GatherFiles (arg, ext, false);
if (files->NumArgs() > 0) if (files->NumArgs() > 0)
{ {
@ -1930,7 +1929,7 @@ bool ConsiderPatches (const char *arg, const char *ext)
} }
noDef = true; noDef = true;
} }
delete files; files->Destroy();
return noDef; return noDef;
} }
@ -2038,8 +2037,7 @@ void D_DoomMain (void)
const IWADInfo *iwad_info; const IWADInfo *iwad_info;
srand(I_MSTime()); srand(I_MSTime());
atterm (DObject::StaticShutdown);
PClass::StaticInit (); PClass::StaticInit ();
atterm (C_DeinitConsole); atterm (C_DeinitConsole);
@ -2121,19 +2119,19 @@ void D_DoomMain (void)
execFiles = new DArgs; execFiles = new DArgs;
GameConfig->AddAutoexec (execFiles, GameNames[gameinfo.gametype]); GameConfig->AddAutoexec (execFiles, GameNames[gameinfo.gametype]);
D_MultiExec (execFiles, true); D_MultiExec (execFiles, true);
delete execFiles; execFiles->Destroy();
// Run .cfg files at the start of the command line. // Run .cfg files at the start of the command line.
execFiles = Args.GatherFiles (NULL, ".cfg", false); execFiles = Args->GatherFiles (NULL, ".cfg", false);
D_MultiExec (execFiles, true); D_MultiExec (execFiles, true);
delete execFiles; execFiles->Destroy();
C_ExecCmdLineParams (); // [RH] do all +set commands on the command line C_ExecCmdLineParams (); // [RH] do all +set commands on the command line
DArgs *files = Args.GatherFiles ("-file", ".wad", true); DArgs *files = Args->GatherFiles ("-file", ".wad", true);
DArgs *files1 = Args.GatherFiles (NULL, ".zip", false); DArgs *files1 = Args->GatherFiles (NULL, ".zip", false);
DArgs *files2 = Args.GatherFiles (NULL, ".pk3", false); DArgs *files2 = Args->GatherFiles (NULL, ".pk3", false);
DArgs *files3 = Args.GatherFiles (NULL, ".txt", false); DArgs *files3 = Args->GatherFiles (NULL, ".txt", false);
if (files->NumArgs() > 0 || files1->NumArgs() > 0 || files2->NumArgs() > 0 || files3->NumArgs() > 0) if (files->NumArgs() > 0 || files1->NumArgs() > 0 || files2->NumArgs() > 0 || files3->NumArgs() > 0)
{ {
// Check for -file in shareware // Check for -file in shareware
@ -2160,10 +2158,10 @@ void D_DoomMain (void)
D_AddWildFile (files3->GetArg (i)); D_AddWildFile (files3->GetArg (i));
} }
} }
delete files; files->Destroy();
delete files1; files1->Destroy();
delete files2; files2->Destroy();
delete files3; files3->Destroy();
Printf ("W_Init: Init WADfiles.\n"); Printf ("W_Init: Init WADfiles.\n");
Wads.InitMultipleFiles (&wadfiles); Wads.InitMultipleFiles (&wadfiles);
@ -2193,18 +2191,18 @@ void D_DoomMain (void)
Printf ("P_Init: Checking cmd-line parameters...\n"); Printf ("P_Init: Checking cmd-line parameters...\n");
flags = dmflags; flags = dmflags;
if (Args.CheckParm ("-nomonsters")) flags |= DF_NO_MONSTERS; if (Args->CheckParm ("-nomonsters")) flags |= DF_NO_MONSTERS;
if (Args.CheckParm ("-respawn")) flags |= DF_MONSTERS_RESPAWN; if (Args->CheckParm ("-respawn")) flags |= DF_MONSTERS_RESPAWN;
if (Args.CheckParm ("-fast")) flags |= DF_FAST_MONSTERS; if (Args->CheckParm ("-fast")) flags |= DF_FAST_MONSTERS;
devparm = !!Args.CheckParm ("-devparm"); devparm = !!Args->CheckParm ("-devparm");
if (Args.CheckParm ("-altdeath")) if (Args->CheckParm ("-altdeath"))
{ {
deathmatch = 1; deathmatch = 1;
flags |= DF_ITEMS_RESPAWN; flags |= DF_ITEMS_RESPAWN;
} }
else if (Args.CheckParm ("-deathmatch")) else if (Args->CheckParm ("-deathmatch"))
{ {
deathmatch = 1; deathmatch = 1;
flags |= DF_WEAPONS_STAY | DF_ITEMS_RESPAWN; flags |= DF_WEAPONS_STAY | DF_ITEMS_RESPAWN;
@ -2223,27 +2221,27 @@ void D_DoomMain (void)
} }
autostart = false; autostart = false;
const char *val = Args.CheckValue ("-skill"); const char *val = Args->CheckValue ("-skill");
if (val) if (val)
{ {
gameskill = val[0] - '1'; gameskill = val[0] - '1';
autostart = true; autostart = true;
} }
p = Args.CheckParm ("-warp"); p = Args->CheckParm ("-warp");
if (p && p < Args.NumArgs() - 1) if (p && p < Args->NumArgs() - 1)
{ {
int ep, map; int ep, map;
if (gameinfo.flags & GI_MAPxx) if (gameinfo.flags & GI_MAPxx)
{ {
ep = 1; ep = 1;
map = atoi (Args.GetArg(p+1)); map = atoi (Args->GetArg(p+1));
} }
else else
{ {
ep = atoi (Args.GetArg(p+1)); ep = atoi (Args->GetArg(p+1));
map = p < Args.NumArgs() - 2 ? atoi (Args.GetArg(p+2)) : 10; map = p < Args->NumArgs() - 2 ? atoi (Args->GetArg(p+2)) : 10;
if (map < 1 || map > 9) if (map < 1 || map > 9)
{ {
map = ep; map = ep;
@ -2256,19 +2254,19 @@ void D_DoomMain (void)
} }
// [RH] Hack to handle +map // [RH] Hack to handle +map
p = Args.CheckParm ("+map"); p = Args->CheckParm ("+map");
if (p && p < Args.NumArgs()-1) if (p && p < Args->NumArgs()-1)
{ {
MapData * map = P_OpenMapData(Args.GetArg (p+1)); MapData * map = P_OpenMapData(Args->GetArg (p+1));
if (map == NULL) if (map == NULL)
{ {
Printf ("Can't find map %s\n", Args.GetArg (p+1)); Printf ("Can't find map %s\n", Args->GetArg (p+1));
} }
else else
{ {
delete map; delete map;
strncpy (startmap, Args.GetArg (p+1), 8); strncpy (startmap, Args->GetArg (p+1), 8);
Args.GetArg (p)[0] = '-'; Args->GetArg (p)[0] = '-';
autostart = true; autostart = true;
} }
} }
@ -2281,7 +2279,7 @@ void D_DoomMain (void)
// We do not need to support -cdrom under Unix, because all the files // We do not need to support -cdrom under Unix, because all the files
// that would go to c:\\zdoomdat are already stored in .zdoom inside // that would go to c:\\zdoomdat are already stored in .zdoom inside
// the user's home directory. // the user's home directory.
if (Args.CheckParm("-cdrom")) if (Args->CheckParm("-cdrom"))
{ {
Printf (GStrings("D_CDROM")); Printf (GStrings("D_CDROM"));
mkdir (CDROM_DIR, 0); mkdir (CDROM_DIR, 0);
@ -2293,7 +2291,7 @@ void D_DoomMain (void)
UCVarValue value; UCVarValue value;
static char one_hundred[] = "100"; static char one_hundred[] = "100";
value.String = Args.CheckValue ("-turbo"); value.String = Args->CheckValue ("-turbo");
if (value.String == NULL) if (value.String == NULL)
value.String = one_hundred; value.String = one_hundred;
else else
@ -2302,7 +2300,7 @@ void D_DoomMain (void)
turbo.SetGenericRepDefault (value, CVAR_String); turbo.SetGenericRepDefault (value, CVAR_String);
} }
v = Args.CheckValue ("-timer"); v = Args->CheckValue ("-timer");
if (v) if (v)
{ {
double time = strtod (v, NULL); double time = strtod (v, NULL);
@ -2310,7 +2308,7 @@ void D_DoomMain (void)
timelimit = (float)time; timelimit = (float)time;
} }
v = Args.CheckValue ("-avg"); v = Args->CheckValue ("-avg");
if (v) if (v)
{ {
Printf ("Austin Virtual Gaming: Levels will end after 20 minutes\n"); Printf ("Austin Virtual Gaming: Levels will end after 20 minutes\n");
@ -2420,10 +2418,10 @@ void D_DoomMain (void)
} }
//Added by MC: //Added by MC:
bglobal.getspawned = Args.GatherFiles ("-bots", "", false); bglobal.getspawned = Args->GatherFiles ("-bots", "", false);
if (bglobal.getspawned->NumArgs() == 0) if (bglobal.getspawned->NumArgs() == 0)
{ {
delete bglobal.getspawned; bglobal.getspawned->Destroy();
bglobal.getspawned = NULL; bglobal.getspawned = NULL;
} }
else else
@ -2459,13 +2457,11 @@ void D_DoomMain (void)
// [RH] Run any saved commands from the command line or autoexec.cfg now. // [RH] Run any saved commands from the command line or autoexec.cfg now.
gamestate = GS_FULLCONSOLE; gamestate = GS_FULLCONSOLE;
Net_NewMakeTic (); Net_NewMakeTic ();
DObject::BeginFrame ();
DThinker::RunThinkers (); DThinker::RunThinkers ();
DObject::EndFrame ();
gamestate = GS_STARTUP; gamestate = GS_STARTUP;
// start the apropriate game based on parms // start the apropriate game based on parms
v = Args.CheckValue ("-record"); v = Args->CheckValue ("-record");
if (v) if (v)
{ {
@ -2477,24 +2473,23 @@ void D_DoomMain (void)
StartScreen = NULL; StartScreen = NULL;
V_Init2(); V_Init2();
files = Args.GatherFiles ("-playdemo", ".lmp", false); files = Args->GatherFiles ("-playdemo", ".lmp", false);
if (files->NumArgs() > 0) if (files->NumArgs() > 0)
{ {
singledemo = true; // quit after one demo singledemo = true; // quit after one demo
G_DeferedPlayDemo (files->GetArg (0)); G_DeferedPlayDemo (files->GetArg (0));
delete files;
D_DoomLoop (); // never returns D_DoomLoop (); // never returns
} }
delete files; files->Destroy();
v = Args.CheckValue ("-timedemo"); v = Args->CheckValue ("-timedemo");
if (v) if (v)
{ {
G_TimeDemo (v); G_TimeDemo (v);
D_DoomLoop (); // never returns D_DoomLoop (); // never returns
} }
v = Args.CheckValue ("-loadgame"); v = Args->CheckValue ("-loadgame");
if (v) if (v)
{ {
file = v; file = v;

View file

@ -921,6 +921,8 @@ void NetUpdate (void)
BYTE *cmddata; BYTE *cmddata;
bool resendOnly; bool resendOnly;
GC::CheckGC();
if (ticdup == 0) if (ticdup == 0)
{ {
return; return;
@ -1586,7 +1588,7 @@ void D_CheckNetGame (void)
consoleplayer = doomcom.consoleplayer; consoleplayer = doomcom.consoleplayer;
v = Args.CheckValue ("-netmode"); v = Args->CheckValue ("-netmode");
if (v != NULL) if (v != NULL)
{ {
NetMode = atoi (v) != 0 ? NET_PacketServer : NET_PeerToPeer; NetMode = atoi (v) != 0 ? NET_PacketServer : NET_PeerToPeer;
@ -1595,7 +1597,7 @@ void D_CheckNetGame (void)
// [RH] Setup user info // [RH] Setup user info
D_SetupUserInfo (); D_SetupUserInfo ();
if (Args.CheckParm ("-debugfile")) if (Args->CheckParm ("-debugfile"))
{ {
char filename[20]; char filename[20];
sprintf (filename,"debug%i.txt",consoleplayer); sprintf (filename,"debug%i.txt",consoleplayer);
@ -1834,12 +1836,11 @@ void TryRunTics (void)
D_DoAdvanceDemo (); D_DoAdvanceDemo ();
} }
if (debugfile) fprintf (debugfile, "run tic %d\n", gametic); if (debugfile) fprintf (debugfile, "run tic %d\n", gametic);
DObject::BeginFrame ();
C_Ticker (); C_Ticker ();
M_Ticker (); M_Ticker ();
I_GetTime (true); I_GetTime (true);
G_Ticker (); G_Ticker ();
DObject::EndFrame (); GC::CheckGC ();
gametic++; gametic++;
NetUpdate (); // check for new console commands NetUpdate (); // check for new console commands

View file

@ -105,8 +105,8 @@ public:
int crouchsprite; int crouchsprite;
int MaxHealth; int MaxHealth;
int RunHealth; int RunHealth;
AInventory *InvFirst; // first inventory item displayed on inventory bar TObjPtr<AInventory> InvFirst; // first inventory item displayed on inventory bar
AInventory *InvSel; // selected inventory item TObjPtr<AInventory> InvSel; // selected inventory item
// [GRB] Player class properties // [GRB] Player class properties
fixed_t JumpZ; fixed_t JumpZ;
@ -192,6 +192,7 @@ public:
void Serialize (FArchive &arc); void Serialize (FArchive &arc);
void FixPointers (const DObject *obj, DObject *replacement); void FixPointers (const DObject *obj, DObject *replacement);
size_t PropagateMark();
void SetLogNumber (int num); void SetLogNumber (int num);
void SetLogText (const char *text); void SetLogText (const char *text);

View file

@ -110,7 +110,7 @@ struct DDecalThinker : public DThinker
public: public:
DDecalThinker (DBaseDecal *decal) : DThinker (STAT_DECALTHINKER), TheDecal (decal) {} DDecalThinker (DBaseDecal *decal) : DThinker (STAT_DECALTHINKER), TheDecal (decal) {}
void Serialize (FArchive &arc); void Serialize (FArchive &arc);
DBaseDecal *TheDecal; TObjPtr<DBaseDecal> TheDecal;
protected: protected:
DDecalThinker () : DThinker (STAT_DECALTHINKER) {} DDecalThinker () : DThinker (STAT_DECALTHINKER) {}
}; };
@ -1143,6 +1143,7 @@ void DDecalFader::Tick ()
{ {
TheDecal->Destroy (); // remove the decal TheDecal->Destroy (); // remove the decal
Destroy (); // remove myself Destroy (); // remove myself
return;
} }
if (StartTrans == -1) if (StartTrans == -1)
{ {

View file

@ -47,6 +47,7 @@
#include "r_state.h" #include "r_state.h"
#include "stats.h" #include "stats.h"
#include "a_sharedglobal.h" #include "a_sharedglobal.h"
#include "dsectoreffect.h"
#include "autosegs.h" #include "autosegs.h"
@ -343,148 +344,120 @@ CCMD (dumpclasses)
Printf ("%d classes shown, %d omitted\n", shown, omitted); Printf ("%d classes shown, %d omitted\n", shown, omitted);
} }
TArray<DObject *> DObject::Objects (TArray<DObject *>::NoInit);
TArray<unsigned int> DObject::FreeIndices (TArray<unsigned int>::NoInit);
TArray<DObject *> DObject::ToDestroy (TArray<DObject *>::NoInit);
bool DObject::Inactive;
void DObject::InPlaceConstructor (void *mem) void DObject::InPlaceConstructor (void *mem)
{ {
new ((EInPlace *)mem) DObject; new ((EInPlace *)mem) DObject;
} }
DObject::DObject () DObject::DObject ()
: ObjectFlags(0), Class(0) : Class(0), ObjectFlags(0)
{ {
if (FreeIndices.Pop (Index)) ObjectFlags = GC::CurrentWhite & OF_WhiteBits;
Objects[Index] = this; ObjNext = GC::Root;
else GC::Root = this;
Index = Objects.Push (this);
} }
DObject::DObject (PClass *inClass) DObject::DObject (PClass *inClass)
: ObjectFlags(0), Class(inClass) : Class(inClass), ObjectFlags(0)
{ {
if (FreeIndices.Pop (Index)) ObjectFlags = GC::CurrentWhite & OF_WhiteBits;
Objects[Index] = this; ObjNext = GC::Root;
else GC::Root = this;
Index = Objects.Push (this);
} }
DObject::~DObject () DObject::~DObject ()
{ {
if (!Inactive) if (!(ObjectFlags & OF_Cleanup))
{ {
if (!(ObjectFlags & OF_MassDestruction)) DObject **probe;
{ PClass *type = GetClass();
RemoveFromArray ();
DestroyScan (this);
}
else if (!(ObjectFlags & OF_Cleanup))
{
// object is queued for deletion, but is not being deleted
// by the destruction process, so remove it from the
// ToDestroy array and do other necessary stuff.
unsigned int i;
for (i = ToDestroy.Size() - 1; i-- > 0; ) if (!(ObjectFlags & OF_YesReallyDelete))
{
Printf ("Warning: '%s' is freed outside the GC process.\n",
type != NULL ? type->TypeName.GetChars() : "==some object==");
}
// Find all pointers that reference this object and NULL them.
PointerSubstitution(this, NULL);
// Now unlink this object from the GC list.
for (probe = &GC::Root; *probe != NULL; probe = &((*probe)->ObjNext))
{
if (*probe == this)
{ {
if (ToDestroy[i] == this) *probe = ObjNext;
if (&ObjNext == GC::SweepPos)
{ {
ToDestroy[i] = NULL; GC::SweepPos = probe;
}
break;
}
}
// If it's gray, also unlink it from the gray list.
if (this->IsGray())
{
for (probe = &GC::Gray; *probe != NULL; probe = &((*probe)->GCNext))
{
if (*probe == this)
{
*probe = GCNext;
break; break;
} }
} }
DestroyScan (this);
} }
} }
} }
void DObject::Destroy () void DObject::Destroy ()
{ {
if (!Inactive) ObjectFlags |= OF_EuthanizeMe;
{
if (!(ObjectFlags & OF_MassDestruction))
{
RemoveFromArray ();
ObjectFlags |= OF_MassDestruction;
ToDestroy.Push (this);
}
}
else
delete this;
} }
void DObject::BeginFrame () size_t DObject::PropagateMark()
{ {
StaleCycles = 0; const PClass *info = GetClass();
StaleCount = 0; const size_t *offsets = info->FlatPointers;
} if (offsets == NULL)
void DObject::EndFrame ()
{
clock (StaleCycles);
if (ToDestroy.Size ())
{ {
StaleCount += (int)ToDestroy.Size (); const_cast<PClass *>(info)->BuildFlatPointers();
DestroyScan (); offsets = info->FlatPointers;
//Printf ("Destroyed %d objects\n", ToDestroy.Size());
DObject *obj;
while (ToDestroy.Pop (obj))
{
if (obj)
{
obj->ObjectFlags |= OF_Cleanup;
delete obj;
}
}
} }
unclock (StaleCycles); while (*offsets != ~(size_t)0)
}
void DObject::RemoveFromArray ()
{
if (Objects.Size() == Index + 1)
{ {
DObject *dummy; GC::Mark((DObject **)((BYTE *)this + *offsets));
Objects.Pop (dummy); offsets++;
}
else if (Objects.Size() > Index)
{
Objects[Index] = NULL;
FreeIndices.Push (Index);
} }
return info->Size;
} }
void DObject::PointerSubstitution (DObject *old, DObject *notOld) void DObject::PointerSubstitution (DObject *old, DObject *notOld)
{ {
unsigned int i, highest; DObject *probe;
highest = Objects.Size (); int i;
for (i = 0; i <= highest; i++) // Go through all objects.
for (probe = GC::Root; probe != NULL; probe = probe->ObjNext)
{ {
DObject *current = i < highest ? Objects[i] : &bglobal; const PClass *info = probe->GetClass();
if (current) const size_t *offsets = info->FlatPointers;
if (offsets == NULL)
{ {
const PClass *info = current->GetClass(); const_cast<PClass *>(info)->BuildFlatPointers();
const size_t *offsets = info->FlatPointers; offsets = info->FlatPointers;
if (offsets == NULL) }
while (*offsets != ~(size_t)0)
{
if (*(DObject **)((BYTE *)probe + *offsets) == old)
{ {
const_cast<PClass *>(info)->BuildFlatPointers(); *(DObject **)((BYTE *)probe + *offsets) = notOld;
offsets = info->FlatPointers;
}
while (*offsets != ~(size_t)0)
{
if (*(DObject **)((BYTE *)current + *offsets) == old)
{
*(DObject **)((BYTE *)current + *offsets) = notOld;
}
offsets++;
} }
offsets++;
} }
} }
// Go through the bodyque.
for (i = 0; i < BODYQUESIZE; ++i) for (i = 0; i < BODYQUESIZE; ++i)
{ {
if (bodyque[i] == old) if (bodyque[i] == old)
@ -493,125 +466,32 @@ void DObject::PointerSubstitution (DObject *old, DObject *notOld)
} }
} }
// This is an ugly hack, but it's the best I can do for now. // Go through players.
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
if (playeringame[i]) if (playeringame[i])
players[i].FixPointers (old, notOld); players[i].FixPointers (old, notOld);
} }
// Go through sectors.
if (sectors != NULL) if (sectors != NULL)
{ {
for (i = 0; i < (unsigned int)numsectors; ++i) for (i = 0; i < numsectors; ++i)
{ {
if (sectors[i].SoundTarget == old) #define SECTOR_CHECK(f,t) \
{ if (sectors[i].f == static_cast<t *>(old)) { sectors[i].f = static_cast<t *>(notOld); }
sectors[i].SoundTarget = static_cast<AActor *>(notOld); SECTOR_CHECK( SoundTarget, AActor );
} SECTOR_CHECK( CeilingSkyBox, ASkyViewpoint );
if (sectors[i].CeilingSkyBox == old) SECTOR_CHECK( FloorSkyBox, ASkyViewpoint );
{ SECTOR_CHECK( SecActTarget, ASectorAction );
sectors[i].CeilingSkyBox = static_cast<ASkyViewpoint *>(notOld); SECTOR_CHECK( floordata, DSectorEffect );
} SECTOR_CHECK( ceilingdata, DSectorEffect );
if (sectors[i].FloorSkyBox == old) SECTOR_CHECK( lightingdata, DSectorEffect );
{ #undef SECTOR_CHECK
sectors[i].FloorSkyBox = static_cast<ASkyViewpoint *>(notOld);
}
} }
} }
} }
// Search for references to a single object and NULL them.
// It should not be listed in ToDestroy.
void DObject::DestroyScan (DObject *obj)
{
PointerSubstitution (obj, NULL);
}
// Search for references to all objects scheduled for
// destruction and NULL them.
void DObject::DestroyScan ()
{
unsigned int i, highest;
int j, destroycount;
DObject **destroybase;
destroycount = (int)ToDestroy.Size ();
if (destroycount == 0)
return;
destroybase = &ToDestroy[0] + destroycount;
destroycount = -destroycount;
highest = Objects.Size ();
for (i = 0; i <= highest; i++)
{
DObject *current = i < highest ? Objects[i] : &bglobal;
if (current)
{
const PClass *info = current->GetClass();
const size_t *offsets = info->FlatPointers;
if (offsets == NULL)
{
const_cast<PClass *>(info)->BuildFlatPointers();
offsets = info->FlatPointers;
}
while (*offsets != ~(size_t)0)
{
j = destroycount;
do
{
if (*(DObject **)((BYTE *)current + *offsets) == *(destroybase + j))
{
*(DObject **)((BYTE *)current + *offsets) = NULL;
}
} while (++j);
offsets++;
}
}
}
j = destroycount;
do
{
for (i = 0; i < BODYQUESIZE; ++i)
{
if (bodyque[i] == *(destroybase + j))
{
bodyque[i] = NULL;
}
}
} while (++j);
// This is an ugly hack, but it's the best I can do for now.
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i])
{
j = destroycount;
do
{
players[i].FixPointers (*(destroybase + j), NULL);
} while (++j);
}
}
for (i = 0; i < (unsigned int)numsectors; ++i)
{
j = destroycount;
do
{
if (sectors[i].SoundTarget == *(destroybase + j))
{
sectors[i].SoundTarget = NULL;
}
} while (++j);
}
}
void DObject::StaticShutdown ()
{
Inactive = true;
}
void DObject::Serialize (FArchive &arc) void DObject::Serialize (FArchive &arc)
{ {
ObjectFlags |= OF_SerialSuccess; ObjectFlags |= OF_SerialSuccess;

View file

@ -2,7 +2,7 @@
** dobject.h ** dobject.h
** **
**--------------------------------------------------------------------------- **---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit ** Copyright 1998-2008 Randy Heit
** All rights reserved. ** All rights reserved.
** **
** Redistribution and use in source and binary forms, with or without ** Redistribution and use in source and binary forms, with or without
@ -48,7 +48,6 @@ class FArchive;
class DObject; class DObject;
class DArgs; class DArgs;
class DBoundingBox;
class DCanvas; class DCanvas;
class DConsoleCommand; class DConsoleCommand;
class DConsoleAlias; class DConsoleAlias;
@ -211,12 +210,205 @@ private: \
enum EObjectFlags enum EObjectFlags
{ {
OF_MassDestruction = 0x00000001, // Object is queued for deletion // GC flags
OF_Cleanup = 0x00000002, // Object is being deconstructed as a result of a queued deletion OF_White0 = 1 << 0, // Object is white (type 0)
OF_JustSpawned = 0x00000004, // Thinker was spawned this tic OF_White1 = 1 << 1, // Object is white (type 1)
OF_SerialSuccess = 0x10000000 // For debugging Serialize() calls OF_Black = 1 << 2, // Object is black
OF_Fixed = 1 << 3, // Object is fixed (should not be collected)
OF_Rooted = 1 << 4, // Object is soft-rooted
OF_EuthanizeMe = 1 << 5, // Object wants to die
OF_Cleanup = 1 << 6, // Object is now being deleted by the collector
OF_YesReallyDelete = 1 << 7, // Object is being deleted outside the collector, and this is okay, so don't print a warning
OF_WhiteBits = OF_White0 | OF_White1,
OF_MarkBits = OF_WhiteBits | OF_Black,
// Other flags
OF_JustSpawned = 1 << 8, // Thinker was spawned this tic
OF_SerialSuccess = 1 << 9, // For debugging Serialize() calls
}; };
template<class T> class TObjPtr;
namespace GC
{
enum EGCState
{
GCS_Pause,
GCS_Propagate,
GCS_Sweep,
GCS_Finalize
};
// Number of bytes currently allocated through M_Malloc/M_Realloc.
extern size_t AllocBytes;
// Amount of memory to allocate before triggering a collection.
extern size_t Threshold;
// List of gray objects.
extern DObject *Gray;
// List of every object.
extern DObject *Root;
// Current white value for potentially-live objects.
extern DWORD CurrentWhite;
// Current collector state.
extern EGCState State;
// Position of GC sweep in the list of objects.
extern DObject **SweepPos;
// Size of GC pause.
extern int Pause;
// Size of GC steps.
extern int StepMul;
// Current white value for known-dead objects.
static inline DWORD OtherWhite()
{
return CurrentWhite ^ OF_WhiteBits;
}
// Frees all objects, whether they're dead or not.
void FreeAll();
// Does one collection step.
void Step();
// Does a complete collection.
void FullGC();
// Handles the grunt work for a write barrier.
void Barrier(DObject *pointing, DObject *pointed);
// Handles a write barrier.
static inline void WriteBarrier(DObject *pointing, DObject *pointed);
// Handles a write barrier for a pointer that isn't inside an object.
static inline void WriteBarrier(DObject *pointed);
// Handles a read barrier.
template<class T> inline T *ReadBarrier(T *&obj)
{
if (obj == NULL || !(obj->ObjectFlags & OF_EuthanizeMe))
{
return obj;
}
return obj = NULL;
}
// Check if it's time to collect, and do a collection step if it is.
static inline void CheckGC()
{
if (AllocBytes >= Threshold)
Step();
}
// Marks a white object gray. If the object wants to die, the pointer
// is NULLed instead.
void Mark(DObject **obj);
// Soft-roots an object.
void AddSoftRoot(DObject *obj);
// Unroots an object.
void DelSoftRoot(DObject *obj);
template<class T> void Mark(T *&obj) { Mark((DObject **)&obj); }
template<class T> void Mark(TObjPtr<T> &obj);
}
// A template class to help with handling read barriers. It does not
// handle write barriers, because those can be handled more efficiently
// with knowledge of the object that holds the pointer.
template<class T>
class TObjPtr
{
T *p;
public:
TObjPtr() throw()
{
}
TObjPtr(T *q) throw()
: p(q)
{
}
TObjPtr(const TObjPtr<T> &q) throw()
: p(q.p)
{
}
T *operator=(T *q) throw()
{
return p = q;
// The caller must now perform a write barrier.
}
operator T*() throw()
{
return GC::ReadBarrier(p);
}
T &operator*()
{
T *q = GC::ReadBarrier(p);
assert(q != NULL);
return *q;
}
T **operator&() throw()
{
// Does not perform a read barrier. The only real use for this is with
// the DECLARE_POINTER macro, where a read barrier would be a very bad
// thing.
return &p;
}
T *operator->() throw()
{
return GC::ReadBarrier(p);
}
bool operator<(T *u) throw()
{
return GC::ReadBarrier(p) < u;
}
bool operator<=(T *u) throw()
{
return GC::ReadBarrier(p) <= u;
}
bool operator>(T *u) throw()
{
return GC::ReadBarrier(p) > u;
}
bool operator>=(T *u) throw()
{
return GC::ReadBarrier(p) >= u;
}
bool operator!=(T *u) throw()
{
return GC::ReadBarrier(p) != u;
}
bool operator==(T *u) throw()
{
return GC::ReadBarrier(p) == u;
}
template<class U> friend inline FArchive &operator<<(FArchive &arc, TObjPtr<U> &o);
};
template<class T> inline FArchive &operator<<(FArchive &arc, TObjPtr<T> &o)
{
return arc << o.p;
}
// Use barrier_cast instead of static_cast when you need to cast
// the contents of a TObjPtr to a related type.
template<class T,class U> inline T barrier_cast(TObjPtr<U> &o)
{
return static_cast<T>(static_cast<U *>(o));
}
template<class T> void GC::Mark(TObjPtr<T> &obj) { GC::Mark((DObject **)&obj); }
class DObject class DObject
{ {
public: public:
@ -227,12 +419,13 @@ public:
private: private:
typedef DObject ThisClass; typedef DObject ThisClass;
// Per-instance variables. There are three. // Per-instance variables. There are four.
public:
DWORD ObjectFlags; // Flags for this object
private: private:
PClass *Class; // This object's type PClass *Class; // This object's type
unsigned int Index; // This object's index in the global object table public:
DObject *ObjNext; // Keep track of all allocated objects
DObject *GCNext; // Next object in this collection list
DWORD ObjectFlags; // Flags for this object
public: public:
DObject (); DObject ();
@ -250,16 +443,11 @@ public:
virtual void Destroy (); virtual void Destroy ();
static void BeginFrame ();
static void EndFrame ();
// If you need to replace one object with another and want to // If you need to replace one object with another and want to
// change any pointers from the old object to the new object, // change any pointers from the old object to the new object,
// use this method. // use this method.
static void PointerSubstitution (DObject *old, DObject *notOld); static void PointerSubstitution (DObject *old, DObject *notOld);
static void StaticShutdown ();
PClass *GetClass() const PClass *GetClass() const
{ {
if (Class == NULL) if (Class == NULL)
@ -286,6 +474,60 @@ public:
M_Free(mem); M_Free(mem);
} }
// GC fiddling
// An object is white if either white bit is set.
bool IsWhite() const
{
return !!(ObjectFlags & OF_WhiteBits);
}
bool IsBlack() const
{
return !!(ObjectFlags & OF_Black);
}
// An object is gray if it isn't white or black.
bool IsGray() const
{
return !(ObjectFlags & OF_MarkBits);
}
// An object is dead if it's the other white.
bool IsDead() const
{
return !!(ObjectFlags & GC::OtherWhite() & OF_WhiteBits);
}
void ChangeWhite()
{
ObjectFlags ^= OF_WhiteBits;
}
void MakeWhite()
{
ObjectFlags = (ObjectFlags & ~OF_MarkBits) | (GC::CurrentWhite & OF_WhiteBits);
}
void White2Gray()
{
ObjectFlags &= ~OF_WhiteBits;
}
void Black2Gray()
{
ObjectFlags &= ~OF_Black;
}
void Gray2Black()
{
ObjectFlags |= OF_Black;
}
// Marks all objects pointed to by this one. Returns the (approximate)
// amount of memory used by this object.
virtual size_t PropagateMark();
protected: protected:
// This form of placement new and delete is for use *only* by PClass's // This form of placement new and delete is for use *only* by PClass's
// CreateNew() method. Do not use them for some other purpose. // CreateNew() method. Do not use them for some other purpose.
@ -296,22 +538,26 @@ protected:
void operator delete (void *mem, EInPlace *) void operator delete (void *mem, EInPlace *)
{ {
free (mem); M_Free (mem);
} }
private:
static TArray<DObject *> Objects;
static TArray<unsigned int> FreeIndices;
static TArray<DObject *> ToDestroy;
static void DestroyScan (DObject *obj);
static void DestroyScan ();
void RemoveFromArray ();
static bool Inactive;
}; };
static inline void GC::WriteBarrier(DObject *pointing, DObject *pointed)
{
if (pointed != NULL && pointed->IsWhite() && pointing->IsBlack())
{
Barrier(pointing, pointed);
}
}
static inline void GC::WriteBarrier(DObject *pointed)
{
if (pointed != NULL && State == GCS_Propagate && pointed->IsWhite())
{
Barrier(NULL, pointed);
}
}
#include "dobjtype.h" #include "dobjtype.h"
inline bool DObject::IsKindOf (const PClass *base) const inline bool DObject::IsKindOf (const PClass *base) const

623
src/dobjgc.cpp Normal file
View file

@ -0,0 +1,623 @@
/*
** dobjgc.cpp
** The garbage collector. Based largely on Lua's.
**
**---------------------------------------------------------------------------
** Copyright 2008 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
/******************************************************************************
* Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
******************************************************************************/
// HEADER FILES ------------------------------------------------------------
#include "dobject.h"
#include "templates.h"
#include "b_bot.h"
#include "p_local.h"
#include "g_game.h"
#include "r_data.h"
#include "a_sharedglobal.h"
#include "sbar.h"
#include "stats.h"
#include "c_dispatch.h"
#include "p_acs.h"
// MACROS ------------------------------------------------------------------
/*
@@ DEFAULT_GCPAUSE defines the default pause between garbage-collector cycles
@* as a percentage.
** CHANGE it if you want the GC to run faster or slower (higher values
** mean larger pauses which mean slower collection.) You can also change
** this value dynamically.
*/
#define DEFAULT_GCPAUSE 150 // 150% (wait for memory to increase by half before next GC)
/*
@@ DEFAULT_GCMUL defines the default speed of garbage collection relative to
@* memory allocation as a percentage.
** CHANGE it if you want to change the granularity of the garbage
** collection. (Higher values mean coarser collections. 0 represents
** infinity, where each step performs a full collection.) You can also
** change this value dynamically.
*/
#define DEFAULT_GCMUL 400 // GC runs 'quadruple the speed' of memory allocation
#define GCSTEPSIZE 1024u
#define GCSWEEPMAX 40
#define GCSWEEPCOST 10
#define GCFINALIZECOST 100
// TYPES -------------------------------------------------------------------
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
// PUBLIC DATA DEFINITIONS -------------------------------------------------
namespace GC
{
size_t AllocBytes;
size_t Threshold;
size_t Estimate;
DObject *Gray;
DObject *Root;
DObject *SoftRoots;
DObject **SweepPos;
DWORD CurrentWhite = OF_White0 | OF_Fixed;
EGCState State = GCS_Pause;
int Pause = DEFAULT_GCPAUSE;
int StepMul = DEFAULT_GCMUL;
int StepCount;
size_t Dept;
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// CODE --------------------------------------------------------------------
//==========================================================================
//
// SetThreshold
//
// Sets the new threshold after a collection is finished.
//
//==========================================================================
void SetThreshold()
{
Threshold = (Estimate / 100) * Pause;
}
//==========================================================================
//
// PropagateMark
//
// Marks the top-most gray object black and marks all objects it points to
// gray.
//
//==========================================================================
size_t PropagateMark()
{
DObject *obj = Gray;
assert(obj->IsGray());
obj->Gray2Black();
Gray = obj->GCNext;
return obj->PropagateMark();
}
//==========================================================================
//
// PropagateAll
//
// Empties the gray list by propagating every single object in it.
//
//==========================================================================
static size_t PropagateAll()
{
size_t m = 0;
while (Gray != NULL)
{
m += PropagateMark();
}
return m;
}
//==========================================================================
//
// SweepList
//
// Runs a limited sweep on a list, returning the location where to resume
// the sweep at next time.
//
//==========================================================================
static DObject **SweepList(DObject **p, size_t count)
{
static int scount;
DObject *curr;
int deadmask = OtherWhite();
while ((curr = *p) != NULL && count-- > 0)
{
if ((curr->ObjectFlags ^ OF_WhiteBits) & deadmask) // not dead?
{
assert(!curr->IsDead() || (curr->ObjectFlags & OF_Fixed));
curr->MakeWhite(); // make it white (for next cycle)
p = &curr->ObjNext;
}
else // must erase 'curr'
{
assert(curr->IsDead());
*p = curr->ObjNext;
if (!(curr->ObjectFlags & OF_EuthanizeMe))
{ // The object must be destroyed before it can be finalized.
assert(!curr->IsKindOf(RUNTIME_CLASS(DThinker)));
curr->Destroy();
}
curr->ObjectFlags |= OF_Cleanup;
delete curr;
}
}
return p;
}
//==========================================================================
//
// Mark
//
// Mark a single object gray.
//
//==========================================================================
void Mark(DObject **obj)
{
DObject *lobj = *obj;
if (lobj != NULL)
{
if (lobj->ObjectFlags & OF_EuthanizeMe)
{
*obj = NULL;
}
else if (lobj->IsWhite())
{
lobj->White2Gray();
lobj->GCNext = Gray;
Gray = lobj;
}
}
}
//==========================================================================
//
// MarkRoot
//
// Mark the root set of objects.
//
//==========================================================================
static void MarkRoot()
{
int i;
Gray = NULL;
Mark(Args);
Mark(screen);
Mark(StatusBar);
DThinker::MarkRoots();
Mark(DACSThinker::ActiveThinker);
for (i = 0; i < BODYQUESIZE; ++i)
{
Mark(bodyque[i]);
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i])
players[i].PropagateMark();
}
if (sectors != NULL)
{
for (i = 0; i < numsectors; ++i)
{
Mark(sectors[i].SoundTarget);
Mark(sectors[i].CeilingSkyBox);
Mark(sectors[i].FloorSkyBox);
Mark(sectors[i].SecActTarget);
Mark(sectors[i].floordata);
Mark(sectors[i].ceilingdata);
Mark(sectors[i].lightingdata);
}
}
{ // Silly bots
DObject *foo = &bglobal;
Mark(foo);
}
// Add soft roots
if (SoftRoots != NULL)
{
DObject **probe = &SoftRoots->ObjNext;
while (*probe != NULL)
{
DObject *soft = *probe;
probe = &soft->ObjNext;
if ((soft->ObjectFlags & (OF_Rooted | OF_EuthanizeMe)) == OF_Rooted)
{
Mark(soft);
}
}
}
State = GCS_Propagate;
StepCount = 0;
}
//==========================================================================
//
// Atomic
//
// If their were any propagations that needed to be done atomicly, they
// would go here. It also sets things up for the sweep state.
//
//==========================================================================
static void Atomic()
{
// Flip current white
CurrentWhite = OtherWhite();
SweepPos = &Root;
State = GCS_Sweep;
Estimate = AllocBytes;
}
//==========================================================================
//
// SingleStep
//
// Performs one step of the collector.
//
//==========================================================================
static size_t SingleStep()
{
switch (State)
{
case GCS_Pause:
MarkRoot(); // Start a new collection
return 0;
case GCS_Propagate:
if (Gray != NULL)
{
return PropagateMark();
}
else
{ // no more gray objects
Atomic(); // finish mark phase
return 0;
}
case GCS_Sweep: {
size_t old = AllocBytes;
SweepPos = SweepList(SweepPos, GCSWEEPMAX);
if (*SweepPos == NULL)
{ // Nothing more to sweep?
State = GCS_Finalize;
}
assert(old >= AllocBytes);
Estimate -= old - AllocBytes;
return GCSWEEPMAX * GCSWEEPCOST;
}
case GCS_Finalize:
State = GCS_Pause; // end collection
Dept = 0;
return 0;
default:
assert(0);
return 0;
}
}
//==========================================================================
//
// Step
//
// Performs enough single steps to cover GCSTEPSIZE * StepMul% bytes of
// memory.
//
//==========================================================================
void Step()
{
size_t lim = (GCSTEPSIZE/100) * StepMul;
size_t olim;
if (lim == 0)
{
lim = (~(size_t)0) / 2; // no limit
}
Dept += AllocBytes - Threshold;
do
{
olim = lim;
lim -= SingleStep();
} while (olim > lim && State != GCS_Pause);
if (State != GCS_Pause)
{
if (Dept < GCSTEPSIZE)
{
Threshold = AllocBytes + GCSTEPSIZE; // - lim/StepMul
}
else
{
Dept -= GCSTEPSIZE;
Threshold = AllocBytes;
}
}
else
{
assert(AllocBytes >= Estimate);
SetThreshold();
}
StepCount++;
}
//==========================================================================
//
// FullGC
//
// Collects everything in one fell swoop.
//
//==========================================================================
void FullGC()
{
if (State <= GCS_Propagate)
{
// Reset sweep mark to sweep all elements (returning them to white)
SweepPos = &Root;
// Reset other collector lists
Gray = NULL;
State = GCS_Sweep;
}
// Finish any pending sweep phase
while (State != GCS_Finalize)
{
SingleStep();
}
MarkRoot();
while (State != GCS_Pause)
{
SingleStep();
}
SetThreshold();
}
//==========================================================================
//
// Barrier
//
// Implements a write barrier to maintain the invariant that a black node
// never points to a white node by making the node pointed at gray.
//
//==========================================================================
void Barrier(DObject *pointing, DObject *pointed)
{
assert(pointing == NULL || (pointing->IsBlack() && !pointing->IsDead()));
assert(pointed->IsWhite() && !pointed->IsDead());
assert(State != GCS_Finalize && State != GCS_Pause);
// The invariant only needs to be maintained in the propagate state.
if (State == GCS_Propagate)
{
pointed->White2Gray();
pointed->GCNext = Gray;
Gray = pointed;
}
// In other states, we can mark the pointing object white so this
// barrier won't be triggered again, saving a few cycles in the future.
else if (pointing != NULL)
{
pointing->MakeWhite();
}
}
//==========================================================================
//
// AddSoftRoot
//
// Marks an object as a soft root. A soft root behaves exactly like a root
// in MarkRoot, except it can be added at run-time.
//
//==========================================================================
void AddSoftRoot(DObject *obj)
{
DObject **probe;
// Are there any soft roots yet?
if (SoftRoots == NULL)
{
// Create a new object to root the soft roots off of, and stick
// it at the end of the object list, so we know that anything
// before it is not a soft root.
SoftRoots = new DObject;
SoftRoots->ObjectFlags |= OF_Fixed;
probe = &Root;
while (*probe != NULL)
{
probe = &(*probe)->ObjNext;
}
Root = SoftRoots->ObjNext;
SoftRoots->ObjNext = NULL;
*probe = SoftRoots;
}
// Mark this object as rooted and move it after the SoftRoots marker.
probe = &Root;
while (*probe != NULL && *probe != obj)
{
probe = &(*probe)->ObjNext;
}
*probe = (*probe)->ObjNext;
obj->ObjNext = SoftRoots->ObjNext;
SoftRoots->ObjNext = obj;
obj->ObjectFlags |= OF_Rooted;
WriteBarrier(obj);
}
//==========================================================================
//
// DelSoftRoot
//
// Unroots an object so that it must be reachable or it will get collected.
//
//==========================================================================
void DelSoftRoot(DObject *obj)
{
DObject **probe;
if (!(obj->ObjectFlags & OF_Rooted))
{ // Not rooted, so nothing to do.
return;
}
obj->ObjectFlags &= ~OF_Rooted;
// Move object out of the soft roots part of the list.
probe = &SoftRoots;
while (*probe != NULL && *probe != obj)
{
probe = &(*probe)->ObjNext;
}
if (*probe == obj)
{
*probe = obj->ObjNext;
obj->ObjNext = Root;
Root = obj;
}
}
}
ADD_STAT(gc)
{
static const char *StateStrings[] = {
" Pause ",
"Propagate",
" Sweep ",
"Finalize " };
FString out;
out.Format("[%s] Alloc:%6uK Thresh:%6uK Est:%6uK Steps: %d",
StateStrings[GC::State],
(GC::AllocBytes + 1023) >> 10,
(GC::Threshold + 1023) >> 10,
(GC::Estimate + 1023) >> 10,
GC::StepCount);
if (GC::State != GC::GCS_Pause)
{
out.AppendFormat(" %uK", (GC::Dept + 1023) >> 10);
}
return out;
}
//==========================================================================
//
// CCMD gc
//
// Controls various aspects of the collector.
//
//==========================================================================
CCMD(gc)
{
if (argv.argc() == 1)
{
Printf ("Usage: gc stop|now|full|pause [size]|stepmul [size]\n");
return;
}
if (stricmp(argv[1], "stop") == 0)
{
GC::Threshold = ~(size_t)0 - 2;
}
else if (stricmp(argv[1], "now") == 0)
{
GC::Threshold = GC::AllocBytes;
}
else if (stricmp(argv[1], "full") == 0)
{
GC::FullGC();
}
else if (stricmp(argv[1], "pause") == 0)
{
if (argv.argc() == 2)
{
Printf ("Current GC pause is %d\n", GC::Pause);
}
else
{
GC::Pause = MAX(1,atoi(argv[2]));
}
}
else if (stricmp(argv[1], "stepmul") == 0)
{
if (argv.argc() == 2)
{
Printf ("Current GC stepmul is %d\n", GC::StepMul);
}
else
{
GC::StepMul = MAX(100, atoi(argv[2]));
}
}
}

View file

@ -33,7 +33,7 @@ DSectorEffect::DSectorEffect ()
m_Sector = NULL; m_Sector = NULL;
} }
DSectorEffect::~DSectorEffect () void DSectorEffect::Destroy()
{ {
if (m_Sector) if (m_Sector)
{ {
@ -52,6 +52,7 @@ DSectorEffect::~DSectorEffect ()
m_Sector->lightingdata = NULL; m_Sector->lightingdata = NULL;
} }
} }
Super::Destroy();
} }
DSectorEffect::DSectorEffect (sector_t *sector) DSectorEffect::DSectorEffect (sector_t *sector)

View file

@ -10,9 +10,9 @@ class DSectorEffect : public DThinker
DECLARE_CLASS (DSectorEffect, DThinker) DECLARE_CLASS (DSectorEffect, DThinker)
public: public:
DSectorEffect (sector_t *sector); DSectorEffect (sector_t *sector);
~DSectorEffect ();
void Serialize (FArchive &arc); void Serialize (FArchive &arc);
void Destroy();
sector_t *GetSector() const { return m_Sector; } sector_t *GetSector() const { return m_Sector; }

View file

@ -155,6 +155,14 @@ DThinker::DThinker (int statnum) throw()
{ {
statnum = MAX_STATNUM; statnum = MAX_STATNUM;
} }
if (FreshThinkers[statnum].TailPred->Pred != NULL)
{
GC::WriteBarrier(static_cast<DThinker*>(FreshThinkers[statnum].Tail, this));
}
else
{
GC::WriteBarrier(this);
}
FreshThinkers[statnum].AddTail (this); FreshThinkers[statnum].AddTail (this);
} }
@ -179,6 +187,16 @@ void DThinker::Destroy ()
Super::Destroy (); Super::Destroy ();
} }
void DThinker::Remove()
{
if (Pred->Pred != NULL && Succ->Succ != NULL)
{
GC::WriteBarrier(static_cast<DThinker *>(Pred), static_cast<DThinker *>(Succ));
GC::WriteBarrier(static_cast<DThinker *>(Succ), static_cast<DThinker *>(Pred));
}
Node::Remove();
}
void DThinker::PostBeginPlay () void DThinker::PostBeginPlay ()
{ {
} }
@ -205,6 +223,8 @@ DThinker *DThinker::FirstThinker (int statnum)
void DThinker::ChangeStatNum (int statnum) void DThinker::ChangeStatNum (int statnum)
{ {
List *list;
if ((unsigned)statnum > MAX_STATNUM) if ((unsigned)statnum > MAX_STATNUM)
{ {
statnum = MAX_STATNUM; statnum = MAX_STATNUM;
@ -212,12 +232,44 @@ void DThinker::ChangeStatNum (int statnum)
Remove (); Remove ();
if ((ObjectFlags & OF_JustSpawned) && statnum >= STAT_FIRST_THINKING) if ((ObjectFlags & OF_JustSpawned) && statnum >= STAT_FIRST_THINKING)
{ {
FreshThinkers[statnum].AddTail (this); list = &FreshThinkers[statnum];
} }
else else
{ {
Thinkers[statnum].AddTail (this); list = &Thinkers[statnum];
} }
if (list->TailPred->Pred != NULL)
{
GC::WriteBarrier(static_cast<DThinker*>(list->Tail, this));
}
else
{
GC::WriteBarrier(this);
}
list->AddTail(this);
}
// Mark the first thinker of each list
void DThinker::MarkRoots()
{
for (int i = 0; i <= MAX_STATNUM; ++i)
{
DThinker *t = static_cast<DThinker *>(Thinkers[i].Head);
GC::Mark(t);
t = static_cast<DThinker *>(FreshThinkers[i].Head);
GC::Mark(t);
}
}
size_t DThinker::PropagateMark()
{
// Mark the next thinker in my list
if (Succ != NULL && Succ->Succ != NULL)
{
DThinker *t = static_cast<DThinker *>(Succ);
GC::Mark(t);
}
return Super::PropagateMark();
} }
// Destroy every thinker // Destroy every thinker
@ -225,7 +277,6 @@ void DThinker::DestroyAllThinkers ()
{ {
int i; int i;
DObject::BeginFrame ();
for (i = 0; i <= MAX_STATNUM; i++) for (i = 0; i <= MAX_STATNUM; i++)
{ {
if (i != STAT_TRAVELLING) if (i != STAT_TRAVELLING)
@ -234,7 +285,7 @@ void DThinker::DestroyAllThinkers ()
DestroyThinkersInList (FreshThinkers[i].Head); DestroyThinkersInList (FreshThinkers[i].Head);
} }
} }
DObject::EndFrame (); GC::FullGC ();
} }
// Destroy all thinkers except for player-controlled actors // Destroy all thinkers except for player-controlled actors
@ -244,7 +295,6 @@ void DThinker::DestroyMostThinkers ()
{ {
int i; int i;
DObject::BeginFrame ();
for (i = 0; i <= MAX_STATNUM; i++) for (i = 0; i <= MAX_STATNUM; i++)
{ {
if (i != STAT_TRAVELLING) if (i != STAT_TRAVELLING)
@ -253,7 +303,7 @@ void DThinker::DestroyMostThinkers ()
DestroyMostThinkersInList (FreshThinkers[i], i); DestroyMostThinkersInList (FreshThinkers[i], i);
} }
} }
DObject::EndFrame (); GC::FullGC ();
} }
void DThinker::DestroyThinkersInList (Node *node) void DThinker::DestroyThinkersInList (Node *node)
@ -327,6 +377,14 @@ int DThinker::TickThinkers (List *list, List *dest)
if (dest != NULL) if (dest != NULL)
{ // Move thinker from this list to the destination list { // Move thinker from this list to the destination list
node->Remove (); node->Remove ();
if (dest->TailPred->Pred != NULL)
{
GC::WriteBarrier(static_cast<DThinker*>(dest->Tail, thinker));
}
else
{
GC::WriteBarrier(thinker);
}
dest->AddTail (node); dest->AddTail (node);
} }
thinker->PostBeginPlay (); thinker->PostBeginPlay ();
@ -336,7 +394,7 @@ int DThinker::TickThinkers (List *list, List *dest)
I_Error ("There is a thinker in the fresh list that has already ticked.\n"); I_Error ("There is a thinker in the fresh list that has already ticked.\n");
} }
if (!(thinker->ObjectFlags & OF_MassDestruction)) if (!(thinker->ObjectFlags & OF_EuthanizeMe))
{ // Only tick thinkers not scheduled for destruction { // Only tick thinkers not scheduled for destruction
thinker->Tick (); thinker->Tick ();
} }

View file

@ -49,13 +49,14 @@ class FThinkerIterator;
enum { MAX_STATNUM = 127 }; enum { MAX_STATNUM = 127 };
// Doubly linked list of thinkers // Doubly linked list of thinkers
class DThinker : public DObject, public Node class DThinker : public DObject, protected Node
{ {
DECLARE_CLASS (DThinker, DObject) DECLARE_CLASS (DThinker, DObject)
public: public:
DThinker (int statnum = MAX_STATNUM) throw(); DThinker (int statnum = MAX_STATNUM) throw();
void Destroy (); void Destroy ();
size_t PropagateMark();
virtual ~DThinker (); virtual ~DThinker ();
virtual void Tick (); virtual void Tick ();
virtual void PostBeginPlay (); // Called just before the first tick virtual void PostBeginPlay (); // Called just before the first tick
@ -67,6 +68,7 @@ public:
static void DestroyAllThinkers (); static void DestroyAllThinkers ();
static void DestroyMostThinkers (); static void DestroyMostThinkers ();
static void SerializeAll (FArchive &arc, bool keepPlayers); static void SerializeAll (FArchive &arc, bool keepPlayers);
static void MarkRoots();
static DThinker *FirstThinker (int statnum); static DThinker *FirstThinker (int statnum);
@ -75,6 +77,7 @@ private:
static void DestroyMostThinkersInList (List &list, int stat); static void DestroyMostThinkersInList (List &list, int stat);
static int TickThinkers (List *list, List *dest); // Returns: # of thinkers ticked static int TickThinkers (List *list, List *dest); // Returns: # of thinkers ticked
static void SaveList(FArchive &arc, Node *node); static void SaveList(FArchive &arc, Node *node);
void Remove();
static List Thinkers[MAX_STATNUM+1]; // Current thinkers static List Thinkers[MAX_STATNUM+1]; // Current thinkers
static List FreshThinkers[MAX_STATNUM+1]; // Newly created thinkers static List FreshThinkers[MAX_STATNUM+1]; // Newly created thinkers

View file

@ -1004,9 +1004,17 @@ FArchive &FArchive::SerializePointer (void *ptrbase, BYTE **ptr, DWORD elemSize)
FArchive &FArchive::SerializeObject (DObject *&object, PClass *type) FArchive &FArchive::SerializeObject (DObject *&object, PClass *type)
{ {
if (IsStoring ()) if (IsStoring ())
{
if (object != (DObject*)~0)
{
GC::ReadBarrier(object);
}
return WriteObject (object); return WriteObject (object);
}
else else
{
return ReadObject (object, type); return ReadObject (object, type);
}
} }
FArchive &FArchive::WriteObject (DObject *obj) FArchive &FArchive::WriteObject (DObject *obj)

View file

@ -1925,6 +1925,11 @@ void FParser::SF_FadeLight(void)
} }
} }
//==========================================================================
//
//
//
//==========================================================================
void FParser::SF_FloorTexture(void) void FParser::SF_FloorTexture(void)
{ {
int tagnum, secnum; int tagnum, secnum;
@ -2066,12 +2071,13 @@ void FParser::SF_StartSkill(void)
I_Error("startskill is not supported by this implementation!\n"); I_Error("startskill is not supported by this implementation!\n");
} }
////////////////////////////////////////////////////////////////////////// //==========================================================================
// //
// Doors // Doors
// //
// opendoor(sectag, [delay], [speed]) // opendoor(sectag, [delay], [speed])
//
//==========================================================================
void FParser::SF_OpenDoor(void) void FParser::SF_OpenDoor(void)
{ {
@ -2204,13 +2210,13 @@ inline line_t * P_FindLine(int tag,int * searchPosition)
return *searchPosition>=0? &lines[*searchPosition]:NULL; return *searchPosition>=0? &lines[*searchPosition]:NULL;
} }
/* //==========================================================================
FParser::SF_SetLineBlocking() //
// FParser::SF_SetLineBlocking()
Sets a line blocking or unblocking //
// Sets a line blocking or unblocking
setlineblocking(tag, [1|0]); //
*/ //==========================================================================
void FParser::SF_SetLineBlocking(void) void FParser::SF_SetLineBlocking(void)
{ {
line_t *line; line_t *line;
@ -2236,12 +2242,10 @@ void FParser::SF_SetLineBlocking(void)
//========================================================================== //==========================================================================
// //
// // similar, but monster blocking
// //
//========================================================================== //==========================================================================
// similar, but monster blocking
void FParser::SF_SetLineMonsterBlocking(void) void FParser::SF_SetLineMonsterBlocking(void)
{ {
line_t *line; line_t *line;
@ -2261,17 +2265,19 @@ void FParser::SF_SetLineMonsterBlocking(void)
} }
} }
/*
FParser::SF_SetLineTexture
#2 in a not-so-long line of ACS-inspired functions
This one is *much* needed, IMO
Eternity: setlinetexture(tag, side, position, texture)
Legacy: setlinetexture(tag, texture, side, sections)
*/
//==========================================================================
//
//FParser::SF_SetLineTexture
//
// #2 in a not-so-long line of ACS-inspired functions
// This one is *much* needed, IMO
//
// Eternity: setlinetexture(tag, side, position, texture)
// Legacy: setlinetexture(tag, texture, side, sections)
//
//==========================================================================
void FParser::SF_SetLineTexture(void) void FParser::SF_SetLineTexture(void)
{ {
@ -2357,11 +2363,10 @@ void FParser::SF_SetLineTexture(void)
//========================================================================== //==========================================================================
// //
// // SoM: Max, Min, Abs math functions.
// //
//========================================================================== //==========================================================================
// SoM: Max, Min, Abs math functions.
void FParser::SF_Max(void) void FParser::SF_Max(void)
{ {
fixed_t n1, n2; fixed_t n1, n2;
@ -2417,13 +2422,15 @@ void FParser::SF_Abs(void)
} }
} }
/* //==========================================================================
FParser::SF_Gameskill, FParser::SF_Gamemode //
// FParser::SF_Gameskill, FParser::SF_Gamemode
Access functions are more elegant for these than variables, //
especially for the game mode, which doesn't exist as a numeric // Access functions are more elegant for these than variables,
variable already. // especially for the game mode, which doesn't exist as a numeric
*/ // variable already.
//
//==========================================================================
void FParser::SF_Gameskill(void) void FParser::SF_Gameskill(void)
{ {
@ -2452,13 +2459,16 @@ void FParser::SF_Gamemode(void)
t_return.value.i = 2; // deathmatch t_return.value.i = 2; // deathmatch
} }
/* //==========================================================================
FParser::SF_IsPlayerObj() //
// FParser::SF_IsPlayerObj()
//
// A function suggested by SoM to help the script coder prevent
// exceptions related to calling player functions on non-player
// objects.
//
//==========================================================================
A function suggested by SoM to help the script coder prevent
exceptions related to calling player functions on non-player
objects.
*/
void FParser::SF_IsPlayerObj(void) void FParser::SF_IsPlayerObj(void)
{ {
AActor *mo; AActor *mo;
@ -2476,6 +2486,11 @@ void FParser::SF_IsPlayerObj(void)
//============================================================================ //============================================================================
// //
// Inventory stuff - mostly new to GZDoom
//
// all the original functions are still supported but they have not
// been expanded from their original and are limited as a result
//
// Since FraggleScript is rather hard coded to the original inventory // Since FraggleScript is rather hard coded to the original inventory
// handling of Doom this is rather messy. // handling of Doom this is rather messy.
// //
@ -2614,14 +2629,14 @@ static int FS_CheckInventory (AActor *activator, const char *type)
//========================================================================== //==========================================================================
// //
// // This function is just kept for backwards compatibility
// and intentionally limited to thr standard keys!
// Use Give/Take/CheckInventory instead!
// //
//========================================================================== //==========================================================================
void FParser::SF_PlayerKeys(void) void FParser::SF_PlayerKeys(void)
{ {
// This function is just kept for backwards compatibility and intentionally limited to thr standard keys!
// Use Give/Take/CheckInventory instead!
static const char * const DoomKeys[]={"BlueCard", "YellowCard", "RedCard", "BlueSkull", "YellowSkull", "RedSkull"}; static const char * const DoomKeys[]={"BlueCard", "YellowCard", "RedCard", "BlueSkull", "YellowSkull", "RedSkull"};
int playernum, keynum, givetake; int playernum, keynum, givetake;
const char * keyname; const char * keyname;
@ -2658,14 +2673,13 @@ void FParser::SF_PlayerKeys(void)
//========================================================================== //==========================================================================
// //
// // This function is just kept for backwards compatibility and intentionally limited!
// Use Give/Take/CheckInventory instead!
// //
//========================================================================== //==========================================================================
void FParser::SF_PlayerAmmo(void) void FParser::SF_PlayerAmmo(void)
{ {
// This function is just kept for backwards compatibility and intentionally limited!
// Use Give/Take/CheckInventory instead!
int playernum, amount; int playernum, amount;
const PClass * ammotype; const PClass * ammotype;
@ -2751,7 +2765,9 @@ void FParser::SF_MaxPlayerAmmo()
//========================================================================== //==========================================================================
// //
// // This function is just kept for backwards compatibility and
// intentionally limited to the standard weapons!
// Use Give/Take/CheckInventory instead!
// //
//========================================================================== //==========================================================================
@ -2762,8 +2778,6 @@ void FParser::SF_PlayerWeapon()
"PlasmaRifle", "BFG9000", "Chainsaw", "SuperShotgun" }; "PlasmaRifle", "BFG9000", "Chainsaw", "SuperShotgun" };
// This function is just kept for backwards compatibility and intentionally limited to the standard weapons!
// Use Give/Take/CheckInventory instead!
int playernum; int playernum;
int weaponnum; int weaponnum;
int newweapon; int newweapon;
@ -2830,7 +2844,8 @@ void FParser::SF_PlayerWeapon()
//========================================================================== //==========================================================================
// //
// // This function is just kept for backwards compatibility and
// intentionally limited to the standard weapons!
// //
//========================================================================== //==========================================================================
@ -2839,7 +2854,6 @@ void FParser::SF_PlayerSelectedWeapon()
int playernum; int playernum;
int weaponnum; int weaponnum;
// This function is just kept for backwards compatibility and intentionally limited to the standard weapons!
static const char * const WeaponNames[]={ static const char * const WeaponNames[]={
"Fist", "Pistol", "Shotgun", "Chaingun", "RocketLauncher", "Fist", "Pistol", "Shotgun", "Chaingun", "RocketLauncher",
@ -2991,13 +3005,11 @@ void FParser::SF_SetWeapon()
} }
} }
// removed FParser::SF_PlayerMaxAmmo //==========================================================================
// //
// movecamera(camera, targetobj, targetheight, movespeed, targetangle, anglespeed) // movecamera(camera, targetobj, targetheight, movespeed, targetangle, anglespeed)
// //
//==========================================================================
void FParser::SF_MoveCamera(void) void FParser::SF_MoveCamera(void)
{ {
@ -3219,12 +3231,10 @@ void FParser::SF_ExitSecret(void)
//========================================================================== //==========================================================================
// //
// // Type forcing functions -- useful with arrays et al
// //
//========================================================================== //==========================================================================
// Type forcing functions -- useful with arrays et al
void FParser::SF_MobjValue(void) void FParser::SF_MobjValue(void)
{ {
if (CheckArgs(1)) if (CheckArgs(1))
@ -3448,7 +3458,7 @@ void FParser::SF_SpawnMissile()
void FParser::SF_MapThingNumExist() void FParser::SF_MapThingNumExist()
{ {
TArray<DActorPointer*> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings; TArray<TObjPtr<AActor>> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings;
int intval; int intval;
@ -3456,7 +3466,7 @@ void FParser::SF_MapThingNumExist()
{ {
intval = intvalue(t_argv[0]); intval = intvalue(t_argv[0]);
if (intval < 0 || intval >= int(SpawnedThings.Size()) || !SpawnedThings[intval]->actor) if (intval < 0 || intval >= int(SpawnedThings.Size()) || !SpawnedThings[intval])
{ {
t_return.type = svt_int; t_return.type = svt_int;
t_return.value.i = 0; t_return.value.i = 0;
@ -3477,7 +3487,7 @@ void FParser::SF_MapThingNumExist()
void FParser::SF_MapThings() void FParser::SF_MapThings()
{ {
TArray<DActorPointer*> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings; TArray<TObjPtr<AActor>> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings;
t_return.type = svt_int; t_return.type = svt_int;
t_return.value.i = SpawnedThings.Size(); t_return.value.i = SpawnedThings.Size();
@ -4330,7 +4340,7 @@ void FParser::SF_WallGlow()
//========================================================================== //==========================================================================
// //
// Spawns a projectile at a map spot //
// //
//========================================================================== //==========================================================================
@ -4354,8 +4364,12 @@ DRunningScript *FParser::SaveCurrentScript()
runscr->next = th->RunningScripts->next; runscr->next = th->RunningScripts->next;
runscr->prev = th->RunningScripts; runscr->prev = th->RunningScripts;
runscr->prev->next = runscr; runscr->prev->next = runscr;
GC::WriteBarrier(runscr->prev, runscr);
if(runscr->next) if(runscr->next)
{
runscr->next->prev = runscr; runscr->next->prev = runscr;
GC::WriteBarrier(runscr->next, runscr);
}
// save the script variables // save the script variables
for(i=0; i<VARIABLESLOTS; i++) for(i=0; i<VARIABLESLOTS; i++)
@ -4516,8 +4530,12 @@ void FParser::SF_StartScript()
runscr->next = th->RunningScripts->next; runscr->next = th->RunningScripts->next;
runscr->prev = th->RunningScripts; runscr->prev = th->RunningScripts;
runscr->prev->next = runscr; runscr->prev->next = runscr;
GC::WriteBarrier(runscr->prev, runscr);
if(runscr->next) if(runscr->next)
{
runscr->next->prev = runscr; runscr->next->prev = runscr;
GC::WriteBarrier(runscr->next, runscr);
}
// save the script variables // save the script variables
for(i=0; i<VARIABLESLOTS; i++) for(i=0; i<VARIABLESLOTS; i++)

View file

@ -268,16 +268,13 @@ void T_LoadLevelInfo(MapData * map)
// If no actor is spawned it will remain NULL, otherwise T_RegisterSpawnThing // If no actor is spawned it will remain NULL, otherwise T_RegisterSpawnThing
// will be called to set it // will be called to set it
// //
// This uses a DActorPointer so that it is subject to automatic pointer cleanup
//
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void T_PrepareSpawnThing() void T_PrepareSpawnThing()
{ {
if (DFraggleThinker::ActiveThinker) if (DFraggleThinker::ActiveThinker)
{ {
DActorPointer * acp = new DActorPointer; DFraggleThinker::ActiveThinker->SpawnedThings.Push(NULL);
DFraggleThinker::ActiveThinker->SpawnedThings.Push(acp);
} }
} }
@ -291,7 +288,7 @@ void T_RegisterSpawnThing(AActor * ac)
{ {
if (DFraggleThinker::ActiveThinker) if (DFraggleThinker::ActiveThinker)
{ {
TArray<DActorPointer*> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings; TArray<TObjPtr<AActor>> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings;
SpawnedThings[SpawnedThings.Size()-1]->actor=ac; SpawnedThings[SpawnedThings.Size()-1] = ac;
} }
} }

View file

@ -51,6 +51,12 @@
EvaluateExpression(right, (b)+1, (c)); }\ EvaluateExpression(right, (b)+1, (c)); }\
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
FParser::operator_t FParser::operators[]= FParser::operator_t FParser::operators[]=
{ {
{"=", &FParser::OPequals, backward}, {"=", &FParser::OPequals, backward},
@ -81,11 +87,11 @@ int FParser::num_operators = sizeof(FParser::operators) / sizeof(FParser::operat
/***************** logic *********************/ /***************** logic *********************/
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPequals(svalue_t &result, int start, int n, int stop) void FParser::OPequals(svalue_t &result, int start, int n, int stop)
{ {
@ -105,11 +111,11 @@ void FParser::OPequals(svalue_t &result, int start, int n, int stop)
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPor(svalue_t &result, int start, int n, int stop) void FParser::OPor(svalue_t &result, int start, int n, int stop)
{ {
@ -132,11 +138,11 @@ void FParser::OPor(svalue_t &result, int start, int n, int stop)
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPand(svalue_t &result, int start, int n, int stop) void FParser::OPand(svalue_t &result, int start, int n, int stop)
{ {
@ -157,11 +163,11 @@ void FParser::OPand(svalue_t &result, int start, int n, int stop)
result.value.i = exprtrue; result.value.i = exprtrue;
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPcmp(svalue_t &result, int start, int n, int stop) void FParser::OPcmp(svalue_t &result, int start, int n, int stop)
{ {
@ -196,11 +202,11 @@ void FParser::OPcmp(svalue_t &result, int start, int n, int stop)
result.value.i = (intvalue(left) == intvalue(right)); result.value.i = (intvalue(left) == intvalue(right));
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPnotcmp(svalue_t &result, int start, int n, int stop) void FParser::OPnotcmp(svalue_t &result, int start, int n, int stop)
{ {
@ -209,11 +215,11 @@ void FParser::OPnotcmp(svalue_t &result, int start, int n, int stop)
result.value.i = !result.value.i; result.value.i = !result.value.i;
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPlessthan(svalue_t &result, int start, int n, int stop) void FParser::OPlessthan(svalue_t &result, int start, int n, int stop)
{ {
@ -230,11 +236,11 @@ void FParser::OPlessthan(svalue_t &result, int start, int n, int stop)
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPgreaterthan(svalue_t &result, int start, int n, int stop) void FParser::OPgreaterthan(svalue_t &result, int start, int n, int stop)
{ {
@ -250,11 +256,11 @@ void FParser::OPgreaterthan(svalue_t &result, int start, int n, int stop)
result.value.i = (intvalue(left) > intvalue(right)); result.value.i = (intvalue(left) > intvalue(right));
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPnot(svalue_t &result, int start, int n, int stop) void FParser::OPnot(svalue_t &result, int start, int n, int stop)
{ {
@ -264,13 +270,11 @@ void FParser::OPnot(svalue_t &result, int start, int n, int stop)
result.type = svt_int; result.type = svt_int;
} }
/************** simple math ***************/ //-----------------------------------------------------------------------------
//==========================================================================
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPplus(svalue_t &result, int start, int n, int stop) void FParser::OPplus(svalue_t &result, int start, int n, int stop)
{ {
@ -307,11 +311,11 @@ void FParser::OPplus(svalue_t &result, int start, int n, int stop)
} }
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPminus(svalue_t &result, int start, int n, int stop) void FParser::OPminus(svalue_t &result, int start, int n, int stop)
{ {
@ -341,11 +345,11 @@ void FParser::OPminus(svalue_t &result, int start, int n, int stop)
} }
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPmultiply(svalue_t &result,int start, int n, int stop) void FParser::OPmultiply(svalue_t &result,int start, int n, int stop)
{ {
@ -366,11 +370,11 @@ void FParser::OPmultiply(svalue_t &result,int start, int n, int stop)
} }
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPdivide(svalue_t &result, int start, int n, int stop) void FParser::OPdivide(svalue_t &result, int start, int n, int stop)
{ {
@ -405,11 +409,11 @@ void FParser::OPdivide(svalue_t &result, int start, int n, int stop)
} }
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPremainder(svalue_t &result, int start, int n, int stop) void FParser::OPremainder(svalue_t &result, int start, int n, int stop)
{ {
@ -429,13 +433,11 @@ void FParser::OPremainder(svalue_t &result, int start, int n, int stop)
/********** binary operators **************/ /********** binary operators **************/
// binary or | //-----------------------------------------------------------------------------
//==========================================================================
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPor_bin(svalue_t &result, int start, int n, int stop) void FParser::OPor_bin(svalue_t &result, int start, int n, int stop)
{ {
@ -448,13 +450,11 @@ void FParser::OPor_bin(svalue_t &result, int start, int n, int stop)
} }
// binary and & //-----------------------------------------------------------------------------
//==========================================================================
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPand_bin(svalue_t &result, int start, int n, int stop) void FParser::OPand_bin(svalue_t &result, int start, int n, int stop)
{ {
@ -466,11 +466,11 @@ void FParser::OPand_bin(svalue_t &result, int start, int n, int stop)
result.value.i = intvalue(left) & intvalue(right); result.value.i = intvalue(left) & intvalue(right);
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPnot_bin(svalue_t &result, int start, int n, int stop) void FParser::OPnot_bin(svalue_t &result, int start, int n, int stop)
{ {
@ -481,11 +481,11 @@ void FParser::OPnot_bin(svalue_t &result, int start, int n, int stop)
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPincrement(svalue_t &result, int start, int n, int stop) void FParser::OPincrement(svalue_t &result, int start, int n, int stop)
{ {
@ -546,11 +546,11 @@ void FParser::OPincrement(svalue_t &result, int start, int n, int stop)
} }
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPdecrement(svalue_t &result, int start, int n, int stop) void FParser::OPdecrement(svalue_t &result, int start, int n, int stop)
{ {
@ -611,11 +611,11 @@ void FParser::OPdecrement(svalue_t &result, int start, int n, int stop)
} }
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPlessthanorequal(svalue_t &result, int start, int n, int stop) void FParser::OPlessthanorequal(svalue_t &result, int start, int n, int stop)
{ {
@ -631,11 +631,11 @@ void FParser::OPlessthanorequal(svalue_t &result, int start, int n, int stop)
result.value.i = (intvalue(left) <= intvalue(right)); result.value.i = (intvalue(left) <= intvalue(right));
} }
//========================================================================== //-----------------------------------------------------------------------------
// //
// //
// //
//========================================================================== //-----------------------------------------------------------------------------
void FParser::OPgreaterthanorequal(svalue_t &result, int start, int n, int stop) void FParser::OPgreaterthanorequal(svalue_t &result, int start, int n, int stop)
{ {

View file

@ -154,6 +154,7 @@ DFsSection *DFsScript::NewSection(const char *brace)
newsec->start_index = MakeIndex(brace); newsec->start_index = MakeIndex(brace);
newsec->next = sections[n]; newsec->next = sections[n];
sections[n] = newsec; sections[n] = newsec;
GC::WriteBarrier(this, newsec);
return newsec; return newsec;
} }

View file

@ -320,10 +320,6 @@ void DRunningScript::Serialize(FArchive &arc)
// The main thinker // The main thinker
// //
//========================================================================== //==========================================================================
IMPLEMENT_POINTY_CLASS(DActorPointer)
DECLARE_POINTER(actor)
END_POINTERS
IMPLEMENT_POINTY_CLASS(DFraggleThinker) IMPLEMENT_POINTY_CLASS(DFraggleThinker)
DECLARE_POINTER(RunningScripts) DECLARE_POINTER(RunningScripts)
DECLARE_POINTER(LevelScript) DECLARE_POINTER(LevelScript)
@ -373,10 +369,6 @@ void DFraggleThinker::Destroy()
LevelScript->Destroy(); LevelScript->Destroy();
LevelScript = NULL; LevelScript = NULL;
for(unsigned i=0; i<SpawnedThings.Size(); i++)
{
SpawnedThings[i]->Destroy();
}
SpawnedThings.Clear(); SpawnedThings.Clear();
ActiveThinker = NULL; ActiveThinker = NULL;
Super::Destroy(); Super::Destroy();
@ -488,7 +480,12 @@ void DFraggleThinker::Tick()
// unhook from chain // unhook from chain
current->prev->next = current->next; current->prev->next = current->next;
if(current->next) current->next->prev = current->prev; GC::WriteBarrier(current->prev, current->next);
if(current->next)
{
current->next->prev = current->prev;
GC::WriteBarrier(current->next, current->prev);
}
next = current->next; // save before freeing next = current->next; // save before freeing
// continue the script // continue the script
@ -503,6 +500,23 @@ void DFraggleThinker::Tick()
} }
} }
//==========================================================================
//
// We have to mark the SpawnedThings array manually because it's not
// in the list of declared pointers.
//
//==========================================================================
size_t DFraggleThinker::PropagateMark()
{
for(unsigned i=0;i<SpawnedThings.Size();i++)
{
GC::Mark(SpawnedThings[i]);
}
return Super::PropagateMark();
}
//========================================================================== //==========================================================================
// //
// //
@ -555,8 +569,12 @@ static bool T_RunScript(int snum, AActor * t_trigger)
runscr->next = th->RunningScripts->next; runscr->next = th->RunningScripts->next;
runscr->prev = th->RunningScripts; runscr->prev = th->RunningScripts;
runscr->prev->next = runscr; runscr->prev->next = runscr;
GC::WriteBarrier(runscr->prev, runscr);
if(runscr->next) if(runscr->next)
{
runscr->next->prev = runscr; runscr->next->prev = runscr;
GC::WriteBarrier(runscr->next, runscr);
}
// save the script variables // save the script variables
for(i=0; i<VARIABLESLOTS; i++) for(i=0; i<VARIABLESLOTS; i++)
@ -612,11 +630,13 @@ void FS_Close()
{ {
next = current->next; // save for after freeing next = current->next; // save for after freeing
//current->ObjectFlags |= OF_YESREALLYDELETE; current->ObjectFlags |= OF_YesReallyDelete;
delete current; delete current;
current = next; // go to next in chain current = next; // go to next in chain
} }
} }
GC::DelSoftRoot(global_script);
global_script->ObjectFlags |= OF_YesReallyDelete;
delete global_script; delete global_script;
} }
@ -627,6 +647,7 @@ AT_GAME_SET(FS_Init)
// I'd rather link the special here than make another source file depend on FS! // I'd rather link the special here than make another source file depend on FS!
LineSpecials[54]=LS_FS_Execute; LineSpecials[54]=LS_FS_Execute;
global_script = new DFsScript; global_script = new DFsScript;
GC::AddSoftRoot(global_script);
init_functions(); init_functions();
atterm(FS_Close); atterm(FS_Close);
} }

View file

@ -59,33 +59,6 @@ inline bool isop(int c)
// //
//========================================================================== //==========================================================================
class DActorPointer : public DObject
{
DECLARE_CLASS(DActorPointer, DObject)
HAS_OBJECT_POINTERS
public:
AActor * actor;
DActorPointer()
{
actor=NULL;
}
void Serialize(FArchive & ar)
{
Super::Serialize(ar);
ar << actor;
}
};
//==========================================================================
//
//
//
//==========================================================================
enum enum
{ {
svt_string, svt_string,
@ -168,11 +141,11 @@ struct DFsVariable : public DObject
public: public:
FString Name; FString Name;
DFsVariable *next; // for hashing TObjPtr<DFsVariable> next; // for hashing
int type; // vt_string or vt_int: same as in svalue_t int type; // vt_string or vt_int: same as in svalue_t
FString string; FString string;
AActor *actor; TObjPtr<AActor> actor;
union value_t union value_t
{ {
@ -190,7 +163,7 @@ public:
DFsVariable(const char *_name = ""); DFsVariable(const char *_name = "");
svalue_t GetValue() const; svalue_t GetValue();
void SetValue(const svalue_t &newvalue); void SetValue(const svalue_t &newvalue);
void Serialize(FArchive &ar); void Serialize(FArchive &ar);
}; };
@ -234,7 +207,7 @@ public:
int start_index; int start_index;
int end_index; int end_index;
int loop_index; int loop_index;
DFsSection *next; // for hashing TObjPtr<DFsSection> next; // for hashing
DFsSection() DFsSection()
{ {
@ -313,27 +286,27 @@ public:
// {} sections // {} sections
DFsSection *sections[SECTIONSLOTS]; TObjPtr<DFsSection> sections[SECTIONSLOTS];
// variables: // variables:
DFsVariable *variables[VARIABLESLOTS]; TObjPtr<DFsVariable> variables[VARIABLESLOTS];
// ptr to the parent script // ptr to the parent script
// the parent script is the script above this level // the parent script is the script above this level
// eg. individual linetrigger scripts are children // eg. individual linetrigger scripts are children
// of the levelscript, which is a child of the // of the levelscript, which is a child of the
// global_script // global_script
DFsScript *parent; TObjPtr<DFsScript> parent;
// haleyjd: 8-17 // haleyjd: 8-17
// child scripts. // child scripts.
// levelscript holds ptrs to all of the level's scripts // levelscript holds ptrs to all of the level's scripts
// here. // here.
DFsScript *children[MAXSCRIPTS]; TObjPtr<DFsScript> children[MAXSCRIPTS];
AActor *trigger; // object which triggered this script TObjPtr<AActor> trigger; // object which triggered this script
bool lastiftrue; // haleyjd: whether last "if" statement was bool lastiftrue; // haleyjd: whether last "if" statement was
// true or false // true or false
@ -654,7 +627,7 @@ public:
void Destroy(); void Destroy();
void Serialize(FArchive &arc); void Serialize(FArchive &arc);
DFsScript *script; TObjPtr<DFsScript> script;
// where we are // where we are
int save_point; int save_point;
@ -663,10 +636,10 @@ public:
int wait_data; // data for wait: tagnum, counter, script number etc int wait_data; // data for wait: tagnum, counter, script number etc
// saved variables // saved variables
DFsVariable *variables[VARIABLESLOTS]; TObjPtr<DFsVariable> variables[VARIABLESLOTS];
DRunningScript *prev, *next; // for chain TObjPtr<DRunningScript> prev, next; // for chain
AActor *trigger; TObjPtr<AActor> trigger;
}; };
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -680,9 +653,9 @@ class DFraggleThinker : public DThinker
HAS_OBJECT_POINTERS HAS_OBJECT_POINTERS
public: public:
DFsScript *LevelScript; TObjPtr<DFsScript> LevelScript;
DRunningScript *RunningScripts; TObjPtr<DRunningScript> RunningScripts;
TArray<DActorPointer*> SpawnedThings; TArray<TObjPtr<AActor>> SpawnedThings;
DFraggleThinker(); DFraggleThinker();
void Destroy(); void Destroy();
@ -690,6 +663,7 @@ public:
void Serialize(FArchive & arc); void Serialize(FArchive & arc);
void Tick(); void Tick();
size_t PropagateMark();
bool wait_finished(DRunningScript *script); bool wait_finished(DRunningScript *script);
static DFraggleThinker *ActiveThinker; static DFraggleThinker *ActiveThinker;

View file

@ -421,6 +421,7 @@ void FParser::spec_script()
// add to scripts list of parent // add to scripts list of parent
Script->children[scriptnum] = newscript; Script->children[scriptnum] = newscript;
GC::WriteBarrier(Script, newscript);
// copy newscript data // copy newscript data
// workout newscript size: -2 to ignore { and } // workout newscript size: -2 to ignore { and }

View file

@ -151,7 +151,7 @@ AActor *actorvalue(const svalue_t &svalue)
} }
else else
{ {
TArray<DActorPointer*> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings; TArray<TObjPtr<AActor>> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings;
// this requires some creativity. We use the intvalue // this requires some creativity. We use the intvalue
// as the thing number of a thing in the level. // as the thing number of a thing in the level.
intval = intvalue(svalue); intval = intvalue(svalue);
@ -161,14 +161,14 @@ AActor *actorvalue(const svalue_t &svalue)
return NULL; return NULL;
} }
// Inventory items in the player's inventory have to be considered non-present. // Inventory items in the player's inventory have to be considered non-present.
if (SpawnedThings[intval]->actor != NULL && if (SpawnedThings[intval] != NULL &&
SpawnedThings[intval]->actor->IsKindOf(RUNTIME_CLASS(AInventory)) && SpawnedThings[intval]->IsKindOf(RUNTIME_CLASS(AInventory)) &&
static_cast<AInventory*>(SpawnedThings[intval]->actor)->Owner != NULL) barrier_cast<AInventory*>(SpawnedThings[intval])->Owner != NULL)
{ {
return NULL; return NULL;
} }
return SpawnedThings[intval]->actor; return SpawnedThings[intval];
} }
} }
@ -203,7 +203,7 @@ DFsVariable::DFsVariable(const char * _name)
// //
//========================================================================== //==========================================================================
svalue_t DFsVariable::GetValue() const svalue_t DFsVariable::GetValue()
{ {
svalue_t returnvar; svalue_t returnvar;
@ -323,6 +323,7 @@ DFsVariable *DFsScript::NewVariable(const char *name, int vtype)
int n = variable_hash(name); int n = variable_hash(name);
newvar->next = variables[n]; newvar->next = variables[n];
variables[n] = newvar; variables[n] = newvar;
GC::WriteBarrier(this, newvar);
return newvar; return newvar;
} }

View file

@ -315,7 +315,7 @@ void A_SpawnFly (AActor *self)
if (newmobj->SeeState != NULL && P_LookForPlayers (newmobj, true)) if (newmobj->SeeState != NULL && P_LookForPlayers (newmobj, true))
newmobj->SetState (newmobj->SeeState); newmobj->SetState (newmobj->SeeState);
if (!(newmobj->ObjectFlags & OF_MassDestruction)) if (!(newmobj->ObjectFlags & OF_EuthanizeMe))
{ {
// telefrag anything in this spot // telefrag anything in this spot
P_TeleportMove (newmobj, newmobj->x, newmobj->y, newmobj->z, true); P_TeleportMove (newmobj, newmobj->x, newmobj->y, newmobj->z, true);

View file

@ -27,10 +27,11 @@
EXTERN_CVAR (Bool, vid_fps) EXTERN_CVAR (Bool, vid_fps)
class FDoomStatusBar : public FBaseStatusBar class DDoomStatusBar : public DBaseStatusBar
{ {
DECLARE_CLASS(DDoomStatusBar, DBaseStatusBar)
public: public:
FDoomStatusBar () : FBaseStatusBar (32) DDoomStatusBar () : DBaseStatusBar (32)
{ {
static const char *sharedLumpNames[] = static const char *sharedLumpNames[] =
{ {
@ -44,8 +45,8 @@ public:
}; };
FTexture *tex; FTexture *tex;
FBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES); DBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES);
tex = FBaseStatusBar::Images[imgBNumbers]; tex = DBaseStatusBar::Images[imgBNumbers];
BigWidth = tex->GetWidth(); BigWidth = tex->GetWidth();
BigHeight = tex->GetHeight(); BigHeight = tex->GetHeight();
@ -53,7 +54,7 @@ public:
bEvilGrin = false; bEvilGrin = false;
} }
~FDoomStatusBar () ~DDoomStatusBar ()
{ {
} }
@ -126,7 +127,7 @@ public:
void MultiplayerChanged () void MultiplayerChanged ()
{ {
FBaseStatusBar::MultiplayerChanged (); DBaseStatusBar::MultiplayerChanged ();
if (multiplayer) if (multiplayer)
{ {
// set face background color // set face background color
@ -138,7 +139,7 @@ public:
{ {
player_t *oldplayer = CPlayer; player_t *oldplayer = CPlayer;
FBaseStatusBar::AttachToPlayer (player); DBaseStatusBar::AttachToPlayer (player);
if (oldplayer != CPlayer) if (oldplayer != CPlayer)
{ {
SetFace (&skins[CPlayer->userinfo.skin]); SetFace (&skins[CPlayer->userinfo.skin]);
@ -153,14 +154,14 @@ public:
void Tick () void Tick ()
{ {
FBaseStatusBar::Tick (); DBaseStatusBar::Tick ();
RandomNumber = M_Random (); RandomNumber = M_Random ();
UpdateFace (); UpdateFace ();
} }
void Draw (EHudState state) void Draw (EHudState state)
{ {
FBaseStatusBar::Draw (state); DBaseStatusBar::Draw (state);
if (state == HUD_Fullscreen) if (state == HUD_Fullscreen)
{ {
@ -375,7 +376,7 @@ private:
void DrawArm (int on, int picnum, int x, int y, bool drawBackground) void DrawArm (int on, int picnum, int x, int y, bool drawBackground)
{ {
int w; int w;
FTexture *pic = on ? FBaseStatusBar::Images[imgSmNumbers + 2 + picnum] : Images[imgGNUM2 + picnum]; FTexture *pic = on ? DBaseStatusBar::Images[imgSmNumbers + 2 + picnum] : Images[imgGNUM2 + picnum];
if (pic != NULL) if (pic != NULL)
{ {
@ -555,7 +556,7 @@ private:
void DrawInventoryBar () void DrawInventoryBar ()
{ {
const AInventory *item; AInventory *item;
int i; int i;
// If the player has no artifacts, don't draw the bar // If the player has no artifacts, don't draw the bar
@ -602,7 +603,7 @@ private:
void DrawFullScreenStuff () void DrawFullScreenStuff ()
{ {
const AInventory *item; AInventory *item;
int i; int i;
int ammotop; int ammotop;
@ -1041,7 +1042,9 @@ private:
bool bEvilGrin; bool bEvilGrin;
}; };
FDoomStatusBar::FDoomStatusBarTexture::FDoomStatusBarTexture () IMPLEMENT_CLASS(DDoomStatusBar)
DDoomStatusBar::FDoomStatusBarTexture::FDoomStatusBarTexture ()
{ {
BaseTexture = TexMan["STBAR"]; BaseTexture = TexMan["STBAR"];
if (BaseTexture==NULL) if (BaseTexture==NULL)
@ -1057,7 +1060,7 @@ FDoomStatusBar::FDoomStatusBarTexture::FDoomStatusBarTexture ()
STBFremap = NULL; STBFremap = NULL;
} }
const BYTE *FDoomStatusBar::FDoomStatusBarTexture::GetColumn (unsigned int column, const Span **spans_out) const BYTE *DDoomStatusBar::FDoomStatusBarTexture::GetColumn (unsigned int column, const Span **spans_out)
{ {
if (Pixels == NULL) if (Pixels == NULL)
{ {
@ -1068,7 +1071,7 @@ const BYTE *FDoomStatusBar::FDoomStatusBarTexture::GetColumn (unsigned int colum
return Pixels + column*Height; return Pixels + column*Height;
} }
const BYTE *FDoomStatusBar::FDoomStatusBarTexture::GetPixels () const BYTE *DDoomStatusBar::FDoomStatusBarTexture::GetPixels ()
{ {
if (Pixels == NULL) if (Pixels == NULL)
{ {
@ -1077,7 +1080,7 @@ const BYTE *FDoomStatusBar::FDoomStatusBarTexture::GetPixels ()
return Pixels; return Pixels;
} }
void FDoomStatusBar::FDoomStatusBarTexture::Unload () void DDoomStatusBar::FDoomStatusBarTexture::Unload ()
{ {
if (Pixels != NULL) if (Pixels != NULL)
{ {
@ -1086,13 +1089,13 @@ void FDoomStatusBar::FDoomStatusBarTexture::Unload ()
} }
} }
FDoomStatusBar::FDoomStatusBarTexture::~FDoomStatusBarTexture () DDoomStatusBar::FDoomStatusBarTexture::~FDoomStatusBarTexture ()
{ {
Unload (); Unload ();
} }
void FDoomStatusBar::FDoomStatusBarTexture::MakeTexture () void DDoomStatusBar::FDoomStatusBarTexture::MakeTexture ()
{ {
Pixels = new BYTE[Width*Height]; Pixels = new BYTE[Width*Height];
const BYTE *pix = BaseTexture->GetPixels(); const BYTE *pix = BaseTexture->GetPixels();
@ -1103,7 +1106,7 @@ void FDoomStatusBar::FDoomStatusBarTexture::MakeTexture ()
if (multiplayer) DrawToBar("STFBANY", 143, 1, STBFremap? STBFremap->Remap : NULL); if (multiplayer) DrawToBar("STFBANY", 143, 1, STBFremap? STBFremap->Remap : NULL);
} }
int FDoomStatusBar::FDoomStatusBarTexture::CopyTrueColorPixels(BYTE *buffer, int buf_pitch, int buf_height, int x, int y) int DDoomStatusBar::FDoomStatusBarTexture::CopyTrueColorPixels(BYTE *buffer, int buf_pitch, int buf_height, int x, int y)
{ {
FTexture *tex; FTexture *tex;
@ -1136,7 +1139,7 @@ int FDoomStatusBar::FDoomStatusBarTexture::CopyTrueColorPixels(BYTE *buffer, int
void FDoomStatusBar::FDoomStatusBarTexture::DrawToBar (const char *name, int x, int y, const BYTE *colormap_in) void DDoomStatusBar::FDoomStatusBarTexture::DrawToBar (const char *name, int x, int y, const BYTE *colormap_in)
{ {
FTexture *pic; FTexture *pic;
BYTE colormap[256]; BYTE colormap[256];
@ -1165,14 +1168,14 @@ void FDoomStatusBar::FDoomStatusBarTexture::DrawToBar (const char *name, int x,
} }
} }
void FDoomStatusBar::FDoomStatusBarTexture::SetPlayerRemap(FRemapTable *remap) void DDoomStatusBar::FDoomStatusBarTexture::SetPlayerRemap(FRemapTable *remap)
{ {
Unload(); Unload();
KillNative(); KillNative();
STBFremap = remap; STBFremap = remap;
} }
FBaseStatusBar *CreateDoomStatusBar () DBaseStatusBar *CreateDoomStatusBar ()
{ {
return new FDoomStatusBar; return new DDoomStatusBar;
} }

View file

@ -1850,7 +1850,7 @@ FString G_BuildSaveName (const char *prefix, int slot)
const char *leader; const char *leader;
const char *slash = ""; const char *slash = "";
if (NULL != (leader = Args.CheckValue ("-savedir"))) if (NULL != (leader = Args->CheckValue ("-savedir")))
{ {
size_t len = strlen (leader); size_t len = strlen (leader);
if (leader[len-1] != '\\' && leader[len-1] != '/') if (leader[len-1] != '\\' && leader[len-1] != '/')
@ -1859,7 +1859,7 @@ FString G_BuildSaveName (const char *prefix, int slot)
} }
} }
#ifndef unix #ifndef unix
else if (Args.CheckParm ("-cdrom")) else if (Args->CheckParm ("-cdrom"))
{ {
leader = CDROM_DIR "/"; leader = CDROM_DIR "/";
} }
@ -2205,7 +2205,7 @@ void G_RecordDemo (char* name)
strcpy (demoname, name); strcpy (demoname, name);
FixPathSeperator (demoname); FixPathSeperator (demoname);
DefaultExtension (demoname, ".lmp"); DefaultExtension (demoname, ".lmp");
v = Args.CheckValue ("-maxdemo"); v = Args->CheckValue ("-maxdemo");
maxdemosize = 0x20000; maxdemosize = 0x20000;
demobuffer = (BYTE *)M_Malloc (maxdemosize); demobuffer = (BYTE *)M_Malloc (maxdemosize);
@ -2510,8 +2510,8 @@ void G_DoPlayDemo (void)
// //
void G_TimeDemo (char* name) void G_TimeDemo (char* name)
{ {
nodrawers = !!Args.CheckParm ("-nodraw"); nodrawers = !!Args->CheckParm ("-nodraw");
noblit = !!Args.CheckParm ("-noblit"); noblit = !!Args->CheckParm ("-noblit");
timingdemo = true; timingdemo = true;
singletics = true; singletics = true;

View file

@ -32,7 +32,7 @@ class APod : public AActor
HAS_OBJECT_POINTERS HAS_OBJECT_POINTERS
public: public:
void BeginPlay (); void BeginPlay ();
AActor *Generator; TObjPtr<AActor> Generator;
void Serialize (FArchive &arc); void Serialize (FArchive &arc);
}; };

View file

@ -803,7 +803,7 @@ public:
protected: protected:
bool DoRespawn (); bool DoRespawn ();
int NumMaceSpots; int NumMaceSpots;
AActor *FirstSpot; TObjPtr<AActor> FirstSpot;
private: private:
friend void A_SpawnMace (AActor *self); friend void A_SpawnMace (AActor *self);

View file

@ -84,10 +84,12 @@ const BYTE *FHereticShader::GetPixels ()
} }
class FHereticStatusBar : public FBaseStatusBar class DHereticStatusBar : public DBaseStatusBar
{ {
DECLARE_CLASS(DHereticStatusBar, DBaseStatusBar)
HAS_OBJECT_POINTERS
public: public:
FHereticStatusBar () : FBaseStatusBar (42) DHereticStatusBar () : DBaseStatusBar (42)
{ {
static const char *hereticLumpNames[NUM_HERETICSB_IMAGES] = static const char *hereticLumpNames[NUM_HERETICSB_IMAGES] =
{ {
@ -118,7 +120,7 @@ public:
hereticLumpNames[5] = "LIFEBAR"; hereticLumpNames[5] = "LIFEBAR";
} }
FBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES); DBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES);
Images.Init (hereticLumpNames, NUM_HERETICSB_IMAGES); Images.Init (hereticLumpNames, NUM_HERETICSB_IMAGES);
oldarti = NULL; oldarti = NULL;
@ -136,7 +138,7 @@ public:
ArtifactFlash = 0; ArtifactFlash = 0;
} }
~FHereticStatusBar () ~DHereticStatusBar ()
{ {
} }
@ -144,7 +146,7 @@ public:
{ {
int curHealth; int curHealth;
FBaseStatusBar::Tick (); DBaseStatusBar::Tick ();
if (level.time & 1) if (level.time & 1)
{ {
ChainWiggle = pr_chainwiggle() & 1; ChainWiggle = pr_chainwiggle() & 1;
@ -174,7 +176,7 @@ public:
void Draw (EHudState state) void Draw (EHudState state)
{ {
FBaseStatusBar::Draw (state); DBaseStatusBar::Draw (state);
if (state == HUD_Fullscreen) if (state == HUD_Fullscreen)
{ {
@ -316,6 +318,7 @@ private:
|| (oldarti != NULL && oldartiCount != oldarti->Amount)) || (oldarti != NULL && oldartiCount != oldarti->Amount))
{ {
oldarti = CPlayer->mo->InvSel; oldarti = CPlayer->mo->InvSel;
GC::WriteBarrier(this, oldarti);
oldartiCount = oldarti != NULL ? oldarti->Amount : 0; oldartiCount = oldarti != NULL ? oldarti->Amount : 0;
ArtiRefresh = screen->GetPageCount (); ArtiRefresh = screen->GetPageCount ();
} }
@ -422,6 +425,8 @@ private:
oldammo2 = ammo2; oldammo2 = ammo2;
oldammocount1 = ammocount1; oldammocount1 = ammocount1;
oldammocount2 = ammocount2; oldammocount2 = ammocount2;
GC::WriteBarrier(this, ammo1);
GC::WriteBarrier(this, ammo2);
AmmoRefresh = screen->GetPageCount (); AmmoRefresh = screen->GetPageCount ();
} }
if (AmmoRefresh) if (AmmoRefresh)
@ -475,7 +480,7 @@ private:
void DrawInventoryBar () void DrawInventoryBar ()
{ {
const AInventory *item; AInventory *item;
int i; int i;
DrawImage (Images[imgINVBAR], 34, 2); DrawImage (Images[imgINVBAR], 34, 2);
@ -517,7 +522,7 @@ private:
void DrawFullScreenStuff () void DrawFullScreenStuff ()
{ {
const AInventory *item; AInventory *item;
FTexture *pic; FTexture *pic;
int i; int i;
@ -713,8 +718,8 @@ private:
static const char patcharti[][10]; static const char patcharti[][10];
static const char ammopic[][10]; static const char ammopic[][10];
AInventory *oldarti; TObjPtr<AInventory> oldarti;
AAmmo *oldammo1, *oldammo2; TObjPtr<AAmmo> oldammo1, oldammo2;
int oldammocount1, oldammocount2; int oldammocount1, oldammocount2;
int oldartiCount; int oldartiCount;
int oldfrags; int oldfrags;
@ -773,7 +778,13 @@ private:
char ArmorRefresh; char ArmorRefresh;
}; };
FBaseStatusBar *CreateHereticStatusBar () IMPLEMENT_POINTY_CLASS(DHereticStatusBar)
DECLARE_POINTER(oldarti)
DECLARE_POINTER(oldammo1)
DECLARE_POINTER(oldammo2)
END_POINTERS
DBaseStatusBar *CreateHereticStatusBar ()
{ {
return new FHereticStatusBar; return new DHereticStatusBar;
} }

View file

@ -386,6 +386,7 @@ bool AHolySpirit::SpecialBlastHandling (AActor *source, fixed_t strength)
{ {
tracer = target; tracer = target;
target = source; target = source;
GC::WriteBarrier(this, source);
} }
return true; return true;
} }

View file

@ -706,7 +706,7 @@ void A_SorcBallOrbit(AActor *ball)
int x,y; int x,y;
angle_t angle, baseangle; angle_t angle, baseangle;
int mode = ball->target->args[3]; int mode = ball->target->args[3];
AHeresiarch *parent = static_cast<AHeresiarch *> (ball->target); AHeresiarch *parent = barrier_cast<AHeresiarch *>(ball->target);
int dist = parent->radius - (ball->radius<<1); int dist = parent->radius - (ball->radius<<1);
angle_t prevangle = ball->special1; angle_t prevangle = ball->special1;

View file

@ -66,7 +66,7 @@ protected:
virtual bool MatchPlayerClass (AActor *toucher); virtual bool MatchPlayerClass (AActor *toucher);
const PClass *FourthWeaponClass; const PClass *FourthWeaponClass;
int PieceValue; int PieceValue;
AInventory *TempFourthWeapon; TObjPtr<AInventory> TempFourthWeapon;
bool PrivateShouldStay (); bool PrivateShouldStay ();
}; };

View file

@ -103,7 +103,7 @@ public:
void Activate (AActor *activator); void Activate (AActor *activator);
void Deactivate (AActor *activator); void Deactivate (AActor *activator);
ADirtClump *DirtClump; TObjPtr<ADirtClump> DirtClump;
}; };
IMPLEMENT_POINTY_CLASS (AThrustFloor) IMPLEMENT_POINTY_CLASS (AThrustFloor)

View file

@ -249,7 +249,7 @@ int ATelOtherFX1::DoSpecialDamage (AActor *target, int damage)
{ {
target->RemoveFromHash (); target->RemoveFromHash ();
LineSpecials[target->special] (NULL, level.flags & LEVEL_ACTOWNSPECIAL LineSpecials[target->special] (NULL, level.flags & LEVEL_ACTOWNSPECIAL
? target : this->target, false, target->args[0], target->args[1], ? target : (AActor *)(this->target), false, target->args[0], target->args[1],
target->args[2], target->args[3], target->args[4]); target->args[2], target->args[3], target->args[4]);
target->special = 0; target->special = 0;
} }

View file

@ -119,10 +119,12 @@ void FManaBar::MakeTexture ()
memset (Pixels + 25+24+24, color0, run); memset (Pixels + 25+24+24, color0, run);
} }
class FHexenStatusBar : public FBaseStatusBar class DHexenStatusBar : public DBaseStatusBar
{ {
DECLARE_CLASS(DHexenStatusBar, DBaseStatusBar)
HAS_OBJECT_POINTERS
public: public:
FHexenStatusBar () : FBaseStatusBar (38) DHexenStatusBar () : DBaseStatusBar (38)
{ {
static const char *hexenLumpNames[NUM_HEXENSB_IMAGES] = static const char *hexenLumpNames[NUM_HEXENSB_IMAGES] =
{ {
@ -166,7 +168,7 @@ public:
"INRED5", "INRED6", "INRED7", "INRED8", "INRED9" "INRED5", "INRED6", "INRED7", "INRED8", "INRED9"
}; };
FBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES + 10); DBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES + 10);
Images.Init (hexenLumpNames, NUM_HEXENSB_IMAGES); Images.Init (hexenLumpNames, NUM_HEXENSB_IMAGES);
ClassImages[0].Init (classLumpNames[0], NUM_HEXENCLASSSB_IMAGES); ClassImages[0].Init (classLumpNames[0], NUM_HEXENCLASSSB_IMAGES);
ClassImages[1].Init (classLumpNames[1], NUM_HEXENCLASSSB_IMAGES); ClassImages[1].Init (classLumpNames[1], NUM_HEXENCLASSSB_IMAGES);
@ -201,7 +203,7 @@ public:
AmmoRefresh = 0; AmmoRefresh = 0;
} }
~FHexenStatusBar () ~DHexenStatusBar ()
{ {
} }
@ -209,7 +211,7 @@ public:
{ {
int curHealth; int curHealth;
FBaseStatusBar::Tick (); DBaseStatusBar::Tick ();
if (CPlayer->mo == NULL) if (CPlayer->mo == NULL)
{ {
curHealth = 0; curHealth = 0;
@ -242,7 +244,7 @@ public:
void Draw (EHudState state) void Draw (EHudState state)
{ {
FBaseStatusBar::Draw (state); DBaseStatusBar::Draw (state);
if (state == HUD_Fullscreen) if (state == HUD_Fullscreen)
{ {
@ -309,7 +311,7 @@ public:
void AttachToPlayer (player_s *player) void AttachToPlayer (player_s *player)
{ {
FBaseStatusBar::AttachToPlayer (player); DBaseStatusBar::AttachToPlayer (player);
if (player->mo != NULL) if (player->mo != NULL)
{ {
if (player->mo->IsKindOf (PClass::FindClass(NAME_MagePlayer))) if (player->mo->IsKindOf (PClass::FindClass(NAME_MagePlayer)))
@ -395,6 +397,7 @@ private:
|| (oldarti != NULL && oldartiCount != oldarti->Amount)) || (oldarti != NULL && oldartiCount != oldarti->Amount))
{ {
oldarti = CPlayer->mo->InvSel; oldarti = CPlayer->mo->InvSel;
GC::WriteBarrier(this, oldarti);
oldartiCount = oldarti != NULL ? oldarti->Amount : 0; oldartiCount = oldarti != NULL ? oldarti->Amount : 0;
ArtiRefresh = screen->GetPageCount (); ArtiRefresh = screen->GetPageCount ();
} }
@ -521,12 +524,14 @@ private:
AmmoRefresh = screen->GetPageCount (); AmmoRefresh = screen->GetPageCount ();
oldammo1 = ammo1; oldammo1 = ammo1;
oldammocount1 = ammocount1; oldammocount1 = ammocount1;
GC::WriteBarrier(this, ammo1);
} }
if (ammo2 != oldammo2 || ammocount2 != oldammocount2) if (ammo2 != oldammo2 || ammocount2 != oldammocount2)
{ {
AmmoRefresh = screen->GetPageCount (); AmmoRefresh = screen->GetPageCount ();
oldammo2 = ammo2; oldammo2 = ammo2;
oldammocount2 = ammocount2; oldammocount2 = ammocount2;
GC::WriteBarrier(this, ammo2);
} }
if (AmmoRefresh) if (AmmoRefresh)
@ -705,7 +710,7 @@ private:
void DrawInventoryBar () void DrawInventoryBar ()
{ {
const AInventory *item; AInventory *item;
int i; int i;
DrawImage (Images[imgINVBAR], 38, 0); DrawImage (Images[imgINVBAR], 38, 0);
@ -772,6 +777,7 @@ private:
if (keys[i] != oldkeys[i]) if (keys[i] != oldkeys[i])
{ {
oldkeys[i] = keys[i]; oldkeys[i] = keys[i];
GC::WriteBarrier(this, keys[i]);
different = true; different = true;
} }
} }
@ -880,7 +886,7 @@ private:
void DrawFullScreenStuff () void DrawFullScreenStuff ()
{ {
const AInventory *item; AInventory *item;
int i; int i;
// Health // Health
@ -1051,9 +1057,9 @@ private:
static const char patcharti[][10]; static const char patcharti[][10];
static const char ammopic[][10]; static const char ammopic[][10];
AInventory *oldarti; TObjPtr<AInventory> oldarti;
AAmmo *oldammo1, *oldammo2; TObjPtr<AAmmo> oldammo1, oldammo2;
AKey *oldkeys[5]; TObjPtr<AKey> oldkeys[5];
int oldammocount1, oldammocount2; int oldammocount1, oldammocount2;
int oldartiCount; int oldartiCount;
int oldfrags; int oldfrags;
@ -1155,7 +1161,18 @@ private:
FManaBar ManaVial2Pic; FManaBar ManaVial2Pic;
}; };
FBaseStatusBar *CreateHexenStatusBar () IMPLEMENT_POINTY_CLASS(DHexenStatusBar)
DECLARE_POINTER(oldarti)
DECLARE_POINTER(oldammo1)
DECLARE_POINTER(oldammo2)
DECLARE_POINTER(oldkeys[0])
DECLARE_POINTER(oldkeys[1])
DECLARE_POINTER(oldkeys[2])
DECLARE_POINTER(oldkeys[3])
DECLARE_POINTER(oldkeys[4])
END_POINTERS
DBaseStatusBar *CreateHexenStatusBar ()
{ {
return new FHexenStatusBar; return new DHexenStatusBar;
} }

View file

@ -1540,12 +1540,12 @@ void G_InitNew (const char *mapname, bool bTitleLevel)
if (StatusBar != NULL) if (StatusBar != NULL)
{ {
delete StatusBar; StatusBar->Destroy();
StatusBar = NULL; StatusBar = NULL;
} }
if (bTitleLevel) if (bTitleLevel)
{ {
StatusBar = new FBaseStatusBar (0); StatusBar = new DBaseStatusBar (0);
} }
else if (SBarInfoScript != NULL) else if (SBarInfoScript != NULL)
{ {
@ -1592,9 +1592,10 @@ void G_InitNew (const char *mapname, bool bTitleLevel)
} }
else else
{ {
StatusBar = new FBaseStatusBar (0); StatusBar = new DBaseStatusBar (0);
} }
} }
GC::WriteBarrier(StatusBar);
StatusBar->AttachToPlayer (&players[consoleplayer]); StatusBar->AttachToPlayer (&players[consoleplayer]);
StatusBar->NewGame (); StatusBar->NewGame ();
setsizeneeded = true; setsizeneeded = true;

View file

@ -332,7 +332,7 @@ public:
DCorpsePointer (AActor *ptr); DCorpsePointer (AActor *ptr);
void Destroy (); void Destroy ();
void Serialize (FArchive &arc); void Serialize (FArchive &arc);
AActor *Corpse; TObjPtr<AActor> Corpse;
DWORD Count; // Only the first corpse pointer's count is valid. DWORD Count; // Only the first corpse pointer's count is valid.
private: private:
DCorpsePointer () {} DCorpsePointer () {}

View file

@ -52,10 +52,10 @@ static int ImpactCount;
CVAR (Bool, cl_spreaddecals, true, CVAR_ARCHIVE) CVAR (Bool, cl_spreaddecals, true, CVAR_ARCHIVE)
// They also overload floorclip to be the fractional distance from the IMPLEMENT_POINTY_CLASS (DBaseDecal)
// left edge of the side. This distance is stored as a 2.30 fixed pt number. DECLARE_POINTER(WallNext)
END_POINTERS
IMPLEMENT_CLASS (DBaseDecal)
IMPLEMENT_CLASS (DImpactDecal) IMPLEMENT_CLASS (DImpactDecal)
DBaseDecal::DBaseDecal () DBaseDecal::DBaseDecal ()

View file

@ -137,7 +137,7 @@ bool P_UndoPlayerMorph (player_t *player, bool force)
{ {
return false; return false;
} }
mo = static_cast<APlayerPawn *>(pmo->tracer); mo = barrier_cast<APlayerPawn *>(pmo->tracer);
mo->SetOrigin (pmo->x, pmo->y, pmo->z); mo->SetOrigin (pmo->x, pmo->y, pmo->z);
mo->flags |= MF_SOLID; mo->flags |= MF_SOLID;
pmo->flags &= ~MF_SOLID; pmo->flags &= ~MF_SOLID;

View file

@ -61,7 +61,7 @@ public:
void Serialize (FArchive &arc); void Serialize (FArchive &arc);
AInterpolationPoint *Next; TObjPtr<AInterpolationPoint> Next;
}; };
IMPLEMENT_POINTY_CLASS (AInterpolationPoint) IMPLEMENT_POINTY_CLASS (AInterpolationPoint)
@ -181,7 +181,7 @@ protected:
void Serialize (FArchive &arc); void Serialize (FArchive &arc);
bool bActive, bJustStepped; bool bActive, bJustStepped;
AInterpolationPoint *PrevNode, *CurrNode; TObjPtr<AInterpolationPoint> PrevNode, CurrNode;
float Time; // Runs from 0.0 to 1.0 between CurrNode and CurrNode->Next float Time; // Runs from 0.0 to 1.0 between CurrNode and CurrNode->Next
int HoldTime; int HoldTime;
}; };
@ -289,8 +289,8 @@ void APathFollower::Activate (AActor *activator)
{ {
if (!bActive) if (!bActive)
{ {
CurrNode = static_cast<AInterpolationPoint *>(target); CurrNode = barrier_cast<AInterpolationPoint *>(target);
PrevNode = static_cast<AInterpolationPoint *>(lastenemy); PrevNode = barrier_cast<AInterpolationPoint *>(lastenemy);
if (CurrNode != NULL) if (CurrNode != NULL)
{ {
@ -638,7 +638,7 @@ public:
protected: protected:
bool Interpolate (); bool Interpolate ();
AActor *Activator; TObjPtr<AActor> Activator;
}; };
IMPLEMENT_POINTY_CLASS (AMovingCamera) IMPLEMENT_POINTY_CLASS (AMovingCamera)

View file

@ -101,7 +101,7 @@ bool AAmmo::HandlePickup (AInventory *item)
(Owner->player->ReadyWeapon == NULL || (Owner->player->ReadyWeapon == NULL ||
(Owner->player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON))) (Owner->player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON)))
{ {
AWeapon *best = static_cast<APlayerPawn *>(Owner)->BestWeapon (GetClass()); AWeapon *best = barrier_cast<APlayerPawn *>(Owner)->BestWeapon (GetClass());
if (best != NULL && (Owner->player->ReadyWeapon == NULL || if (best != NULL && (Owner->player->ReadyWeapon == NULL ||
best->SelectionOrder < Owner->player->ReadyWeapon->SelectionOrder)) best->SelectionOrder < Owner->player->ReadyWeapon->SelectionOrder))
{ {
@ -1052,7 +1052,7 @@ PalEntry AInventory::GetBlend ()
// //
//=========================================================================== //===========================================================================
AInventory *AInventory::PrevItem () const AInventory *AInventory::PrevItem ()
{ {
AInventory *item = Owner->Inventory; AInventory *item = Owner->Inventory;
@ -1071,7 +1071,7 @@ AInventory *AInventory::PrevItem () const
// //
//=========================================================================== //===========================================================================
AInventory *AInventory::PrevInv () const AInventory *AInventory::PrevInv ()
{ {
AInventory *lastgood = NULL; AInventory *lastgood = NULL;
AInventory *item = Owner->Inventory; AInventory *item = Owner->Inventory;
@ -1094,7 +1094,7 @@ AInventory *AInventory::PrevInv () const
// //
//=========================================================================== //===========================================================================
AInventory *AInventory::NextInv () const AInventory *AInventory::NextInv ()
{ {
AInventory *item = Inventory; AInventory *item = Inventory;

View file

@ -124,11 +124,11 @@ public:
virtual const char *PickupMessage (); virtual const char *PickupMessage ();
virtual void PlayPickupSound (AActor *toucher); virtual void PlayPickupSound (AActor *toucher);
AInventory *PrevItem () const; // Returns the item preceding this one in the list. AInventory *PrevItem(); // Returns the item preceding this one in the list.
AInventory *PrevInv () const; // Returns the previous item with IF_INVBAR set. AInventory *PrevInv(); // Returns the previous item with IF_INVBAR set.
AInventory *NextInv () const; // Returns the next item with IF_INVBAR set. AInventory *NextInv(); // Returns the next item with IF_INVBAR set.
AActor *Owner; // Who owns this item? NULL if it's still a pickup. TObjPtr<AActor> Owner; // Who owns this item? NULL if it's still a pickup.
int Amount; // Amount of item this instance has int Amount; // Amount of item this instance has
int MaxAmount; // Max amount of item this instance can have int MaxAmount; // Max amount of item this instance can have
int RespawnTics; // Tics from pickup time to respawn time int RespawnTics; // Tics from pickup time to respawn time
@ -217,8 +217,8 @@ public:
fixed_t MoveCombatDist; // Used by bots, but do they *really* need it? fixed_t MoveCombatDist; // Used by bots, but do they *really* need it?
// In-inventory instance variables // In-inventory instance variables
AAmmo *Ammo1, *Ammo2; TObjPtr<AAmmo> Ammo1, Ammo2;
AWeapon *SisterWeapon; TObjPtr<AWeapon> SisterWeapon;
bool bAltFire; // Set when this weapon's alternate fire is used. bool bAltFire; // Set when this weapon's alternate fire is used.

View file

@ -88,7 +88,7 @@ void ASectorAction::Deactivate (AActor *source)
bool ASectorAction::TriggerAction (AActor *triggerer, int activationType) bool ASectorAction::TriggerAction (AActor *triggerer, int activationType)
{ {
if (tracer != NULL) if (tracer != NULL)
return static_cast<ASectorAction *>(tracer)->TriggerAction (triggerer, activationType); return barrier_cast<ASectorAction *>(tracer)->TriggerAction (triggerer, activationType);
else else
return false; return false;
} }

View file

@ -24,6 +24,7 @@ struct F3DFloor;
class DBaseDecal : public DThinker class DBaseDecal : public DThinker
{ {
DECLARE_CLASS (DBaseDecal, DThinker) DECLARE_CLASS (DBaseDecal, DThinker)
HAS_OBJECT_POINTERS
public: public:
DBaseDecal (); DBaseDecal ();
DBaseDecal (fixed_t z); DBaseDecal (fixed_t z);
@ -149,7 +150,7 @@ protected:
float Blends[2][4]; float Blends[2][4];
int TotalTics; int TotalTics;
int StartTic; int StartTic;
AActor *ForWho; TObjPtr<AActor> ForWho;
void SetBlend (float time); void SetBlend (float time);
DFlashFader (); DFlashFader ();
@ -165,7 +166,7 @@ public:
void Serialize (FArchive &arc); void Serialize (FArchive &arc);
void Tick (); void Tick ();
AActor *m_Spot; TObjPtr<AActor> m_Spot;
fixed_t m_TremorRadius, m_DamageRadius; fixed_t m_TremorRadius, m_DamageRadius;
int m_Intensity; int m_Intensity;
int m_Countdown; int m_Countdown;
@ -197,7 +198,7 @@ public:
void Die (AActor *source, AActor *inflictor); void Die (AActor *source, AActor *inflictor);
void Destroy (); void Destroy ();
AActor *UnmorphedMe; TObjPtr<AActor> UnmorphedMe;
int UnmorphTime; int UnmorphTime;
DWORD FlagsSave; DWORD FlagsSave;
}; };

View file

@ -27,7 +27,11 @@ IMPLEMENT_STATELESS_ACTOR (AWeaponHolder, Any, -1, 0)
PROP_Inventory_FlagsSet (IF_UNDROPPABLE) PROP_Inventory_FlagsSet (IF_UNDROPPABLE)
END_DEFAULTS END_DEFAULTS
IMPLEMENT_STATELESS_ACTOR (AWeaponPiece, Any, -1, 0)
IMPLEMENT_POINTY_CLASS (AWeaponPiece)
DECLARE_POINTER (FullWeapon)
END_POINTERS
BEGIN_STATELESS_DEFAULTS (AWeaponPiece, Any, -1, 0)
END_DEFAULTS END_DEFAULTS

View file

@ -2,6 +2,7 @@
class AWeaponPiece : public AInventory class AWeaponPiece : public AInventory
{ {
DECLARE_CLASS (AWeaponPiece, AInventory) DECLARE_CLASS (AWeaponPiece, AInventory)
HAS_OBJECT_POINTERS
protected: protected:
bool PrivateShouldStay (); bool PrivateShouldStay ();
public: public:
@ -13,5 +14,5 @@ public:
int PieceValue; int PieceValue;
const PClass * WeaponClass; const PClass * WeaponClass;
AWeapon * FullWeapon; TObjPtr<AWeapon> FullWeapon;
}; };

View file

@ -370,7 +370,7 @@ bool AWeapon::CheckAmmo (int fireMode, bool autoSwitch, bool requireAmmo)
bool gotSome = CheckAmmo (PrimaryFire, false) || CheckAmmo (AltFire, false); bool gotSome = CheckAmmo (PrimaryFire, false) || CheckAmmo (AltFire, false);
if (!gotSome && autoSwitch) if (!gotSome && autoSwitch)
{ {
static_cast<APlayerPawn *> (Owner)->PickNewWeapon (NULL); barrier_cast<APlayerPawn *>(Owner)->PickNewWeapon (NULL);
} }
return gotSome; return gotSome;
} }
@ -402,7 +402,7 @@ bool AWeapon::CheckAmmo (int fireMode, bool autoSwitch, bool requireAmmo)
// out of ammo, pick a weapon to change to // out of ammo, pick a weapon to change to
if (autoSwitch) if (autoSwitch)
{ {
static_cast<APlayerPawn *> (Owner)->PickNewWeapon (NULL); barrier_cast<APlayerPawn *>(Owner)->PickNewWeapon (NULL);
} }
return false; return false;
} }

View file

@ -41,7 +41,10 @@
EXTERN_CVAR (Int, con_scaletext) EXTERN_CVAR (Int, con_scaletext)
IMPLEMENT_CLASS (DHUDMessage) IMPLEMENT_POINTY_CLASS (DHUDMessage)
DECLARE_POINTER(Next)
END_POINTERS
IMPLEMENT_CLASS (DHUDMessageFadeOut) IMPLEMENT_CLASS (DHUDMessageFadeOut)
IMPLEMENT_CLASS (DHUDMessageFadeInOut) IMPLEMENT_CLASS (DHUDMessageFadeInOut)
IMPLEMENT_CLASS (DHUDMessageTypeOnFadeOut) IMPLEMENT_CLASS (DHUDMessageTypeOnFadeOut)

View file

@ -55,6 +55,7 @@ class AWeapon;
class DHUDMessage : public DObject class DHUDMessage : public DObject
{ {
DECLARE_CLASS (DHUDMessage, DObject) DECLARE_CLASS (DHUDMessage, DObject)
HAS_OBJECT_POINTERS
public: public:
DHUDMessage (const char *text, float x, float y, int hudwidth, int hudheight, DHUDMessage (const char *text, float x, float y, int hudwidth, int hudheight,
EColorRange textColor, float holdTime); EColorRange textColor, float holdTime);
@ -84,11 +85,11 @@ protected:
DHUDMessage () : SourceText(NULL) {} DHUDMessage () : SourceText(NULL) {}
private: private:
DHUDMessage *Next; TObjPtr<DHUDMessage> Next;
DWORD SBarID; DWORD SBarID;
char *SourceText; char *SourceText;
friend class FBaseStatusBar; friend class DBaseStatusBar;
}; };
class DHUDMessageFadeOut : public DHUDMessage class DHUDMessageFadeOut : public DHUDMessage
@ -149,8 +150,10 @@ protected:
class FTexture; class FTexture;
class AAmmo; class AAmmo;
class FBaseStatusBar class DBaseStatusBar : public DObject
{ {
DECLARE_CLASS (DBaseStatusBar, DObject)
HAS_OBJECT_POINTERS
public: public:
// Popup screens for Strife's status bar // Popup screens for Strife's status bar
enum enum
@ -162,8 +165,8 @@ public:
POP_Status POP_Status
}; };
FBaseStatusBar (int reltop); DBaseStatusBar (int reltop);
virtual ~FBaseStatusBar (); void Destroy ();
void SetScaled (bool scale); void SetScaled (bool scale);
@ -246,20 +249,21 @@ public:
player_s *CPlayer; player_s *CPlayer;
private: private:
DBaseStatusBar() {}
bool RepositionCoords (int &x, int &y, int xo, int yo, const int w, const int h) const; bool RepositionCoords (int &x, int &y, int xo, int yo, const int w, const int h) const;
void DrawMessages (int bottom) const; void DrawMessages (int bottom);
void DrawConsistancy () const; void DrawConsistancy () const;
static BYTE DamageToAlpha[114]; static BYTE DamageToAlpha[114];
DHUDMessage *Messages; TObjPtr<DHUDMessage> Messages;
bool ShowLog; bool ShowLog;
}; };
extern FBaseStatusBar *StatusBar; extern DBaseStatusBar *StatusBar;
FBaseStatusBar *CreateDoomStatusBar (); DBaseStatusBar *CreateDoomStatusBar ();
FBaseStatusBar *CreateHereticStatusBar (); DBaseStatusBar *CreateHereticStatusBar ();
FBaseStatusBar *CreateHexenStatusBar (); DBaseStatusBar *CreateHexenStatusBar ();
FBaseStatusBar *CreateStrifeStatusBar (); DBaseStatusBar *CreateStrifeStatusBar ();
FBaseStatusBar *CreateCustomStatusBar (); DBaseStatusBar *CreateCustomStatusBar ();

View file

@ -1238,10 +1238,11 @@ SBarInfoBlock::SBarInfoBlock()
} }
//SBarInfo Display //SBarInfo Display
class FSBarInfo : public FBaseStatusBar class DSBarInfo : public DBaseStatusBar
{ {
DECLARE_CLASS(DSBarInfo, DBaseStatusBar)
public: public:
FSBarInfo () : FBaseStatusBar (SBarInfoScript->height), DSBarInfo () : DBaseStatusBar (SBarInfoScript->height),
shader_horz_normal(false, false), shader_horz_normal(false, false),
shader_horz_reverse(false, true), shader_horz_reverse(false, true),
shader_vert_normal(true, false), shader_vert_normal(true, false),
@ -1279,7 +1280,7 @@ public:
artiflash = 4; artiflash = 4;
} }
~FSBarInfo () ~DSBarInfo ()
{ {
Images.Uninit(); Images.Uninit();
Faces.Uninit(); Faces.Uninit();
@ -1287,7 +1288,7 @@ public:
void Draw (EHudState state) void Draw (EHudState state)
{ {
FBaseStatusBar::Draw(state); DBaseStatusBar::Draw(state);
int hud = 2; int hud = 2;
if(state == HUD_StatusBar) if(state == HUD_StatusBar)
{ {
@ -1341,7 +1342,7 @@ public:
void AttachToPlayer (player_t *player) void AttachToPlayer (player_t *player)
{ {
player_t *oldplayer = CPlayer; player_t *oldplayer = CPlayer;
FBaseStatusBar::AttachToPlayer(player); DBaseStatusBar::AttachToPlayer(player);
if (oldplayer != CPlayer) if (oldplayer != CPlayer)
{ {
SetFace(&skins[CPlayer->userinfo.skin], "STF"); SetFace(&skins[CPlayer->userinfo.skin], "STF");
@ -1350,7 +1351,7 @@ public:
void Tick () void Tick ()
{ {
FBaseStatusBar::Tick(); DBaseStatusBar::Tick();
if(level.time & 1) if(level.time & 1)
chainWiggle = pr_chainwiggle() & 1; chainWiggle = pr_chainwiggle() & 1;
getNewFace(M_Random()); getNewFace(M_Random());
@ -2310,7 +2311,7 @@ private:
void DrawInventoryBar(int type, int num, int x, int y, bool alwaysshow, void DrawInventoryBar(int type, int num, int x, int y, bool alwaysshow,
int counterx, int countery, EColorRange translation, bool drawArtiboxes, bool noArrows, bool alwaysshowcounter) int counterx, int countery, EColorRange translation, bool drawArtiboxes, bool noArrows, bool alwaysshowcounter)
{ //yes, there is some Copy & Paste here too { //yes, there is some Copy & Paste here too
const AInventory *item; AInventory *item;
int i; int i;
// If the player has no artifacts, don't draw the bar // If the player has no artifacts, don't draw the bar
@ -2408,7 +2409,9 @@ private:
FBarShader shader_vert_reverse; FBarShader shader_vert_reverse;
}; };
FBaseStatusBar *CreateCustomStatusBar () IMPLEMENT_CLASS(DSBarInfo);
DBaseStatusBar *CreateCustomStatusBar ()
{ {
return new FSBarInfo; return new DSBarInfo;
} }

View file

@ -53,6 +53,10 @@
#define XHAIRPICKUPSIZE (FRACUNIT*2+XHAIRSHRINKSIZE) #define XHAIRPICKUPSIZE (FRACUNIT*2+XHAIRSHRINKSIZE)
#define POWERUPICONSIZE 32 #define POWERUPICONSIZE 32
IMPLEMENT_POINTY_CLASS(DBaseStatusBar)
DECLARE_POINTER(Messages)
END_POINTERS
EXTERN_CVAR (Bool, am_showmonsters) EXTERN_CVAR (Bool, am_showmonsters)
EXTERN_CVAR (Bool, am_showsecrets) EXTERN_CVAR (Bool, am_showsecrets)
EXTERN_CVAR (Bool, am_showitems) EXTERN_CVAR (Bool, am_showitems)
@ -62,7 +66,7 @@ EXTERN_CVAR (Bool, noisedebug)
EXTERN_CVAR (Bool, hud_scale) EXTERN_CVAR (Bool, hud_scale)
EXTERN_CVAR (Int, con_scaletext) EXTERN_CVAR (Int, con_scaletext)
FBaseStatusBar *StatusBar; DBaseStatusBar *StatusBar;
extern int setblocks; extern int setblocks;
@ -126,7 +130,7 @@ CVAR (Bool, idmypos, false, 0);
// [RH] Amount of red flash for up to 114 damage points. Calculated by hand // [RH] Amount of red flash for up to 114 damage points. Calculated by hand
// using a logarithmic scale and my trusty HP48G. // using a logarithmic scale and my trusty HP48G.
BYTE FBaseStatusBar::DamageToAlpha[114] = BYTE DBaseStatusBar::DamageToAlpha[114] =
{ {
0, 8, 16, 23, 30, 36, 42, 47, 53, 58, 62, 67, 71, 75, 79, 0, 8, 16, 23, 30, 36, 42, 47, 53, 58, 62, 67, 71, 75, 79,
83, 87, 90, 94, 97, 100, 103, 107, 109, 112, 115, 118, 120, 123, 125, 83, 87, 90, 94, 97, 100, 103, 107, 109, 112, 115, 118, 120, 123, 125,
@ -144,7 +148,7 @@ BYTE FBaseStatusBar::DamageToAlpha[114] =
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
FBaseStatusBar::FBaseStatusBar (int reltop) DBaseStatusBar::DBaseStatusBar (int reltop)
{ {
Centering = false; Centering = false;
FixedOrigin = false; FixedOrigin = false;
@ -160,11 +164,11 @@ FBaseStatusBar::FBaseStatusBar (int reltop)
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// Destructor // PROP Destroy
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
FBaseStatusBar::~FBaseStatusBar () void DBaseStatusBar::Destroy ()
{ {
DHUDMessage *msg; DHUDMessage *msg;
@ -172,9 +176,10 @@ FBaseStatusBar::~FBaseStatusBar ()
while (msg) while (msg)
{ {
DHUDMessage *next = msg->Next; DHUDMessage *next = msg->Next;
delete msg; msg->Destroy();
msg = next; msg = next;
} }
Super::Destroy();
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -183,7 +188,7 @@ FBaseStatusBar::~FBaseStatusBar ()
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::SetScaled (bool scale) void DBaseStatusBar::SetScaled (bool scale)
{ {
Scaled = RelTop != 0 && (SCREENWIDTH != 320 && scale); Scaled = RelTop != 0 && (SCREENWIDTH != 320 && scale);
if (!Scaled) if (!Scaled)
@ -225,7 +230,7 @@ void FBaseStatusBar::SetScaled (bool scale)
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::AttachToPlayer (player_s *player) void DBaseStatusBar::AttachToPlayer (player_s *player)
{ {
CPlayer = player; CPlayer = player;
SB_state = screen->GetPageCount (); SB_state = screen->GetPageCount ();
@ -237,7 +242,7 @@ void FBaseStatusBar::AttachToPlayer (player_s *player)
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
int FBaseStatusBar::GetPlayer () int DBaseStatusBar::GetPlayer ()
{ {
return int(CPlayer - players); return int(CPlayer - players);
} }
@ -248,7 +253,7 @@ int FBaseStatusBar::GetPlayer ()
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::MultiplayerChanged () void DBaseStatusBar::MultiplayerChanged ()
{ {
SB_state = screen->GetPageCount (); SB_state = screen->GetPageCount ();
} }
@ -259,7 +264,7 @@ void FBaseStatusBar::MultiplayerChanged ()
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::Tick () void DBaseStatusBar::Tick ()
{ {
DHUDMessage *msg = Messages; DHUDMessage *msg = Messages;
DHUDMessage **prev = &Messages; DHUDMessage **prev = &Messages;
@ -271,7 +276,7 @@ void FBaseStatusBar::Tick ()
if (msg->Tick ()) if (msg->Tick ())
{ {
*prev = next; *prev = next;
delete msg; msg->Destroy();
} }
else else
{ {
@ -297,15 +302,16 @@ void FBaseStatusBar::Tick ()
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::AttachMessage (DHUDMessage *msg, DWORD id) void DBaseStatusBar::AttachMessage (DHUDMessage *msg, DWORD id)
{ {
DHUDMessage *old = NULL; DHUDMessage *old = NULL;
DHUDMessage **prev; DHUDMessage **prev;
DObject *container = this;
old = (id == 0 || id == 0xFFFFFFFF) ? NULL : DetachMessage (id); old = (id == 0 || id == 0xFFFFFFFF) ? NULL : DetachMessage (id);
if (old != NULL) if (old != NULL)
{ {
delete old; old->Destroy();
} }
prev = &Messages; prev = &Messages;
@ -315,12 +321,14 @@ void FBaseStatusBar::AttachMessage (DHUDMessage *msg, DWORD id)
// it gets drawn back to front.) // it gets drawn back to front.)
while (*prev != NULL && (*prev)->SBarID > id) while (*prev != NULL && (*prev)->SBarID > id)
{ {
container = *prev;
prev = &(*prev)->Next; prev = &(*prev)->Next;
} }
msg->Next = *prev; msg->Next = *prev;
msg->SBarID = id; msg->SBarID = id;
*prev = msg; *prev = msg;
GC::WriteBarrier(container, msg);
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -329,7 +337,7 @@ void FBaseStatusBar::AttachMessage (DHUDMessage *msg, DWORD id)
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
DHUDMessage *FBaseStatusBar::DetachMessage (DHUDMessage *msg) DHUDMessage *DBaseStatusBar::DetachMessage (DHUDMessage *msg)
{ {
DHUDMessage *probe = Messages; DHUDMessage *probe = Messages;
DHUDMessage **prev = &Messages; DHUDMessage **prev = &Messages;
@ -349,7 +357,7 @@ DHUDMessage *FBaseStatusBar::DetachMessage (DHUDMessage *msg)
return probe; return probe;
} }
DHUDMessage *FBaseStatusBar::DetachMessage (DWORD id) DHUDMessage *DBaseStatusBar::DetachMessage (DWORD id)
{ {
DHUDMessage *probe = Messages; DHUDMessage *probe = Messages;
DHUDMessage **prev = &Messages; DHUDMessage **prev = &Messages;
@ -375,7 +383,7 @@ DHUDMessage *FBaseStatusBar::DetachMessage (DWORD id)
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DetachAllMessages () void DBaseStatusBar::DetachAllMessages ()
{ {
DHUDMessage *probe = Messages; DHUDMessage *probe = Messages;
@ -383,7 +391,7 @@ void FBaseStatusBar::DetachAllMessages ()
while (probe != NULL) while (probe != NULL)
{ {
DHUDMessage *next = probe->Next; DHUDMessage *next = probe->Next;
delete probe; probe->Destroy();
probe = next; probe = next;
} }
} }
@ -394,7 +402,7 @@ void FBaseStatusBar::DetachAllMessages ()
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
bool FBaseStatusBar::CheckMessage (DHUDMessage *msg) bool DBaseStatusBar::CheckMessage (DHUDMessage *msg)
{ {
DHUDMessage *probe = Messages; DHUDMessage *probe = Messages;
while (probe && probe != msg) while (probe && probe != msg)
@ -410,7 +418,7 @@ bool FBaseStatusBar::CheckMessage (DHUDMessage *msg)
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::ShowPlayerName () void DBaseStatusBar::ShowPlayerName ()
{ {
EColorRange color; EColorRange color;
@ -427,7 +435,7 @@ void FBaseStatusBar::ShowPlayerName ()
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrawImage (FTexture *img, void DBaseStatusBar::DrawImage (FTexture *img,
int x, int y, FRemapTable *translation) const int x, int y, FRemapTable *translation) const
{ {
if (img != NULL) if (img != NULL)
@ -448,7 +456,7 @@ void FBaseStatusBar::DrawImage (FTexture *img,
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrawDimImage (FTexture *img, void DBaseStatusBar::DrawDimImage (FTexture *img,
int x, int y, bool dimmed) const int x, int y, bool dimmed) const
{ {
if (img != NULL) if (img != NULL)
@ -469,7 +477,7 @@ void FBaseStatusBar::DrawDimImage (FTexture *img,
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrawFadedImage (FTexture *img, void DBaseStatusBar::DrawFadedImage (FTexture *img,
int x, int y, fixed_t shade) const int x, int y, fixed_t shade) const
{ {
if (img != NULL) if (img != NULL)
@ -491,7 +499,7 @@ void FBaseStatusBar::DrawFadedImage (FTexture *img,
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrawPartialImage (FTexture *img, int wx, int ww) const void DBaseStatusBar::DrawPartialImage (FTexture *img, int wx, int ww) const
{ {
if (img != NULL) if (img != NULL)
{ {
@ -511,7 +519,7 @@ void FBaseStatusBar::DrawPartialImage (FTexture *img, int wx, int ww) const
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrINumber (signed int val, int x, int y, int imgBase) const void DBaseStatusBar::DrINumber (signed int val, int x, int y, int imgBase) const
{ {
int oldval; int oldval;
@ -551,7 +559,7 @@ void FBaseStatusBar::DrINumber (signed int val, int x, int y, int imgBase) const
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrBNumber (signed int val, int x, int y, int size) const void DBaseStatusBar::DrBNumber (signed int val, int x, int y, int size) const
{ {
bool neg; bool neg;
int i, w; int i, w;
@ -611,7 +619,7 @@ void FBaseStatusBar::DrBNumber (signed int val, int x, int y, int size) const
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrSmallNumber (int val, int x, int y) const void DBaseStatusBar::DrSmallNumber (int val, int x, int y) const
{ {
int digit = 0; int digit = 0;
@ -642,7 +650,7 @@ void FBaseStatusBar::DrSmallNumber (int val, int x, int y) const
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrINumberOuter (signed int val, int x, int y, bool center, int w) const void DBaseStatusBar::DrINumberOuter (signed int val, int x, int y, bool center, int w) const
{ {
bool negative = false; bool negative = false;
@ -706,7 +714,7 @@ void FBaseStatusBar::DrINumberOuter (signed int val, int x, int y, bool center,
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrBNumberOuter (signed int val, int x, int y, int size) const void DBaseStatusBar::DrBNumberOuter (signed int val, int x, int y, int size) const
{ {
int xpos; int xpos;
int w; int w;
@ -813,7 +821,7 @@ void FBaseStatusBar::DrBNumberOuter (signed int val, int x, int y, int size) con
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrBNumberOuterFont (signed int val, int x, int y, int size) const void DBaseStatusBar::DrBNumberOuterFont (signed int val, int x, int y, int size) const
{ {
int xpos; int xpos;
int w, v; int w, v;
@ -914,7 +922,7 @@ void FBaseStatusBar::DrBNumberOuterFont (signed int val, int x, int y, int size)
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrSmallNumberOuter (int val, int x, int y, bool center) const void DBaseStatusBar::DrSmallNumberOuter (int val, int x, int y, bool center) const
{ {
int digit = 0; int digit = 0;
@ -946,7 +954,7 @@ void FBaseStatusBar::DrSmallNumberOuter (int val, int x, int y, bool center) con
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::RefreshBackground () const void DBaseStatusBar::RefreshBackground () const
{ {
int x, x2, y, ratio; int x, x2, y, ratio;
@ -981,7 +989,7 @@ void FBaseStatusBar::RefreshBackground () const
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrawCrosshair () void DBaseStatusBar::DrawCrosshair ()
{ {
static DWORD prevcolor = 0xffffffff; static DWORD prevcolor = 0xffffffff;
static int palettecolor = 0; static int palettecolor = 0;
@ -1072,7 +1080,7 @@ void FBaseStatusBar::DrawCrosshair ()
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::FlashCrosshair () void DBaseStatusBar::FlashCrosshair ()
{ {
CrosshairSize = XHAIRPICKUPSIZE; CrosshairSize = XHAIRPICKUPSIZE;
} }
@ -1083,7 +1091,7 @@ void FBaseStatusBar::FlashCrosshair ()
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrawMessages (int bottom) const void DBaseStatusBar::DrawMessages (int bottom)
{ {
DHUDMessage *msg = Messages; DHUDMessage *msg = Messages;
while (msg) while (msg)
@ -1100,7 +1108,7 @@ void FBaseStatusBar::DrawMessages (int bottom) const
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::Draw (EHudState state) void DBaseStatusBar::Draw (EHudState state)
{ {
char line[64+10]; char line[64+10];
@ -1254,7 +1262,7 @@ void FBaseStatusBar::Draw (EHudState state)
} }
void FBaseStatusBar::DrawLog () void DBaseStatusBar::DrawLog ()
{ {
int hudwidth, hudheight; int hudwidth, hudheight;
@ -1318,7 +1326,7 @@ void FBaseStatusBar::DrawLog ()
} }
} }
bool FBaseStatusBar::MustDrawLog(EHudState) bool DBaseStatusBar::MustDrawLog(EHudState)
{ {
return true; return true;
} }
@ -1329,7 +1337,7 @@ bool FBaseStatusBar::MustDrawLog(EHudState)
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrawTopStuff (EHudState state) void DBaseStatusBar::DrawTopStuff (EHudState state)
{ {
if (demoplayback && demover != DEMOGAMEVERSION) if (demoplayback && demover != DEMOGAMEVERSION)
{ {
@ -1360,7 +1368,7 @@ void FBaseStatusBar::DrawTopStuff (EHudState state)
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::DrawPowerups () void DBaseStatusBar::DrawPowerups ()
{ {
// Each icon gets a 32x32 block to draw itself in. // Each icon gets a 32x32 block to draw itself in.
int x, y; int x, y;
@ -1388,7 +1396,7 @@ SV_AddBlend
[RH] This is from Q2. [RH] This is from Q2.
============= =============
*/ */
void FBaseStatusBar::AddBlend (float r, float g, float b, float a, float v_blend[4]) void DBaseStatusBar::AddBlend (float r, float g, float b, float a, float v_blend[4])
{ {
float a2, a3; float a2, a3;
@ -1409,7 +1417,7 @@ void FBaseStatusBar::AddBlend (float r, float g, float b, float a, float v_blend
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void FBaseStatusBar::BlendView (float blend[4]) void DBaseStatusBar::BlendView (float blend[4])
{ {
int cnt; int cnt;
@ -1480,7 +1488,7 @@ void FBaseStatusBar::BlendView (float blend[4])
(int)(blend[2] * 255.0f), (int)(blend[3] * 256.0f)); (int)(blend[2] * 255.0f), (int)(blend[3] * 256.0f));
} }
void FBaseStatusBar::DrawConsistancy () const void DBaseStatusBar::DrawConsistancy () const
{ {
static bool firsttime = true; static bool firsttime = true;
int i; int i;
@ -1524,37 +1532,37 @@ void FBaseStatusBar::DrawConsistancy () const
} }
} }
void FBaseStatusBar::FlashItem (const PClass *itemtype) void DBaseStatusBar::FlashItem (const PClass *itemtype)
{ {
} }
void FBaseStatusBar::SetFace (void *) void DBaseStatusBar::SetFace (void *)
{ {
} }
void FBaseStatusBar::NewGame () void DBaseStatusBar::NewGame ()
{ {
} }
void FBaseStatusBar::SetInteger (int pname, int param) void DBaseStatusBar::SetInteger (int pname, int param)
{ {
} }
void FBaseStatusBar::ShowPop (int popnum) void DBaseStatusBar::ShowPop (int popnum)
{ {
ShowLog = (popnum == POP_Log && !ShowLog); ShowLog = (popnum == POP_Log && !ShowLog);
} }
void FBaseStatusBar::ReceivedWeapon (AWeapon *weapon) void DBaseStatusBar::ReceivedWeapon (AWeapon *weapon)
{ {
} }
void FBaseStatusBar::Serialize (FArchive &arc) void DBaseStatusBar::Serialize (FArchive &arc)
{ {
arc << Messages; arc << Messages;
} }
void FBaseStatusBar::ScreenSizeChanged () void DBaseStatusBar::ScreenSizeChanged ()
{ {
st_scale.Callback (); st_scale.Callback ();
SB_state = screen->GetPageCount (); SB_state = screen->GetPageCount ();
@ -1576,7 +1584,7 @@ void FBaseStatusBar::ScreenSizeChanged ()
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
AInventory *FBaseStatusBar::ValidateInvFirst (int numVisible) const AInventory *DBaseStatusBar::ValidateInvFirst (int numVisible) const
{ {
AInventory *item; AInventory *item;
int i; int i;
@ -1660,14 +1668,14 @@ AInventory *FBaseStatusBar::ValidateInvFirst (int numVisible) const
//============================================================================ //============================================================================
// //
// FBaseStatusBar :: GetCurrentAmmo // DBaseStatusBar :: GetCurrentAmmo
// //
// Returns the types and amounts of ammo used by the current weapon. If the // Returns the types and amounts of ammo used by the current weapon. If the
// weapon only uses one type of ammo, it is always returned as ammo1. // weapon only uses one type of ammo, it is always returned as ammo1.
// //
//============================================================================ //============================================================================
void FBaseStatusBar::GetCurrentAmmo (AAmmo *&ammo1, AAmmo *&ammo2, int &ammocount1, int &ammocount2) const void DBaseStatusBar::GetCurrentAmmo (AAmmo *&ammo1, AAmmo *&ammo2, int &ammocount1, int &ammocount2) const
{ {
if (CPlayer->ReadyWeapon != NULL) if (CPlayer->ReadyWeapon != NULL)
{ {

View file

@ -180,10 +180,11 @@ void FHealthBar::FillBar (int min, int max, BYTE light, BYTE dark)
} }
} }
class FStrifeStatusBar : public FBaseStatusBar class DStrifeStatusBar : public DBaseStatusBar
{ {
DECLARE_CLASS(DStrifeStatusBar, DBaseStatusBar)
public: public:
FStrifeStatusBar () : FBaseStatusBar (32) DStrifeStatusBar () : DBaseStatusBar (32)
{ {
static const char *sharedLumpNames[] = static const char *sharedLumpNames[] =
{ {
@ -196,11 +197,11 @@ public:
"INVFONG7", "INVFONG8", "INVFONG9" "INVFONG7", "INVFONG8", "INVFONG9"
}; };
FBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES); DBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES);
DoCommonInit (); DoCommonInit ();
} }
~FStrifeStatusBar () ~DStrifeStatusBar ()
{ {
} }
@ -216,7 +217,7 @@ public:
void Draw (EHudState state) void Draw (EHudState state)
{ {
FBaseStatusBar::Draw (state); DBaseStatusBar::Draw (state);
if (state == HUD_Fullscreen) if (state == HUD_Fullscreen)
{ {
@ -235,7 +236,7 @@ public:
void ShowPop (int popnum) void ShowPop (int popnum)
{ {
FBaseStatusBar::ShowPop(popnum); DBaseStatusBar::ShowPop(popnum);
if (popnum == CurrentPop) if (popnum == CurrentPop)
{ {
if (popnum == POP_Keys) if (popnum == POP_Keys)
@ -310,7 +311,7 @@ private:
void Tick () void Tick ()
{ {
FBaseStatusBar::Tick (); DBaseStatusBar::Tick ();
if (ItemFlash > 0) if (ItemFlash > 0)
{ {
@ -845,7 +846,9 @@ private:
fixed_t ItemFlash; fixed_t ItemFlash;
}; };
FBaseStatusBar *CreateStrifeStatusBar () IMPLEMENT_CLASS(DStrifeStatusBar);
DBaseStatusBar *CreateStrifeStatusBar ()
{ {
return new FStrifeStatusBar; return new DStrifeStatusBar;
} }

View file

@ -295,6 +295,16 @@ void FGameConfigFile::DoGlobalSetup ()
vsync->ResetToDefault (); vsync->ResetToDefault ();
} }
} }
if (last < 206)
{ // spc_amp is now a float, not an int.
FBaseCVar *amp = FindCVar ("spc_amp", NULL);
if (amp != NULL)
{
UCVarValue val = amp->GetGenericRep(CVAR_Float);
val.Float /= 16.f;
amp->SetGenericRep(val, CVAR_Float);
}
}
} }
} }
} }
@ -515,7 +525,7 @@ FString FGameConfigFile::GetConfigPath (bool tryProg)
char *pathval; char *pathval;
FString path; FString path;
pathval = Args.CheckValue ("-config"); pathval = Args->CheckValue ("-config");
if (pathval != NULL) if (pathval != NULL)
return FString(pathval); return FString(pathval);
@ -568,7 +578,7 @@ FString FGameConfigFile::GetConfigPath (bool tryProg)
if (path.IsEmpty()) if (path.IsEmpty())
{ {
if (Args.CheckParm ("-cdrom")) if (Args->CheckParm ("-cdrom"))
return CDROM_DIR "\\zdoom.ini"; return CDROM_DIR "\\zdoom.ini";
path = progdir; path = progdir;
@ -615,7 +625,7 @@ void FGameConfigFile::AddAutoexec (DArgs *list, const char *game)
FString path; FString path;
#ifndef unix #ifndef unix
if (Args.CheckParm ("-cdrom")) if (Args->CheckParm ("-cdrom"))
{ {
path = CDROM_DIR "\\autoexec.cfg"; path = CDROM_DIR "\\autoexec.cfg";
} }

View file

@ -162,7 +162,7 @@ static void PrepareSectorData()
{ {
int i; int i;
int j; int j;
DBoundingBox bbox; FBoundingBox bbox;
size_t /*ii,*/ jj; size_t /*ii,*/ jj;
TArray<subsector_t *> undetermined; TArray<subsector_t *> undetermined;
subsector_t * ss; subsector_t * ss;

View file

@ -1022,7 +1022,7 @@ void gl_SetActorLights(AActor *actor)
(LightAssociations[i]->Frame()==frame || LightAssociations[i]->Frame()==-1)) (LightAssociations[i]->Frame()==frame || LightAssociations[i]->Frame()==-1))
{ {
// I'm skipping the single rotations because that really doesn't make sense! // I'm skipping the single rotations because that really doesn't make sense!
if (count < actor->dynamiclights.Size()) light = (ADynamicLight*)actor->dynamiclights[count]; if (count < actor->dynamiclights.Size()) light = barrier_cast<ADynamicLight*>(actor->dynamiclights[count]);
else else
{ {
light = Spawn<ADynamicLight>(actor->x, actor->y, actor->z, NO_REPLACE); light = Spawn<ADynamicLight>(actor->x, actor->y, actor->z, NO_REPLACE);

View file

@ -114,7 +114,7 @@ public:
BYTE lighttype; BYTE lighttype;
bool owned; bool owned;
bool halo; bool halo;
AActor *Owner; // NOTE: This is *NOT* subject to pointer cleanup!!! AActor *Owner; // NOTE: This is *NOT* subject to pointer cleanup or garbage collection!!!
// intermediate texture coordinate data // intermediate texture coordinate data
// this is stored in the light object to avoid recalculating it // this is stored in the light object to avoid recalculating it
@ -249,9 +249,6 @@ enum
STAT_DLIGHT=64 STAT_DLIGHT=64
}; };
extern TArray<ADynamicLight *> PointLights;
// //
// Light helper methods // Light helper methods

Some files were not shown because too many files have changed in this diff Show more