mirror of
https://github.com/ZDoom/gzdoom-last-svn.git
synced 2025-06-04 19:20:53 +00:00
- 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:
parent
e8e77c8122
commit
0c2560bb36
262 changed files with 11637 additions and 16759 deletions
241
docs/rh-log.txt
241
docs/rh-log.txt
|
@ -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
|
||||
- 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
|
||||
|
@ -11,12 +168,54 @@ February 26, 2008
|
|||
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
|
||||
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)
|
||||
- Fixed: The DECORATE expression evaluator's random function could produce
|
||||
incorrect results for ranges > 255. Changed so that FRandom's default
|
||||
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
|
||||
- Fixed: DThinker::SerializeAll() did not serialize any thinkers in the
|
||||
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
|
||||
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)
|
||||
- Added a modified version of Karate Chris's submission for killing specific
|
||||
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
|
||||
- Added vid_refreshrate cvar to override Windows' automatic refresh rate
|
||||
selection.
|
||||
|
@ -632,7 +851,7 @@ January 1, 2008
|
|||
- 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
|
||||
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.
|
||||
- Restored the console's darkening level to its old table-based amount.
|
||||
- Fixed D3DFB::SetColorOverlay()'s incorrect calculations.
|
||||
|
@ -4330,7 +4549,7 @@ April 18, 2006 (Changes by Graf Zahl)
|
|||
fighting the programmer.
|
||||
|
||||
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
|
||||
the textures left offset to the x coordinate before drawing.
|
||||
These fix Twice Risen's status bar.
|
||||
|
@ -5143,7 +5362,7 @@ February 11, 2005
|
|||
|
||||
February 10, 2005
|
||||
- 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
|
||||
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
|
||||
|
@ -5318,7 +5537,7 @@ January 26, 2005
|
|||
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
|
||||
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.
|
||||
|
||||
January 15, 2005
|
||||
|
@ -5494,7 +5713,7 @@ December 18, 2004
|
|||
on top of the next digit.
|
||||
- Fixed: The Doom HUD drew the selected inventory item on top of the secondary
|
||||
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
|
||||
- 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
|
||||
|
@ -6350,7 +6569,7 @@ July 15, 2004
|
|||
- Updated to FMOD 3.73.
|
||||
|
||||
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
|
||||
themselves without needing to hook into the status bar.
|
||||
- Reimplemented the Tome of Power.
|
||||
|
@ -6919,7 +7138,7 @@ December 12, 2003
|
|||
up okay in Doom.)
|
||||
- Added a fix to the blockmap tracers where all the blocks along the trace are
|
||||
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.
|
||||
- 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
|
||||
|
@ -7067,7 +7286,7 @@ November 23, 2003
|
|||
things spawned afterward.
|
||||
- Added calls to SetWindowLongPtr to take away the window's border in
|
||||
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.
|
||||
|
||||
November 21, 2003
|
||||
|
@ -7799,7 +8018,7 @@ July 30, 2003
|
|||
- Added nonexistant texture check to the warping texture setup.
|
||||
|
||||
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.
|
||||
- Changed the pitch calculations in the DSimpleCanvas constructor to provide
|
||||
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
|
||||
fixedlightlev.
|
||||
- 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.
|
||||
- Fixed: The wait console command waited one tic too few.
|
||||
- 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.
|
||||
- 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
|
||||
in FBaseStatusBar::SetScaled().
|
||||
in DBaseStatusBar::SetScaled().
|
||||
- Added separate pickup sounds for health, armor, and ammo items.
|
||||
- Improved sound link resolution in S_StartSound() so that regular sounds
|
||||
can alias player sounds.
|
||||
|
|
|
@ -11,8 +11,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zdoom", "gzdoom.vcproj", "{
|
|||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "zlib\zlib.vcproj", "{F9D9E7D4-E1A2-4866-9E85-B1B14137EE63}"
|
||||
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}"
|
||||
EndProject
|
||||
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}
|
||||
EndProjectSection
|
||||
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}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fixrtext", "tools\fixrtext\fixrtext.vcproj", "{DA47396F-60C1-4BDE-A977-7F7DE461CF77}"
|
||||
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}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{24A19C02-F041-4AB0-A1A1-02E1E88EDBD3} = {24A19C02-F041-4AB0-A1A1-02E1E88EDBD3}
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
Description="Checking svnrevision.h..."
|
||||
CommandLine=""$(OutDir)\updaterevision.exe" src src/svnrevision_gz.h"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
|
@ -54,7 +56,7 @@
|
|||
EnableIntrinsicFunctions="true"
|
||||
FavorSizeOrSpeed="1"
|
||||
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"
|
||||
StringPooling="true"
|
||||
RuntimeLibrary="0"
|
||||
|
@ -146,6 +148,8 @@
|
|||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
Description="Checking svnrevision.h..."
|
||||
CommandLine="$(OutDir)\updaterevision.exe src src/svnrevision_gz.h"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
|
@ -168,7 +172,7 @@
|
|||
Name="VCCLCompilerTool"
|
||||
AdditionalOptions="" /I /fmod/api/inc" " /I /fmod/api/inc" " /I /fmod/api/inc" " /I /fmod/api/inc" "
|
||||
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"
|
||||
MinimalRebuild="true"
|
||||
RuntimeLibrary="1"
|
||||
|
@ -1401,6 +1405,10 @@
|
|||
/>
|
||||
</FileConfiguration>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\dobjgc.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\dobjtype.cpp"
|
||||
>
|
||||
|
|
52
snes_spc/Makefile
Normal file
52
snes_spc/Makefile
Normal 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
107
snes_spc/changes.txt
Normal 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
58
snes_spc/demo/benchmark.c
Normal 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
70
snes_spc/demo/comm.c
Normal 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
57
snes_spc/demo/demo_util.c
Normal 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
31
snes_spc/demo/demo_util.h
Normal 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
57
snes_spc/demo/play_spc.c
Normal 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
107
snes_spc/demo/save_state.c
Normal 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
83
snes_spc/demo/trim_spc.c
Normal 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
153
snes_spc/demo/wave_writer.c
Normal 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;
|
||||
}
|
||||
}
|
20
snes_spc/demo/wave_writer.h
Normal file
20
snes_spc/demo/wave_writer.h
Normal 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
|
703
snes_spc/fast_dsp/SPC_DSP.cpp
Normal file
703
snes_spc/fast_dsp/SPC_DSP.cpp
Normal 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
212
snes_spc/fast_dsp/SPC_DSP.h
Normal 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
504
snes_spc/license.txt
Normal 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
86
snes_spc/readme.txt
Normal 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>
|
1018
snes_spc/slow_dsp/SPC_DSP.cpp
Normal file
1018
snes_spc/slow_dsp/SPC_DSP.cpp
Normal file
File diff suppressed because it is too large
Load diff
304
snes_spc/slow_dsp/SPC_DSP.h
Normal file
304
snes_spc/slow_dsp/SPC_DSP.h
Normal 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
318
snes_spc/snes_spc.txt
Normal 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
397
snes_spc/snes_spc.vcproj
Normal 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>
|
564
snes_spc/snes_spc/SNES_SPC.cpp
Normal file
564
snes_spc/snes_spc/SNES_SPC.cpp
Normal 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, ®S [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 ®S [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"
|
279
snes_spc/snes_spc/SNES_SPC.h
Normal file
279
snes_spc/snes_spc/SNES_SPC.h
Normal 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
|
380
snes_spc/snes_spc/SNES_SPC_misc.cpp
Normal file
380
snes_spc/snes_spc/SNES_SPC_misc.cpp
Normal 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 );
|
||||
}
|
129
snes_spc/snes_spc/SNES_SPC_state.cpp
Normal file
129
snes_spc/snes_spc/SNES_SPC_state.cpp
Normal 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, ®S [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( ®S [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
1220
snes_spc/snes_spc/SPC_CPU.h
Normal file
File diff suppressed because it is too large
Load diff
703
snes_spc/snes_spc/SPC_DSP.cpp
Normal file
703
snes_spc/snes_spc/SPC_DSP.cpp
Normal 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
212
snes_spc/snes_spc/SPC_DSP.h
Normal 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
|
68
snes_spc/snes_spc/SPC_Filter.cpp
Normal file
68
snes_spc/snes_spc/SPC_Filter.cpp
Normal 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 );
|
||||
}
|
47
snes_spc/snes_spc/SPC_Filter.h
Normal file
47
snes_spc/snes_spc/SPC_Filter.h
Normal 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
|
186
snes_spc/snes_spc/blargg_common.h
Normal file
186
snes_spc/snes_spc/blargg_common.h
Normal 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
|
24
snes_spc/snes_spc/blargg_config.h
Normal file
24
snes_spc/snes_spc/blargg_config.h
Normal 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
|
185
snes_spc/snes_spc/blargg_endian.h
Normal file
185
snes_spc/snes_spc/blargg_endian.h
Normal 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
|
100
snes_spc/snes_spc/blargg_source.h
Normal file
100
snes_spc/snes_spc/blargg_source.h
Normal 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
48
snes_spc/snes_spc/dsp.cpp
Normal 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
83
snes_spc/snes_spc/dsp.h
Normal 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
73
snes_spc/snes_spc/spc.cpp
Normal 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
147
snes_spc/snes_spc/spc.h
Normal 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
|
42
src/actor.h
42
src/actor.h
|
@ -545,9 +545,9 @@ public:
|
|||
bool CheckLocalView (int playernum) const;
|
||||
|
||||
// Finds the first item of a particular type.
|
||||
AInventory *FindInventory (const PClass *type) const;
|
||||
AInventory *FindInventory (FName type) const;
|
||||
template<class T> T *FindInventory () const
|
||||
AInventory *FindInventory (const PClass *type);
|
||||
AInventory *FindInventory (FName type);
|
||||
template<class T> T *FindInventory ()
|
||||
{
|
||||
return static_cast<T *> (FindInventory (RUNTIME_CLASS(T)));
|
||||
}
|
||||
|
@ -556,7 +556,7 @@ public:
|
|||
AInventory *GiveInventoryType (const PClass *type);
|
||||
|
||||
// Returns the first item held with IF_INVBAR set.
|
||||
AInventory *FirstInv () const;
|
||||
AInventory *FirstInv ();
|
||||
|
||||
// Tries to give the actor some ammo.
|
||||
bool GiveAmmo (const PClass *type, int amount);
|
||||
|
@ -572,7 +572,7 @@ public:
|
|||
void ConversationAnimation (int animnum);
|
||||
|
||||
// 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
|
||||
void ObtainInventory (AActor *other);
|
||||
|
@ -647,35 +647,32 @@ public:
|
|||
BYTE movedir; // 0-7
|
||||
SBYTE visdir;
|
||||
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
|
||||
AActor *lastenemy; // Last known enemy -- killogh 2/15/98
|
||||
AActor *LastHeard; // [RH] Last actor this one heard
|
||||
TObjPtr<AActor> lastenemy; // Last known enemy -- killogh 2/15/98
|
||||
TObjPtr<AActor> LastHeard; // [RH] Last actor this one heard
|
||||
SDWORD reactiontime; // if non 0, don't attack yet; used by
|
||||
// player to freeze a bit after teleporting
|
||||
SDWORD threshold; // if > 0, the target will be chased
|
||||
// no matter what (even if shot)
|
||||
player_s *player; // only valid if type of APlayerPawn
|
||||
union
|
||||
{
|
||||
AActor *Actor; // Actor last looked for (if TIDtoHate != 0)
|
||||
SDWORD PlayerNumber; // Player number last looked for
|
||||
} LastLook;
|
||||
TObjPtr<AActor> LastLookActor; // Actor last looked for (if TIDtoHate != 0)
|
||||
WORD SpawnPoint[3]; // For nightmare respawn
|
||||
WORD SpawnAngle;
|
||||
int skillrespawncount;
|
||||
AActor *tracer; // Thing being chased/attacked for tracers
|
||||
AActor *master; // Thing which spawned this one (prevents mutual attacks)
|
||||
TObjPtr<AActor> tracer; // Thing being chased/attacked for tracers
|
||||
TObjPtr<AActor> master; // Thing which spawned this one (prevents mutual attacks)
|
||||
fixed_t floorclip; // value to use for floor clipping
|
||||
SWORD tid; // thing identifier
|
||||
BYTE special; // special
|
||||
int args[5]; // special arguments
|
||||
|
||||
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 boomwaterlevel; // splash information for non-swimmable water sectors
|
||||
BYTE MinMissileChance;// [RH] If a random # is > than this, then missile attack.
|
||||
SBYTE LastLookPlayerNumber;// Player number last looked for (if TIDtoHate == 0)
|
||||
WORD SpawnFlags;
|
||||
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
|
||||
|
@ -686,9 +683,10 @@ public:
|
|||
int bouncecount; // Strife's grenades only bounce twice before exploding
|
||||
fixed_t gravity; // [GRB] Gravity factor
|
||||
int FastChaseStrafeCount;
|
||||
// [KS] These temporary-use properties are needed to allow A_LookEx to pass it's parameters to LookFor*InBlock in
|
||||
// P_BlockmapSearch so that friendly enemies and monsters that look for 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.
|
||||
// [KS] These temporary-use properties are needed to allow A_LookEx to pass its parameters to
|
||||
// LookFor*InBlock in P_BlockmapSearch so that friendly enemies and monsters that look for
|
||||
// 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 LookExMaxDist; // Maximum sight distance
|
||||
angle_t LookExFOV; // Field of Vision
|
||||
|
@ -696,7 +694,7 @@ public:
|
|||
// a linked list of sectors where this object appears
|
||||
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
|
||||
|
||||
//Added by MC:
|
||||
|
@ -769,10 +767,12 @@ public:
|
|||
|
||||
enum { S_NULL = 2, S_GENERICFREEZEDEATH = 3 };
|
||||
|
||||
TArray<AActor*> dynamiclights;
|
||||
TArray<TObjPtr<AActor>> dynamiclights;
|
||||
void * lightassociations;
|
||||
bool hasmodel;
|
||||
subsector_s * subsector;
|
||||
|
||||
size_t PropagateMark();
|
||||
};
|
||||
|
||||
class FActorIterator
|
||||
|
|
|
@ -117,7 +117,7 @@ public:
|
|||
botinfo_t *botinfo;
|
||||
int spawn_tries;
|
||||
int wanted_botnum;
|
||||
AActor *firstthing;
|
||||
TObjPtr<AActor> firstthing;
|
||||
|
||||
bool m_Thinking;
|
||||
|
||||
|
@ -141,8 +141,8 @@ protected:
|
|||
bool ctf;
|
||||
int loaded_bots;
|
||||
int t_join;
|
||||
AActor *body1;
|
||||
AActor *body2;
|
||||
TObjPtr<AActor> body1;
|
||||
TObjPtr<AActor> body2;
|
||||
bool observer; //Consoleplayer is observer.
|
||||
};
|
||||
|
||||
|
|
|
@ -453,16 +453,26 @@ void DCajunMaster::SetBodyAt (fixed_t x, fixed_t y, fixed_t z, int hostnum)
|
|||
if (hostnum == 1)
|
||||
{
|
||||
if (body1)
|
||||
{
|
||||
body1->SetOrigin (x, y, z);
|
||||
}
|
||||
else
|
||||
{
|
||||
body1 = Spawn ("CajunBodyNode", x, y, z, NO_REPLACE);
|
||||
GC::WriteBarrier(this, body1);
|
||||
}
|
||||
}
|
||||
else if (hostnum == 2)
|
||||
{
|
||||
if (body2)
|
||||
{
|
||||
body2->SetOrigin (x, y, z);
|
||||
}
|
||||
else
|
||||
{
|
||||
body2 = Spawn ("CajunBodyNode", x, y, z, NO_REPLACE);
|
||||
GC::WriteBarrier(this, body2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -94,9 +94,11 @@ DCajunMaster::~DCajunMaster()
|
|||
ForgetBots();
|
||||
if (getspawned != NULL)
|
||||
{
|
||||
delete getspawned;
|
||||
getspawned->Destroy();
|
||||
getspawned = NULL;
|
||||
}
|
||||
// FIXME: Make this object proper
|
||||
ObjectFlags |= OF_Cleanup | OF_YesReallyDelete;
|
||||
}
|
||||
|
||||
//This function is called every tick (from g_game.c),
|
||||
|
|
|
@ -229,7 +229,7 @@ CUSTOM_CVAR (Int, msgmidcolor2, 4, CVAR_ARCHIVE)
|
|||
static void maybedrawnow (bool tick, bool force)
|
||||
{
|
||||
// FIXME: Does not work right with hw2d
|
||||
if (ConsoleDrawing || !gotconback || screen->IsLocked () || screen->Accel2D)
|
||||
if (ConsoleDrawing || !gotconback || screen == NULL || screen->IsLocked () || screen->Accel2D)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1250,23 +1250,23 @@ CCMD (key)
|
|||
// These all begin with '+' as opposed to '-'.
|
||||
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;
|
||||
int cmdlen = 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;
|
||||
currArg++;
|
||||
cmdlen++;
|
||||
}
|
||||
|
||||
cmdString = BuildString (cmdlen, Args.GetArgList (argstart));
|
||||
cmdString = BuildString (cmdlen, Args->GetArgList (argstart));
|
||||
if (!cmdString.IsEmpty())
|
||||
{
|
||||
C_DoCommand (&cmdString[1]);
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#include <stdarg.h>
|
||||
|
||||
// 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);
|
||||
bool FileExists (const char *filename);
|
||||
|
|
|
@ -161,7 +161,7 @@ public:
|
|||
void DoPickupSpecial (AActor *toucher);
|
||||
private:
|
||||
const PClass *DetermineType ();
|
||||
AInventory *RealPickup;
|
||||
TObjPtr<AInventory> RealPickup;
|
||||
};
|
||||
|
||||
IMPLEMENT_POINTY_CLASS (ADehackedPickup)
|
||||
|
|
|
@ -745,7 +745,6 @@ void D_DoomLoop ()
|
|||
if (singletics)
|
||||
{
|
||||
I_StartTic ();
|
||||
DObject::BeginFrame ();
|
||||
D_ProcessEvents ();
|
||||
G_BuildTiccmd (&netcmds[consoleplayer][maketic%BACKUPTICS]);
|
||||
//Added by MC: For some of that bot stuff. The main bot function.
|
||||
|
@ -774,7 +773,7 @@ void D_DoomLoop ()
|
|||
G_Ticker ();
|
||||
gametic++;
|
||||
maketic++;
|
||||
DObject::EndFrame ();
|
||||
GC::CheckGC ();
|
||||
Net_NewMakeTic ();
|
||||
}
|
||||
else
|
||||
|
@ -1644,7 +1643,7 @@ static EIWADType IdentifyVersion (const char *zdoom_wad)
|
|||
{
|
||||
WadStuff wads[sizeof(IWADNames)/sizeof(char *)];
|
||||
size_t foundwads[NUM_IWAD_TYPES] = { 0 };
|
||||
const char *iwadparm = Args.CheckValue ("-iwad");
|
||||
const char *iwadparm = Args->CheckValue ("-iwad");
|
||||
size_t numwads;
|
||||
int pickwad;
|
||||
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 noDef = false;
|
||||
DArgs *files = Args.GatherFiles (arg, ext, false);
|
||||
DArgs *files = Args->GatherFiles (arg, ext, false);
|
||||
|
||||
if (files->NumArgs() > 0)
|
||||
{
|
||||
|
@ -1930,7 +1929,7 @@ bool ConsiderPatches (const char *arg, const char *ext)
|
|||
}
|
||||
noDef = true;
|
||||
}
|
||||
delete files;
|
||||
files->Destroy();
|
||||
return noDef;
|
||||
}
|
||||
|
||||
|
@ -2039,7 +2038,6 @@ void D_DoomMain (void)
|
|||
|
||||
srand(I_MSTime());
|
||||
|
||||
atterm (DObject::StaticShutdown);
|
||||
PClass::StaticInit ();
|
||||
atterm (C_DeinitConsole);
|
||||
|
||||
|
@ -2121,19 +2119,19 @@ void D_DoomMain (void)
|
|||
execFiles = new DArgs;
|
||||
GameConfig->AddAutoexec (execFiles, GameNames[gameinfo.gametype]);
|
||||
D_MultiExec (execFiles, true);
|
||||
delete execFiles;
|
||||
execFiles->Destroy();
|
||||
|
||||
// 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);
|
||||
delete execFiles;
|
||||
execFiles->Destroy();
|
||||
|
||||
C_ExecCmdLineParams (); // [RH] do all +set commands on the command line
|
||||
|
||||
DArgs *files = Args.GatherFiles ("-file", ".wad", true);
|
||||
DArgs *files1 = Args.GatherFiles (NULL, ".zip", false);
|
||||
DArgs *files2 = Args.GatherFiles (NULL, ".pk3", false);
|
||||
DArgs *files3 = Args.GatherFiles (NULL, ".txt", false);
|
||||
DArgs *files = Args->GatherFiles ("-file", ".wad", true);
|
||||
DArgs *files1 = Args->GatherFiles (NULL, ".zip", false);
|
||||
DArgs *files2 = Args->GatherFiles (NULL, ".pk3", false);
|
||||
DArgs *files3 = Args->GatherFiles (NULL, ".txt", false);
|
||||
if (files->NumArgs() > 0 || files1->NumArgs() > 0 || files2->NumArgs() > 0 || files3->NumArgs() > 0)
|
||||
{
|
||||
// Check for -file in shareware
|
||||
|
@ -2160,10 +2158,10 @@ void D_DoomMain (void)
|
|||
D_AddWildFile (files3->GetArg (i));
|
||||
}
|
||||
}
|
||||
delete files;
|
||||
delete files1;
|
||||
delete files2;
|
||||
delete files3;
|
||||
files->Destroy();
|
||||
files1->Destroy();
|
||||
files2->Destroy();
|
||||
files3->Destroy();
|
||||
|
||||
Printf ("W_Init: Init WADfiles.\n");
|
||||
Wads.InitMultipleFiles (&wadfiles);
|
||||
|
@ -2193,18 +2191,18 @@ void D_DoomMain (void)
|
|||
|
||||
Printf ("P_Init: Checking cmd-line parameters...\n");
|
||||
flags = dmflags;
|
||||
if (Args.CheckParm ("-nomonsters")) flags |= DF_NO_MONSTERS;
|
||||
if (Args.CheckParm ("-respawn")) flags |= DF_MONSTERS_RESPAWN;
|
||||
if (Args.CheckParm ("-fast")) flags |= DF_FAST_MONSTERS;
|
||||
if (Args->CheckParm ("-nomonsters")) flags |= DF_NO_MONSTERS;
|
||||
if (Args->CheckParm ("-respawn")) flags |= DF_MONSTERS_RESPAWN;
|
||||
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;
|
||||
flags |= DF_ITEMS_RESPAWN;
|
||||
}
|
||||
else if (Args.CheckParm ("-deathmatch"))
|
||||
else if (Args->CheckParm ("-deathmatch"))
|
||||
{
|
||||
deathmatch = 1;
|
||||
flags |= DF_WEAPONS_STAY | DF_ITEMS_RESPAWN;
|
||||
|
@ -2223,27 +2221,27 @@ void D_DoomMain (void)
|
|||
}
|
||||
autostart = false;
|
||||
|
||||
const char *val = Args.CheckValue ("-skill");
|
||||
const char *val = Args->CheckValue ("-skill");
|
||||
if (val)
|
||||
{
|
||||
gameskill = val[0] - '1';
|
||||
autostart = true;
|
||||
}
|
||||
|
||||
p = Args.CheckParm ("-warp");
|
||||
if (p && p < Args.NumArgs() - 1)
|
||||
p = Args->CheckParm ("-warp");
|
||||
if (p && p < Args->NumArgs() - 1)
|
||||
{
|
||||
int ep, map;
|
||||
|
||||
if (gameinfo.flags & GI_MAPxx)
|
||||
{
|
||||
ep = 1;
|
||||
map = atoi (Args.GetArg(p+1));
|
||||
map = atoi (Args->GetArg(p+1));
|
||||
}
|
||||
else
|
||||
{
|
||||
ep = atoi (Args.GetArg(p+1));
|
||||
map = p < Args.NumArgs() - 2 ? atoi (Args.GetArg(p+2)) : 10;
|
||||
ep = atoi (Args->GetArg(p+1));
|
||||
map = p < Args->NumArgs() - 2 ? atoi (Args->GetArg(p+2)) : 10;
|
||||
if (map < 1 || map > 9)
|
||||
{
|
||||
map = ep;
|
||||
|
@ -2256,19 +2254,19 @@ void D_DoomMain (void)
|
|||
}
|
||||
|
||||
// [RH] Hack to handle +map
|
||||
p = Args.CheckParm ("+map");
|
||||
if (p && p < Args.NumArgs()-1)
|
||||
p = Args->CheckParm ("+map");
|
||||
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)
|
||||
{
|
||||
Printf ("Can't find map %s\n", Args.GetArg (p+1));
|
||||
Printf ("Can't find map %s\n", Args->GetArg (p+1));
|
||||
}
|
||||
else
|
||||
{
|
||||
delete map;
|
||||
strncpy (startmap, Args.GetArg (p+1), 8);
|
||||
Args.GetArg (p)[0] = '-';
|
||||
strncpy (startmap, Args->GetArg (p+1), 8);
|
||||
Args->GetArg (p)[0] = '-';
|
||||
autostart = true;
|
||||
}
|
||||
}
|
||||
|
@ -2281,7 +2279,7 @@ void D_DoomMain (void)
|
|||
// 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
|
||||
// the user's home directory.
|
||||
if (Args.CheckParm("-cdrom"))
|
||||
if (Args->CheckParm("-cdrom"))
|
||||
{
|
||||
Printf (GStrings("D_CDROM"));
|
||||
mkdir (CDROM_DIR, 0);
|
||||
|
@ -2293,7 +2291,7 @@ void D_DoomMain (void)
|
|||
UCVarValue value;
|
||||
static char one_hundred[] = "100";
|
||||
|
||||
value.String = Args.CheckValue ("-turbo");
|
||||
value.String = Args->CheckValue ("-turbo");
|
||||
if (value.String == NULL)
|
||||
value.String = one_hundred;
|
||||
else
|
||||
|
@ -2302,7 +2300,7 @@ void D_DoomMain (void)
|
|||
turbo.SetGenericRepDefault (value, CVAR_String);
|
||||
}
|
||||
|
||||
v = Args.CheckValue ("-timer");
|
||||
v = Args->CheckValue ("-timer");
|
||||
if (v)
|
||||
{
|
||||
double time = strtod (v, NULL);
|
||||
|
@ -2310,7 +2308,7 @@ void D_DoomMain (void)
|
|||
timelimit = (float)time;
|
||||
}
|
||||
|
||||
v = Args.CheckValue ("-avg");
|
||||
v = Args->CheckValue ("-avg");
|
||||
if (v)
|
||||
{
|
||||
Printf ("Austin Virtual Gaming: Levels will end after 20 minutes\n");
|
||||
|
@ -2420,10 +2418,10 @@ void D_DoomMain (void)
|
|||
}
|
||||
|
||||
//Added by MC:
|
||||
bglobal.getspawned = Args.GatherFiles ("-bots", "", false);
|
||||
bglobal.getspawned = Args->GatherFiles ("-bots", "", false);
|
||||
if (bglobal.getspawned->NumArgs() == 0)
|
||||
{
|
||||
delete bglobal.getspawned;
|
||||
bglobal.getspawned->Destroy();
|
||||
bglobal.getspawned = NULL;
|
||||
}
|
||||
else
|
||||
|
@ -2459,13 +2457,11 @@ void D_DoomMain (void)
|
|||
// [RH] Run any saved commands from the command line or autoexec.cfg now.
|
||||
gamestate = GS_FULLCONSOLE;
|
||||
Net_NewMakeTic ();
|
||||
DObject::BeginFrame ();
|
||||
DThinker::RunThinkers ();
|
||||
DObject::EndFrame ();
|
||||
gamestate = GS_STARTUP;
|
||||
|
||||
// start the apropriate game based on parms
|
||||
v = Args.CheckValue ("-record");
|
||||
v = Args->CheckValue ("-record");
|
||||
|
||||
if (v)
|
||||
{
|
||||
|
@ -2477,24 +2473,23 @@ void D_DoomMain (void)
|
|||
StartScreen = NULL;
|
||||
V_Init2();
|
||||
|
||||
files = Args.GatherFiles ("-playdemo", ".lmp", false);
|
||||
files = Args->GatherFiles ("-playdemo", ".lmp", false);
|
||||
if (files->NumArgs() > 0)
|
||||
{
|
||||
singledemo = true; // quit after one demo
|
||||
G_DeferedPlayDemo (files->GetArg (0));
|
||||
delete files;
|
||||
D_DoomLoop (); // never returns
|
||||
}
|
||||
delete files;
|
||||
files->Destroy();
|
||||
|
||||
v = Args.CheckValue ("-timedemo");
|
||||
v = Args->CheckValue ("-timedemo");
|
||||
if (v)
|
||||
{
|
||||
G_TimeDemo (v);
|
||||
D_DoomLoop (); // never returns
|
||||
}
|
||||
|
||||
v = Args.CheckValue ("-loadgame");
|
||||
v = Args->CheckValue ("-loadgame");
|
||||
if (v)
|
||||
{
|
||||
file = v;
|
||||
|
|
|
@ -921,6 +921,8 @@ void NetUpdate (void)
|
|||
BYTE *cmddata;
|
||||
bool resendOnly;
|
||||
|
||||
GC::CheckGC();
|
||||
|
||||
if (ticdup == 0)
|
||||
{
|
||||
return;
|
||||
|
@ -1586,7 +1588,7 @@ void D_CheckNetGame (void)
|
|||
|
||||
consoleplayer = doomcom.consoleplayer;
|
||||
|
||||
v = Args.CheckValue ("-netmode");
|
||||
v = Args->CheckValue ("-netmode");
|
||||
if (v != NULL)
|
||||
{
|
||||
NetMode = atoi (v) != 0 ? NET_PacketServer : NET_PeerToPeer;
|
||||
|
@ -1595,7 +1597,7 @@ void D_CheckNetGame (void)
|
|||
// [RH] Setup user info
|
||||
D_SetupUserInfo ();
|
||||
|
||||
if (Args.CheckParm ("-debugfile"))
|
||||
if (Args->CheckParm ("-debugfile"))
|
||||
{
|
||||
char filename[20];
|
||||
sprintf (filename,"debug%i.txt",consoleplayer);
|
||||
|
@ -1834,12 +1836,11 @@ void TryRunTics (void)
|
|||
D_DoAdvanceDemo ();
|
||||
}
|
||||
if (debugfile) fprintf (debugfile, "run tic %d\n", gametic);
|
||||
DObject::BeginFrame ();
|
||||
C_Ticker ();
|
||||
M_Ticker ();
|
||||
I_GetTime (true);
|
||||
G_Ticker ();
|
||||
DObject::EndFrame ();
|
||||
GC::CheckGC ();
|
||||
gametic++;
|
||||
|
||||
NetUpdate (); // check for new console commands
|
||||
|
|
|
@ -105,8 +105,8 @@ public:
|
|||
int crouchsprite;
|
||||
int MaxHealth;
|
||||
int RunHealth;
|
||||
AInventory *InvFirst; // first inventory item displayed on inventory bar
|
||||
AInventory *InvSel; // selected inventory item
|
||||
TObjPtr<AInventory> InvFirst; // first inventory item displayed on inventory bar
|
||||
TObjPtr<AInventory> InvSel; // selected inventory item
|
||||
|
||||
// [GRB] Player class properties
|
||||
fixed_t JumpZ;
|
||||
|
@ -192,6 +192,7 @@ public:
|
|||
|
||||
void Serialize (FArchive &arc);
|
||||
void FixPointers (const DObject *obj, DObject *replacement);
|
||||
size_t PropagateMark();
|
||||
|
||||
void SetLogNumber (int num);
|
||||
void SetLogText (const char *text);
|
||||
|
|
|
@ -110,7 +110,7 @@ struct DDecalThinker : public DThinker
|
|||
public:
|
||||
DDecalThinker (DBaseDecal *decal) : DThinker (STAT_DECALTHINKER), TheDecal (decal) {}
|
||||
void Serialize (FArchive &arc);
|
||||
DBaseDecal *TheDecal;
|
||||
TObjPtr<DBaseDecal> TheDecal;
|
||||
protected:
|
||||
DDecalThinker () : DThinker (STAT_DECALTHINKER) {}
|
||||
};
|
||||
|
@ -1143,6 +1143,7 @@ void DDecalFader::Tick ()
|
|||
{
|
||||
TheDecal->Destroy (); // remove the decal
|
||||
Destroy (); // remove myself
|
||||
return;
|
||||
}
|
||||
if (StartTrans == -1)
|
||||
{
|
||||
|
|
286
src/dobject.cpp
286
src/dobject.cpp
|
@ -47,6 +47,7 @@
|
|||
#include "r_state.h"
|
||||
#include "stats.h"
|
||||
#include "a_sharedglobal.h"
|
||||
#include "dsectoreffect.h"
|
||||
|
||||
#include "autosegs.h"
|
||||
|
||||
|
@ -343,131 +344,80 @@ CCMD (dumpclasses)
|
|||
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)
|
||||
{
|
||||
new ((EInPlace *)mem) DObject;
|
||||
}
|
||||
|
||||
DObject::DObject ()
|
||||
: ObjectFlags(0), Class(0)
|
||||
: Class(0), ObjectFlags(0)
|
||||
{
|
||||
if (FreeIndices.Pop (Index))
|
||||
Objects[Index] = this;
|
||||
else
|
||||
Index = Objects.Push (this);
|
||||
ObjectFlags = GC::CurrentWhite & OF_WhiteBits;
|
||||
ObjNext = GC::Root;
|
||||
GC::Root = this;
|
||||
}
|
||||
|
||||
DObject::DObject (PClass *inClass)
|
||||
: ObjectFlags(0), Class(inClass)
|
||||
: Class(inClass), ObjectFlags(0)
|
||||
{
|
||||
if (FreeIndices.Pop (Index))
|
||||
Objects[Index] = this;
|
||||
else
|
||||
Index = Objects.Push (this);
|
||||
ObjectFlags = GC::CurrentWhite & OF_WhiteBits;
|
||||
ObjNext = GC::Root;
|
||||
GC::Root = this;
|
||||
}
|
||||
|
||||
DObject::~DObject ()
|
||||
{
|
||||
if (!Inactive)
|
||||
if (!(ObjectFlags & OF_Cleanup))
|
||||
{
|
||||
if (!(ObjectFlags & OF_MassDestruction))
|
||||
{
|
||||
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;
|
||||
DObject **probe;
|
||||
PClass *type = GetClass();
|
||||
|
||||
for (i = ToDestroy.Size() - 1; i-- > 0; )
|
||||
if (!(ObjectFlags & OF_YesReallyDelete))
|
||||
{
|
||||
if (ToDestroy[i] == this)
|
||||
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))
|
||||
{
|
||||
ToDestroy[i] = NULL;
|
||||
if (*probe == this)
|
||||
{
|
||||
*probe = ObjNext;
|
||||
if (&ObjNext == GC::SweepPos)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
DestroyScan (this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DObject::Destroy ()
|
||||
{
|
||||
if (!Inactive)
|
||||
{
|
||||
if (!(ObjectFlags & OF_MassDestruction))
|
||||
{
|
||||
RemoveFromArray ();
|
||||
ObjectFlags |= OF_MassDestruction;
|
||||
ToDestroy.Push (this);
|
||||
}
|
||||
}
|
||||
else
|
||||
delete this;
|
||||
ObjectFlags |= OF_EuthanizeMe;
|
||||
}
|
||||
|
||||
void DObject::BeginFrame ()
|
||||
size_t DObject::PropagateMark()
|
||||
{
|
||||
StaleCycles = 0;
|
||||
StaleCount = 0;
|
||||
}
|
||||
|
||||
void DObject::EndFrame ()
|
||||
{
|
||||
clock (StaleCycles);
|
||||
if (ToDestroy.Size ())
|
||||
{
|
||||
StaleCount += (int)ToDestroy.Size ();
|
||||
DestroyScan ();
|
||||
//Printf ("Destroyed %d objects\n", ToDestroy.Size());
|
||||
|
||||
DObject *obj;
|
||||
while (ToDestroy.Pop (obj))
|
||||
{
|
||||
if (obj)
|
||||
{
|
||||
obj->ObjectFlags |= OF_Cleanup;
|
||||
delete obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
unclock (StaleCycles);
|
||||
}
|
||||
|
||||
void DObject::RemoveFromArray ()
|
||||
{
|
||||
if (Objects.Size() == Index + 1)
|
||||
{
|
||||
DObject *dummy;
|
||||
Objects.Pop (dummy);
|
||||
}
|
||||
else if (Objects.Size() > Index)
|
||||
{
|
||||
Objects[Index] = NULL;
|
||||
FreeIndices.Push (Index);
|
||||
}
|
||||
}
|
||||
|
||||
void DObject::PointerSubstitution (DObject *old, DObject *notOld)
|
||||
{
|
||||
unsigned int i, highest;
|
||||
highest = Objects.Size ();
|
||||
|
||||
for (i = 0; i <= highest; i++)
|
||||
{
|
||||
DObject *current = i < highest ? Objects[i] : &bglobal;
|
||||
if (current)
|
||||
{
|
||||
const PClass *info = current->GetClass();
|
||||
const PClass *info = GetClass();
|
||||
const size_t *offsets = info->FlatPointers;
|
||||
if (offsets == NULL)
|
||||
{
|
||||
|
@ -476,15 +426,38 @@ void DObject::PointerSubstitution (DObject *old, DObject *notOld)
|
|||
}
|
||||
while (*offsets != ~(size_t)0)
|
||||
{
|
||||
if (*(DObject **)((BYTE *)current + *offsets) == old)
|
||||
GC::Mark((DObject **)((BYTE *)this + *offsets));
|
||||
offsets++;
|
||||
}
|
||||
return info->Size;
|
||||
}
|
||||
|
||||
void DObject::PointerSubstitution (DObject *old, DObject *notOld)
|
||||
{
|
||||
DObject *probe;
|
||||
int i;
|
||||
|
||||
// Go through all objects.
|
||||
for (probe = GC::Root; probe != NULL; probe = probe->ObjNext)
|
||||
{
|
||||
*(DObject **)((BYTE *)current + *offsets) = notOld;
|
||||
const PClass *info = probe->GetClass();
|
||||
const size_t *offsets = info->FlatPointers;
|
||||
if (offsets == NULL)
|
||||
{
|
||||
const_cast<PClass *>(info)->BuildFlatPointers();
|
||||
offsets = info->FlatPointers;
|
||||
}
|
||||
while (*offsets != ~(size_t)0)
|
||||
{
|
||||
if (*(DObject **)((BYTE *)probe + *offsets) == old)
|
||||
{
|
||||
*(DObject **)((BYTE *)probe + *offsets) = notOld;
|
||||
}
|
||||
offsets++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go through the bodyque.
|
||||
for (i = 0; i < BODYQUESIZE; ++i)
|
||||
{
|
||||
if (bodyque[i] == old)
|
||||
|
@ -493,123 +466,30 @@ 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++)
|
||||
{
|
||||
if (playeringame[i])
|
||||
players[i].FixPointers (old, notOld);
|
||||
}
|
||||
|
||||
// Go through sectors.
|
||||
if (sectors != NULL)
|
||||
{
|
||||
for (i = 0; i < (unsigned int)numsectors; ++i)
|
||||
for (i = 0; i < numsectors; ++i)
|
||||
{
|
||||
if (sectors[i].SoundTarget == old)
|
||||
{
|
||||
sectors[i].SoundTarget = static_cast<AActor *>(notOld);
|
||||
}
|
||||
if (sectors[i].CeilingSkyBox == old)
|
||||
{
|
||||
sectors[i].CeilingSkyBox = static_cast<ASkyViewpoint *>(notOld);
|
||||
}
|
||||
if (sectors[i].FloorSkyBox == old)
|
||||
{
|
||||
sectors[i].FloorSkyBox = static_cast<ASkyViewpoint *>(notOld);
|
||||
#define SECTOR_CHECK(f,t) \
|
||||
if (sectors[i].f == static_cast<t *>(old)) { sectors[i].f = static_cast<t *>(notOld); }
|
||||
SECTOR_CHECK( SoundTarget, AActor );
|
||||
SECTOR_CHECK( CeilingSkyBox, ASkyViewpoint );
|
||||
SECTOR_CHECK( FloorSkyBox, ASkyViewpoint );
|
||||
SECTOR_CHECK( SecActTarget, ASectorAction );
|
||||
SECTOR_CHECK( floordata, DSectorEffect );
|
||||
SECTOR_CHECK( ceilingdata, DSectorEffect );
|
||||
SECTOR_CHECK( lightingdata, DSectorEffect );
|
||||
#undef SECTOR_CHECK
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
|
302
src/dobject.h
302
src/dobject.h
|
@ -2,7 +2,7 @@
|
|||
** dobject.h
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 1998-2006 Randy Heit
|
||||
** Copyright 1998-2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
|
@ -48,7 +48,6 @@ class FArchive;
|
|||
|
||||
class DObject;
|
||||
class DArgs;
|
||||
class DBoundingBox;
|
||||
class DCanvas;
|
||||
class DConsoleCommand;
|
||||
class DConsoleAlias;
|
||||
|
@ -211,12 +210,205 @@ private: \
|
|||
|
||||
enum EObjectFlags
|
||||
{
|
||||
OF_MassDestruction = 0x00000001, // Object is queued for deletion
|
||||
OF_Cleanup = 0x00000002, // Object is being deconstructed as a result of a queued deletion
|
||||
OF_JustSpawned = 0x00000004, // Thinker was spawned this tic
|
||||
OF_SerialSuccess = 0x10000000 // For debugging Serialize() calls
|
||||
// GC flags
|
||||
OF_White0 = 1 << 0, // Object is white (type 0)
|
||||
OF_White1 = 1 << 1, // Object is white (type 1)
|
||||
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
|
||||
{
|
||||
public:
|
||||
|
@ -227,12 +419,13 @@ public:
|
|||
private:
|
||||
typedef DObject ThisClass;
|
||||
|
||||
// Per-instance variables. There are three.
|
||||
public:
|
||||
DWORD ObjectFlags; // Flags for this object
|
||||
// Per-instance variables. There are four.
|
||||
private:
|
||||
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:
|
||||
DObject ();
|
||||
|
@ -250,16 +443,11 @@ public:
|
|||
|
||||
virtual void Destroy ();
|
||||
|
||||
static void BeginFrame ();
|
||||
static void EndFrame ();
|
||||
|
||||
// If you need to replace one object with another and want to
|
||||
// change any pointers from the old object to the new object,
|
||||
// use this method.
|
||||
static void PointerSubstitution (DObject *old, DObject *notOld);
|
||||
|
||||
static void StaticShutdown ();
|
||||
|
||||
PClass *GetClass() const
|
||||
{
|
||||
if (Class == NULL)
|
||||
|
@ -286,6 +474,60 @@ public:
|
|||
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:
|
||||
// This form of placement new and delete is for use *only* by PClass's
|
||||
// CreateNew() method. Do not use them for some other purpose.
|
||||
|
@ -296,22 +538,26 @@ protected:
|
|||
|
||||
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"
|
||||
|
||||
inline bool DObject::IsKindOf (const PClass *base) const
|
||||
|
|
623
src/dobjgc.cpp
Normal file
623
src/dobjgc.cpp
Normal 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]));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ DSectorEffect::DSectorEffect ()
|
|||
m_Sector = NULL;
|
||||
}
|
||||
|
||||
DSectorEffect::~DSectorEffect ()
|
||||
void DSectorEffect::Destroy()
|
||||
{
|
||||
if (m_Sector)
|
||||
{
|
||||
|
@ -52,6 +52,7 @@ DSectorEffect::~DSectorEffect ()
|
|||
m_Sector->lightingdata = NULL;
|
||||
}
|
||||
}
|
||||
Super::Destroy();
|
||||
}
|
||||
|
||||
DSectorEffect::DSectorEffect (sector_t *sector)
|
||||
|
|
|
@ -10,9 +10,9 @@ class DSectorEffect : public DThinker
|
|||
DECLARE_CLASS (DSectorEffect, DThinker)
|
||||
public:
|
||||
DSectorEffect (sector_t *sector);
|
||||
~DSectorEffect ();
|
||||
|
||||
void Serialize (FArchive &arc);
|
||||
void Destroy();
|
||||
|
||||
sector_t *GetSector() const { return m_Sector; }
|
||||
|
||||
|
|
|
@ -155,6 +155,14 @@ DThinker::DThinker (int statnum) throw()
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -179,6 +187,16 @@ void DThinker::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 ()
|
||||
{
|
||||
}
|
||||
|
@ -205,6 +223,8 @@ DThinker *DThinker::FirstThinker (int statnum)
|
|||
|
||||
void DThinker::ChangeStatNum (int statnum)
|
||||
{
|
||||
List *list;
|
||||
|
||||
if ((unsigned)statnum > MAX_STATNUM)
|
||||
{
|
||||
statnum = MAX_STATNUM;
|
||||
|
@ -212,12 +232,44 @@ void DThinker::ChangeStatNum (int statnum)
|
|||
Remove ();
|
||||
if ((ObjectFlags & OF_JustSpawned) && statnum >= STAT_FIRST_THINKING)
|
||||
{
|
||||
FreshThinkers[statnum].AddTail (this);
|
||||
list = &FreshThinkers[statnum];
|
||||
}
|
||||
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
|
||||
|
@ -225,7 +277,6 @@ void DThinker::DestroyAllThinkers ()
|
|||
{
|
||||
int i;
|
||||
|
||||
DObject::BeginFrame ();
|
||||
for (i = 0; i <= MAX_STATNUM; i++)
|
||||
{
|
||||
if (i != STAT_TRAVELLING)
|
||||
|
@ -234,7 +285,7 @@ void DThinker::DestroyAllThinkers ()
|
|||
DestroyThinkersInList (FreshThinkers[i].Head);
|
||||
}
|
||||
}
|
||||
DObject::EndFrame ();
|
||||
GC::FullGC ();
|
||||
}
|
||||
|
||||
// Destroy all thinkers except for player-controlled actors
|
||||
|
@ -244,7 +295,6 @@ void DThinker::DestroyMostThinkers ()
|
|||
{
|
||||
int i;
|
||||
|
||||
DObject::BeginFrame ();
|
||||
for (i = 0; i <= MAX_STATNUM; i++)
|
||||
{
|
||||
if (i != STAT_TRAVELLING)
|
||||
|
@ -253,7 +303,7 @@ void DThinker::DestroyMostThinkers ()
|
|||
DestroyMostThinkersInList (FreshThinkers[i], i);
|
||||
}
|
||||
}
|
||||
DObject::EndFrame ();
|
||||
GC::FullGC ();
|
||||
}
|
||||
|
||||
void DThinker::DestroyThinkersInList (Node *node)
|
||||
|
@ -327,6 +377,14 @@ int DThinker::TickThinkers (List *list, List *dest)
|
|||
if (dest != NULL)
|
||||
{ // Move thinker from this list to the destination list
|
||||
node->Remove ();
|
||||
if (dest->TailPred->Pred != NULL)
|
||||
{
|
||||
GC::WriteBarrier(static_cast<DThinker*>(dest->Tail, thinker));
|
||||
}
|
||||
else
|
||||
{
|
||||
GC::WriteBarrier(thinker);
|
||||
}
|
||||
dest->AddTail (node);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
if (!(thinker->ObjectFlags & OF_MassDestruction))
|
||||
if (!(thinker->ObjectFlags & OF_EuthanizeMe))
|
||||
{ // Only tick thinkers not scheduled for destruction
|
||||
thinker->Tick ();
|
||||
}
|
||||
|
|
|
@ -49,13 +49,14 @@ class FThinkerIterator;
|
|||
enum { MAX_STATNUM = 127 };
|
||||
|
||||
// Doubly linked list of thinkers
|
||||
class DThinker : public DObject, public Node
|
||||
class DThinker : public DObject, protected Node
|
||||
{
|
||||
DECLARE_CLASS (DThinker, DObject)
|
||||
|
||||
public:
|
||||
DThinker (int statnum = MAX_STATNUM) throw();
|
||||
void Destroy ();
|
||||
size_t PropagateMark();
|
||||
virtual ~DThinker ();
|
||||
virtual void Tick ();
|
||||
virtual void PostBeginPlay (); // Called just before the first tick
|
||||
|
@ -67,6 +68,7 @@ public:
|
|||
static void DestroyAllThinkers ();
|
||||
static void DestroyMostThinkers ();
|
||||
static void SerializeAll (FArchive &arc, bool keepPlayers);
|
||||
static void MarkRoots();
|
||||
|
||||
static DThinker *FirstThinker (int statnum);
|
||||
|
||||
|
@ -75,6 +77,7 @@ private:
|
|||
static void DestroyMostThinkersInList (List &list, int stat);
|
||||
static int TickThinkers (List *list, List *dest); // Returns: # of thinkers ticked
|
||||
static void SaveList(FArchive &arc, Node *node);
|
||||
void Remove();
|
||||
|
||||
static List Thinkers[MAX_STATNUM+1]; // Current thinkers
|
||||
static List FreshThinkers[MAX_STATNUM+1]; // Newly created thinkers
|
||||
|
|
|
@ -1004,9 +1004,17 @@ FArchive &FArchive::SerializePointer (void *ptrbase, BYTE **ptr, DWORD elemSize)
|
|||
FArchive &FArchive::SerializeObject (DObject *&object, PClass *type)
|
||||
{
|
||||
if (IsStoring ())
|
||||
{
|
||||
if (object != (DObject*)~0)
|
||||
{
|
||||
GC::ReadBarrier(object);
|
||||
}
|
||||
return WriteObject (object);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ReadObject (object, type);
|
||||
}
|
||||
}
|
||||
|
||||
FArchive &FArchive::WriteObject (DObject *obj)
|
||||
|
|
|
@ -1925,6 +1925,11 @@ void FParser::SF_FadeLight(void)
|
|||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
void FParser::SF_FloorTexture(void)
|
||||
{
|
||||
int tagnum, secnum;
|
||||
|
@ -2066,12 +2071,13 @@ void FParser::SF_StartSkill(void)
|
|||
I_Error("startskill is not supported by this implementation!\n");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//==========================================================================
|
||||
//
|
||||
// Doors
|
||||
//
|
||||
|
||||
// opendoor(sectag, [delay], [speed])
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FParser::SF_OpenDoor(void)
|
||||
{
|
||||
|
@ -2204,13 +2210,13 @@ inline line_t * P_FindLine(int tag,int * searchPosition)
|
|||
return *searchPosition>=0? &lines[*searchPosition]:NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
FParser::SF_SetLineBlocking()
|
||||
|
||||
Sets a line blocking or unblocking
|
||||
|
||||
setlineblocking(tag, [1|0]);
|
||||
*/
|
||||
//==========================================================================
|
||||
//
|
||||
// FParser::SF_SetLineBlocking()
|
||||
//
|
||||
// Sets a line blocking or unblocking
|
||||
//
|
||||
//==========================================================================
|
||||
void FParser::SF_SetLineBlocking(void)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
fixed_t n1, n2;
|
||||
|
@ -2417,13 +2422,15 @@ void FParser::SF_Abs(void)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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
|
||||
variable already.
|
||||
*/
|
||||
//==========================================================================
|
||||
//
|
||||
// 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
|
||||
// variable already.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FParser::SF_Gameskill(void)
|
||||
{
|
||||
|
@ -2452,13 +2459,16 @@ void FParser::SF_Gamemode(void)
|
|||
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)
|
||||
{
|
||||
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
|
||||
// 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)
|
||||
{
|
||||
// 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"};
|
||||
int playernum, keynum, givetake;
|
||||
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)
|
||||
{
|
||||
// This function is just kept for backwards compatibility and intentionally limited!
|
||||
// Use Give/Take/CheckInventory instead!
|
||||
int playernum, amount;
|
||||
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" };
|
||||
|
||||
|
||||
// This function is just kept for backwards compatibility and intentionally limited to the standard weapons!
|
||||
// Use Give/Take/CheckInventory instead!
|
||||
int playernum;
|
||||
int weaponnum;
|
||||
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 weaponnum;
|
||||
|
||||
// This function is just kept for backwards compatibility and intentionally limited to the standard weapons!
|
||||
|
||||
static const char * const WeaponNames[]={
|
||||
"Fist", "Pistol", "Shotgun", "Chaingun", "RocketLauncher",
|
||||
|
@ -2991,13 +3005,11 @@ void FParser::SF_SetWeapon()
|
|||
}
|
||||
}
|
||||
|
||||
// removed FParser::SF_PlayerMaxAmmo
|
||||
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// movecamera(camera, targetobj, targetheight, movespeed, targetangle, anglespeed)
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
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)
|
||||
{
|
||||
if (CheckArgs(1))
|
||||
|
@ -3448,7 +3458,7 @@ void FParser::SF_SpawnMissile()
|
|||
|
||||
void FParser::SF_MapThingNumExist()
|
||||
{
|
||||
TArray<DActorPointer*> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings;
|
||||
TArray<TObjPtr<AActor>> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings;
|
||||
|
||||
int intval;
|
||||
|
||||
|
@ -3456,7 +3466,7 @@ void FParser::SF_MapThingNumExist()
|
|||
{
|
||||
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.value.i = 0;
|
||||
|
@ -3477,7 +3487,7 @@ void FParser::SF_MapThingNumExist()
|
|||
|
||||
void FParser::SF_MapThings()
|
||||
{
|
||||
TArray<DActorPointer*> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings;
|
||||
TArray<TObjPtr<AActor>> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings;
|
||||
|
||||
t_return.type = svt_int;
|
||||
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->prev = th->RunningScripts;
|
||||
runscr->prev->next = runscr;
|
||||
GC::WriteBarrier(runscr->prev, runscr);
|
||||
if(runscr->next)
|
||||
{
|
||||
runscr->next->prev = runscr;
|
||||
GC::WriteBarrier(runscr->next, runscr);
|
||||
}
|
||||
|
||||
// save the script variables
|
||||
for(i=0; i<VARIABLESLOTS; i++)
|
||||
|
@ -4516,8 +4530,12 @@ void FParser::SF_StartScript()
|
|||
runscr->next = th->RunningScripts->next;
|
||||
runscr->prev = th->RunningScripts;
|
||||
runscr->prev->next = runscr;
|
||||
GC::WriteBarrier(runscr->prev, runscr);
|
||||
if(runscr->next)
|
||||
{
|
||||
runscr->next->prev = runscr;
|
||||
GC::WriteBarrier(runscr->next, runscr);
|
||||
}
|
||||
|
||||
// save the script variables
|
||||
for(i=0; i<VARIABLESLOTS; i++)
|
||||
|
|
|
@ -268,16 +268,13 @@ void T_LoadLevelInfo(MapData * map)
|
|||
// If no actor is spawned it will remain NULL, otherwise T_RegisterSpawnThing
|
||||
// will be called to set it
|
||||
//
|
||||
// This uses a DActorPointer so that it is subject to automatic pointer cleanup
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void T_PrepareSpawnThing()
|
||||
{
|
||||
if (DFraggleThinker::ActiveThinker)
|
||||
{
|
||||
DActorPointer * acp = new DActorPointer;
|
||||
DFraggleThinker::ActiveThinker->SpawnedThings.Push(acp);
|
||||
DFraggleThinker::ActiveThinker->SpawnedThings.Push(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,7 +288,7 @@ void T_RegisterSpawnThing(AActor * ac)
|
|||
{
|
||||
if (DFraggleThinker::ActiveThinker)
|
||||
{
|
||||
TArray<DActorPointer*> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings;
|
||||
SpawnedThings[SpawnedThings.Size()-1]->actor=ac;
|
||||
TArray<TObjPtr<AActor>> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings;
|
||||
SpawnedThings[SpawnedThings.Size()-1] = ac;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,12 @@
|
|||
EvaluateExpression(right, (b)+1, (c)); }\
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
FParser::operator_t FParser::operators[]=
|
||||
{
|
||||
{"=", &FParser::OPequals, backward},
|
||||
|
@ -81,11 +87,11 @@ int FParser::num_operators = sizeof(FParser::operators) / sizeof(FParser::operat
|
|||
|
||||
/***************** logic *********************/
|
||||
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -157,11 +163,11 @@ void FParser::OPand(svalue_t &result, int start, int n, int stop)
|
|||
result.value.i = exprtrue;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -250,11 +256,11 @@ void FParser::OPgreaterthan(svalue_t &result, int start, int n, int stop)
|
|||
result.value.i = (intvalue(left) > intvalue(right));
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/************** simple math ***************/
|
||||
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -429,13 +433,11 @@ void FParser::OPremainder(svalue_t &result, int start, int n, int stop)
|
|||
|
||||
/********** binary operators **************/
|
||||
|
||||
// binary or |
|
||||
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -466,11 +466,11 @@ void FParser::OPand_bin(svalue_t &result, int start, int n, int stop)
|
|||
result.value.i = intvalue(left) & intvalue(right);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -631,11 +631,11 @@ void FParser::OPlessthanorequal(svalue_t &result, int start, int n, int stop)
|
|||
result.value.i = (intvalue(left) <= intvalue(right));
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void FParser::OPgreaterthanorequal(svalue_t &result, int start, int n, int stop)
|
||||
{
|
||||
|
|
|
@ -154,6 +154,7 @@ DFsSection *DFsScript::NewSection(const char *brace)
|
|||
newsec->start_index = MakeIndex(brace);
|
||||
newsec->next = sections[n];
|
||||
sections[n] = newsec;
|
||||
GC::WriteBarrier(this, newsec);
|
||||
return newsec;
|
||||
}
|
||||
|
||||
|
|
|
@ -320,10 +320,6 @@ void DRunningScript::Serialize(FArchive &arc)
|
|||
// The main thinker
|
||||
//
|
||||
//==========================================================================
|
||||
IMPLEMENT_POINTY_CLASS(DActorPointer)
|
||||
DECLARE_POINTER(actor)
|
||||
END_POINTERS
|
||||
|
||||
IMPLEMENT_POINTY_CLASS(DFraggleThinker)
|
||||
DECLARE_POINTER(RunningScripts)
|
||||
DECLARE_POINTER(LevelScript)
|
||||
|
@ -373,10 +369,6 @@ void DFraggleThinker::Destroy()
|
|||
LevelScript->Destroy();
|
||||
LevelScript = NULL;
|
||||
|
||||
for(unsigned i=0; i<SpawnedThings.Size(); i++)
|
||||
{
|
||||
SpawnedThings[i]->Destroy();
|
||||
}
|
||||
SpawnedThings.Clear();
|
||||
ActiveThinker = NULL;
|
||||
Super::Destroy();
|
||||
|
@ -488,7 +480,12 @@ void DFraggleThinker::Tick()
|
|||
|
||||
// unhook from chain
|
||||
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
|
||||
|
||||
// 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->prev = th->RunningScripts;
|
||||
runscr->prev->next = runscr;
|
||||
GC::WriteBarrier(runscr->prev, runscr);
|
||||
if(runscr->next)
|
||||
{
|
||||
runscr->next->prev = runscr;
|
||||
GC::WriteBarrier(runscr->next, runscr);
|
||||
}
|
||||
|
||||
// save the script variables
|
||||
for(i=0; i<VARIABLESLOTS; i++)
|
||||
|
@ -612,11 +630,13 @@ void FS_Close()
|
|||
{
|
||||
next = current->next; // save for after freeing
|
||||
|
||||
//current->ObjectFlags |= OF_YESREALLYDELETE;
|
||||
current->ObjectFlags |= OF_YesReallyDelete;
|
||||
delete current;
|
||||
current = next; // go to next in chain
|
||||
}
|
||||
}
|
||||
GC::DelSoftRoot(global_script);
|
||||
global_script->ObjectFlags |= OF_YesReallyDelete;
|
||||
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!
|
||||
LineSpecials[54]=LS_FS_Execute;
|
||||
global_script = new DFsScript;
|
||||
GC::AddSoftRoot(global_script);
|
||||
init_functions();
|
||||
atterm(FS_Close);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
svt_string,
|
||||
|
@ -168,11 +141,11 @@ struct DFsVariable : public DObject
|
|||
|
||||
public:
|
||||
FString Name;
|
||||
DFsVariable *next; // for hashing
|
||||
TObjPtr<DFsVariable> next; // for hashing
|
||||
|
||||
int type; // vt_string or vt_int: same as in svalue_t
|
||||
FString string;
|
||||
AActor *actor;
|
||||
TObjPtr<AActor> actor;
|
||||
|
||||
union value_t
|
||||
{
|
||||
|
@ -190,7 +163,7 @@ public:
|
|||
|
||||
DFsVariable(const char *_name = "");
|
||||
|
||||
svalue_t GetValue() const;
|
||||
svalue_t GetValue();
|
||||
void SetValue(const svalue_t &newvalue);
|
||||
void Serialize(FArchive &ar);
|
||||
};
|
||||
|
@ -234,7 +207,7 @@ public:
|
|||
int start_index;
|
||||
int end_index;
|
||||
int loop_index;
|
||||
DFsSection *next; // for hashing
|
||||
TObjPtr<DFsSection> next; // for hashing
|
||||
|
||||
DFsSection()
|
||||
{
|
||||
|
@ -313,27 +286,27 @@ public:
|
|||
|
||||
// {} sections
|
||||
|
||||
DFsSection *sections[SECTIONSLOTS];
|
||||
TObjPtr<DFsSection> sections[SECTIONSLOTS];
|
||||
|
||||
// variables:
|
||||
|
||||
DFsVariable *variables[VARIABLESLOTS];
|
||||
TObjPtr<DFsVariable> variables[VARIABLESLOTS];
|
||||
|
||||
// ptr to the parent script
|
||||
// the parent script is the script above this level
|
||||
// eg. individual linetrigger scripts are children
|
||||
// of the levelscript, which is a child of the
|
||||
// global_script
|
||||
DFsScript *parent;
|
||||
TObjPtr<DFsScript> parent;
|
||||
|
||||
// haleyjd: 8-17
|
||||
// child scripts.
|
||||
// levelscript holds ptrs to all of the level's scripts
|
||||
// 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
|
||||
// true or false
|
||||
|
@ -654,7 +627,7 @@ public:
|
|||
void Destroy();
|
||||
void Serialize(FArchive &arc);
|
||||
|
||||
DFsScript *script;
|
||||
TObjPtr<DFsScript> script;
|
||||
|
||||
// where we are
|
||||
int save_point;
|
||||
|
@ -663,10 +636,10 @@ public:
|
|||
int wait_data; // data for wait: tagnum, counter, script number etc
|
||||
|
||||
// saved variables
|
||||
DFsVariable *variables[VARIABLESLOTS];
|
||||
TObjPtr<DFsVariable> variables[VARIABLESLOTS];
|
||||
|
||||
DRunningScript *prev, *next; // for chain
|
||||
AActor *trigger;
|
||||
TObjPtr<DRunningScript> prev, next; // for chain
|
||||
TObjPtr<AActor> trigger;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -680,9 +653,9 @@ class DFraggleThinker : public DThinker
|
|||
HAS_OBJECT_POINTERS
|
||||
public:
|
||||
|
||||
DFsScript *LevelScript;
|
||||
DRunningScript *RunningScripts;
|
||||
TArray<DActorPointer*> SpawnedThings;
|
||||
TObjPtr<DFsScript> LevelScript;
|
||||
TObjPtr<DRunningScript> RunningScripts;
|
||||
TArray<TObjPtr<AActor>> SpawnedThings;
|
||||
|
||||
DFraggleThinker();
|
||||
void Destroy();
|
||||
|
@ -690,6 +663,7 @@ public:
|
|||
|
||||
void Serialize(FArchive & arc);
|
||||
void Tick();
|
||||
size_t PropagateMark();
|
||||
bool wait_finished(DRunningScript *script);
|
||||
|
||||
static DFraggleThinker *ActiveThinker;
|
||||
|
|
|
@ -421,6 +421,7 @@ void FParser::spec_script()
|
|||
|
||||
// add to scripts list of parent
|
||||
Script->children[scriptnum] = newscript;
|
||||
GC::WriteBarrier(Script, newscript);
|
||||
|
||||
// copy newscript data
|
||||
// workout newscript size: -2 to ignore { and }
|
||||
|
|
|
@ -151,7 +151,7 @@ AActor *actorvalue(const svalue_t &svalue)
|
|||
}
|
||||
else
|
||||
{
|
||||
TArray<DActorPointer*> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings;
|
||||
TArray<TObjPtr<AActor>> &SpawnedThings = DFraggleThinker::ActiveThinker->SpawnedThings;
|
||||
// this requires some creativity. We use the intvalue
|
||||
// as the thing number of a thing in the level.
|
||||
intval = intvalue(svalue);
|
||||
|
@ -161,14 +161,14 @@ AActor *actorvalue(const svalue_t &svalue)
|
|||
return NULL;
|
||||
}
|
||||
// Inventory items in the player's inventory have to be considered non-present.
|
||||
if (SpawnedThings[intval]->actor != NULL &&
|
||||
SpawnedThings[intval]->actor->IsKindOf(RUNTIME_CLASS(AInventory)) &&
|
||||
static_cast<AInventory*>(SpawnedThings[intval]->actor)->Owner != NULL)
|
||||
if (SpawnedThings[intval] != NULL &&
|
||||
SpawnedThings[intval]->IsKindOf(RUNTIME_CLASS(AInventory)) &&
|
||||
barrier_cast<AInventory*>(SpawnedThings[intval])->Owner != 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;
|
||||
|
||||
|
@ -323,6 +323,7 @@ DFsVariable *DFsScript::NewVariable(const char *name, int vtype)
|
|||
int n = variable_hash(name);
|
||||
newvar->next = variables[n];
|
||||
variables[n] = newvar;
|
||||
GC::WriteBarrier(this, newvar);
|
||||
return newvar;
|
||||
}
|
||||
|
||||
|
|
|
@ -315,7 +315,7 @@ void A_SpawnFly (AActor *self)
|
|||
if (newmobj->SeeState != NULL && P_LookForPlayers (newmobj, true))
|
||||
newmobj->SetState (newmobj->SeeState);
|
||||
|
||||
if (!(newmobj->ObjectFlags & OF_MassDestruction))
|
||||
if (!(newmobj->ObjectFlags & OF_EuthanizeMe))
|
||||
{
|
||||
// telefrag anything in this spot
|
||||
P_TeleportMove (newmobj, newmobj->x, newmobj->y, newmobj->z, true);
|
||||
|
|
|
@ -27,10 +27,11 @@
|
|||
EXTERN_CVAR (Bool, vid_fps)
|
||||
|
||||
|
||||
class FDoomStatusBar : public FBaseStatusBar
|
||||
class DDoomStatusBar : public DBaseStatusBar
|
||||
{
|
||||
DECLARE_CLASS(DDoomStatusBar, DBaseStatusBar)
|
||||
public:
|
||||
FDoomStatusBar () : FBaseStatusBar (32)
|
||||
DDoomStatusBar () : DBaseStatusBar (32)
|
||||
{
|
||||
static const char *sharedLumpNames[] =
|
||||
{
|
||||
|
@ -44,8 +45,8 @@ public:
|
|||
};
|
||||
FTexture *tex;
|
||||
|
||||
FBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES);
|
||||
tex = FBaseStatusBar::Images[imgBNumbers];
|
||||
DBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES);
|
||||
tex = DBaseStatusBar::Images[imgBNumbers];
|
||||
BigWidth = tex->GetWidth();
|
||||
BigHeight = tex->GetHeight();
|
||||
|
||||
|
@ -53,7 +54,7 @@ public:
|
|||
bEvilGrin = false;
|
||||
}
|
||||
|
||||
~FDoomStatusBar ()
|
||||
~DDoomStatusBar ()
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -126,7 +127,7 @@ public:
|
|||
|
||||
void MultiplayerChanged ()
|
||||
{
|
||||
FBaseStatusBar::MultiplayerChanged ();
|
||||
DBaseStatusBar::MultiplayerChanged ();
|
||||
if (multiplayer)
|
||||
{
|
||||
// set face background color
|
||||
|
@ -138,7 +139,7 @@ public:
|
|||
{
|
||||
player_t *oldplayer = CPlayer;
|
||||
|
||||
FBaseStatusBar::AttachToPlayer (player);
|
||||
DBaseStatusBar::AttachToPlayer (player);
|
||||
if (oldplayer != CPlayer)
|
||||
{
|
||||
SetFace (&skins[CPlayer->userinfo.skin]);
|
||||
|
@ -153,14 +154,14 @@ public:
|
|||
|
||||
void Tick ()
|
||||
{
|
||||
FBaseStatusBar::Tick ();
|
||||
DBaseStatusBar::Tick ();
|
||||
RandomNumber = M_Random ();
|
||||
UpdateFace ();
|
||||
}
|
||||
|
||||
void Draw (EHudState state)
|
||||
{
|
||||
FBaseStatusBar::Draw (state);
|
||||
DBaseStatusBar::Draw (state);
|
||||
|
||||
if (state == HUD_Fullscreen)
|
||||
{
|
||||
|
@ -375,7 +376,7 @@ private:
|
|||
void DrawArm (int on, int picnum, int x, int y, bool drawBackground)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -555,7 +556,7 @@ private:
|
|||
|
||||
void DrawInventoryBar ()
|
||||
{
|
||||
const AInventory *item;
|
||||
AInventory *item;
|
||||
int i;
|
||||
|
||||
// If the player has no artifacts, don't draw the bar
|
||||
|
@ -602,7 +603,7 @@ private:
|
|||
|
||||
void DrawFullScreenStuff ()
|
||||
{
|
||||
const AInventory *item;
|
||||
AInventory *item;
|
||||
int i;
|
||||
int ammotop;
|
||||
|
||||
|
@ -1041,7 +1042,9 @@ private:
|
|||
bool bEvilGrin;
|
||||
};
|
||||
|
||||
FDoomStatusBar::FDoomStatusBarTexture::FDoomStatusBarTexture ()
|
||||
IMPLEMENT_CLASS(DDoomStatusBar)
|
||||
|
||||
DDoomStatusBar::FDoomStatusBarTexture::FDoomStatusBarTexture ()
|
||||
{
|
||||
BaseTexture = TexMan["STBAR"];
|
||||
if (BaseTexture==NULL)
|
||||
|
@ -1057,7 +1060,7 @@ FDoomStatusBar::FDoomStatusBarTexture::FDoomStatusBarTexture ()
|
|||
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)
|
||||
{
|
||||
|
@ -1068,7 +1071,7 @@ const BYTE *FDoomStatusBar::FDoomStatusBarTexture::GetColumn (unsigned int colum
|
|||
return Pixels + column*Height;
|
||||
}
|
||||
|
||||
const BYTE *FDoomStatusBar::FDoomStatusBarTexture::GetPixels ()
|
||||
const BYTE *DDoomStatusBar::FDoomStatusBarTexture::GetPixels ()
|
||||
{
|
||||
if (Pixels == NULL)
|
||||
{
|
||||
|
@ -1077,7 +1080,7 @@ const BYTE *FDoomStatusBar::FDoomStatusBarTexture::GetPixels ()
|
|||
return Pixels;
|
||||
}
|
||||
|
||||
void FDoomStatusBar::FDoomStatusBarTexture::Unload ()
|
||||
void DDoomStatusBar::FDoomStatusBarTexture::Unload ()
|
||||
{
|
||||
if (Pixels != NULL)
|
||||
{
|
||||
|
@ -1086,13 +1089,13 @@ void FDoomStatusBar::FDoomStatusBarTexture::Unload ()
|
|||
}
|
||||
}
|
||||
|
||||
FDoomStatusBar::FDoomStatusBarTexture::~FDoomStatusBarTexture ()
|
||||
DDoomStatusBar::FDoomStatusBarTexture::~FDoomStatusBarTexture ()
|
||||
{
|
||||
Unload ();
|
||||
}
|
||||
|
||||
|
||||
void FDoomStatusBar::FDoomStatusBarTexture::MakeTexture ()
|
||||
void DDoomStatusBar::FDoomStatusBarTexture::MakeTexture ()
|
||||
{
|
||||
Pixels = new BYTE[Width*Height];
|
||||
const BYTE *pix = BaseTexture->GetPixels();
|
||||
|
@ -1103,7 +1106,7 @@ void FDoomStatusBar::FDoomStatusBarTexture::MakeTexture ()
|
|||
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;
|
||||
|
||||
|
@ -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;
|
||||
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();
|
||||
KillNative();
|
||||
STBFremap = remap;
|
||||
}
|
||||
|
||||
FBaseStatusBar *CreateDoomStatusBar ()
|
||||
DBaseStatusBar *CreateDoomStatusBar ()
|
||||
{
|
||||
return new FDoomStatusBar;
|
||||
return new DDoomStatusBar;
|
||||
}
|
||||
|
|
|
@ -1850,7 +1850,7 @@ FString G_BuildSaveName (const char *prefix, int slot)
|
|||
const char *leader;
|
||||
const char *slash = "";
|
||||
|
||||
if (NULL != (leader = Args.CheckValue ("-savedir")))
|
||||
if (NULL != (leader = Args->CheckValue ("-savedir")))
|
||||
{
|
||||
size_t len = strlen (leader);
|
||||
if (leader[len-1] != '\\' && leader[len-1] != '/')
|
||||
|
@ -1859,7 +1859,7 @@ FString G_BuildSaveName (const char *prefix, int slot)
|
|||
}
|
||||
}
|
||||
#ifndef unix
|
||||
else if (Args.CheckParm ("-cdrom"))
|
||||
else if (Args->CheckParm ("-cdrom"))
|
||||
{
|
||||
leader = CDROM_DIR "/";
|
||||
}
|
||||
|
@ -2205,7 +2205,7 @@ void G_RecordDemo (char* name)
|
|||
strcpy (demoname, name);
|
||||
FixPathSeperator (demoname);
|
||||
DefaultExtension (demoname, ".lmp");
|
||||
v = Args.CheckValue ("-maxdemo");
|
||||
v = Args->CheckValue ("-maxdemo");
|
||||
maxdemosize = 0x20000;
|
||||
demobuffer = (BYTE *)M_Malloc (maxdemosize);
|
||||
|
||||
|
@ -2510,8 +2510,8 @@ void G_DoPlayDemo (void)
|
|||
//
|
||||
void G_TimeDemo (char* name)
|
||||
{
|
||||
nodrawers = !!Args.CheckParm ("-nodraw");
|
||||
noblit = !!Args.CheckParm ("-noblit");
|
||||
nodrawers = !!Args->CheckParm ("-nodraw");
|
||||
noblit = !!Args->CheckParm ("-noblit");
|
||||
timingdemo = true;
|
||||
singletics = true;
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class APod : public AActor
|
|||
HAS_OBJECT_POINTERS
|
||||
public:
|
||||
void BeginPlay ();
|
||||
AActor *Generator;
|
||||
TObjPtr<AActor> Generator;
|
||||
|
||||
void Serialize (FArchive &arc);
|
||||
};
|
||||
|
|
|
@ -803,7 +803,7 @@ public:
|
|||
protected:
|
||||
bool DoRespawn ();
|
||||
int NumMaceSpots;
|
||||
AActor *FirstSpot;
|
||||
TObjPtr<AActor> FirstSpot;
|
||||
private:
|
||||
|
||||
friend void A_SpawnMace (AActor *self);
|
||||
|
|
|
@ -84,10 +84,12 @@ const BYTE *FHereticShader::GetPixels ()
|
|||
}
|
||||
|
||||
|
||||
class FHereticStatusBar : public FBaseStatusBar
|
||||
class DHereticStatusBar : public DBaseStatusBar
|
||||
{
|
||||
DECLARE_CLASS(DHereticStatusBar, DBaseStatusBar)
|
||||
HAS_OBJECT_POINTERS
|
||||
public:
|
||||
FHereticStatusBar () : FBaseStatusBar (42)
|
||||
DHereticStatusBar () : DBaseStatusBar (42)
|
||||
{
|
||||
static const char *hereticLumpNames[NUM_HERETICSB_IMAGES] =
|
||||
{
|
||||
|
@ -118,7 +120,7 @@ public:
|
|||
hereticLumpNames[5] = "LIFEBAR";
|
||||
}
|
||||
|
||||
FBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES);
|
||||
DBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES);
|
||||
Images.Init (hereticLumpNames, NUM_HERETICSB_IMAGES);
|
||||
|
||||
oldarti = NULL;
|
||||
|
@ -136,7 +138,7 @@ public:
|
|||
ArtifactFlash = 0;
|
||||
}
|
||||
|
||||
~FHereticStatusBar ()
|
||||
~DHereticStatusBar ()
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -144,7 +146,7 @@ public:
|
|||
{
|
||||
int curHealth;
|
||||
|
||||
FBaseStatusBar::Tick ();
|
||||
DBaseStatusBar::Tick ();
|
||||
if (level.time & 1)
|
||||
{
|
||||
ChainWiggle = pr_chainwiggle() & 1;
|
||||
|
@ -174,7 +176,7 @@ public:
|
|||
|
||||
void Draw (EHudState state)
|
||||
{
|
||||
FBaseStatusBar::Draw (state);
|
||||
DBaseStatusBar::Draw (state);
|
||||
|
||||
if (state == HUD_Fullscreen)
|
||||
{
|
||||
|
@ -316,6 +318,7 @@ private:
|
|||
|| (oldarti != NULL && oldartiCount != oldarti->Amount))
|
||||
{
|
||||
oldarti = CPlayer->mo->InvSel;
|
||||
GC::WriteBarrier(this, oldarti);
|
||||
oldartiCount = oldarti != NULL ? oldarti->Amount : 0;
|
||||
ArtiRefresh = screen->GetPageCount ();
|
||||
}
|
||||
|
@ -422,6 +425,8 @@ private:
|
|||
oldammo2 = ammo2;
|
||||
oldammocount1 = ammocount1;
|
||||
oldammocount2 = ammocount2;
|
||||
GC::WriteBarrier(this, ammo1);
|
||||
GC::WriteBarrier(this, ammo2);
|
||||
AmmoRefresh = screen->GetPageCount ();
|
||||
}
|
||||
if (AmmoRefresh)
|
||||
|
@ -475,7 +480,7 @@ private:
|
|||
|
||||
void DrawInventoryBar ()
|
||||
{
|
||||
const AInventory *item;
|
||||
AInventory *item;
|
||||
int i;
|
||||
|
||||
DrawImage (Images[imgINVBAR], 34, 2);
|
||||
|
@ -517,7 +522,7 @@ private:
|
|||
|
||||
void DrawFullScreenStuff ()
|
||||
{
|
||||
const AInventory *item;
|
||||
AInventory *item;
|
||||
FTexture *pic;
|
||||
int i;
|
||||
|
||||
|
@ -713,8 +718,8 @@ private:
|
|||
static const char patcharti[][10];
|
||||
static const char ammopic[][10];
|
||||
|
||||
AInventory *oldarti;
|
||||
AAmmo *oldammo1, *oldammo2;
|
||||
TObjPtr<AInventory> oldarti;
|
||||
TObjPtr<AAmmo> oldammo1, oldammo2;
|
||||
int oldammocount1, oldammocount2;
|
||||
int oldartiCount;
|
||||
int oldfrags;
|
||||
|
@ -773,7 +778,13 @@ private:
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -386,6 +386,7 @@ bool AHolySpirit::SpecialBlastHandling (AActor *source, fixed_t strength)
|
|||
{
|
||||
tracer = target;
|
||||
target = source;
|
||||
GC::WriteBarrier(this, source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -706,7 +706,7 @@ void A_SorcBallOrbit(AActor *ball)
|
|||
int x,y;
|
||||
angle_t angle, baseangle;
|
||||
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);
|
||||
angle_t prevangle = ball->special1;
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ protected:
|
|||
virtual bool MatchPlayerClass (AActor *toucher);
|
||||
const PClass *FourthWeaponClass;
|
||||
int PieceValue;
|
||||
AInventory *TempFourthWeapon;
|
||||
TObjPtr<AInventory> TempFourthWeapon;
|
||||
bool PrivateShouldStay ();
|
||||
};
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ public:
|
|||
void Activate (AActor *activator);
|
||||
void Deactivate (AActor *activator);
|
||||
|
||||
ADirtClump *DirtClump;
|
||||
TObjPtr<ADirtClump> DirtClump;
|
||||
};
|
||||
|
||||
IMPLEMENT_POINTY_CLASS (AThrustFloor)
|
||||
|
|
|
@ -249,7 +249,7 @@ int ATelOtherFX1::DoSpecialDamage (AActor *target, int damage)
|
|||
{
|
||||
target->RemoveFromHash ();
|
||||
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->special = 0;
|
||||
}
|
||||
|
|
|
@ -119,10 +119,12 @@ void FManaBar::MakeTexture ()
|
|||
memset (Pixels + 25+24+24, color0, run);
|
||||
}
|
||||
|
||||
class FHexenStatusBar : public FBaseStatusBar
|
||||
class DHexenStatusBar : public DBaseStatusBar
|
||||
{
|
||||
DECLARE_CLASS(DHexenStatusBar, DBaseStatusBar)
|
||||
HAS_OBJECT_POINTERS
|
||||
public:
|
||||
FHexenStatusBar () : FBaseStatusBar (38)
|
||||
DHexenStatusBar () : DBaseStatusBar (38)
|
||||
{
|
||||
static const char *hexenLumpNames[NUM_HEXENSB_IMAGES] =
|
||||
{
|
||||
|
@ -166,7 +168,7 @@ public:
|
|||
"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);
|
||||
ClassImages[0].Init (classLumpNames[0], NUM_HEXENCLASSSB_IMAGES);
|
||||
ClassImages[1].Init (classLumpNames[1], NUM_HEXENCLASSSB_IMAGES);
|
||||
|
@ -201,7 +203,7 @@ public:
|
|||
AmmoRefresh = 0;
|
||||
}
|
||||
|
||||
~FHexenStatusBar ()
|
||||
~DHexenStatusBar ()
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -209,7 +211,7 @@ public:
|
|||
{
|
||||
int curHealth;
|
||||
|
||||
FBaseStatusBar::Tick ();
|
||||
DBaseStatusBar::Tick ();
|
||||
if (CPlayer->mo == NULL)
|
||||
{
|
||||
curHealth = 0;
|
||||
|
@ -242,7 +244,7 @@ public:
|
|||
|
||||
void Draw (EHudState state)
|
||||
{
|
||||
FBaseStatusBar::Draw (state);
|
||||
DBaseStatusBar::Draw (state);
|
||||
|
||||
if (state == HUD_Fullscreen)
|
||||
{
|
||||
|
@ -309,7 +311,7 @@ public:
|
|||
|
||||
void AttachToPlayer (player_s *player)
|
||||
{
|
||||
FBaseStatusBar::AttachToPlayer (player);
|
||||
DBaseStatusBar::AttachToPlayer (player);
|
||||
if (player->mo != NULL)
|
||||
{
|
||||
if (player->mo->IsKindOf (PClass::FindClass(NAME_MagePlayer)))
|
||||
|
@ -395,6 +397,7 @@ private:
|
|||
|| (oldarti != NULL && oldartiCount != oldarti->Amount))
|
||||
{
|
||||
oldarti = CPlayer->mo->InvSel;
|
||||
GC::WriteBarrier(this, oldarti);
|
||||
oldartiCount = oldarti != NULL ? oldarti->Amount : 0;
|
||||
ArtiRefresh = screen->GetPageCount ();
|
||||
}
|
||||
|
@ -521,12 +524,14 @@ private:
|
|||
AmmoRefresh = screen->GetPageCount ();
|
||||
oldammo1 = ammo1;
|
||||
oldammocount1 = ammocount1;
|
||||
GC::WriteBarrier(this, ammo1);
|
||||
}
|
||||
if (ammo2 != oldammo2 || ammocount2 != oldammocount2)
|
||||
{
|
||||
AmmoRefresh = screen->GetPageCount ();
|
||||
oldammo2 = ammo2;
|
||||
oldammocount2 = ammocount2;
|
||||
GC::WriteBarrier(this, ammo2);
|
||||
}
|
||||
|
||||
if (AmmoRefresh)
|
||||
|
@ -705,7 +710,7 @@ private:
|
|||
|
||||
void DrawInventoryBar ()
|
||||
{
|
||||
const AInventory *item;
|
||||
AInventory *item;
|
||||
int i;
|
||||
|
||||
DrawImage (Images[imgINVBAR], 38, 0);
|
||||
|
@ -772,6 +777,7 @@ private:
|
|||
if (keys[i] != oldkeys[i])
|
||||
{
|
||||
oldkeys[i] = keys[i];
|
||||
GC::WriteBarrier(this, keys[i]);
|
||||
different = true;
|
||||
}
|
||||
}
|
||||
|
@ -880,7 +886,7 @@ private:
|
|||
|
||||
void DrawFullScreenStuff ()
|
||||
{
|
||||
const AInventory *item;
|
||||
AInventory *item;
|
||||
int i;
|
||||
|
||||
// Health
|
||||
|
@ -1051,9 +1057,9 @@ private:
|
|||
static const char patcharti[][10];
|
||||
static const char ammopic[][10];
|
||||
|
||||
AInventory *oldarti;
|
||||
AAmmo *oldammo1, *oldammo2;
|
||||
AKey *oldkeys[5];
|
||||
TObjPtr<AInventory> oldarti;
|
||||
TObjPtr<AAmmo> oldammo1, oldammo2;
|
||||
TObjPtr<AKey> oldkeys[5];
|
||||
int oldammocount1, oldammocount2;
|
||||
int oldartiCount;
|
||||
int oldfrags;
|
||||
|
@ -1155,7 +1161,18 @@ private:
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -1540,12 +1540,12 @@ void G_InitNew (const char *mapname, bool bTitleLevel)
|
|||
|
||||
if (StatusBar != NULL)
|
||||
{
|
||||
delete StatusBar;
|
||||
StatusBar->Destroy();
|
||||
StatusBar = NULL;
|
||||
}
|
||||
if (bTitleLevel)
|
||||
{
|
||||
StatusBar = new FBaseStatusBar (0);
|
||||
StatusBar = new DBaseStatusBar (0);
|
||||
}
|
||||
else if (SBarInfoScript != NULL)
|
||||
{
|
||||
|
@ -1592,9 +1592,10 @@ void G_InitNew (const char *mapname, bool bTitleLevel)
|
|||
}
|
||||
else
|
||||
{
|
||||
StatusBar = new FBaseStatusBar (0);
|
||||
StatusBar = new DBaseStatusBar (0);
|
||||
}
|
||||
}
|
||||
GC::WriteBarrier(StatusBar);
|
||||
StatusBar->AttachToPlayer (&players[consoleplayer]);
|
||||
StatusBar->NewGame ();
|
||||
setsizeneeded = true;
|
||||
|
|
|
@ -332,7 +332,7 @@ public:
|
|||
DCorpsePointer (AActor *ptr);
|
||||
void Destroy ();
|
||||
void Serialize (FArchive &arc);
|
||||
AActor *Corpse;
|
||||
TObjPtr<AActor> Corpse;
|
||||
DWORD Count; // Only the first corpse pointer's count is valid.
|
||||
private:
|
||||
DCorpsePointer () {}
|
||||
|
|
|
@ -52,10 +52,10 @@ static int ImpactCount;
|
|||
|
||||
CVAR (Bool, cl_spreaddecals, true, CVAR_ARCHIVE)
|
||||
|
||||
// They also overload floorclip to be the fractional distance from the
|
||||
// left edge of the side. This distance is stored as a 2.30 fixed pt number.
|
||||
IMPLEMENT_POINTY_CLASS (DBaseDecal)
|
||||
DECLARE_POINTER(WallNext)
|
||||
END_POINTERS
|
||||
|
||||
IMPLEMENT_CLASS (DBaseDecal)
|
||||
IMPLEMENT_CLASS (DImpactDecal)
|
||||
|
||||
DBaseDecal::DBaseDecal ()
|
||||
|
|
|
@ -137,7 +137,7 @@ bool P_UndoPlayerMorph (player_t *player, bool force)
|
|||
{
|
||||
return false;
|
||||
}
|
||||
mo = static_cast<APlayerPawn *>(pmo->tracer);
|
||||
mo = barrier_cast<APlayerPawn *>(pmo->tracer);
|
||||
mo->SetOrigin (pmo->x, pmo->y, pmo->z);
|
||||
mo->flags |= MF_SOLID;
|
||||
pmo->flags &= ~MF_SOLID;
|
||||
|
|
|
@ -61,7 +61,7 @@ public:
|
|||
|
||||
void Serialize (FArchive &arc);
|
||||
|
||||
AInterpolationPoint *Next;
|
||||
TObjPtr<AInterpolationPoint> Next;
|
||||
};
|
||||
|
||||
IMPLEMENT_POINTY_CLASS (AInterpolationPoint)
|
||||
|
@ -181,7 +181,7 @@ protected:
|
|||
void Serialize (FArchive &arc);
|
||||
|
||||
bool bActive, bJustStepped;
|
||||
AInterpolationPoint *PrevNode, *CurrNode;
|
||||
TObjPtr<AInterpolationPoint> PrevNode, CurrNode;
|
||||
float Time; // Runs from 0.0 to 1.0 between CurrNode and CurrNode->Next
|
||||
int HoldTime;
|
||||
};
|
||||
|
@ -289,8 +289,8 @@ void APathFollower::Activate (AActor *activator)
|
|||
{
|
||||
if (!bActive)
|
||||
{
|
||||
CurrNode = static_cast<AInterpolationPoint *>(target);
|
||||
PrevNode = static_cast<AInterpolationPoint *>(lastenemy);
|
||||
CurrNode = barrier_cast<AInterpolationPoint *>(target);
|
||||
PrevNode = barrier_cast<AInterpolationPoint *>(lastenemy);
|
||||
|
||||
if (CurrNode != NULL)
|
||||
{
|
||||
|
@ -638,7 +638,7 @@ public:
|
|||
protected:
|
||||
bool Interpolate ();
|
||||
|
||||
AActor *Activator;
|
||||
TObjPtr<AActor> Activator;
|
||||
};
|
||||
|
||||
IMPLEMENT_POINTY_CLASS (AMovingCamera)
|
||||
|
|
|
@ -101,7 +101,7 @@ bool AAmmo::HandlePickup (AInventory *item)
|
|||
(Owner->player->ReadyWeapon == NULL ||
|
||||
(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 ||
|
||||
best->SelectionOrder < Owner->player->ReadyWeapon->SelectionOrder))
|
||||
{
|
||||
|
@ -1052,7 +1052,7 @@ PalEntry AInventory::GetBlend ()
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
AInventory *AInventory::PrevItem () const
|
||||
AInventory *AInventory::PrevItem ()
|
||||
{
|
||||
AInventory *item = Owner->Inventory;
|
||||
|
||||
|
@ -1071,7 +1071,7 @@ AInventory *AInventory::PrevItem () const
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
AInventory *AInventory::PrevInv () const
|
||||
AInventory *AInventory::PrevInv ()
|
||||
{
|
||||
AInventory *lastgood = NULL;
|
||||
AInventory *item = Owner->Inventory;
|
||||
|
@ -1094,7 +1094,7 @@ AInventory *AInventory::PrevInv () const
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
AInventory *AInventory::NextInv () const
|
||||
AInventory *AInventory::NextInv ()
|
||||
{
|
||||
AInventory *item = Inventory;
|
||||
|
||||
|
|
|
@ -124,11 +124,11 @@ public:
|
|||
virtual const char *PickupMessage ();
|
||||
virtual void PlayPickupSound (AActor *toucher);
|
||||
|
||||
AInventory *PrevItem () const; // Returns the item preceding this one in the list.
|
||||
AInventory *PrevInv () const; // Returns the previous item with IF_INVBAR set.
|
||||
AInventory *NextInv () const; // Returns the next item with IF_INVBAR set.
|
||||
AInventory *PrevItem(); // Returns the item preceding this one in the list.
|
||||
AInventory *PrevInv(); // Returns the previous 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 MaxAmount; // Max amount of item this instance can have
|
||||
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?
|
||||
|
||||
// In-inventory instance variables
|
||||
AAmmo *Ammo1, *Ammo2;
|
||||
AWeapon *SisterWeapon;
|
||||
TObjPtr<AAmmo> Ammo1, Ammo2;
|
||||
TObjPtr<AWeapon> SisterWeapon;
|
||||
|
||||
bool bAltFire; // Set when this weapon's alternate fire is used.
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ void ASectorAction::Deactivate (AActor *source)
|
|||
bool ASectorAction::TriggerAction (AActor *triggerer, int activationType)
|
||||
{
|
||||
if (tracer != NULL)
|
||||
return static_cast<ASectorAction *>(tracer)->TriggerAction (triggerer, activationType);
|
||||
return barrier_cast<ASectorAction *>(tracer)->TriggerAction (triggerer, activationType);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ struct F3DFloor;
|
|||
class DBaseDecal : public DThinker
|
||||
{
|
||||
DECLARE_CLASS (DBaseDecal, DThinker)
|
||||
HAS_OBJECT_POINTERS
|
||||
public:
|
||||
DBaseDecal ();
|
||||
DBaseDecal (fixed_t z);
|
||||
|
@ -149,7 +150,7 @@ protected:
|
|||
float Blends[2][4];
|
||||
int TotalTics;
|
||||
int StartTic;
|
||||
AActor *ForWho;
|
||||
TObjPtr<AActor> ForWho;
|
||||
|
||||
void SetBlend (float time);
|
||||
DFlashFader ();
|
||||
|
@ -165,7 +166,7 @@ public:
|
|||
void Serialize (FArchive &arc);
|
||||
void Tick ();
|
||||
|
||||
AActor *m_Spot;
|
||||
TObjPtr<AActor> m_Spot;
|
||||
fixed_t m_TremorRadius, m_DamageRadius;
|
||||
int m_Intensity;
|
||||
int m_Countdown;
|
||||
|
@ -197,7 +198,7 @@ public:
|
|||
void Die (AActor *source, AActor *inflictor);
|
||||
void Destroy ();
|
||||
|
||||
AActor *UnmorphedMe;
|
||||
TObjPtr<AActor> UnmorphedMe;
|
||||
int UnmorphTime;
|
||||
DWORD FlagsSave;
|
||||
};
|
||||
|
|
|
@ -27,7 +27,11 @@ IMPLEMENT_STATELESS_ACTOR (AWeaponHolder, Any, -1, 0)
|
|||
PROP_Inventory_FlagsSet (IF_UNDROPPABLE)
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
class AWeaponPiece : public AInventory
|
||||
{
|
||||
DECLARE_CLASS (AWeaponPiece, AInventory)
|
||||
HAS_OBJECT_POINTERS
|
||||
protected:
|
||||
bool PrivateShouldStay ();
|
||||
public:
|
||||
|
@ -13,5 +14,5 @@ public:
|
|||
|
||||
int PieceValue;
|
||||
const PClass * WeaponClass;
|
||||
AWeapon * FullWeapon;
|
||||
TObjPtr<AWeapon> FullWeapon;
|
||||
};
|
||||
|
|
|
@ -370,7 +370,7 @@ bool AWeapon::CheckAmmo (int fireMode, bool autoSwitch, bool requireAmmo)
|
|||
bool gotSome = CheckAmmo (PrimaryFire, false) || CheckAmmo (AltFire, false);
|
||||
if (!gotSome && autoSwitch)
|
||||
{
|
||||
static_cast<APlayerPawn *> (Owner)->PickNewWeapon (NULL);
|
||||
barrier_cast<APlayerPawn *>(Owner)->PickNewWeapon (NULL);
|
||||
}
|
||||
return gotSome;
|
||||
}
|
||||
|
@ -402,7 +402,7 @@ bool AWeapon::CheckAmmo (int fireMode, bool autoSwitch, bool requireAmmo)
|
|||
// out of ammo, pick a weapon to change to
|
||||
if (autoSwitch)
|
||||
{
|
||||
static_cast<APlayerPawn *> (Owner)->PickNewWeapon (NULL);
|
||||
barrier_cast<APlayerPawn *>(Owner)->PickNewWeapon (NULL);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -41,7 +41,10 @@
|
|||
|
||||
EXTERN_CVAR (Int, con_scaletext)
|
||||
|
||||
IMPLEMENT_CLASS (DHUDMessage)
|
||||
IMPLEMENT_POINTY_CLASS (DHUDMessage)
|
||||
DECLARE_POINTER(Next)
|
||||
END_POINTERS
|
||||
|
||||
IMPLEMENT_CLASS (DHUDMessageFadeOut)
|
||||
IMPLEMENT_CLASS (DHUDMessageFadeInOut)
|
||||
IMPLEMENT_CLASS (DHUDMessageTypeOnFadeOut)
|
||||
|
|
|
@ -55,6 +55,7 @@ class AWeapon;
|
|||
class DHUDMessage : public DObject
|
||||
{
|
||||
DECLARE_CLASS (DHUDMessage, DObject)
|
||||
HAS_OBJECT_POINTERS
|
||||
public:
|
||||
DHUDMessage (const char *text, float x, float y, int hudwidth, int hudheight,
|
||||
EColorRange textColor, float holdTime);
|
||||
|
@ -84,11 +85,11 @@ protected:
|
|||
DHUDMessage () : SourceText(NULL) {}
|
||||
|
||||
private:
|
||||
DHUDMessage *Next;
|
||||
TObjPtr<DHUDMessage> Next;
|
||||
DWORD SBarID;
|
||||
char *SourceText;
|
||||
|
||||
friend class FBaseStatusBar;
|
||||
friend class DBaseStatusBar;
|
||||
};
|
||||
|
||||
class DHUDMessageFadeOut : public DHUDMessage
|
||||
|
@ -149,8 +150,10 @@ protected:
|
|||
class FTexture;
|
||||
class AAmmo;
|
||||
|
||||
class FBaseStatusBar
|
||||
class DBaseStatusBar : public DObject
|
||||
{
|
||||
DECLARE_CLASS (DBaseStatusBar, DObject)
|
||||
HAS_OBJECT_POINTERS
|
||||
public:
|
||||
// Popup screens for Strife's status bar
|
||||
enum
|
||||
|
@ -162,8 +165,8 @@ public:
|
|||
POP_Status
|
||||
};
|
||||
|
||||
FBaseStatusBar (int reltop);
|
||||
virtual ~FBaseStatusBar ();
|
||||
DBaseStatusBar (int reltop);
|
||||
void Destroy ();
|
||||
|
||||
void SetScaled (bool scale);
|
||||
|
||||
|
@ -246,20 +249,21 @@ public:
|
|||
player_s *CPlayer;
|
||||
|
||||
private:
|
||||
DBaseStatusBar() {}
|
||||
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;
|
||||
|
||||
static BYTE DamageToAlpha[114];
|
||||
|
||||
DHUDMessage *Messages;
|
||||
TObjPtr<DHUDMessage> Messages;
|
||||
bool ShowLog;
|
||||
};
|
||||
|
||||
extern FBaseStatusBar *StatusBar;
|
||||
extern DBaseStatusBar *StatusBar;
|
||||
|
||||
FBaseStatusBar *CreateDoomStatusBar ();
|
||||
FBaseStatusBar *CreateHereticStatusBar ();
|
||||
FBaseStatusBar *CreateHexenStatusBar ();
|
||||
FBaseStatusBar *CreateStrifeStatusBar ();
|
||||
FBaseStatusBar *CreateCustomStatusBar ();
|
||||
DBaseStatusBar *CreateDoomStatusBar ();
|
||||
DBaseStatusBar *CreateHereticStatusBar ();
|
||||
DBaseStatusBar *CreateHexenStatusBar ();
|
||||
DBaseStatusBar *CreateStrifeStatusBar ();
|
||||
DBaseStatusBar *CreateCustomStatusBar ();
|
||||
|
|
|
@ -1238,10 +1238,11 @@ SBarInfoBlock::SBarInfoBlock()
|
|||
}
|
||||
|
||||
//SBarInfo Display
|
||||
class FSBarInfo : public FBaseStatusBar
|
||||
class DSBarInfo : public DBaseStatusBar
|
||||
{
|
||||
DECLARE_CLASS(DSBarInfo, DBaseStatusBar)
|
||||
public:
|
||||
FSBarInfo () : FBaseStatusBar (SBarInfoScript->height),
|
||||
DSBarInfo () : DBaseStatusBar (SBarInfoScript->height),
|
||||
shader_horz_normal(false, false),
|
||||
shader_horz_reverse(false, true),
|
||||
shader_vert_normal(true, false),
|
||||
|
@ -1279,7 +1280,7 @@ public:
|
|||
artiflash = 4;
|
||||
}
|
||||
|
||||
~FSBarInfo ()
|
||||
~DSBarInfo ()
|
||||
{
|
||||
Images.Uninit();
|
||||
Faces.Uninit();
|
||||
|
@ -1287,7 +1288,7 @@ public:
|
|||
|
||||
void Draw (EHudState state)
|
||||
{
|
||||
FBaseStatusBar::Draw(state);
|
||||
DBaseStatusBar::Draw(state);
|
||||
int hud = 2;
|
||||
if(state == HUD_StatusBar)
|
||||
{
|
||||
|
@ -1341,7 +1342,7 @@ public:
|
|||
void AttachToPlayer (player_t *player)
|
||||
{
|
||||
player_t *oldplayer = CPlayer;
|
||||
FBaseStatusBar::AttachToPlayer(player);
|
||||
DBaseStatusBar::AttachToPlayer(player);
|
||||
if (oldplayer != CPlayer)
|
||||
{
|
||||
SetFace(&skins[CPlayer->userinfo.skin], "STF");
|
||||
|
@ -1350,7 +1351,7 @@ public:
|
|||
|
||||
void Tick ()
|
||||
{
|
||||
FBaseStatusBar::Tick();
|
||||
DBaseStatusBar::Tick();
|
||||
if(level.time & 1)
|
||||
chainWiggle = pr_chainwiggle() & 1;
|
||||
getNewFace(M_Random());
|
||||
|
@ -2310,7 +2311,7 @@ private:
|
|||
void DrawInventoryBar(int type, int num, int x, int y, bool alwaysshow,
|
||||
int counterx, int countery, EColorRange translation, bool drawArtiboxes, bool noArrows, bool alwaysshowcounter)
|
||||
{ //yes, there is some Copy & Paste here too
|
||||
const AInventory *item;
|
||||
AInventory *item;
|
||||
int i;
|
||||
|
||||
// If the player has no artifacts, don't draw the bar
|
||||
|
@ -2408,7 +2409,9 @@ private:
|
|||
FBarShader shader_vert_reverse;
|
||||
};
|
||||
|
||||
FBaseStatusBar *CreateCustomStatusBar ()
|
||||
IMPLEMENT_CLASS(DSBarInfo);
|
||||
|
||||
DBaseStatusBar *CreateCustomStatusBar ()
|
||||
{
|
||||
return new FSBarInfo;
|
||||
return new DSBarInfo;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,10 @@
|
|||
#define XHAIRPICKUPSIZE (FRACUNIT*2+XHAIRSHRINKSIZE)
|
||||
#define POWERUPICONSIZE 32
|
||||
|
||||
IMPLEMENT_POINTY_CLASS(DBaseStatusBar)
|
||||
DECLARE_POINTER(Messages)
|
||||
END_POINTERS
|
||||
|
||||
EXTERN_CVAR (Bool, am_showmonsters)
|
||||
EXTERN_CVAR (Bool, am_showsecrets)
|
||||
EXTERN_CVAR (Bool, am_showitems)
|
||||
|
@ -62,7 +66,7 @@ EXTERN_CVAR (Bool, noisedebug)
|
|||
EXTERN_CVAR (Bool, hud_scale)
|
||||
EXTERN_CVAR (Int, con_scaletext)
|
||||
|
||||
FBaseStatusBar *StatusBar;
|
||||
DBaseStatusBar *StatusBar;
|
||||
|
||||
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
|
||||
// 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,
|
||||
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;
|
||||
FixedOrigin = false;
|
||||
|
@ -160,11 +164,11 @@ FBaseStatusBar::FBaseStatusBar (int reltop)
|
|||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Destructor
|
||||
// PROP Destroy
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
FBaseStatusBar::~FBaseStatusBar ()
|
||||
void DBaseStatusBar::Destroy ()
|
||||
{
|
||||
DHUDMessage *msg;
|
||||
|
||||
|
@ -172,9 +176,10 @@ FBaseStatusBar::~FBaseStatusBar ()
|
|||
while (msg)
|
||||
{
|
||||
DHUDMessage *next = msg->Next;
|
||||
delete msg;
|
||||
msg->Destroy();
|
||||
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);
|
||||
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;
|
||||
SB_state = screen->GetPageCount ();
|
||||
|
@ -237,7 +242,7 @@ void FBaseStatusBar::AttachToPlayer (player_s *player)
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
int FBaseStatusBar::GetPlayer ()
|
||||
int DBaseStatusBar::GetPlayer ()
|
||||
{
|
||||
return int(CPlayer - players);
|
||||
}
|
||||
|
@ -248,7 +253,7 @@ int FBaseStatusBar::GetPlayer ()
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
void FBaseStatusBar::MultiplayerChanged ()
|
||||
void DBaseStatusBar::MultiplayerChanged ()
|
||||
{
|
||||
SB_state = screen->GetPageCount ();
|
||||
}
|
||||
|
@ -259,7 +264,7 @@ void FBaseStatusBar::MultiplayerChanged ()
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
void FBaseStatusBar::Tick ()
|
||||
void DBaseStatusBar::Tick ()
|
||||
{
|
||||
DHUDMessage *msg = Messages;
|
||||
DHUDMessage **prev = &Messages;
|
||||
|
@ -271,7 +276,7 @@ void FBaseStatusBar::Tick ()
|
|||
if (msg->Tick ())
|
||||
{
|
||||
*prev = next;
|
||||
delete msg;
|
||||
msg->Destroy();
|
||||
}
|
||||
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 **prev;
|
||||
DObject *container = this;
|
||||
|
||||
old = (id == 0 || id == 0xFFFFFFFF) ? NULL : DetachMessage (id);
|
||||
if (old != NULL)
|
||||
{
|
||||
delete old;
|
||||
old->Destroy();
|
||||
}
|
||||
|
||||
prev = &Messages;
|
||||
|
@ -315,12 +321,14 @@ void FBaseStatusBar::AttachMessage (DHUDMessage *msg, DWORD id)
|
|||
// it gets drawn back to front.)
|
||||
while (*prev != NULL && (*prev)->SBarID > id)
|
||||
{
|
||||
container = *prev;
|
||||
prev = &(*prev)->Next;
|
||||
}
|
||||
|
||||
msg->Next = *prev;
|
||||
msg->SBarID = id;
|
||||
*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 **prev = &Messages;
|
||||
|
@ -349,7 +357,7 @@ DHUDMessage *FBaseStatusBar::DetachMessage (DHUDMessage *msg)
|
|||
return probe;
|
||||
}
|
||||
|
||||
DHUDMessage *FBaseStatusBar::DetachMessage (DWORD id)
|
||||
DHUDMessage *DBaseStatusBar::DetachMessage (DWORD id)
|
||||
{
|
||||
DHUDMessage *probe = Messages;
|
||||
DHUDMessage **prev = &Messages;
|
||||
|
@ -375,7 +383,7 @@ DHUDMessage *FBaseStatusBar::DetachMessage (DWORD id)
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
void FBaseStatusBar::DetachAllMessages ()
|
||||
void DBaseStatusBar::DetachAllMessages ()
|
||||
{
|
||||
DHUDMessage *probe = Messages;
|
||||
|
||||
|
@ -383,7 +391,7 @@ void FBaseStatusBar::DetachAllMessages ()
|
|||
while (probe != NULL)
|
||||
{
|
||||
DHUDMessage *next = probe->Next;
|
||||
delete probe;
|
||||
probe->Destroy();
|
||||
probe = next;
|
||||
}
|
||||
}
|
||||
|
@ -394,7 +402,7 @@ void FBaseStatusBar::DetachAllMessages ()
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
bool FBaseStatusBar::CheckMessage (DHUDMessage *msg)
|
||||
bool DBaseStatusBar::CheckMessage (DHUDMessage *msg)
|
||||
{
|
||||
DHUDMessage *probe = Messages;
|
||||
while (probe && probe != msg)
|
||||
|
@ -410,7 +418,7 @@ bool FBaseStatusBar::CheckMessage (DHUDMessage *msg)
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
void FBaseStatusBar::ShowPlayerName ()
|
||||
void DBaseStatusBar::ShowPlayerName ()
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 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 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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -981,7 +989,7 @@ void FBaseStatusBar::RefreshBackground () const
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
void FBaseStatusBar::DrawCrosshair ()
|
||||
void DBaseStatusBar::DrawCrosshair ()
|
||||
{
|
||||
static DWORD prevcolor = 0xffffffff;
|
||||
static int palettecolor = 0;
|
||||
|
@ -1072,7 +1080,7 @@ void FBaseStatusBar::DrawCrosshair ()
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
void FBaseStatusBar::FlashCrosshair ()
|
||||
void DBaseStatusBar::FlashCrosshair ()
|
||||
{
|
||||
CrosshairSize = XHAIRPICKUPSIZE;
|
||||
}
|
||||
|
@ -1083,7 +1091,7 @@ void FBaseStatusBar::FlashCrosshair ()
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
void FBaseStatusBar::DrawMessages (int bottom) const
|
||||
void DBaseStatusBar::DrawMessages (int bottom)
|
||||
{
|
||||
DHUDMessage *msg = Messages;
|
||||
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];
|
||||
|
||||
|
@ -1254,7 +1262,7 @@ void FBaseStatusBar::Draw (EHudState state)
|
|||
}
|
||||
|
||||
|
||||
void FBaseStatusBar::DrawLog ()
|
||||
void DBaseStatusBar::DrawLog ()
|
||||
{
|
||||
int hudwidth, hudheight;
|
||||
|
||||
|
@ -1318,7 +1326,7 @@ void FBaseStatusBar::DrawLog ()
|
|||
}
|
||||
}
|
||||
|
||||
bool FBaseStatusBar::MustDrawLog(EHudState)
|
||||
bool DBaseStatusBar::MustDrawLog(EHudState)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -1329,7 +1337,7 @@ bool FBaseStatusBar::MustDrawLog(EHudState)
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
void FBaseStatusBar::DrawTopStuff (EHudState state)
|
||||
void DBaseStatusBar::DrawTopStuff (EHudState state)
|
||||
{
|
||||
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.
|
||||
int x, y;
|
||||
|
@ -1388,7 +1396,7 @@ SV_AddBlend
|
|||
[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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -1480,7 +1488,7 @@ void FBaseStatusBar::BlendView (float blend[4])
|
|||
(int)(blend[2] * 255.0f), (int)(blend[3] * 256.0f));
|
||||
}
|
||||
|
||||
void FBaseStatusBar::DrawConsistancy () const
|
||||
void DBaseStatusBar::DrawConsistancy () const
|
||||
{
|
||||
static bool firsttime = true;
|
||||
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);
|
||||
}
|
||||
|
||||
void FBaseStatusBar::ReceivedWeapon (AWeapon *weapon)
|
||||
void DBaseStatusBar::ReceivedWeapon (AWeapon *weapon)
|
||||
{
|
||||
}
|
||||
|
||||
void FBaseStatusBar::Serialize (FArchive &arc)
|
||||
void DBaseStatusBar::Serialize (FArchive &arc)
|
||||
{
|
||||
arc << Messages;
|
||||
}
|
||||
|
||||
void FBaseStatusBar::ScreenSizeChanged ()
|
||||
void DBaseStatusBar::ScreenSizeChanged ()
|
||||
{
|
||||
st_scale.Callback ();
|
||||
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;
|
||||
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
|
||||
// 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)
|
||||
{
|
||||
|
|
|
@ -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:
|
||||
FStrifeStatusBar () : FBaseStatusBar (32)
|
||||
DStrifeStatusBar () : DBaseStatusBar (32)
|
||||
{
|
||||
static const char *sharedLumpNames[] =
|
||||
{
|
||||
|
@ -196,11 +197,11 @@ public:
|
|||
"INVFONG7", "INVFONG8", "INVFONG9"
|
||||
};
|
||||
|
||||
FBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES);
|
||||
DBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES);
|
||||
DoCommonInit ();
|
||||
}
|
||||
|
||||
~FStrifeStatusBar ()
|
||||
~DStrifeStatusBar ()
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -216,7 +217,7 @@ public:
|
|||
|
||||
void Draw (EHudState state)
|
||||
{
|
||||
FBaseStatusBar::Draw (state);
|
||||
DBaseStatusBar::Draw (state);
|
||||
|
||||
if (state == HUD_Fullscreen)
|
||||
{
|
||||
|
@ -235,7 +236,7 @@ public:
|
|||
|
||||
void ShowPop (int popnum)
|
||||
{
|
||||
FBaseStatusBar::ShowPop(popnum);
|
||||
DBaseStatusBar::ShowPop(popnum);
|
||||
if (popnum == CurrentPop)
|
||||
{
|
||||
if (popnum == POP_Keys)
|
||||
|
@ -310,7 +311,7 @@ private:
|
|||
|
||||
void Tick ()
|
||||
{
|
||||
FBaseStatusBar::Tick ();
|
||||
DBaseStatusBar::Tick ();
|
||||
|
||||
if (ItemFlash > 0)
|
||||
{
|
||||
|
@ -845,7 +846,9 @@ private:
|
|||
fixed_t ItemFlash;
|
||||
};
|
||||
|
||||
FBaseStatusBar *CreateStrifeStatusBar ()
|
||||
IMPLEMENT_CLASS(DStrifeStatusBar);
|
||||
|
||||
DBaseStatusBar *CreateStrifeStatusBar ()
|
||||
{
|
||||
return new FStrifeStatusBar;
|
||||
return new DStrifeStatusBar;
|
||||
}
|
||||
|
|
|
@ -295,6 +295,16 @@ void FGameConfigFile::DoGlobalSetup ()
|
|||
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;
|
||||
FString path;
|
||||
|
||||
pathval = Args.CheckValue ("-config");
|
||||
pathval = Args->CheckValue ("-config");
|
||||
if (pathval != NULL)
|
||||
return FString(pathval);
|
||||
|
||||
|
@ -568,7 +578,7 @@ FString FGameConfigFile::GetConfigPath (bool tryProg)
|
|||
|
||||
if (path.IsEmpty())
|
||||
{
|
||||
if (Args.CheckParm ("-cdrom"))
|
||||
if (Args->CheckParm ("-cdrom"))
|
||||
return CDROM_DIR "\\zdoom.ini";
|
||||
|
||||
path = progdir;
|
||||
|
@ -615,7 +625,7 @@ void FGameConfigFile::AddAutoexec (DArgs *list, const char *game)
|
|||
FString path;
|
||||
|
||||
#ifndef unix
|
||||
if (Args.CheckParm ("-cdrom"))
|
||||
if (Args->CheckParm ("-cdrom"))
|
||||
{
|
||||
path = CDROM_DIR "\\autoexec.cfg";
|
||||
}
|
||||
|
|
|
@ -162,7 +162,7 @@ static void PrepareSectorData()
|
|||
{
|
||||
int i;
|
||||
int j;
|
||||
DBoundingBox bbox;
|
||||
FBoundingBox bbox;
|
||||
size_t /*ii,*/ jj;
|
||||
TArray<subsector_t *> undetermined;
|
||||
subsector_t * ss;
|
||||
|
|
|
@ -1022,7 +1022,7 @@ void gl_SetActorLights(AActor *actor)
|
|||
(LightAssociations[i]->Frame()==frame || LightAssociations[i]->Frame()==-1))
|
||||
{
|
||||
// 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
|
||||
{
|
||||
light = Spawn<ADynamicLight>(actor->x, actor->y, actor->z, NO_REPLACE);
|
||||
|
|
|
@ -114,7 +114,7 @@ public:
|
|||
BYTE lighttype;
|
||||
bool owned;
|
||||
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
|
||||
// this is stored in the light object to avoid recalculating it
|
||||
|
@ -249,9 +249,6 @@ enum
|
|||
STAT_DLIGHT=64
|
||||
};
|
||||
|
||||
extern TArray<ADynamicLight *> PointLights;
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Light helper methods
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue