diff --git a/README.md b/neotools/README.md similarity index 100% rename from README.md rename to neotools/README.md diff --git a/adpcm.Makefile b/neotools/adpcm.Makefile similarity index 100% rename from adpcm.Makefile rename to neotools/adpcm.Makefile diff --git a/adpcm.c b/neotools/adpcm.c similarity index 100% rename from adpcm.c rename to neotools/adpcm.c diff --git a/adpcm.txt b/neotools/adpcm.txt similarity index 100% rename from adpcm.txt rename to neotools/adpcm.txt diff --git a/adpcmb.Makefile b/neotools/adpcmb.Makefile similarity index 100% rename from adpcmb.Makefile rename to neotools/adpcmb.Makefile diff --git a/adpcmb.c b/neotools/adpcmb.c similarity index 100% rename from adpcmb.c rename to neotools/adpcmb.c diff --git a/autoextract.bat b/neotools/autoextract.bat similarity index 100% rename from autoextract.bat rename to neotools/autoextract.bat diff --git a/neoadpcmextract.cpp b/neotools/neoadpcmextract.cpp similarity index 100% rename from neoadpcmextract.cpp rename to neotools/neoadpcmextract.cpp diff --git a/spctools/ripsamples.py b/spctools/ripsamples.py new file mode 100755 index 0000000..268af74 --- /dev/null +++ b/spctools/ripsamples.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 + +''' ripsamples.py -- a python script for mass extracting samples from SPC files. + + Copyright (C) 2018 Nicholas Curtis (a_dinosaur) + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +''' + +import os +import subprocess; +import pathlib +import struct +import hashlib + +# Directory constants. +SPCDIR = "./spc" +ITDIR = "./it" +SMPDIR = "./sample" + +# External programs used by this script. +SPC2IT = "spc2it" + + +class Sample: + length = 0 + loopBeg = 0 + loopEnd = 0 + rate = 0 + + data = None + +def writesmp(smp, path): + with open(path, "wb") as wav: + + # Make sure sample rate is nonzero. + #TODO: figure out why this even happens... + if smp.rate == 0: + smp.rate = 32000 + #print(path + " may be corrupted...") + + writeLoop = True if smp.loopEnd > smp.loopBeg else False + + # Write RIFF chunk. + wav.write(b"RIFF") + # Size of entire file following + riffSize = 104 if writeLoop else 36 + wav.write(struct.pack(" 1024: return + if smpNum > 4000: return + if insNum > 256: return + if patNum > 256: return + + smpOfsTable = 0xC0 + ordNum + insNum * 4; + + for i in range(0, smpNum): + f.seek(smpOfsTable + i * 4) + smpOfs = int.from_bytes(f.read(4), byteorder="little", signed=False) + smp = readsmp(f, smpOfs, i + 1) + if smp != None: + outwav = os.path.join(outpath, smp.hash + ".wav"); + if not os.path.isfile(outwav): + pathlib.Path(outpath).mkdir(parents=True, exist_ok=True) + writesmp(smp, outwav); + +def scanit(srcPath, dstPath): + for directory, subdirectories, files in os.walk(srcPath): + for file in files: + if file.endswith(".it"): + path = os.path.join(directory, file) + outpath = dstPath + path[len(srcPath):-len(file)]; + readit(path, outpath) + +def scanspc(srcPath, dstPath): + for directory, subdirectories, files in os.walk(srcPath): + + # Create output dir for each game. + for sub in subdirectories: + path = os.path.join(dstPath, sub) + pathlib.Path(path).mkdir(parents=True, exist_ok=True) + + # Convert spc files. + for file in files: + if file.endswith(".spc"): + # Don't convert files that have already been converted. + itpath = os.path.join(dstPath + directory[len(srcPath):], file[:-3] + "it") + if not os.path.isfile(itpath): + path = os.path.join(directory, file) + subprocess.call([SPC2IT, path]); + path = path[:-3] + "it"; + if os.path.isfile(path): + os.rename(path, itpath) + + +# Actual main stuff. +scanspc(SPCDIR, ITDIR) +scanit(ITDIR, SMPDIR) diff --git a/spctools/spc2it/.clang-format b/spctools/spc2it/.clang-format new file mode 100644 index 0000000..8a204cc --- /dev/null +++ b/spctools/spc2it/.clang-format @@ -0,0 +1,10 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +AllowShortFunctionsOnASingleLine: Inline +BreakConstructorInitializersBeforeComma: true +ColumnLimit: 120 +IndentWidth: 4 +TabWidth: 4 +UseTab: ForIndentation +BreakBeforeBraces: Allman diff --git a/spctools/spc2it/.gitignore b/spctools/spc2it/.gitignore new file mode 100644 index 0000000..c0e7a6a --- /dev/null +++ b/spctools/spc2it/.gitignore @@ -0,0 +1,7 @@ +_obj +spc2it +.DS_Store +Thumbs.db +*.spc +*.it +lol \ No newline at end of file diff --git a/spctools/spc2it/.travis.yml b/spctools/spc2it/.travis.yml new file mode 100644 index 0000000..497bed6 --- /dev/null +++ b/spctools/spc2it/.travis.yml @@ -0,0 +1,22 @@ +language: c +compiler: + - gcc + - clang +before_install: + - sudo apt-get update -qq + - sudo apt-get install cmake +script: mkdir build && cd build && cmake .. && make +env: + global: + # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created + # via the "travis encrypt" command using the project repo's public key + - secure: "NuRR5ZZ/PC58JrBv+n2fzsLWORMFk1VDcn7KsixV0X8+4kQ/k5hsHZ2guafzNl6ASGeaslmTO3Qe6UOJNMpd9kyDlsVGLAA6fsu25Q/R7BReOhSpn1hVQIptgspYh8gagMETldOLl/w0udwRqKVROgcooWa+ltp1R8UGds2R1nM=" + +addons: + coverity_scan: + project: + name: "uyjulian/spc2it" + description: "spc to it" + build_command_prepend: "mkdir build && cd build && cmake .." + build_command: "make" + branch_pattern: coverity_scan diff --git a/spctools/spc2it/CMakeLists.txt b/spctools/spc2it/CMakeLists.txt new file mode 100644 index 0000000..0e1b6fe --- /dev/null +++ b/spctools/spc2it/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 2.8) +project(spc2it) +set(spc2it_sources + emu.c + it.c + main.c + sound.c + spc700.c + emu.h + it.h + sneese_spc.h + sound.h + spc2ittypes.h +) + +add_executable(spc2it ${spc2it_sources}) +target_link_libraries(spc2it m) \ No newline at end of file diff --git a/spctools/spc2it/doc/LICENSE_SNEESE b/spctools/spc2it/doc/LICENSE_SNEESE new file mode 100644 index 0000000..87492e4 --- /dev/null +++ b/spctools/spc2it/doc/LICENSE_SNEESE @@ -0,0 +1,160 @@ +This license is a free software license, compatible with the GNU General +Public License (GPL). It is the minimal set of changes needed to correct +the vagueness of the Original Artistic License. + +Text'ified by Randy McDowell (stainless* *at* *users.sourceforge.net). + + +SNEeSe is copyright (c) 1998-2006, Charles Bilyue'. +Portions copyright (c) 1998-2003, Brad Martin. +Portions copyright (c) 2003-2004, Daniel Horchner. +Portions copyright (c) 2004-2005, Nach. ( http://nsrt.edgeemu.com/ ) +Unzip Technology, copyright (c) 1998 Gilles Vollant. +zlib Technology ( www.gzip.org/zlib/ ), Copyright (c) 1995-2003, + Jean-loup Gailly ( jloup* *at* *gzip.org ) and Mark Adler + ( madler* *at* *alumni.caltech.edu ). +JMA Technology, copyright (c) 2004-2005 NSRT Team. ( http://nsrt.edgeemu.com/ ) +LZMA Technology, copyright (c) 2001-4 Igor Pavlov. ( http://www.7-zip.org ) +Portions copyright (c) 2002 Andrea Mazzoleni. ( http://advancemame.sf.net ) + + + - The Clarified Artistic License - + +i. Preamble + + The intent of this document is to state the conditions under which a + Package may be copied, such that the Copyright Holder maintains some + semblance of artistic control over the development of the package, while + giving the users of the package the right to use and distribute the + Package in a more-or-less customary fashion, plus the right to make + reasonable modifications. + +ii. Definitions: + + "Package" refers to the collection of files distributed by the Copyright + Holder, and derivatives of that collection of files created through + textual modification. + + "Standard Version" refers to such a Package if it has not been modified, + or has been modified in accordance with the wishes of the Copyright + Holder as specified below. + + "Copyright Holder" is whoever is named in the copyright or copyrights + for the package. + + "You" is you, if you're thinking about copying or distributing this + Package. + + "Distribution fee" is a fee you charge for providing a copy of this + Package to another party. + + "Freely Available" means that no fee is charged for the right to use the + item, though there may be fees involved in handling the item. It also + means that recipients of the item may redistribute it under the same + conditions they received it. + +iii. Context + + 1. You may make and give away verbatim copies of the source form of the + Standard Version of this Package without restriction, provided that + you duplicate all of the original copyright notices and associated + disclaimers. + + 2. You may apply bug fixes, portability fixes and other modifications + derived from the Public Domain, or those made Freely Available, or + from the Copyright Holder. A Package modified in such a way shall + still be considered the Standard Version. + + 3. You may otherwise modify your copy of this Package in any way, + provided that you insert a prominent notice in each changed file + stating how and when you changed that file, and provided that you do + at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise make + them Freely Available, such as by posting said modifications to + Usenet or an equivalent medium, or placing the modifications on a + major network archive site allowing unrestricted access to them, + or by allowing the Copyright Holder to include your modifications + in the Standard Version of the Package. + + b) use the modified Package only within your corporation or + organization. + + c) rename any non-standard executables so the names do not conflict + with standard executables, which must also be provided, and + provide a separate manual page for each non-standard executable + that clearly documents how it differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + + e) permit and encourge anyone who receives a copy of the modified + Package permission to make your modifications Freely Available in + some specific way. + + 4. You may distribute the programs of this Package in object code or + executable form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library + files, together with instructions (in the manual page or + equivalent) on where to get the Standard Version. + + b) accompany the distribution with the machine-readable source of the + Package with your modifications. + + c) give non-standard executables non-standard names, and clearly + document the differences in manual pages (or equivalent), together + with instructions on where to get the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + + e) offer the machine-readable source of the Package, with your + modifications, by mail order. + + 5. You may charge a distribution fee for any distribution of this + Package. If you offer support for this Package, you may charge any + fee you choose for that support. You may not charge a license fee + for the right to use this Package itself. You may distribute this + Package in aggregate with other (possibly commercial and possibly + nonfree) programs as part of a larger (possibly commercial and + possibly nonfree) software distribution, and charge license fees for + other parts of that software distribution, provided that you do not + advertise this Package as a product of your own. If the Package + includes an interpreter, You may embed this Package's interpreter + within an executable of yours (by linking); this shall be construed + as a mere form of aggregation, provided that the complete Standard + Version of the interpreter is so embedded. + + 6. The scripts and library files supplied as input to or produced as + output from the programs of this Package do not automatically fall + under the copyright of this Package, but belong to whoever generated + them, and may be sold commercially, and may be aggregated with this + Package. If such scripts or library files are aggregated with this + Package via the so-called "undump" or "unexec" methods of producing a + binary executable image, then distribution of such an image shall + neither be construed as a distribution of this Package nor shall it + fall under the restrictions of Paragraphs 3 and 4, provided that you + do not represent such an executable image as a Standard Version of + this Package. + + 7. C subroutines (or comparably compiled subroutines in other languages) + supplied by you and linked into this Package in order to emulate + subroutines and variables of the language defined by this Package + shall not be considered part of this Package, but are the equivalent + of input as in Paragraph 6, provided these subroutines do not change + the language in any way that would cause it to fail the regression + tests for the language. + + 8. Aggregation of the Standard Version of the Package with a commercial + distribution is always permitted provided that the use of this + Package is embedded; that is, when no overt attempt is made to make + this Package's interfaces visible to the end user of the commercial + distribution. Such use shall not be construed as a distribution of + this Package. + + 9. The name of the Copyright Holder may not be used to endorse or + promote products derived from this software without specific prior + written permission. + + 10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. diff --git a/spctools/spc2it/doc/OPENSPC.TXT b/spctools/spc2it/doc/OPENSPC.TXT new file mode 100755 index 0000000..f4a4431 --- /dev/null +++ b/spctools/spc2it/doc/OPENSPC.TXT @@ -0,0 +1,296 @@ +This is a modified version of OpenSPC 0.301, improved by Dwedit, +with support of fractional update rates to generate better IT files. + +See http://www.romhacking.net/forum/index.php?topic=10164.0 + +/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ + + OpenSPC ver.301 + +/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ + + Intro + ----- +OpenSPC is an SPC Player created using a modified version of SNEeSe's ASM SPC +core. The rest was written in C using DJGPP. You can find new versions and +a FAQ for OpenSPC at: +http://home.gvi.net/~martin + +OpenSPC was created by Butcha, and has been contributed to by Cyber Warrior X, +Crono, Kadar, and Cait Sith 2. + +++++++++++++++++ +Table of Contents +-------------------------------------------- + +1.) What's New +2.) Disclaimer and License +3.) System Requirements +4.) Current Progress +5.) Future Progress +6.) Configuration File +7.) IT Dumping +8.) Credits/Special Thanks + ++++++++++++++ +1.) What's New +-------------------------------------------- + +v0.301 - Relatively small update + + * Added ID support for SPC files (thanks to Cait Sith 2). Displays info for + SPC files that contain it, in both the lite version and the full version, + non-gui mode. + * Changed "SPC->IT conversion" name in IT files to the ID song name if + specified in SPC file. + * Updated to SNEeSe version .36 SPC code, including all optimizations and + bugfixes that were present in it. + +v0.300 - Another big (and overdue) update + + * Prevented notes that are out of IT's range from writing the invalid data - + prevents IT corruption, but probably still won't sound quite right. + * Checked for invalid loop points supplied by an SPC file (wouldn't think + you'd *need* to check... %#!@$). + * Updated display screen with new pitch when sliding. + * Fixed rare bug which allowed volumes with opposite signs in both channels + to cancel each other out when in mono mode. + * Corrected bug where if a voice was keyed off at the same time the sample + ended, the voice would not get turned off. Prevents the big long strings of + note cut commands sometimes seen in the IT files. + * Decided to scrap my sound drivers in favor of Allegro's. This means both + the Ensoniq SoundScape and ESS AudioDrive should now be supported in addition + to all SoundBlaster types. Note only the sound driver, not the mixer, has + been replaced. + * As a side effect of this, the onscreen display updates slower now, because + Allegro requires much more sound data to be mixed and delivered at once. + This is much more noticable with frequencies less than 22050 Hz. + * Config file rewritten to reflect changes in sound driver. Should still be + compatible if you have any old frontends, but I'm not sure. + * Corrected a bug in ADSR/GAIN which could cause the envelope height to wrap + around infinitely. (Jurassic Park) + * Implemented main volume registers + * Changed to the newest version of SNEeSe SPC core. TCALL (and therefore + Terranigma) now works! Other optimizations and fixes are also present. + * Changed to SNEeSe's SPC timer code. Timers are now cycle-exact rather than + calculated once every update. + * Reimplemented ADSR/GAIN in the style of the timer code, which means it is + now cycle-exact. Any song that changed envelope states while monitoring ENVX + should now work much better. + * Doubled output volume both in the mixer and in IT dumping. This can + potentially cause clipping distortion while mixing and maxed-out volumes in + IT files if the volume ever gets too high, but it seems SNES games rarely + use high volume which is why it sounded so quiet in the first place. + * Fixed up the display for text mode. Colors and layout should be identical + to the graphics mode, especially if used with an 80x50 screen. Use setgui=2 + in the config file to activate. + * Implemented a command line switch equivalent to the setgui parameter in the + config file. (-g x) + * Fixed up the colors on the display and allowed the colors to be changed in + the config file. + * Did a little cleaning and optimization of the source code. + +v0.200 - Lots new in this version + + * Allowed IT dumping with other than 200 rows/pattern (change in CFG file) + * Fixed bug with volume and IT dumping (played too loud and would + sometimes result in undesired volume column effects being applied) + * Fixed segmentation fault bug when a negative volume applied to a channel + * Fixed a bug with GAIN linear decrease mode + * Fixed file loader bug when filename preceded with "..\" + * Fixed file loader bug when loading files with extension .SP1, .SP2, etc. + * Increased number of SPC cycles to execute, while simultaneously + implementing system to pause emulation if SPC idle. Should prevent games + like Starfox from slowing down and speeding up, and *might* even save some + CPU power in other games + * Got rid of the "SPC Sample" for samples without lengths. (Thanks to Crono) + * Added .IT pattern compression. (Crono) + * Reduced memory requirements when *NOT* logging a .IT file (Crono) + * Now uses "specific" mixing method for each style of output. (Crono) + * Old Soundblaster drivers tweaked, should sound better and can now go up to + 45454 Hz. May introduce some incompatibility, let me know. (Crono) + * Soundblaster Pro drivers tweaked, hopefully sound better. (Crono) + * Moved actual emulation stuff from main.c to emu.c. main.c now only + contains main() and other graphics-related stuff. + * Added support for Ensoniq Soundscape, SoundFX and MediaFX cards (thanks to + Kadar) + * Added support for non-standard IRQs. Let me know if you still have + problems getting it to play on your SB-compatible sound card. + * Found and eliminated something WinAmp wasn't liking about the way unlooped + samples were saved. + * Created 'lite' version! Now capture SPC's to either IT or WAV files + direct to disk at maximum speed rather than having to listen to them. + Uses command line options to specify parameters. (Replaced 'main.c' with + 'lite.c' and removed sound drivers in project file) + * Added many of the same command line options to the full version. If no + options are specified it will default to what's in the OPENSPC.CFG file. + * Added a preset time limit and current time indicator to both versions. + * Replaced the SNEeSe SPC core with a newer version; had hoped this would fix + some SPC bugs, but no luck there. Oh well, hopefully its faster or + something. + * Broke down and did some SPC debugging myself; managed to get Prince of + Persia, WWF, Doom, FF2 (Fabul Castle), SOM2 (Boss Theme), and Simpsons: + Bart's Nightmare working a lot better. + Details: - all SBC to memory were subtracting in the wrong order + - CMP YA, dp didn't complement the carry flag + - membit addressing mode operand bits were mapped incorrectly + * Corrected some bugs in GAIN Increase and Decrease modes and re-enabled + ENVX reporting; made WWF, Doom, and Simpsons sound even better. + * Added a reverse stereo option for those of you experiencing swapped sound + channels with an SBPro compatible. + +v0.100 - Initial release + ++++++++++++++++++++++++++ +2.) Disclaimer and License +-------------------------------------------- + +This program is still considered to be in beta status. Neither Butcha nor any +of the other contributors are responsible for any undesirable effects of using +this program. Also, none of the authors are responsible for the possesion or +use of any copyrighted material taken from copyrighted ROMs using this +program. + +Please do not redistribute this program without its included documentation. +Also, do not package this program with SPC or IT files from copyrighted games. + +++++++++++++++++++++++ +3.) System Requirements +-------------------------------------------- + +The light version should have very few requirements. Probably a 386 with an +FPU could run it, but I can't say for sure. Memory shouldn't be a problem as +long as you have some available swap space. The following are some tentative +guidelines for the full version: + +Minimum Recommended System: + +- 486/100 processor +- 8MB RAM +- Sound Blaster or compatible card + +Strongly Recommended System : + +- Pentium processor (P133 or higher) +- The more RAM the merrier (especially with Windows 9x) +- VGA card (for graphical display) +- Sound Blaster 16 or 100% compatible + ++++++++++++++++++++ +4.) Current Progress +-------------------------------------------- + +The following are implemented in the sound engine: + +- 16bit digital stereo sound +- SPC700 Sound CPU (likely still bugs to be found) +- FULL ADSR and GAIN volume effects (now cycle-exact!) +- Song capture in the IT format +- Sound recording to WAV format (lite version only) + +The following are missing in the sound engine: +- ECHO +- FIR filter +- Noise +- Modulation + +The following are some known bugs: +- If the pitch changes too fast while a note is on, an IT file will not be + able to reproduce it fast enough, resulting in a subtle "rounding" effect. + (i.e. FF3 Atma Weapon) +- A few games' music doesn't like being played with a fixed update-per-second + method, resulting in unpredictable effects +- Any game that changes the sample location or data while playing will sound + strange (Doom, Cannon Fodder). No easy work around this, because ITs always + expect the same sample to stay the same. +- Dragon Quest seems to have some severe bugs, no idea why + +++++++++++++++++++ +5.) Future Progress +-------------------------------------------- + +Future progress is completely indefinite at this point, due to my currently +attending college at UMR. I haven't had a lot of free time here, and that +which I do have I will most likely be devoted to other things. As far as +major features missing from the program go, there isn't a whole lot more +I can add, because it wouldn't transfer into an IT file anyway (echo, filter). +Noise and modulation might be possibilities, I'd have to look more into it. I +would like to have a much better display, but I keep saying that and never get +around to working on it. Also, there are the above bugs to fix. I would +however like to rewrite the IT code to save more data temporarily into a +proprietary format, and then convert this data to an IT file. This would make +it easier for other people to add support for other filetypes. It could also +make the IT code itself more efficient in compressing the resulting file. I +have no idea when or if I'll get to this, however. We'll see. + + ++++++++++++++++++++++ +6.) Configuration File (not in lite version) +-------------------------------------------- + +Any documentation you need for the configuration file should be found in +comments inside the OPENSPC.CFG file itself. If you've let some frontend +overwrite it and you can't find any comments, you'll have to restore from the +original ZIP. I recommend backing up the config before using any frontends, +if the frontend doesn't do so itself. Do NOT delete this file, OpenSPC will +NOT regenerate it. + ++++++++++++++ +7.) IT Dumping +-------------------------------------------- + +IT dumping is done via 16 channels(8 channels each with independent left and +right volume control). When you switch on IT dumping, everything you hear in +the player is saved into the IT file. This means the IT file starts on the +first note of the SPC and ends when you hit a key to end recording. Upon +pressing a key, a note cut command is issued across all 16 channels, along +with a command to loop back to the beginning of the song. This makes it easy +to record simple looping songs-simply hit a key just before the first note of +the song plays again, and it should sound pretty decent. However, many songs +have an "intro" that they play first, and then go into another section and +loop. To make these loop correctly, you will have to manually edit the IT +yourself. I will describe how to do it using Impulse Tracker; it is up to you +to figure it out if you use a different one, but it shouldn't be much +different. + +- First of all, switch on IT dumping and play the song. Stop recording a + second or two after you hear it loop. +- Open up the resulting IT file in Impulse Tracker. +- Find the first note of the section that loops. If it is at the beginning of + a pattern, you are incredibly lucky. If not, go to the next pattern and see + where it begins with relation to the music(for example, say the pattern + begins 5 rows before channel 2 plays a C#5). Also, remember which pattern + number it is. +- Go find the end of the song. Locate the first note of the looped section, + then look forward until you find the spot you remembered(5 rows before + channel 2 plays C#5, in our example). Hit ALT-DEL to delete everything in + this pattern after that spot. When the pattern is clear after that spot, + put the cursor on the effects column of any channel and enter in effect 'B' + followed by the number of the pattern you found in the last step(in hex). +- Once you've completed this, you'll probably want to delete any extra + patterns after this one, along with their respective orders. +- Play it. Sound right? Save it. If not you either found the wrong spot or + entered in the wrong pattern number. + +It may sound a little complicated or awkward, but its really not that bad when +you've done it a few times. I did it 16 times to create the examples on my +webpage. If you'd like you can open them up for an example. + ++++++++++++++++++++++++++ +8.) Credits/Special Thanks +-------------------------------------------- + +Thanks go to: + +- Savoury SnaX and Charles Bilyu‚ for the SPC code used in this project. +- Shawn Hargreaves (and others) for the Allegro library +- Citizen X for letting CWX see the WinSPC source for ideas +- Gary Henderson for helping CWX with SPC Dumping and with Terranigma +- The rest of the Snes9x team for their hard work and the release of the + Snes9x source code, although this program contains no Snes9x-derived code. +- zsKnight and _Demo_ for an awesome emulator and for inventing SPC files + (even though I still think I came up with the idea for playing a sound save- + state first :) +- TheGun and SailorStarFighter for some early betatesting (hi guys :) + + --Butcha diff --git a/spctools/spc2it/doc/Readme.linux b/spctools/spc2it/doc/Readme.linux new file mode 100644 index 0000000..1f29eed --- /dev/null +++ b/spctools/spc2it/doc/Readme.linux @@ -0,0 +1,180 @@ +OpenSPC v.300Linux (based on Lite/Direct To Disk version) + Linux port and general code tweaking by Chris Timmerberg (ctimmerb@usa.net) + +Hi! Here's the long-awaited port of OpenSPC to linux. I say linux, primarily +because that's all I'm able to test it with (at least presently). If your +platform is relatively portable and has a /dev/dsp it may very well work +there as well. It may even work with code that pretends to be /dev/dsp, such +as I've seen for Os/2. This is completely speculation, I've never tried it. + +Even in the lack of /dev/dsp, you should still be able to compile it almost +everywhere there's a gcc or other standard compiler. This will give you full +wav/it dumping facilities, as well as the ability to pipe stdout into +another player app which handles wav-like data. I've found splay to work +fine. Again, I havent tested others. Feel free to do so yourself. + +The tty files were copied from another project and as such contain alot of +extra stuff #ifdef'ed which linux doesnt use. I can't say whether or not +these other code blocks work on their respective platforms, sorry. + +The binary I send along was built on my 486 running Stampede. It uses no +extra libraries beyond Glibc 2.1. Everyone should have this by now. If you +dont, feel free to compile your own copy (of the OpenSPC, or of glibc). + +PGCC 1.1.3 was used. The makefile includes -O6 -mpentium optimization levels +by default. This seems to generate the best quality code for most people's +systems. And no, mpentium DOESNT seem to harm a 486 any. My 486 is doing +just lovely playing back the spc's I've tried. I always use at least these +options when compiling everything (occasionally a couple extras in +addition). + +The overall behavior of this port is somewhat different from the original. +Assuming one has enough cpu power (it shouldnt be hard to, my 486 did it) +you can dump to several destinations at the same time. This includes wav +file, IT file, stdout, and soundcard. The program code sets all output +variables to 0. Options you specify for -w -i -p and -l then turn on any of +the 4. If none was specified it turns on wav. Each enabled option is then +checked for success and if necessary turned off. If no output options remain +it finally gives up, and exits. + +Data is written directly to /dev/dsp. The original code made no attempt to +buffer writes, and I didn't make any modifications in that area. As far as I +can tell, /dev/dsp and/or the soundcard itself have at least a small buffer. +This is probably not sufficient to prevent breakups if you're doing +something cpu-intense. A buffer may be added eventually... + +Several command line options were added: + +-l enables the audio card output code. As many sound cards do not possess an +exact frequency match, the audio output is by default tweaked to match what +your soundcard reports it was set to. This does not SEEM TO affect it +output, but does affect wav. If your card's status report was in error or +your goal was to dump an exact frequency wav you may disable the adjustment +with the -a option. + +-p enables piping support. This data is identical to that which is fed to a +wav file or /dev/dsp. You may send this directly to an audio player of your +choice. + +The -P option to accept input from stdin is wishful thinking for a next +version. It'd be nice to be able to zcat, bzcat, or unzip -c compressed spc +file(s) into this player, and beats trying to write support code for +handling different compression methods. + +Aside from everything mentioned above, the base code remains for the most +part, unchanged. I include the two text files from the original dos version, +renamed to openspc.dos.txt and source.dos.txt. + +The next version may also include as well as the mentioned earlier buffering +system and input pipes, the ability to batch play multiple files, general +code cleanup and optimization, and maybe some other sound file formats... +Candidates for other formats include gym, cym, nsf, and anything else that +isnt supported by other linux players. Within reason, and as long as my 486 +handles it reliably. :) + +If you want a netscape plugin, write it yourself. I dont do netscape +plugins. (Netscape is piggish enough on a 486 without having MORE overhead, +thank you very much!!) Meanwhile this existing program SHOULD work as a +'helper app' type thingy. No I didnt test that either. + +Misc note: This program uses <__math.h> for the pow2 and __log2 functions. +These functions generate inline assembly code, which is both shorter and +faster than calling external library functions from linking with the -lm +option. + +Using code from the NORMAL header I've determined to +require an alternate sequence of: + +#define __USE_EXTERN_INLINES 1 +#define __USE_ISOC9X 1 +#include + +This also changes the function names required, to log2f and __pow2 + +Oddly enough, the code generated by this contains many more instructions of +overhead. + +<__math.h> generates a warning about __log1p which I dont even use. This +warning seems to be due to a bad header file, not my code. The warning is +harmless and may be ignored. The other warnings are going to be cleaned up +eventually. + +Release notes for version .201Linux: +* Overlooked changing a few instances of 'dump' into 'itout'. This caused IT + dumping to generate incomplete files. +* WAV dumping no longer generates any temp files +* Upgraded from PGCC 1.1.1 to 1.1.3, and threw in -ffast-math in + compilation options. +* Accidentally packed 'source.dos.txt' into the archive twice the first + time around. Only need ONE copy of this :) +Release notes for version .300Linux: +* Downloaded dos version .300 and re-implemented all my changes including + stripping _ symbols in .S files, #including <__math.h> in sound.c, and + tons of stuff in lite.c +* lite.c now features a shutdown function for orderly shutting down. +* lite.c has two extra \r characters by popular demand :) +* Patched spcmain.S to not use self-modifying code in SPC_READ_RAM_ROM +Release notes for version .301Linux: +* Features migrated from the .301 dos version finally - id666 tags and new + spc core. The id tags allow the player to know how long to play a + particular file for. +* The code from the dos version's lite.c was WRONG WRONG WRONG and + generated really silly play times. So I fixed that and changed it to + read the whole id tag header into a 'struct' all in one read. +* The code for -t was also slightly incorrect in the dos version and my + previous release. We now handle all three cases: seconds only with and + without a leading colon, as well as the previous minutes:seconds case. + Also properly handles stuff like 90 or :90 or 0:90 (or even 1:90)... +* spcmain.S from the dos version included more silly self-modifying code in + .301. When will they learn??? .... fixed +* Dos version collided with my usage of the -l parameter. I kept mine, -l + is still linuxout audio method, and a capitalized -T is used to disable + the playback length mentioned in the idtag resulting in unlimited length + playbacks. +* Changed some of that math.h stuff. Gcc 2.95.2 (and pgcc) seem ok with it + the way I have it now, and I didnt feel like playing with it anymore... +* Tried to implement a 'large buffer' option to avoid audio breakups. Ended + up with WORSE breakups. Someone wanna look that over and see what I + missed? +* Meanwhile, I upgraded to a nice Trident 4Dwave card and now am running + with the Alsa drivers (.58a as of this writing). Program works lovely by + default. (At least as long as you've got the oss emulation modules, but + you do, dont you?? :) This card at least gives me the exact sample rates + I ask for, but I'm sure alot of people out there still have cards which + dont so I didnt touch the rate-tweak logic. + + If you want a large buffer and to use native alsa playback, try + something like: ./ospc -p CT-1-22-TheHiddenTruth.spc | bag | aplay -m + 'bag' is a nice buffering utility that I think came with Timidity's + tools, and aplay is part of the Alsa stuff. Dont bother trying this + approach with certain other play utilities, in particular avoid the one + from sox. You can identify this crappy 'play' program by typing 'play' + and seeing if it says its a frontend to sox. Sox's play just soaks up + tons of cpu and does NOT help (the point of HAVING a buffer is to try + to avoid cpu usage spikes from breaking up the audio, after all!!) +* The dos version did not yet support the id tag's 'fadeout length' value, + and I have not tried to do anything about this yet either. Feel free to + do so for me. :) + + I was thinking maybe to use the oss or alsa mixer controls. Note that at + least for my trident, the oss-emulation 'main volume' seems to be using + one of trident's 32-position ones (despite showing in the oss mixers as a + 1...100 range), making for rather unsmooth fading if one were to use that. + In fact, all of the trident's volume knobs can get a bit confusing to sort + out. There are at least several availible from alsa mixers with a full 255 + position range at least. (I use xamixer. gamix is also nice. Neither is + totally perfect though.)... +* Some extra keypresses - q and escape quit as well as the previous + spacebar. New key functions include p or s to pause. Other activity + such as fast forward and rewind seem awfully difficult to implement. Feel + free to try doing so yourself as always :) Note that quiting or pausing + seem to take place after the buffer is emptied (or at least not QUITE + instantly). Good enough though. +* If you feel compelled to, + make ospcshared + cp -a libspc.so /usr/lib/libspc.so.0 + (or any other suitable directory, or just edit your LD_LIBRARY_PATH) + Works here, your milage may vary. +* Changed Makefile to use CFLAGS, and included a nice flag setting for + optimization which I got from who knows where (probably an old posting to + the xmame mailing list). diff --git a/spctools/spc2it/doc/SOURCE.TXT b/spctools/spc2it/doc/SOURCE.TXT new file mode 100755 index 0000000..7c90856 --- /dev/null +++ b/spctools/spc2it/doc/SOURCE.TXT @@ -0,0 +1,173 @@ +/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ + + OpenSPC ver.300 + Source Code +/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ + + /ntro + ----- +OpenSPC is an SPC Player created using a very modified SNEeSe SPC CPU core. +It was made in C (although the SPC core is in Assembly) using DJGPP. You can +find new versions and a FAQ for OpenSPC at: +http://home.gvi.net/~martin + +OpenSPC was created by Butcha, and has been contributed to by Cyber Warrior X, +Crono, and Kadar. + +++++++++++++++++ +Table of Contents +-------------------------------------------- + +1.) Disclaimer +2.) License Rights +3.) Programming Conventions +4.) Brief Outline + ++++++++++++++ +1.) Disclaimer +-------------------------------------------- + +This program is still considered to be in beta status. Neither Butcha nor any +of the other contributors are responsible for any undesirable effects of using +this program. Also, none of the authors are responsible for the possesion or +use of any copyrighted material taken from copyrighted ROMs using this +program. + ++++++++++++++++++ +2.) License Rights +-------------------------------------------- + +- OpenSPC Program and Code - General + ---------------------------------- +Please do not redistribute either of these without their respective +documentation. Also, do not package them with either SPC or IT files from +copyrighted games. + +- OpenSPC Code - Usage Rights + --------------------------- +If you would like to modify the source to OpenSPC in some way, you may send +your version of it to the authors, who will choose whether to include your +improvements in future versions. Please do not make a change or improvement +and release your own, separate program. However, independent ports of +OpenSPC to other platforms are welcome, as long as you let the authors know +what you've done. If you would like to use some part of the source in a +program of your own design, you are free to do so, provided the primary +author is aware of what you are doing. Please give the author(s) credit for +any benefits you receive from either the direct use or the examination of this +code. + +Blah, legal crap. :( I'm sure none of you out there will try to rip me off... +But please read and follow this section anyway. + ++++++++++++++++++++ +3.) Programming Conventions +-------------------------------------------- + +I have tried to make the source as uniform as possible, to make it easier to +read and understand. Also I have tried to comment wherever possible. What +follows is a list of different programming conventions used by me and which +should be used when modifying the source: + +- Tab Spacing is 8 +- if, for, and other bracketed statements should take the following form: + if(something) + { + //Do some stuff + //Do some more stuff + } + Here are some examples of what NOT to do: + + if(something){/*Do some stuff*//*Do some + more stuff*/} + + if(something) + { + //Do some stuff + //Do some more stuff + } + if(something){ + //Do some stuff + //Do some more stuff + } + If there is only one statement within the brackets, they may be omitted. + However, the statement should be on the following line and tabbed once: + if(something) + //Do only one thing + +- Global variables: + - Try to use them as little as possible + - When you do use them, define them in the .c file whose function they most + correspond with. If that .c file is the only one in the project that + needs that variable, define it as static. This goes for functions as + well. + - If a global variable must be seen outside the .c file it is defined in, + include it in the .h file associated with that .c file, and give it a name + that corresponds with that file. For example, any global variable you + will find in the sound.c file is preceded by the letters SND: SNDvoices, + SNDsamples, etc. This also goes for functions. + +This is all I can think of right now. For the most part, just observe what +is already there and try to make your code look and work similarly. + +++++++++++++++++ +4.) Brief Outline +-------------------------------------------- + +What follows is a rough outline of how the program works: + +- Startup: display message, read config file, parse command line +- Init the SPC: allocate memory, call SNEeSe's Reset command, then load in + registers from the input file +- Init the mixer: make up some tables, init some variables +- If in graphics mode, initialize allegro graphics and fader routine +- Init IT dump if capturing to an IT only +- Queue the notes that started when state was saved +- Initialize the Allegro sound stream to receive data from the mixer +- Main loop: + - Check to see whether Allegro wants more data + - If so, keep emulating and mixing until buffer is full + - Otherwise, render the display (if enabled) + - Dump the IT pattern buffers to disk if recording + - Check current time against time limit + - Check to see if we need to toggle voices or exit as a result of keypress +- Shut down stuff +- If dumping to an IT, figure out what the filename would be and save it. +- End program + +Below is an explanation of the processing done when Allegro requests data, +found in the function SNDMix() in sound.c: + +- Run the SNEeSe SPC + While this is going on, certain actions have been intercepted from SNEeSe + control: + - (new in v300) Timer control is now cycle-exact, and is handled by the + SNEeSe code. + - Reads and writes to the DSP are intercepted so as to return current sound + data and update sound data when something new is written. + - Control returns from the SNEeSe SPC when the number of cycles that should + have gone by in one update have been emulated. + (2048000 cycles / num_updates_per_second) +- Mixer then proceeds to process each of the 8 voices that are currently keyed + on. This includes updating the envelope, and detecting pitch and volume + changes so as to update the IT, as well as mixing the audio. It also + updates a global variable used by the display functions to determine the + current level of each voice and both channels. +- IT code writes the information recorded as having changed since the last + update. It must write it twice, once per each channel, left and right, + because the SPC has two independent volume controls rather than a volume and + a pan. (The display is somewhat faked... using that method to set the pan + doesn't result in a very good recording. Besides, as near as I can tell + volume and pan cannot be set simultaneously in an IT file.) +- Record IT end of row for this completed update. If we have filled a + pattern, use the next buffer. If no other buffer is available, the main + loop must be running too slow. Either way, simply overwrite the pattern we + just recorded since the data has to go SOMEWHERE. It will skip a pattern + though, so if this happens either turn down the sound quality in the config + file, or turn up NUM_PATT_BUFS in it.h. +- End of update, return from interrupt + +I'm sure a lot of the mixer, envelope, and IT data stuff could use some +optimization; if you'd like to do it, as long as it doesn't lose any of the +current functionality I'll use it. + + --Butcha diff --git a/spctools/spc2it/emu.c b/spctools/spc2it/emu.c new file mode 100644 index 0000000..6db68b6 --- /dev/null +++ b/spctools/spc2it/emu.c @@ -0,0 +1,210 @@ +/**************************************************** +*Part of SPC2IT, read readme.md for more information* +****************************************************/ + +#include +#include +#include + +#include "sound.h" +#include "spc2ittypes.h" +#include "sneese_spc.h" + +u8 SPC_DSP[256]; +u8 SPCRAM[65536]; +u32 Map_Address; +u32 SPC_DSP_DATA; +u32 Map_Byte; +u8 In_CPU; +u32 SPC_CPU_cycle_divisor; +u32 SPC_CPU_cycle_multiplicand; +u32 SPC_CPU_cycles; +u32 SPC_CPU_cycles_mul; +u32 sound_cycle_latch; + +void (*SPC_Write_DSP_Hook)(u8); // function pointer + +// ID tag stuff +s32 SPCtime; +SPCFileInformation *SPCInfo; + +static s32 LoadZState(char *fn) +{ + SPCFile *sFile = calloc(1, sizeof(SPCFile)); + if (sFile == NULL) + { + printf("Error: could not allocate memory for SPCFile struct\n"); + exit(1); + } + FILE *f = fopen(fn, "rb"); + if (f == NULL) + { + printf("Error: can't open file\n"); + exit(1); + } + fseek(f, 0, SEEK_SET); + fread(sFile, sizeof(SPCFile), 1, f); + fclose(f); + if (strncmp("SNES-SPC700 Sound File Data", sFile->FileTag, 27)) + { + printf("Error: invalid file format\n"); + exit(1); + } + memcpy(&active_context->PC.w, sFile->Registers.PC, 2); + active_context->YA.b.l = sFile->Registers.A; + active_context->X = sFile->Registers.X; + active_context->YA.b.h = sFile->Registers.Y; + active_context->SP = 0x100 + sFile->Registers.SP; + active_context->PSW = sFile->Registers.PSW; + memcpy(SPCRAM, sFile->RAM, 65536); + memcpy(SPC_DSP, sFile->DSPBuffer, 128); + SPCInfo = calloc(1, sizeof(SPCFileInformation)); + if (SPCInfo == NULL) + { + printf("Error: could not allocate memory for SPCInfo struct\n"); + exit(1); + } + memcpy(SPCInfo, &sFile->Information, sizeof(SPCFileInformation)); + char songLen[4]; + strncpy(songLen, SPCInfo->SongLength, 3); + if (songLen[0] >= 0) + SPCtime = atoi(songLen); + else + SPCtime = 0; + if (0 == (SPC_CTRL & 0x80)) + active_context->FFC0_Address = SPCRAM; + active_context->timers[0].target = (u8)(SPCRAM[0xFA] - 1) + 1; + active_context->timers[1].target = (u8)(SPCRAM[0xFB] - 1) + 1; + active_context->timers[2].target = (u8)(SPCRAM[0xFC] - 1) + 1; + active_context->timers[0].counter = SPCRAM[0xFD] & 0xF; + active_context->timers[1].counter = SPCRAM[0xFE] & 0xF; + active_context->timers[2].counter = SPCRAM[0xFF] & 0xF; + active_context->PORT_R[0] = SPCRAM[0xF4]; + active_context->PORT_R[1] = SPCRAM[0xF5]; + active_context->PORT_R[2] = SPCRAM[0xF6]; + active_context->PORT_R[3] = SPCRAM[0xF7]; + + SPC_CPU_cycle_multiplicand = 1; + SPC_CPU_cycle_divisor = 1; + SPC_CPU_cycles_mul = 0; + spc_restore_flags(); + free(sFile); + return (0); +} + +// PUBLIC (non-static) functions + +s32 SPCInit(char *fn) +{ + Reset_SPC(); + if (LoadZState(fn)) + return 1; + return 0; +} + +void SPCAddWriteDSPCallback(void (*ToAddCallback)(u8)) +{ + SPC_Write_DSP_Hook = ToAddCallback; +} + +// Called from SPC 700 engine + +void DisplaySPC() +{ +} + +void InvalidSPCOpcode() +{ + exit(-1); +} + +void SPC_READ_DSP() +{ + if ((SPC_DSP_ADDR & 0xf) == 8) // ENVX + SPC_DSP[SPC_DSP_ADDR] = SNDDoEnv(SPC_DSP_ADDR >> 4) >> 24; +} + +void SPC_WRITE_DSP() +{ + s32 addr_lo = SPC_DSP_ADDR & 0xF, addr_hi = SPC_DSP_ADDR >> 4; + switch (addr_lo) + { + case 3: // Pitch hi + SPC_DSP_DATA &= 0x3F; + break; + case 5: // ADSR1 + if ((SPC_DSP[0x4C] & (1 << addr_hi)) && ((SPC_DSP_DATA & 0x80) != (SPC_DSP[SPC_DSP_ADDR] & 0x80))) + { + s32 i; + // First of all, in case anything was already + // going on, finish it up + SNDDoEnv(addr_hi); + if (SPC_DSP_DATA & 0x80) + { + // switch to ADSR--not sure what to do + i = SPC_DSP[(addr_hi << 4) + 6]; + SNDvoices[addr_hi].envstate = ATTACK; + SNDvoices[addr_hi].ar = SPC_DSP_DATA & 0xF; + SNDvoices[addr_hi].dr = SPC_DSP_DATA >> 4 & 7; + SNDvoices[addr_hi].sr = i & 0x1f; + SNDvoices[addr_hi].sl = i >> 5; + } + else + { + // switch to a GAIN mode + i = SPC_DSP[(addr_hi << 4) + 7]; + if (i & 0x80) + { + SNDvoices[addr_hi].envstate = i >> 5; + SNDvoices[addr_hi].gn = i & 0x1F; + } + else + { + SNDvoices[addr_hi].envx = (i & 0x7F) << 24; + SNDvoices[addr_hi].envstate = DIRECT; + } + } + } + break; + case 6: // ADSR2 + // Finish up what was going on + SNDDoEnv(addr_hi); + SNDvoices[addr_hi].sr = SPC_DSP_DATA & 0x1f; + SNDvoices[addr_hi].sl = SPC_DSP_DATA >> 5; + break; + case 7: // GAIN + if ((SPC_DSP[0x4C] & (1 << addr_hi)) && (SPC_DSP_DATA != SPC_DSP[SPC_DSP_ADDR]) && + !(SPC_DSP[(addr_hi << 4) + 5] & 0x80)) + { + if (SPC_DSP_DATA & 0x80) + { + // Finish up what was going on + SNDDoEnv(addr_hi); + SNDvoices[addr_hi].envstate = SPC_DSP_DATA >> 5; + SNDvoices[addr_hi].gn = SPC_DSP_DATA & 0x1F; + } + else + { + SNDvoices[addr_hi].envx = (SPC_DSP_DATA & 0x7F) << 24; + SNDvoices[addr_hi].envstate = DIRECT; + } + } + break; + // These are general registers + case 12: // 0xc + switch (addr_hi) + { + case 4: // Key on + SNDNoteOn(SPC_DSP_DATA); + SPC_DSP_DATA = SPC_DSP[0x4C]; + break; + case 5: // Key off + SNDNoteOff(SPC_DSP_DATA); + SPC_DSP_DATA = 0; + break; + } + break; + } + + SPC_DSP[SPC_DSP_ADDR] = SPC_DSP_DATA; +} diff --git a/spctools/spc2it/emu.h b/spctools/spc2it/emu.h new file mode 100644 index 0000000..0d9f29a --- /dev/null +++ b/spctools/spc2it/emu.h @@ -0,0 +1,23 @@ +/**************************************************** +*Part of SPC2IT, read readme.md for more information* +****************************************************/ + +#ifndef EMU_H +#define EMU_H +#include "spc2ittypes.h" +#include "sneese_spc.h" +extern u8 SPC_DSP[256]; +extern u8 SPCRAM[65536]; +#define TotalCycles (active_context->TotalCycles) +extern u32 SPC_DSP_DATA; + +extern s32 SPCtime; +extern SPCFileInformation *SPCInfo; +extern void (*SPC_Write_DSP_Hook)(u8); + +#define SPCUpdateRate 100 + +s32 SPCInit(char *); +void SPCAddWriteDSPCallback(void (*ToAddCallback)(u8)); + +#endif diff --git a/spctools/spc2it/it.c b/spctools/spc2it/it.c new file mode 100644 index 0000000..7bea514 --- /dev/null +++ b/spctools/spc2it/it.c @@ -0,0 +1,546 @@ +/**************************************************** +*Part of SPC2IT, read readme.md for more information* +****************************************************/ + +#include +#include +#include +#include + +#include "it.h" +#include "sound.h" +#include "emu.h" + +static itdata ITdata[8]; // Temp memory for patterns before going to file +static sndsamp *ITSamples[IT_SAMPLE_MAX]; +static u8 *ITpattbuf[NUM_PATT_BUFS]; // Where patterns are going to be , before writing to file +static u8 *ITPatterns; +static u32 ITPatternsSize; +static s32 ITpattlen[NUM_PATT_BUFS]; // lengths of each pattern +static s32 ITcurbuf, ITbufpos, ITcurrow; // Pointers into temp pattern buffers +static s32 ITrows; // Number of rows per pattern + +static pcm_t p1, p2; +static s32 offset[IT_PATTERN_MAX]; // table of offsets into temp file to each pattern +static s32 curpatt; // which pattern we are on in temp file +static s32 curoffs; // where we are in file + +static sndsamp *ITAllocateSample(s32 size) +{ + sndsamp *s; + if (((s = calloc(1, sizeof(sndsamp))) == NULL) || ((s->buf = calloc(1, size * 2)) == NULL)) + return (NULL); + s->length = size; + s->loopto = -1; + s->freq = 0; + return (s); +} + +static s32 ITGetBRRPrediction(u8 filter, pcm_t p1, pcm_t p2) +{ + s32 p; + switch (filter) + { + case 0: + return 0; + + case 1: + p = p1; + p -= p1 >> 4; + return p; + + case 2: + p = p1 << 1; + p += (-(p1 + (p1 << 1))) >> 5; + p -= p2; + p += p2 >> 4; + return p; + + case 3: + p = p1 << 1; + p += (-(p1 + (p1 << 2) + (p1 << 3))) >> 6; + p -= p2; + p += (p2 + (p2 << 1)) >> 4; + return p; + } + return 0; +} + +static void ITDecodeSampleInternal(s8 s, u8 shift_am, u8 filter) +{ + s32 a; + if (shift_am <= 0x0c) // Valid shift count + a = ((s < 8 ? s : s - 16) << shift_am) >> 1; + else + a = s < 8 ? 1 << 11 : (-1) << 11; // Values "invalid" shift counts + + a += ITGetBRRPrediction(filter, p1, p2); + + if (a > 0x7fff) + a = 0x7fff; + else if (a < -0x8000) + a = -0x8000; + if (a > 0x3fff) + a -= 0x8000; + else if (a < -0x4000) + a += 0x8000; + + p2 = p1; + p1 = a; +} + +static s32 ITDecodeSample(u16 start, sndsamp **sp) +{ + sndsamp *s; + u8 *src; + u16 end; + u32 brrptr, sampptr = 0; + s32 i; + src = &SPCRAM[start]; + for (end = 0; !(src[end] & 1); end += 9) + ; + i = (end + 9) / 9 * 16; + *sp = s = ITAllocateSample(i); + if (s == NULL) + return 1; + if (src[end] & 2) + s->loopto = 0; + for (brrptr = 0; brrptr <= end;) + { + u8 range = src[brrptr++]; + u8 filter = (range & 0x0c) >> 2; + u8 shift_amount = (range >> 4) & 0x0F; + for (i = 0; i < 8; i++, brrptr++) + { + ITDecodeSampleInternal(src[brrptr] >> 4, shift_amount, filter); // Decode high nybble + s->buf[sampptr++] = 2 * p1; + ITDecodeSampleInternal(src[brrptr] & 0x0F, shift_amount, filter); // Decode low nybble + s->buf[sampptr++] = 2 * p1; + } + } + return 0; +} + +static void ITUpdateSample(s32 s) +{ + s32 i; + struct + { + u16 vptr, lptr; + } *SRCDIR; + i = SPC_DSP[0x5D] << 8; //sample directory table... + SRCDIR = (void *)&SPCRAM[i]; + if (ITDecodeSample(SRCDIR[s].vptr, &ITSamples[s])) + return; + if (ITSamples[s]->loopto != -1) + { + ITSamples[s]->loopto = (SRCDIR[s].lptr - SRCDIR[s].vptr) / 9 * 16; + if ((ITSamples[s]->loopto > ITSamples[s]->length) || (ITSamples[s]->loopto < 0)) + ITSamples[s]->loopto = -1; + } +} + +static s32 ITPitchToNote(s32 pitch, s32 base) +{ + f64 tmp; + s32 note; + tmp = log2((f64)pitch / (f64)base) * 12 + 60; + if (tmp > 127) + tmp = 127; + else if (tmp < 0) + tmp = 0; + note = (s32)tmp; + if ((s32)(tmp * 2) != (note * 2)) + note++; // correct rounding + return note; +} + +static void ITWriteDSPCallback(u8 v) +{ + s32 addr_lo = SPC_DSP_ADDR & 0xF; + s32 addr_hi = SPC_DSP_ADDR >> 4; + if (!(addr_lo == 12)) + return; + + if (!(addr_hi == 4)) + return; + + s32 i, cursamp, pitch; + v &= 0xFF; // ext + for (i = 0; i < 8; i++) + { + if (v & (1 << i)) + { + cursamp = SPC_DSP[4 + (i << 4)]; // Number of current sample + if (cursamp < IT_SAMPLE_MAX) // Only 99 samples supported, sorry + { + if (ITSamples[cursamp] == NULL) + ITUpdateSample(cursamp); + pitch = (s32)(*(u16 *)&SPC_DSP[(i << 4) + 0x02]) * 7.8125; // Ext, Get pitch + if (ITSamples[cursamp]->freq == 0) + ITSamples[cursamp]->freq = pitch; + if ((pitch != 0) && (ITSamples[cursamp] != NULL) && + (ITSamples[cursamp]->freq != 0)) // Ext, Sample is actually useful? + { + ITdata[i].mask |= IT_MASK_NOTE_SAMPLE_ADJUSTVOLUME; // Update note, sample, and adjust the volume. + ITdata[i].note = ITPitchToNote(pitch, ITSamples[cursamp]->freq); // change pitch to note + ITdata[i].pitch = (s32)(pow(2, ((f64)ITdata[i].note - 60) / 12) * + (f64)ITSamples[cursamp]->freq); // needed for pitch slide detection + ITdata[i].lvol = 0; + ITdata[i].rvol = 0; + // IT code will get sample from DSP buffer + } + } + } + } +} + +static void ITSSave(sndsamp *s, FILE *f) // Save sample +{ + s32 loopto = -1; + s32 length = 0; + s32 freq = 0; + s32 ofs = ftell(f); + ITFileSample *sHeader = calloc(1, sizeof(ITFileSample)); + if (sHeader == NULL) + { + printf("Error: could not allocate memory for ITFileSample struct\n"); + exit(1); + } + if (s != NULL) + { + loopto = s->loopto; + length = s->length; + freq = s->freq; + } + else + { + freq = 8363; + loopto = 0; + } + memcpy(sHeader->magic, "IMPS", 4); + if (length) + strcpy(sHeader->fileName, "SPC2ITSAMPLE"); + sHeader->GlobalVolume = 64; + sHeader->Flags |= 2; // Bit 1 (16 bit) + if (length) + sHeader->Flags |= 1; // Bit 0 (sample included with header) + sHeader->Volume = 64; + if (length) + strcpy(sHeader->SampleName, "SPC2ITSAMPLE"); + sHeader->Convert = 1; + sHeader->DefaultPan = 0; + sHeader->NumberOfSamples = length; + if (loopto != -1) + { + sHeader->Flags |= 16; // Bit 4 (Use loop) + sHeader->LoopBeginning = loopto; + sHeader->LoopEnd = length; + } + sHeader->C5Speed = freq; + if (length) + sHeader->SampleOffset = ofs + sizeof(ITFileSample); + fwrite(sHeader, sizeof(ITFileSample), 1, f); + free(sHeader); + if (length) + fwrite(s->buf, s->length * 2, 1, f); // Write the sample itself... 2x length. +} + +static void ITWritePattern(ITPatternInfo *pInfo) { + ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Channel; + ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Mask; + + if (pInfo->Mask & IT_MASK_NOTE) + ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Note; + if (pInfo->Mask & IT_MASK_SAMPLE) + ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Sample; + if (pInfo->Mask & IT_MASK_ADJUSTVOLUME) + ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Volume; + if (pInfo->Mask & IT_MASK_PITCHSLIDE) + { + ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->Command; + ITpattbuf[ITcurbuf][ITbufpos++] = pInfo->CommandValue; + } +} + +s32 ITStart(s32 rows) // Opens up temporary file and inits writing +{ + SPCAddWriteDSPCallback(&ITWriteDSPCallback); + s32 i; + ITrows = rows; + + for (i = 0; i < NUM_PATT_BUFS; i++) + { + ITpattbuf[i] = calloc(1, Mem64k - 8); //Don't include the 8 byte header + if (ITpattbuf[i] == NULL) + { + printf("Error: could not allocate memory for IT pattern buffer\n"); + exit(1); + } + ITpattlen[i] = 0; + } + + ITPatterns = calloc(1, Mem64k * IT_PATTERN_MAX); + if (ITPatterns == NULL) + { + printf("Error: could not allocate memory for IT pattern storage\n"); + exit(1); + } + ITPatternsSize = 0; + ITcurbuf = 0; + ITbufpos = 0; + ITcurrow = 0; + + curoffs = 0; + for (i = 0; i < IT_PATTERN_MAX; i++) + offset[i] = -1; // -1 means unused pattern + curpatt = 0; + + for (i = 0; i < 8; i++) + ITdata[i].mask = 0; + return 0; +} + +s32 ITUpdate() // Dumps pattern buffers to file +{ + u8 *tmpptr; + s32 i; + ITFilePattern *pHeader = calloc(1, sizeof(ITFilePattern)); + if (pHeader == NULL) + { + printf("Error: could not allocate memory for ITFilePattern struct\n"); + exit(1); + } + for (i = 0; i < ITcurbuf; i++) + { + offset[curpatt] = curoffs; + pHeader->Length = ITpattlen[i]; + pHeader->Rows = ITrows; + memcpy(&ITPatterns[ITPatternsSize], pHeader, sizeof(ITFilePattern)); + ITPatternsSize += sizeof(ITFilePattern); + memcpy(&ITPatterns[ITPatternsSize], ITpattbuf[i], ITpattlen[i]); + ITPatternsSize += ITpattlen[i]; + curoffs += ITpattlen[i] + 8; + if (curpatt < IT_PATTERN_MAX) + curpatt++; // Continue counting if we haven't reached the limit yet + } + free(pHeader); + tmpptr = ITpattbuf[0]; + ITpattbuf[0] = ITpattbuf[ITcurbuf]; + ITpattbuf[ITcurbuf] = tmpptr; + ITcurbuf = 0; + return 0; +} + +s32 ITWrite(char *fn) // Write the final IT file +{ + FILE *f; + s32 i, t, numsamps, ofs; + ITPatternInfo *pInfo = calloc(1, sizeof(ITPatternInfo)); + if (pInfo == NULL) + { + printf("Error: could not allocate memory for ITPatternInfo struct\n"); + exit(1); + } + // START IT CLEANUP + if (fn == NULL) + { + printf("Error: no IT filename\n"); + exit(1); + } + pInfo->Mask = 1; + pInfo->Note = 254; //note cut + // Stop all notes and loop back to the beginning + for (i = 0; i < 15; i++) // Save the last channel to put loop in + { + pInfo->Channel = (i + 1) | 128; // Channels are 1 based (Channels start at 1, not 0, ITTECH.TXT is WRONG) !!! + ITWritePattern(pInfo); + } + pInfo->Channel = (15 + 1) | 128; + pInfo->Mask = 9; // 1001 (note, special command) + pInfo->Command = 2; // Effect B: jump to... + pInfo->CommandValue = 0; //...order 0 (Loop to beginning) + ITWritePattern(pInfo); + free(pInfo); + while (ITcurrow++ < ITrows) + ITpattbuf[ITcurbuf][ITbufpos++] = 0; // end-of-row + + ITpattlen[ITcurbuf++] = ITbufpos; + ITUpdate(); // Save the changes we just made + // END IT CLEANUP + f = fopen(fn, "wb"); + if (f == NULL) + { + printf("Error: could not open IT file\n"); + exit(1); + } + ITFileHeader *fHeader = calloc(1, sizeof(ITFileHeader)); + if (fHeader == NULL) + { + printf("Error: could not allocate memory for ITFileHeader struct\n"); + exit(1); + } + memcpy(fHeader->magic, "IMPM", 4); + if (SPCInfo->SongTitle[0]) + strncpy(fHeader->songName, SPCInfo->SongTitle, 25); + else + strcpy(fHeader->songName, "spc2it conversion"); // default string + fHeader->OrderNumber = curpatt + 1; // number of orders + terminating order + for (numsamps = IT_SAMPLE_MAX; ITSamples[numsamps - 1] == NULL; numsamps--) + ; // Count the number of samples (the reason of the minus one is because c arrays start at 0) + numsamps++; + fHeader->SampleNumber = numsamps; // Number of samples + fHeader->PatternNumber = curpatt; // Number of patterns + fHeader->TrackerCreatorVersion = 0xDAEB; // Created with this tracker version + fHeader->TrackerFormatVersion = 0x200; // Compatible with this tracker version + fHeader->Flags = 9; // Flags: Stereo, Linear Slides + fHeader->GlobalVolume = 128; // Global volume + fHeader->MixVolume = 100; // Mix volume + fHeader->InitialSpeed = 1; // Initial speed (fastest) + fHeader->InitialTempo = (u8)(SPCUpdateRate * 2.5); // Initial tempo (determined by update rate) + fHeader->PanningSeperation = 128; // Stereo separation (max) + for (i = 0; i < 8; i++) + fHeader->ChannelPan[i] = 0; // Channel pan: Set 8 channels to left + for (i = 8; i < 16; i++) + fHeader->ChannelPan[i] = 64; // Set 8 channels to right + for (i = 16; i < 64; i++) + fHeader->ChannelPan[i] = 128; // Disable the rest of the channels (Value: +128) + for (i = 0; i < 16; i++) + fHeader->ChannelVolume[i] = 64; // Channel Vol: set 16 channels loud + fwrite(fHeader, sizeof(ITFileHeader), 1, f); + free(fHeader); + // orders + for (i = 0; i < curpatt; i++) + fputc(i, f); // Write from 0 to the number of patterns (max: 0xFD) + fputc(255, f); // terminating order + // Sample offsets + ofs = sizeof(ITFileHeader) + (curpatt + 1) + ((numsamps * sizeof(s32)) + (curpatt * sizeof(s32))); + for (i = 0; i < numsamps; i++) + { + fwrite(&ofs, sizeof(s32), 1, f); + ofs += sizeof(ITFileSample); + if (ITSamples[i] != NULL) // Sample is going to be put in file? Add the length of the sample. + ofs += (ITSamples[i]->length * 2); + } + // Pattern offsets + for (i = 0; i < curpatt; i++) + { + t = offset[i] + ofs; + fwrite(&t, sizeof(s32), 1, f); + } + // samples + for (i = 0; i < numsamps; i++) + ITSSave(ITSamples[i], f); + // patterns + fwrite(ITPatterns, ITPatternsSize, 1, f); + for (i = 0; i < NUM_PATT_BUFS; i++) + free(ITpattbuf[i]); + free(ITPatterns); + fclose(f); + return 0; +} + +void ITMix() +{ + s32 envx, pitchslide, lvol = 0, rvol = 0, pitch, temp = 0, voice; + ITPatternInfo *pInfo = calloc(1, sizeof(ITPatternInfo)); + if (pInfo == NULL) + { + printf("Error: could not allocate memory for ITPatternInfo struct\n"); + exit(1); + } + u8 mastervolume = SPC_DSP[0x0C]; + for (voice = 0; voice < 8; voice++) + { + if ((SPC_DSP[0x4C] & (1 << voice))) // 0x4C == key on + { + envx = SNDDoEnv(voice); + lvol = (envx >> 24) * (s32)((s8)SPC_DSP[(voice << 4) ]) * mastervolume >> 14; // Ext + rvol = (envx >> 24) * (s32)((s8)SPC_DSP[(voice << 4) + 0x01]) * mastervolume >> 14; // Ext + // Volume no echo: (s32)((s8)SPC_DSP[(voice << 4) ]) * mastervolume >> 7; + + + pitch = (s32)(*(u16 *)&SPC_DSP[(voice << 4) + 0x02]) * 7.8125; // This code merges 2 numbers together, high and low 8 bits, to make 16 bits. + // adjust for negative volumes + if (lvol < 0) + lvol = -lvol; + if (rvol < 0) + rvol = -rvol; + // lets see if we need to pitch slide + if (pitch && ITdata[voice].pitch) + { + pitchslide = (s32)(log2((f64)pitch / (f64)ITdata[voice].pitch) * 768.0); + if (pitchslide) + ITdata[voice].mask |= IT_MASK_PITCHSLIDE; // enable pitch slide + } + // adjust volume? + if ((lvol != ITdata[voice].lvol) || (rvol != ITdata[voice].rvol)) + { + ITdata[voice].mask |= IT_MASK_ADJUSTVOLUME; // Enable adjust volume + ITdata[voice].lvol = lvol; + ITdata[voice].rvol = rvol; + } + } + + pInfo->Channel = (voice + 1) | 128; //Channels here are 1 based! + pInfo->Mask = ITdata[voice].mask; + if (ITdata[voice].mask & IT_MASK_NOTE) + pInfo->Note = ITdata[voice].note; + if (ITdata[voice].mask & IT_MASK_SAMPLE) + pInfo->Sample = SPC_DSP[(voice << 4) + 4] + 1; + if (ITdata[voice].mask & IT_MASK_ADJUSTVOLUME) + pInfo->Volume = (lvol > 64) ? 64 : lvol; + if (ITdata[voice].mask & IT_MASK_PITCHSLIDE) + { + if (pitchslide > 0xF) + { + temp = pitchslide >> 2; + if (temp > 0xF) + temp = 0xF; + temp |= FINE_SLIDE; + ITdata[voice].pitch = (s32)((f64)ITdata[voice].pitch * pow(2, (f64)((temp & 0xF) << 2) / 768.0)); + pInfo->Command = EFFECT_F; + pInfo->CommandValue = temp; + } + else if (pitchslide > 0) + { + temp = pitchslide | EXTRA_FINE_SLIDE; + ITdata[voice].pitch = (s32)((f64)ITdata[voice].pitch * pow(2, (f64)(temp & 0xF) / 768.0)); + pInfo->Command = EFFECT_F; + pInfo->CommandValue = temp; + } + else if (pitchslide > -0x10) + { + temp = (-pitchslide) | EXTRA_FINE_SLIDE; + ITdata[voice].pitch = (s32)((f64)ITdata[voice].pitch * pow(2, (f64)(temp & 0xF) / -768.0)); + pInfo->Command = EFFECT_E; + pInfo->CommandValue = temp; + } + else + { + temp = (-pitchslide) >> 2; + if (temp > 0xF) + temp = 0xF; + temp |= FINE_SLIDE; + ITdata[voice].pitch = (s32)((f64)ITdata[voice].pitch * pow(2, (f64)((temp & 0xF) << 2) / -768.0)); + pInfo->Command = EFFECT_E; + pInfo->CommandValue = temp; + } + } + ITWritePattern(pInfo); // Write for left channel + pInfo->Channel = (voice + 8 + 1) | 128; + if (ITdata[voice].mask & IT_MASK_ADJUSTVOLUME) + pInfo->Volume = (rvol > 64) ? 64 : rvol; + ITWritePattern(pInfo); // Write for right channel + + ITdata[voice].mask = 0; // Clear the mask + } + ITpattbuf[ITcurbuf][ITbufpos++] = 0; // End-of-row + if (++ITcurrow >= ITrows) + { + ITpattlen[ITcurbuf++] = ITbufpos; + ITbufpos = 0; // Reset buffer pos + ITcurrow = 0; // Reset current row + } + free(pInfo); +} diff --git a/spctools/spc2it/it.h b/spctools/spc2it/it.h new file mode 100644 index 0000000..f2b5416 --- /dev/null +++ b/spctools/spc2it/it.h @@ -0,0 +1,33 @@ +/**************************************************** +*Part of SPC2IT, read readme.md for more information* +****************************************************/ + +#ifndef IT_H +#define IT_H + +#define NUM_PATT_BUFS 128 + +#include "spc2ittypes.h" + +s32 ITStart(s32); // Opens temp file, inits writing +s32 ITUpdate(); // Dumps pattern buffers to file +s32 ITWrite(char *fn); // Stops recording and writes IT file from temp data +void ITMix(); + +// Macros + +#define FINE_SLIDE 0xF0 +#define EXTRA_FINE_SLIDE 0xE0 +#define EFFECT_F 6 +#define EFFECT_E 5 + +#define IT_PATTERN_MAX 0xFD // The original Impulse Tracker has 200 patterns max +#define IT_SAMPLE_MAX 0xFF // The original Impulse Tracker has 99 samples max + +#define IT_MASK_NOTE 1 // 0001 (Note) +#define IT_MASK_SAMPLE 2 // 0010 (Sample/instrument marker) +#define IT_MASK_ADJUSTVOLUME 4 // 0100 (volume/panning) +#define IT_MASK_NOTE_SAMPLE_ADJUSTVOLUME (IT_MASK_NOTE | IT_MASK_SAMPLE | IT_MASK_ADJUSTVOLUME) +#define IT_MASK_PITCHSLIDE 8 // 1000 (some special command, we use effect F and effect E) + +#endif \ No newline at end of file diff --git a/spctools/spc2it/main.c b/spctools/spc2it/main.c new file mode 100644 index 0000000..79fe88b --- /dev/null +++ b/spctools/spc2it/main.c @@ -0,0 +1,171 @@ +/**************************************************** +*Part of SPC2IT, read readme.md for more information* +****************************************************/ + +#include +#include +#include +#include +#include + +#include "sound.h" +#include "it.h" +#include "emu.h" +#include "sneese_spc.h" + +#ifdef _WIN32 +#undef realpath +#define realpath(N,R) _fullpath((R),(N),_MAX_PATH) +#endif + +int main(int argc, char **argv) +{ + size_t u8Size = sizeof(u8); + if (!(u8Size == 1)) + printf("Warning: wrong size u8: %zu \n", u8Size); + size_t u16Size = sizeof(u16); + if (!(u16Size == 2)) + printf("Warning: wrong size u16: %zu \n", u16Size); + size_t u32Size = sizeof(u32); + if (!(u32Size == 4)) + printf("Warning: wrong size u32: %zu \n", u32Size); + size_t u64Size = sizeof(u64); + if (!(u64Size == 8)) + printf("Warning: wrong size u64: %zu \n", u64Size); + size_t s8Size = sizeof(s8); + if (!(s8Size == 1)) + printf("Warning: wrong size s8: %zu \n", s8Size); + size_t s16Size = sizeof(s16); + if (!(s16Size == 2)) + printf("Warning: wrong size s16: %zu \n", s16Size); + size_t s32Size = sizeof(s32); + if (!(s32Size == 4)) + printf("Warning: wrong size s32: %zu \n", s32Size); + size_t s64Size = sizeof(s64); + if (!(s64Size == 8)) + printf("Warning: wrong size s64: %zu \n", s64Size); + size_t ITFileHeaderSize = sizeof(ITFileHeader); + if (!(ITFileHeaderSize == 192)) + printf("Warning: wrong size ITFileHeader: %zu \n", ITFileHeaderSize); + size_t ITFileSampleSize = sizeof(ITFileSample); + if (!(ITFileSampleSize == 80)) + printf("Warning: wrong size ITFileSample: %zu \n", ITFileSampleSize); + size_t ITFilePatternSize = sizeof(ITFilePattern); + if (!(ITFilePatternSize == 8)) + printf("Warning: wrong size ITFilePattern: %zu \n", ITFilePatternSize); + size_t SPCFileSize = sizeof(SPCFile); + if (!(SPCFileSize == 65920)) + printf("Warning: wrong size SPCFile: %zu \n", SPCFileSize); + s32 seconds, limit, ITrows; + char fn[PATH_MAX]; + s32 i; + fn[0] = 0; + ITrows = 200; // Default 200 IT rows/pattern + limit = 0; // Will be set later + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + switch (argv[i][1]) + { + case 'r': + i++; + ITrows = atoi(argv[i]); + break; + case 't': + i++; + limit = atoi(argv[i]); + break; + default: + printf("Warning: unrecognized option '-%c'\n", argv[i][1]); + } + else + realpath(argv[i], fn); + } + if (fn[0] == 0) + { + printf(" SPC2IT - converts SPC700 sound files to the Impulse Tracker format\n\n"); + printf(" Usage: spc2it [options] \n"); + printf(" Where is any .spc or .sp# file\n\n"); + printf(" Options: "); + printf("-t x Specify a time limit in seconds [60 default]\n"); + printf(" -d xxxxxxxx Voices to disable (1-8) [none default]\n"); + printf(" -r xxx Specify IT rows per pattern [200 default]\n"); + exit(0); + } + printf("\n"); + printf("Filepath: %s\n", fn); + if (ITStart(ITrows)) + { + printf("Error: failed to initialize pattern buffers\n"); + exit(1); + } + if (SPCInit(fn)) // Reset SPC and load state + { + printf("Error: failed to initialize emulation\n"); + exit(1); + } + if (SNDInit()) + { + printf("Error: failed to initialize sound\n"); + exit(1); + } + + if ((!limit) && (SPCtime)) + limit = SPCtime; + else if (!limit) + limit = 60; + + printf("Time (seconds): %i\n", limit); + + printf("IT Parameters:\n"); + printf(" Rows/pattern: %d\n", ITrows); + + printf("ID info:\n"); + printf(" Song: %s\n", SPCInfo->SongTitle); + printf(" Game: %s\n", SPCInfo->GameTitle); + printf(" Dumper: %s\n", SPCInfo->DumperName); + printf(" Comments: %s\n", SPCInfo->Comment); + printf(" Created on: %s\n", SPCInfo->Date); + + printf("\n"); + + fflush(stdout); + + SNDNoteOn(SPC_DSP[0x4c]); + + seconds = SNDratecnt = 0; + while (true) + { + ITMix(); + if (ITUpdate()) + break; + SNDratecnt += 1; + SPC_START(2048000 / (SPCUpdateRate * 2)); // emulate the SPC700 + + if (SNDratecnt >= SPCUpdateRate) + { + SNDratecnt -= SPCUpdateRate; + seconds++; // count number of seconds + printf("Progress: %f%%\r", (((f64)seconds / limit) * 100)); + fflush(stdout); + if (seconds == limit) + break; + } + } + printf("\n\nSaving file...\n"); + for (i = 0; i < PATH_MAX; i++) + if (fn[i] == 0) + break; + for (; i > 0; i--) + if (fn[i] == '.') + { + strcpy(&fn[i + 1], "it"); + break; + } + if (ITWrite(fn)) + printf("Error: failed to write %s.\n", fn); + else + printf("Wrote to %s successfully.\n", fn); + Reset_SPC(); + return 0; +} diff --git a/spctools/spc2it/readme.md b/spctools/spc2it/readme.md new file mode 100755 index 0000000..5369092 --- /dev/null +++ b/spctools/spc2it/readme.md @@ -0,0 +1,29 @@ +Cloned from: https://github.com/uyjulian/spc2it + +SPC2IT +====== + +Convert SPC files to IT (Impulse Tracker) files. + +Compiling +========= + +You need cmake. + +> mkdir b && cd b && cmake .. && make + +Running +======= + +Enter the executable's path in the command line, then press enter to see the syntax. + +Bugs +==== + +• None... + +More information +================ + +For more information, read the documentation in ./doc/ +Also, see http://www.romhacking.net/forum/index.php?topic=10164.0 diff --git a/spctools/spc2it/sneese_spc.h b/spctools/spc2it/sneese_spc.h new file mode 100644 index 0000000..be1370b --- /dev/null +++ b/spctools/spc2it/sneese_spc.h @@ -0,0 +1,134 @@ +/**************************************************** +*Part of SPC2IT, read readme.md for more information* +****************************************************/ + +/************************************************************************** + + Copyright (c) 2005 Brad Martin. + Some portions copyright (c) 1998-2005 Charles Bilyue'. + +This file is part of OpenSPC. + +sneese_spc.h: This file defines the interface between the SNEeSe SPC700 +core and the associated wrapper files. As the licensing rights for SNEeSe +are different from the rest of OpenSPC, none of the files in this directory +are LGPL. Although this file was created by me (Brad Martin), it contains +some code derived from SNEeSe and therefore falls under its license. See +the file './doc/LICENSE_SNEESE' in this directory for more information. + + **************************************************************************/ + +#if !defined(_SNEESE_SPC_H) +#define _SNEESE_SPC_H + +#include "spc2ittypes.h" + +/*========== DEFINES ==========*/ + +#define SPC_CTRL (SPCRAM[0xF1]) +#define SPC_DSP_ADDR (SPCRAM[0xF2]) + +/*========== TYPES ==========*/ + +typedef union +{ + u16 w; + struct + { + u8 l; + u8 h; + } b; +} word_2b; + +typedef struct +{ + u8 B_flag; + u8 C_flag; + u8 H_flag; + u8 I_flag; + u8 N_flag; + u8 P_flag; + u8 PSW; + u8 SP; + u8 V_flag; + u8 X; + u8 Z_flag; + u8 cycle; + u8 data; + u8 data2; + u8 opcode; + u8 offset; + + word_2b PC; + word_2b YA; + word_2b address; + word_2b address2; + word_2b data16; + word_2b direct_page; + + u32 Cycles; + void *FFC0_Address; + u32 TotalCycles; + s32 WorkCycles; + u32 last_cycles; + + u8 PORT_R[4]; + u8 PORT_W[4]; + struct + { + u8 counter; + s16 position; + s16 target; + u32 cycle_latch; + } timers[4]; +} SPC700_CONTEXT; + +/*========== VARIABLES ==========*/ + +/* SPCimpl.c variables */ +extern u8 In_CPU; +extern u32 Map_Address; +extern u32 Map_Byte; +extern u32 SPC_CPU_cycle_divisor; +extern u32 SPC_CPU_cycle_multiplicand; +extern u32 SPC_CPU_cycles; +extern u32 SPC_CPU_cycles_mul; +extern u8 SPC_DSP[256]; +extern u32 SPC_DSP_DATA; +extern u8 SPCRAM[65536]; +extern u32 sound_cycle_latch; + +/* spc700.c variables */ +extern SPC700_CONTEXT *active_context; + +/*========== MACROS ==========*/ + +#define Wrap_SDSP_Cyclecounter() +#define update_sound() +#define BIT(bit) (1 << (bit)) + +/*========== PROCEDURES ==========*/ + +/* SPCimpl.c procedures */ +void DisplaySPC(void); + +void InvalidSPCOpcode(void); + +void SPC_READ_DSP(void); + +void SPC_WRITE_DSP(void); + +/* spc700.c procedures */ +void Reset_SPC(void); + +u8 SPC_READ_PORT_W(u16 address); + +void SPC_START(u32 cycles); + +void SPC_WRITE_PORT_R(u16 address, u8 data); + +u8 get_SPC_PSW(void); + +void spc_restore_flags(void); + +#endif diff --git a/spctools/spc2it/sound.c b/spctools/spc2it/sound.c new file mode 100644 index 0000000..2e85696 --- /dev/null +++ b/spctools/spc2it/sound.c @@ -0,0 +1,263 @@ +/**************************************************** +*Part of SPC2IT, read readme.md for more information* +****************************************************/ + +#include +#include +#include +#include + +#include "emu.h" +#include "sound.h" + +sndvoice SNDvoices[8]; +s32 SNDratecnt; + +static const u32 C[0x20] = { + 0x0, 0x20000, 0x18000, 0x14000, 0x10000, 0xC000, 0xA000, 0x8000, 0x6000, 0x5000, 0x4000, + 0x3000, 0x2800, 0x2000, 0x1800, 0x1400, 0x1000, 0xC00, 0xA00, 0x800, 0x600, 0x500, + 0x400, 0x300, 0x280, 0x200, 0x180, 0x140, 0x100, 0xC0, 0x80, 0x40}; // How many cycles till adjust + // ADSR/GAIN + +// PUBLIC (non-static) functions: + +s32 SNDDoEnv(s32 voice) +{ + u32 envx, c; + envx = SNDvoices[voice].envx; + for (;;) + { + u32 cyc = TotalCycles - SNDvoices[voice].envcyc; + switch (SNDvoices[voice].envstate) + { + case ATTACK: + c = C[(SNDvoices[voice].ar << 1) + 1]; + if (c == 0) + { + SNDvoices[voice].envcyc = TotalCycles; + return SNDvoices[voice].envx = envx; + } + if (cyc > c) + { + SNDvoices[voice].envcyc += c; + envx += 0x2000000; // add 1/64th + if (envx >= 0x7F000000) + { + envx = 0x7F000000; + if (SNDvoices[voice].sl != 7) + SNDvoices[voice].envstate = DECAY; + else + SNDvoices[voice].envstate = SUSTAIN; + } + } + else + return SNDvoices[voice].envx = envx; + break; + case DECAY: + c = C[(SNDvoices[voice].dr << 1) + 0x10]; + if (c == 0) + { + SNDvoices[voice].envcyc = TotalCycles; + return SNDvoices[voice].envx = envx; + } + if (cyc > c) + { + SNDvoices[voice].envcyc += c; + envx = (envx >> 8) * 255; // mult by 1-1/256 + if (envx <= 0x10000000 * (SNDvoices[voice].sl + 1)) + { + envx = 0x10000000 * (SNDvoices[voice].sl + 1); + SNDvoices[voice].envstate = SUSTAIN; + } + } + else + return SNDvoices[voice].envx = envx; + break; + case SUSTAIN: + c = C[SNDvoices[voice].sr]; + if (c == 0) + { + SNDvoices[voice].envcyc = TotalCycles; + return SNDvoices[voice].envx = envx; + } + if (cyc > c) + { + SNDvoices[voice].envcyc += c; + envx = (envx >> 8) * 255; // mult by 1-1/256 + } + else + return SNDvoices[voice].envx = envx; + break; + case RELEASE: + // says add 1/256?? That won't release, must be subtract. + // But how often? Oh well, who cares, I'll just + // pick a number. :) + c = C[0x1A]; + if (c == 0) + { + SNDvoices[voice].envcyc = TotalCycles; + return SNDvoices[voice].envx = envx; + } + if (cyc > c) + { + SNDvoices[voice].envcyc += c; + envx -= 0x800000; // sub 1/256th + if ((envx == 0) || (envx > 0x7F000000)) + { + SPC_DSP[0x4C] &= ~(1 << voice); + return SNDvoices[voice].envx = 0; + } + } + else + return SNDvoices[voice].envx = envx; + break; + case INCREASE: + c = C[SNDvoices[voice].gn]; + if (c == 0) + { + SNDvoices[voice].envcyc = TotalCycles; + return SNDvoices[voice].envx = envx; + } + if (cyc > c) + { + SNDvoices[voice].envcyc += c; + envx += 0x2000000; // add 1/64th + if (envx > 0x7F000000) + { + SNDvoices[voice].envcyc = TotalCycles; + return SNDvoices[voice].envx = 0x7F000000; + } + } + else + return SNDvoices[voice].envx = envx; + break; + case DECREASE: + c = C[SNDvoices[voice].gn]; + if (c == 0) + { + SNDvoices[voice].envcyc = TotalCycles; + return SNDvoices[voice].envx = envx; + } + if (cyc > c) + { + SNDvoices[voice].envcyc += c; + envx -= 0x2000000; // sub 1/64th + if (envx > 0x7F000000) // underflow + { + SNDvoices[voice].envcyc = TotalCycles; + return SNDvoices[voice].envx = 0; + } + } + else + return SNDvoices[voice].envx = envx; + break; + case EXP: + c = C[SNDvoices[voice].gn]; + if (c == 0) + { + SNDvoices[voice].envcyc = TotalCycles; + return SNDvoices[voice].envx = envx; + } + if (cyc > c) + { + SNDvoices[voice].envcyc += c; + envx = (envx >> 8) * 255; // mult by 1-1/256 + } + else + return SNDvoices[voice].envx = envx; + break; + case BENT: + c = C[SNDvoices[voice].gn]; + if (c == 0) + { + SNDvoices[voice].envcyc = TotalCycles; + return SNDvoices[voice].envx = envx; + } + if (cyc > c) + { + SNDvoices[voice].envcyc += c; + if (envx < 0x60000000) + envx += 0x2000000; // add 1/64th + else + envx += 0x800000; // add 1/256th + if (envx > 0x7F000000) + { + SNDvoices[voice].envcyc = TotalCycles; + return SNDvoices[voice].envx = 0x7F000000; + } + } + else + return SNDvoices[voice].envx = envx; + break; + case DIRECT: + SNDvoices[voice].envcyc = TotalCycles; + return envx; + } + } +} + +void SNDNoteOn(u8 v) +{ + s32 i, cursamp, adsr1, adsr2, gain; + v &= 0xFF; + for (i = 0; i < 8; i++) + if (v & (1 << i)) + { + cursamp = SPC_DSP[4 + (i << 4)]; + if (cursamp < 512) + { + SPC_DSP[0x4C] |= (1 << i); + // figure ADSR/GAIN + adsr1 = SPC_DSP[(i << 4) + 5]; + if (adsr1 & 0x80) + { + // ADSR mode + adsr2 = SPC_DSP[(i << 4) + 6]; + SNDvoices[i].envx = 0; + SNDvoices[i].envcyc = TotalCycles; + SNDvoices[i].envstate = ATTACK; + SNDvoices[i].ar = adsr1 & 0xF; + SNDvoices[i].dr = adsr1 >> 4 & 7; + SNDvoices[i].sr = adsr2 & 0x1f; + SNDvoices[i].sl = adsr2 >> 5; + } + else + { + // GAIN mode + gain = SPC_DSP[(i << 4) + 7]; + if (gain & 0x80) + { + SNDvoices[i].envcyc = TotalCycles; + SNDvoices[i].envstate = gain >> 5; + SNDvoices[i].gn = gain & 0x1F; + } + else + { + SNDvoices[i].envx = (gain & 0x7F) << 24; + SNDvoices[i].envstate = DIRECT; + } + } + } + } + if (SPC_Write_DSP_Hook) + (*SPC_Write_DSP_Hook)(v); +} + +void SNDNoteOff(u8 v) +{ + s32 i; + for (i = 0; i < 8; i++) + if (v & (1 << i)) + { + SNDDoEnv(i); + SNDvoices[i].envstate = RELEASE; + } +} + +s32 SNDInit() +{ + s32 i; + for (i = 0; i < 8; i++) + SNDvoices[i].envx = 0; + return (0); +} diff --git a/spctools/spc2it/sound.h b/spctools/spc2it/sound.h new file mode 100644 index 0000000..8123106 --- /dev/null +++ b/spctools/spc2it/sound.h @@ -0,0 +1,28 @@ +/**************************************************** +*Part of SPC2IT, read readme.md for more information* +****************************************************/ + +#ifndef SOUND_H +#define SOUND_H + +#include "spc2ittypes.h" + +#define ATTACK 0 // A of ADSR +#define DECAY 1 // D of ADSR +#define SUSTAIN 2 // S of ADSR +#define RELEASE 3 // R of ADSR +#define DECREASE 4 // GAIN linear decrease mode +#define EXP 5 // GAIN exponential decrease mode +#define INCREASE 6 // GAIN linear increase mode +#define BENT 7 // GAIN bent line increase mode +#define DIRECT 8 // Directly specify ENVX + +extern sndvoice SNDvoices[8]; +extern s32 SNDkeys, SNDratecnt; + +s32 SNDDoEnv(s32); +void SNDNoteOn(u8); +void SNDNoteOff(u8); +s32 SNDInit(); + +#endif diff --git a/spctools/spc2it/spc2ittypes.h b/spctools/spc2it/spc2ittypes.h new file mode 100644 index 0000000..4274e13 --- /dev/null +++ b/spctools/spc2it/spc2ittypes.h @@ -0,0 +1,155 @@ +/**************************************************** +*Part of SPC2IT, read readme.md for more information* +****************************************************/ + +#ifndef SPC2ITTYPES_H +#define SPC2ITTYPES_H + +#include + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; + +typedef double f64; + +typedef s16 pcm_t; + +#define OneKB (1024) + +#define Mem64k (OneKB * 64) + +typedef struct +{ + char magic[4]; // IMPM + char songName[26]; + u16 PHiligt; // Pattern row hilight stuff + u16 OrderNumber; // Number of orders in song + u16 InstrumentNumber; // Number of instruments + u16 SampleNumber; // Number of samples + u16 PatternNumber; // Number of patterns + u16 TrackerCreatorVersion; // The version of the tracker that created the IT file + u16 TrackerFormatVersion; // Format version + u16 Flags; // Information flags + u16 Special; // Special st0ff + u8 GlobalVolume; // Global volume + u8 MixVolume; // Mix volume + u8 InitialSpeed; // Initial speed + u8 InitialTempo; // Initial tempo + u8 PanningSeperation; // Panning separation between channels + u8 PitchWheelDepth; // Pitch wheel depth for MIDI controllers + u16 MessageLength; // Length of message if Bit 0 of Special is 1 + u32 MessageOffset; // Offset of message if Bit 0 of Special is 1 + u32 Reserved; // Reserved stuff + u8 ChannelPan[64]; // Channel pan + u8 ChannelVolume[64]; // Channel volume +} ITFileHeader; + +typedef struct +{ + char magic[4]; // IMPS + char fileName[13]; // 8.3 DOS filename (including null termating) + u8 GlobalVolume; // Global volume for sample + u8 Flags; + u8 Volume; // Default volume + char SampleName[26]; + u8 Convert; + u8 DefaultPan; + u32 NumberOfSamples; // not bytes! in samples! + u32 LoopBeginning; // not bytes! in samples! + u32 LoopEnd; // not bytes! in samples! + u32 C5Speed; // Num of bytes a second for C-5 + u32 SustainLoopBeginning; + u32 SustainLoopEnd; + u32 SampleOffset; // the offset of the sample in file + u8 VibratoSpeed; + u8 VibratoDepth; + u8 VibratoRate; + u8 VibratoType; +} ITFileSample; + +typedef struct +{ + u16 Length; + u16 Rows; + u8 Padding[4]; +} ITFilePattern; + +typedef struct +{ + u8 PC[2]; // Don't change this to u16, because of the way structs work, that will misalign the struct with the file! + u8 A; + u8 X; + u8 Y; + u8 PSW; + u8 SP; + u8 Reserved[2]; +} SPCFileRegisters; + +typedef struct +{ + char SongTitle[32]; + char GameTitle[32]; + char DumperName[16]; + char Comment[32]; + char Date[11]; + char SongLength[3]; + char FadeLength[5]; + char Artist[32]; + u8 ChannelDisabled; + u8 EmulatorDumpedWith; //0 unknown, 1 ZSNES, 2 Snes9x + u8 Reserved[45]; +} SPCFileInformation; + +typedef struct +{ + char FileTag[33]; // SNES-SPC700 Sound File Data v0.30 + u8 FileTagTerminator[2]; // 0x1A, 0x1A + u8 ContainsID666; // 0x1A for contains ID666, 0x1B for no ID666 + u8 Version; //Version minor (30) + SPCFileRegisters Registers; // 9bytes + SPCFileInformation Information; // 163bytes + u8 RAM[65536]; + u8 DSPBuffer[128]; +} SPCFile; + +typedef struct +{ + u8 Channel; + u8 Mask; + u8 Note; + u8 Sample; + u8 Volume; + u8 Command; + u8 CommandValue; +} ITPatternInfo; + +typedef struct +{ + s32 mask, pitch, lvol, rvol; + u8 note; +} itdata; + +typedef struct +{ + s32 length; + s32 loopto; + s16 *buf; + s32 freq; +} sndsamp; + +typedef struct +{ + s32 ave; + u32 envx, envcyc; + s32 envstate; + u32 ar, dr, sl, sr, gn; +} sndvoice; + +#endif \ No newline at end of file diff --git a/spctools/spc2it/spc700.c b/spctools/spc2it/spc700.c new file mode 100644 index 0000000..7134f38 --- /dev/null +++ b/spctools/spc2it/spc700.c @@ -0,0 +1,3526 @@ +/**************************************************** +*Part of SPC2IT, read readme.md for more information* +****************************************************/ + +/* + +SNEeSe, an Open Source Super NES emulator. + + +Copyright (c) 1998-2006, Charles Bilyue'. +Portions copyright (c) 1998-2003, Brad Martin. +Portions copyright (c) 2003-2004, Daniel Horchner. +Portions copyright (c) 2004-2005, Nach. ( http://nsrt.edgeemu.com/ ) +Unzip Technology, copyright (c) 1998 Gilles Vollant. +zlib Technology ( www.gzip.org/zlib/ ), Copyright (c) 1995-2003, + Jean-loup Gailly ( jloup* *at* *gzip.org ) and Mark Adler + ( madler* *at* *alumni.caltech.edu ). +JMA Technology, copyright (c) 2004-2005 NSRT Team. ( http://nsrt.edgeemu.com/ ) +LZMA Technology, copyright (c) 2001-4 Igor Pavlov. ( http://www.7-zip.org ) +Portions copyright (c) 2002 Andrea Mazzoleni. ( http://advancemame.sf.net ) + +This is free software. See './doc/LICENSE_SNEESE' for details. +You must read and accept the license prior to use. + +*/ + +#define SNEeSe_apu_spc700_c + +#include "sneese_spc.h" + +SPC700_CONTEXT primary_context; + +SPC700_CONTEXT *active_context = &primary_context; + +/* + SNEeSe SPC700 CPU emulation core + Originally written by Lee Hammerton in AT&T assembly + Maintained/rewritten/ported to NASM by Charles Bilyue' + Maintained/ported to C by Charles Bilyue' + + This file contains: + CPU core info + Reset + Execution Loop + Invalid Opcode Handler or Dispatcher + Variable definitions (registers, cycle counters, etc.) + CPU opcode emulation handlers + CPU opcode handler table + CPU opcode timing table + + CPU core info: + A register - active_context->YA.b.A + Y register - active_context->YA.b.Y + YA register pair - active_context->YA.w + X register - active_context->X + Stack pointer - active_context->SP + Program Counter - active_context->PC + Processor status word - active_context->PSW + Individual flags - N_flag, V_flag, P_flag, B_flag, + H_flag, I_flag, Z_flag, C_flag + + + SPC timers + SPC700 timing is not directly related to 65c816 timing, but for + simplicity in emulation we act as if it is. SPC700 gets 5632 + cycles for every 118125 (21.47727..MHz) 5A22 cycles. Since the + timers run at ~8KHz and ~64KHz and the CPU core runs at + 1.024Mhz, the timers are clocked as follows: + 1.024MHz / 8KHz = 128 cycles (Timers 0 and 1) + 1.024MHz / 64KHz = 16 cycles (Timer 2) +*/ + +/* lots of #define's! */ + +/* These are the bits for flag set/clr operations */ +#define SPC_FLAG_C 1 /* Carry */ +#define SPC_FLAG_Z 2 /* Zero result */ +#define SPC_FLAG_I 4 /* Interrupt Disable */ +#define SPC_FLAG_H 8 /* Half-carry */ +#define SPC_FLAG_B 0x10 /* Break */ +#define SPC_FLAG_P 0x20 /* Page (direct page) */ +#define SPC_FLAG_V 0x40 /* Overflow */ +#define SPC_FLAG_N 0x80 /* Negative result */ + +#define SPC_FLAG_NZ (SPC_FLAG_N | SPC_FLAG_Z) +#define SPC_FLAG_NZC (SPC_FLAG_NZ | SPC_FLAG_C) +#define SPC_FLAG_NHZC (SPC_FLAG_NZC | SPC_FLAG_H) + +#define _Cycles (active_context->Cycles) +#define _last_cycles (active_context->last_cycles) +#define _TotalCycles (active_context->TotalCycles) +#define _WorkCycles (active_context->WorkCycles) + +#define _PORT_R (active_context->PORT_R) +#define _PORT0R (_PORT_R[0]) +#define _PORT1R (_PORT_R[1]) +#define _PORT2R (_PORT_R[2]) +#define _PORT3R (_PORT_R[3]) + +#define _PORT_W (active_context->PORT_W) +#define _PORT0W (_PORT_W[0]) +#define _PORT1W (_PORT_W[1]) +#define _PORT2W (_PORT_W[2]) +#define _PORT3W (_PORT_W[3]) + +#define _FFC0_Address (active_context->FFC0_Address) + +#define _PC (active_context->PC.w) +#define _PCL (active_context->PC.b.l) +#define _PCH (active_context->PC.b.h) +#define _YA (active_context->YA.w) +#define _A (active_context->YA.b.l) +#define _Y (active_context->YA.b.h) +#define _dp (active_context->direct_page.w) +#define _direct_page (active_context->direct_page.b.h) +#define _SP (active_context->SP) +#define _X (active_context->X) +#define _PSW (active_context->PSW) + +#define _cycle (active_context->cycle) +#define _opcode (active_context->opcode) +#define _data (active_context->data) +#define _data2 (active_context->data2) +#define _data16 (active_context->data16.w) +#define _offset (active_context->offset) +#define _address (active_context->address.w) +#define _address_l (active_context->address.b.l) +#define _address_h (active_context->address.b.h) +#define _address2 (active_context->address2.w) +#define _address2_l (active_context->address2.b.l) +#define _address2_h (active_context->address2.b.h) + +#define _N_flag (active_context->N_flag) +#define _V_flag (active_context->V_flag) +#define _P_flag (active_context->P_flag) +#define _B_flag (active_context->B_flag) +#define _H_flag (active_context->H_flag) +#define _I_flag (active_context->I_flag) +#define _Z_flag (active_context->Z_flag) +#define _C_flag (active_context->C_flag) + +#define _timers (active_context->timers) + +/* bits used all over the core */ +void set_flag_spc(u8 flag) +{ + if (flag & SPC_FLAG_N) + { + _N_flag = 0x80; + } + if (flag & SPC_FLAG_V) + { + _V_flag = 1; + } + if (flag & SPC_FLAG_P) + { + _P_flag = 1; + _direct_page = 0x01; + } + if (flag & SPC_FLAG_B) + { + _B_flag = 1; + } + if (flag & SPC_FLAG_H) + { + _H_flag = 1; + } + if (flag & SPC_FLAG_I) + { + _I_flag = 1; + } + if (flag & SPC_FLAG_Z) + { + _Z_flag = 0; + } + if (flag & SPC_FLAG_C) + { + _C_flag = 1; + } +} + +void clr_flag_spc(u8 flag) +{ + if (flag & SPC_FLAG_N) + { + _N_flag = 0; + } + if (flag & SPC_FLAG_V) + { + _V_flag = 0; + } + if (flag & SPC_FLAG_P) + { + _P_flag = 0; + _direct_page = 0x00; + } + if (flag & SPC_FLAG_B) + { + _B_flag = 0; + } + if (flag & SPC_FLAG_H) + { + _H_flag = 0; + } + if (flag & SPC_FLAG_I) + { + _I_flag = 0; + } + if (flag & SPC_FLAG_Z) + { + _Z_flag = 1; + } + if (flag & SPC_FLAG_C) + { + _C_flag = 0; + } +} + +void complement_carry_spc(void) +{ + _C_flag = !_C_flag; +} + +u8 flag_state_spc(u8 flag) +{ + if (flag == SPC_FLAG_N) + { + return _N_flag & 0x80; + } + else if (flag == SPC_FLAG_V) + { + return _V_flag; + } + else if (flag == SPC_FLAG_P) + { + return _P_flag; + } + else if (flag == SPC_FLAG_B) + { + return _B_flag; + } + else if (flag == SPC_FLAG_H) + { + return _H_flag; + } + else if (flag == SPC_FLAG_I) + { + return _I_flag; + } + else if (flag == SPC_FLAG_Z) + { + return !_Z_flag; + } + else if (flag == SPC_FLAG_C) + { + return _C_flag; + } + + return 0; +} + +void load_cycles_spc(void) +{ + _WorkCycles = _TotalCycles - _Cycles; +} + +u32 get_cycles_spc(void) +{ + return _WorkCycles + _Cycles; +} + +void save_cycles_spc(void) +{ + _TotalCycles = _WorkCycles + _Cycles; +} + +/* Set up the flags from our flag format to SPC flag format */ +void spc_setup_flags(s32 B_flag) +{ + u8 PSW = 0; + + PSW += _N_flag & 0x80; + PSW += _V_flag ? 0x40 : 0; + PSW += _P_flag ? 0x20 : 0; + PSW += B_flag ? 0x10 : 0; + PSW += _H_flag ? 0x08 : 0; + PSW += _I_flag ? 0x04 : 0; + PSW += !_Z_flag ? 0x02 : 0; + PSW += _C_flag ? 0x01 : 0; + + _PSW = PSW; +} + +/* Restore the flags from SPC flag format to our flag format */ +void spc_restore_flags(void) +{ + u8 PSW = _PSW; + + _N_flag = PSW; + _V_flag = PSW & SPC_FLAG_V; + + if (PSW & SPC_FLAG_P) + set_flag_spc(SPC_FLAG_P); + else + clr_flag_spc(SPC_FLAG_P); + + _B_flag = PSW & SPC_FLAG_B; + _H_flag = PSW & SPC_FLAG_H; + _I_flag = PSW & SPC_FLAG_I; + _Z_flag = ~PSW & SPC_FLAG_Z; + _C_flag = PSW & SPC_FLAG_C; +} + +void store_flag_n(u8 value) +{ + _N_flag = value; +} + +void store_flag_v(u8 value) +{ + _V_flag = value; +} + +void store_flag_p(u8 value) +{ + _P_flag = value; + _direct_page = value ? 0x01 : 0x00; +} + +void store_flag_h(u8 value) +{ + _H_flag = value; +} + +void store_flag_i(u8 value) +{ + _I_flag = value; +} + +void store_flag_z(u8 value) +{ + _Z_flag = value; +} + +void store_flag_c(u8 value) +{ + _C_flag = value; +} + +void store_flags_nz(u8 value) +{ + store_flag_n(value); + store_flag_z(value); +} + +void store_flags_nzc(u8 nz, u8 c) +{ + store_flag_n(nz); + store_flag_z(nz); + store_flag_c(c); +} + +/* bits for external access by the 5A22 core */ +u8 SPC_READ_PORT_W(u16 address) +{ + return _PORT_W[address & 3]; +} + +void SPC_WRITE_PORT_R(u16 address, u8 data) +{ + _PORT_R[address & 3] = data; +} + +/* bits for handling cycle counter overflows */ +void Wrap_SPC_Cyclecounter() +{ + _TotalCycles -= 0xF0000000; + _Cycles -= 0xF0000000; + _timers[0].cycle_latch -= 0xF0000000; + _timers[1].cycle_latch -= 0xF0000000; + _timers[2].cycle_latch -= 0xF0000000; + + Wrap_SDSP_Cyclecounter(); +} + +/* This code should be mapped into the top of the address space */ +static u8 SPC_ROM_CODE[64] = {0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0, 0xFC, 0x8F, 0xAA, 0xF4, 0x8F, + 0xBB, 0xF5, 0x78, 0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19, 0xEB, 0xF4, 0xD0, 0xFC, + 0x7E, 0xF4, 0xD0, 0x0B, 0xE4, 0xF5, 0xCB, 0xF4, 0xD7, 0x00, 0xFC, 0xD0, 0xF3, + 0xAB, 0x01, 0x10, 0xEF, 0x7E, 0xF4, 0x10, 0xEB, 0xBA, 0xF6, 0xDA, 0x00, 0xBA, + 0xF4, 0xC4, 0xF4, 0xDD, 0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF}; + +static u8 SPC_READ_INVALID(u16 address) +{ +#ifdef TRAP_INVALID_READ +#ifdef DEBUG + /* Set up address so message works */ + Map_Address = address; + Map_Byte = 0; + + InvalidSPCHWRead(); /* Display read from invalid HW warning */ +#endif +#endif + return 0; +} + +static u8 SPC_READ_RAM(u16 address) +{ + return SPCRAM[address]; +} + +static u8 SPC_READ_DSP_DATA(u16 address) +{ + SPC_READ_DSP(); + + /* read from DSP register */ + /* DSP address bit 7 ignored during reads only! */ + return SPC_DSP[SPC_DSP_ADDR & 0x7F]; +} + +u8 SPC_READ_PORT_R(u16 address) +{ + return _PORT_R[address & 3]; +} + +/* timer registers are write-only, actual timer clock is internal and */ +/* not accessible! */ +/* counters are 4-bit, upon read/write they reset to 0 */ + +void Update_SPC_Timer(s32 timer) +{ + u32 shift, mask, cycles, position; + + if (timer != 2) + { + shift = 7; + } + else + { + shift = 4; + } + mask = -BIT(shift); + + cycles = _TotalCycles - _timers[timer].cycle_latch; + _timers[timer].cycle_latch += cycles & mask; + + /* nothing to do if timer turned off */ + if (!(SPC_CTRL & BIT(timer))) + return; + + position = _timers[timer].position + (cycles >> shift); + _timers[timer].position = position; + if (position < _timers[timer].target) + { + return; + } + + _timers[timer].counter += position / _timers[timer].target; + /* 4-bit counter without saturation */ + _timers[timer].counter &= 0x0F; + + _timers[timer].position = position % _timers[timer].target; +} + +static u8 SPC_READ_COUNTER(u16 address) +{ + /* 0xFD = read address for first timer's counter */ + s32 timer = address - 0xFD; + u8 counter; + + Update_SPC_Timer(timer); + counter = _timers[timer].counter; + _timers[timer].counter = 0; + + return counter; +} + +/* + | ROMEN | ----- | PC32 | PC10 | ----- | ST2 | ST1 | ST0 | + + ROMEN - enable mask ROM in top 64-bytes of address space for CPU read + PC32 - clear SPC read ports 2 & 3 + PC10 - clear SPC read ports 0 & 1 + ST2 - start timer 2 (64kHz) + ST1 - start timer 1 (8kHz) + ST0 - start timer 0 (8kHz) +*/ + +void spc_start_timer(s32 timer) +{ + u32 shift, mask; + + if (timer != 2) + { + shift = 7; + } + else + { + shift = 4; + } + mask = -BIT(shift); + + _timers[timer].cycle_latch = _TotalCycles & mask; + _timers[timer].position = 0; + _timers[timer].counter = 0; +} + +static void SPC_WRITE_INVALID(u16 address, u8 data) +{ +#ifdef TRAP_INVALID_WRITE +#ifdef DEBUG + /* Set up address so message works */ + Map_Address = address; + Map_Byte = data; + + InvalidSPCHWWrite(); /* Display write to invalid HW warning */ +#endif +#endif +} + +static void SPC_WRITE_CTRL(u16 address, u8 data) +{ + /* IPL ROM enable */ + _FFC0_Address = data & 0x80 ? SPC_ROM_CODE - 0xFFC0 : SPCRAM; + + /* read ports 0/1 reset */ + if (data & 0x10) + { + _PORT_R[0] = 0; + _PORT_R[1] = 0; + } + + /* read ports 2/3 reset */ + if (data & 0x20) + { + _PORT_R[2] = 0; + _PORT_R[3] = 0; + } + + /* timer 0 control */ + if (!(SPCRAM[address] & 1) && (data & 1)) + { + spc_start_timer(0); + } + + /* timer 0 control */ + if (!(SPCRAM[address] & 2) && (data & 2)) + { + spc_start_timer(1); + } + + /* timer 2 control */ + if (!(SPCRAM[address] & 4) && (data & 4)) + { + spc_start_timer(2); + } + + SPC_CTRL = data; +} + +static void SPC_WRITE_RAM(u16 address, u8 data) +{ + SPCRAM[address] = data; +} + +static void SPC_WRITE_DSP_DATA(u16 address, u8 data) +{ + SPC_DSP_DATA = data; + + /* write to DSP register */ + SPC_WRITE_DSP(); +} + +void SPC_WRITE_PORT_W(u16 address, u8 data) +{ + _PORT_W[address & 3] = data; +} + +static void SPC_WRITE_TIMER(u16 address, u8 data) +{ + /* 0xFA = write address for first timer's target */ + s32 timer = address - 0xFA; + s32 target; + + if ((_timers[timer].target & 0xFF) == data) + { + return; + } + + target = data ? data : 256; + + /* Timer must catch up before changing target */ + Update_SPC_Timer(timer); + + _timers[timer].target = target; + + /* does setting target for current position raise counter? assuming not */ + if (target <= _timers[timer].position) + /* handle 'delay' where new target is set below position */ + { + _timers[timer].position -= 256; + } +} + +/* Mappings for SPC Registers */ +static u8 (*Read_Func_Map[16])(u16 address) = {SPC_READ_INVALID, SPC_READ_INVALID, SPC_READ_RAM, SPC_READ_DSP_DATA, + SPC_READ_PORT_R, SPC_READ_PORT_R, SPC_READ_PORT_R, SPC_READ_PORT_R, + SPC_READ_RAM, SPC_READ_RAM, SPC_READ_INVALID, SPC_READ_INVALID, + SPC_READ_INVALID, SPC_READ_COUNTER, SPC_READ_COUNTER, SPC_READ_COUNTER}; + +static void (*Write_Func_Map[16])(u16 address, u8 data) = { + SPC_WRITE_INVALID, SPC_WRITE_CTRL, SPC_WRITE_RAM, SPC_WRITE_DSP_DATA, SPC_WRITE_PORT_W, SPC_WRITE_PORT_W, + SPC_WRITE_PORT_W, SPC_WRITE_PORT_W, SPC_WRITE_RAM, SPC_WRITE_RAM, SPC_WRITE_TIMER, SPC_WRITE_TIMER, + SPC_WRITE_TIMER, SPC_WRITE_RAM, SPC_WRITE_RAM, SPC_WRITE_RAM}; + +static u8 offset_to_bit[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; + +static u8 offset_to_not[8] = {0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F}; + +u8 get_byte_spc(u16 address) +{ + /* Note: need to update sound if echo write enabled and accessing echo */ + /* region */ + if (address >= 0x0100) + /* not zero page */ + { + if (address >= 0xFFC0) + /* return ROM if it's mapped in, else RAM */ + { + return ((u8 *)_FFC0_Address)[address]; + } + /* return RAM */ + return SPCRAM[address]; + } + + /* zero page */ + if (address < 0xF0) + /* RAM */ + { + return SPCRAM[address]; + } + + save_cycles_spc(); /* Set cycle counter */ + return Read_Func_Map[address - 0xF0](address); +} + +/* -------- */ + +void set_byte_spc(u16 address, u8 data) +{ + /* Note: need to update sound always, since all (?) writes affect RAM */ + if (address >= 0x0100 || address < 0xF0) + /* write to RAM */ + { + save_cycles_spc(); /* Set cycle counter */ + update_sound(); + SPCRAM[address] = data; + } + else + { + save_cycles_spc(); /* Set cycle counter */ + Write_Func_Map[address - 0xF0](address, data); + } +} + +void Reset_SPC(void) +{ + s32 i; + + /* Get ROM reset vector and setup Program Counter */ + _PC = SPC_ROM_CODE[0xFFFE - 0xFFC0] + (SPC_ROM_CODE[0xFFFF - 0xFFC0] << 8); + + /* Reset cycle counts */ + _TotalCycles = 6; /* 5-7 cycles before execution begins */ + + _Cycles = 0; + _last_cycles = 0; + + _cycle = 0; + + /* Reset SSMP registers */ + _dp = 0; /* Used to save P flag check for dp addressing */ + _SP = 0xEF; + _YA = 0; + _X = 0; + /* Clear flags register */ + _PSW = 0; + clr_flag_spc(SPC_FLAG_N); + clr_flag_spc(SPC_FLAG_V); + clr_flag_spc(SPC_FLAG_P); + clr_flag_spc(SPC_FLAG_B); + clr_flag_spc(SPC_FLAG_H); + clr_flag_spc(SPC_FLAG_I); + clr_flag_spc(SPC_FLAG_Z); + clr_flag_spc(SPC_FLAG_C); + + SPC_CTRL = 0x80; + _FFC0_Address = SPC_ROM_CODE - 0xFFC0; + + /* Reset timers */ + for (i = 0; i < 3; i++) + { + _timers[i].cycle_latch = 0; + _timers[i].position = 0; + _timers[i].target = 256; + _timers[i].counter = 0; + } + + sound_cycle_latch = 0; + + /* Reset SPC700 input ports */ + _PORT_R[0] = 0; + _PORT_R[1] = 0; + _PORT_R[2] = 0; + _PORT_R[3] = 0; + + /* Reset SPC700 output ports */ + _PORT_W[0] = 0; + _PORT_W[1] = 0; + _PORT_W[2] = 0; + _PORT_W[3] = 0; + + /* Reset sound DSP port address */ + SPC_DSP_ADDR = 0; + SPC_DSP_DATA = 0; +} + +void SPC_SHOW_REGISTERS(void) +{ + DisplaySPC(); +} + +u8 get_SPC_PSW(void) +{ + spc_setup_flags(_B_flag); + + return _PSW; +} + +#ifdef OPCODE_TRACE_LOG +#define SS_WAIT_FOR_KEY /*if ((readkey() & 0xFF) == 'g') { s32 i = 0; while (i++ < 49) simulate_keypress(' ' + \ + (KEY_SPACE << 8)); }*/ + +#else +#define SS_WAIT_FOR_KEY + +void dummy_fprintf() +{ +} +#define fprintf dummy_fprintf + +#endif + +#ifdef OPCODE_TRACE_LOG +/* cycle #, PC, TotalCycles */ +#define SINGLE_STEP_START(c) \ + if (dump_flag && debug_log_file) \ + { \ + fprintf(debug_log_file, "START_CYCLE(%u) PC:%04X %u\n", c, _PC & 0xFFFF, get_cycles_spc()); \ + if ((c == 1) && (_PC == 0x02C4)) \ + exit(0); \ + } + +void single_step_end(void) +{ + if (!dump_flag || !debug_log_file) + return; + fprintf(debug_log_file, "NVPBHIZC R:%02X %02X %02X %02X X:%02X Y:%02X A:%02X SP:%02X dp:%02X Op:%02X\n", + _PORT0R & 0xFF, _PORT1R & 0xFF, _PORT2R & 0xFF, _PORT3R & 0xFF, _X & 0xFF, _Y & 0xFF, _A & 0xFF, _SP & 0xFF, + _direct_page & 0xFF, _opcode & 0xFF); + fprintf(debug_log_file, "%c%c%c%c%c%c%c%c W:%02X %02X %02X %02X Ad%04X %04X Off%02X D%02X %02X D16 %04X", + flag_state_spc(SPC_FLAG_N) ? '1' : '0', flag_state_spc(SPC_FLAG_V) ? '1' : '0', + flag_state_spc(SPC_FLAG_P) ? '1' : '0', flag_state_spc(SPC_FLAG_B) ? '1' : '0', + flag_state_spc(SPC_FLAG_H) ? '1' : '0', flag_state_spc(SPC_FLAG_I) ? '1' : '0', + flag_state_spc(SPC_FLAG_Z) ? '1' : '0', flag_state_spc(SPC_FLAG_C) ? '1' : '0', _PORT0W & 0xFF, + _PORT1W & 0xFF, _PORT2W & 0xFF, _PORT3W & 0xFF, _address & 0xFFFF, _address2 & 0xFFFF, _offset & 0xFF, + _data & 0xFF, _data2 & 0xFF, _data16 & 0xFFFF); + if (_cycle == 0) + fprintf(debug_log_file, " %s\n", SPC_OpID[_opcode]); + else + fprintf(debug_log_file, "\n"); +} + +/* op, R ports, W ports, X Y A */ +/* address1 address2 offset data1 data2 data16 */ +#define SINGLE_STEP_END single_step_end(); + +#else +#define SINGLE_STEP_START(c) +#define SINGLE_STEP_END +#endif + +#define START_CYCLE(c) \ + if (_cycle <= ((c)-1)) \ + { \ + SINGLE_STEP_START(c) + +#define END_FETCH_CYCLE() \ + _WorkCycles++; \ + SINGLE_STEP_END if (_WorkCycles >= 0) \ + { \ + _cycle = 1; \ + opcode_done = 0; \ + break; \ + } \ + } + +#define END_CYCLE(c, n) \ + _WorkCycles += n; \ + SINGLE_STEP_END if (_WorkCycles >= 0) \ + { \ + _cycle = c; \ + opcode_done = 0; \ + break; \ + } \ + } + +#define EXIT_OPCODE(n) \ + { \ + _WorkCycles += n; \ + SINGLE_STEP_END break; \ + } + +#define END_OPCODE(n) \ + EXIT_OPCODE(n) \ + } + +#define END_BRANCH_OPCODE(cycle, TEST) \ + TEST END_CYCLE((cycle), 1) \ + \ + /* sign extend offset and add to PC */ \ + START_CYCLE((cycle)+1) _address = _PC + (((s32)_offset ^ 0x80) - 0x80); \ + END_CYCLE((cycle)+1, 1) \ + \ + START_CYCLE((cycle)+2) \ + _PC = _address; \ + END_OPCODE(1) + +#define REL_TEST_BRA ; +#define REL_TEST_BPL \ + if (flag_state_spc(SPC_FLAG_N)) \ + EXIT_OPCODE(1) +#define REL_TEST_BMI \ + if (!flag_state_spc(SPC_FLAG_N)) \ + EXIT_OPCODE(1) +#define REL_TEST_BVC \ + if (flag_state_spc(SPC_FLAG_V)) \ + EXIT_OPCODE(1) +#define REL_TEST_BVS \ + if (!flag_state_spc(SPC_FLAG_V)) \ + EXIT_OPCODE(1) +#define REL_TEST_BCC \ + if (flag_state_spc(SPC_FLAG_C)) \ + EXIT_OPCODE(1) +#define REL_TEST_BCS \ + if (!flag_state_spc(SPC_FLAG_C)) \ + EXIT_OPCODE(1) +#define REL_TEST_BNE \ + if (flag_state_spc(SPC_FLAG_Z)) \ + EXIT_OPCODE(1) +#define REL_TEST_BEQ \ + if (!flag_state_spc(SPC_FLAG_Z)) \ + EXIT_OPCODE(1) + +#define OP_TCALL(vector) \ + /* 8 cycles - opcode, new PCL, new PCH, stack address load, PCH */ \ + /* write, PCL write, dummy cycle (PSW write in BRK?) */ \ + /* SP decrement */ \ + /* fetch address for PC */ \ + START_CYCLE(2) \ + _address = 0xFFC0 + ((15 - (vector)) * 2); \ + _address2 = get_byte_spc(_address); \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address2 += (get_byte_spc(_address + 1) << 8); \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _address = 0x0100 + _SP; \ + END_CYCLE(4, 1) \ + \ + START_CYCLE(5) \ + set_byte_spc(_address, _PC >> 8); \ + _SP--; \ + _address = 0x0100 + _SP; \ + END_CYCLE(5, 1) \ + \ + START_CYCLE(6) \ + set_byte_spc(_address, _PC); \ + _SP--; \ + _address = 0x0100 + _SP; \ + END_CYCLE(6, 1) \ + \ + START_CYCLE(7) \ + /* should we write PSW to stack here? */ \ + END_CYCLE(7, 1) \ + \ + START_CYCLE(8) \ + _PC = _address2; \ + END_OPCODE(1) + +#define COND_REL(TEST) \ + /* 2 cycles - opcode, branch logic + offset; */ \ + /* +2 cycles (taken branch) add PC to offset, reload PC */ \ + \ + START_CYCLE(2) \ + _offset = get_byte_spc(_PC); \ + _PC++; \ + END_BRANCH_OPCODE(2, TEST) + +#define DP_REL_TEST_BBS \ + if (!(_data & offset_to_bit[_opcode >> 5])) \ + EXIT_OPCODE(1) + +#define DP_REL_TEST_BBC \ + if ((_data & offset_to_bit[_opcode >> 5])) \ + EXIT_OPCODE(1) + +#define TEST_CBNE \ + if (_data == _A) \ + EXIT_OPCODE(1) + +#define DP_REL_TEST_DBNZ \ + --_data; \ + set_byte_spc(_address, _data); \ + if (!_data) \ + EXIT_OPCODE(1) + +#define COND_DP_REL(TEST) \ + /* 5 cycles - opcode, address, branch offset, data read, branch logic */ \ + /* and data write (DBNZ only); +2 cycles (taken branch) add PC to */ \ + /* offset, reload PC */ \ + START_CYCLE(2) \ + _address = _dp + get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _offset = get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _data = get_byte_spc(_address); \ + END_CYCLE(4, 1) \ + \ + START_CYCLE(5) \ + END_BRANCH_OPCODE(5, TEST) + +#define OP_READ_DP(OP, dest) \ + /* 3 cycles - opcode, address, data read + op */ \ + START_CYCLE(2) \ + _address = _dp + get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _data = get_byte_spc(_address); \ + OP_##OP((dest), _data) END_OPCODE(1) + +/* xxx00100 */ +#define OP_READ_DP_A(OP) OP_READ_DP(OP, _A) + +#define OP_READ_ABS(OP, dest) \ + /* 4 cycles - opcode, address low, address high, data read + op */ \ + START_CYCLE(2) \ + _address = get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address += get_byte_spc(_PC) << 8; \ + _PC++; \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _data = get_byte_spc(_address); \ + OP_##OP((dest), _data) END_OPCODE(1) + +/* xxx00101 */ +#define OP_READ_ABS_A(OP) OP_READ_ABS(OP, _A) + +#define OP_READ_INDIRECT(OP, dest) \ + /* 3 cycles - opcode, address calc, data read + op */ \ + START_CYCLE(2) \ + _address = _dp + _X; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _data = get_byte_spc(_address); \ + OP_##OP(_A, _data) END_OPCODE(1) + +#define OP_RMW_INDIRECT(OP, NEED_OLD_DATA) \ + /* 4 cycles - opcode, address calc, data read, data write */ \ + START_CYCLE(2) \ + _address = _dp + _X; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + if (NEED_OLD_DATA) \ + _data = get_byte_spc(_address); \ + else \ + get_byte_spc(_address); \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + OP END_OPCODE(1) + +/* xxx00110 */ +#define OP_READ_INDIRECT_A(OP) OP_READ_INDIRECT(OP, _A) + +#define OP_READ_INDEXED_INDIRECT(OP, dest) \ + /* 6 cycles - opcode, offset, address calc, address low, */ \ + /* address high, data read + op */ \ + START_CYCLE(2) \ + _address2 = get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address2 = _dp + ((_address2 + _X) & 0xFF); \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _address = get_byte_spc(_address2); \ + END_CYCLE(4, 1) \ + \ + START_CYCLE(5) \ + _address += get_byte_spc(_address2 + 1) << 8; \ + END_CYCLE(5, 1) \ + \ + START_CYCLE(6) \ + _data = get_byte_spc(_address); \ + OP_##OP((dest), _data) END_OPCODE(1) + +#define OP_RMW_INDEXED_INDIRECT(OP, NEED_OLD_DATA) \ + /* 7 cycles - opcode, offset, address calc, address low, */ \ + /* address high, data read, data read + op */ \ + START_CYCLE(2) \ + _address2 = get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address2 = _dp + ((_address2 + _X) & 0xFF); \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _address = get_byte_spc(_address2); \ + END_CYCLE(4, 1) \ + \ + START_CYCLE(5) \ + _address += get_byte_spc(_address2 + 1) << 8; \ + END_CYCLE(5, 1) \ + \ + START_CYCLE(6) \ + if (NEED_OLD_DATA) \ + _data = get_byte_spc(_address); \ + else \ + get_byte_spc(_address); \ + END_CYCLE(6, 1) \ + \ + START_CYCLE(7) \ + OP END_OPCODE(1) + +/* xxx00111 */ +#define OP_READ_INDEXED_INDIRECT_A(OP) OP_READ_INDEXED_INDIRECT(OP, _A) + +#define OP_READ_IMM(OP, dest) \ + /* 2 cycles - opcode, data read + op */ \ + START_CYCLE(2) \ + _data = get_byte_spc(_PC); \ + _PC++; \ + OP_##OP((dest), _data) END_OPCODE(1) + +/* xxx01000 */ +#define OP_READ_IMM_A(OP) OP_READ_IMM(OP, _A) + +#define OP_OR(dest, src) \ + { \ + (dest) |= (src); \ + store_flags_nz(dest); \ + } + +#define OP_AND(dest, src) \ + { \ + (dest) &= (src); \ + store_flags_nz(dest); \ + } + +#define OP_EOR(dest, src) \ + { \ + (dest) ^= (src); \ + store_flags_nz(dest); \ + } + +#define OP_CMP(dest, src) \ + { \ + u32 temp = (dest) - (src); \ + \ + store_flag_c(temp <= 0xFF); \ + store_flags_nz(temp); \ + } + +#define OP_ADC(dest, src) \ + { \ + u32 result = (dest) + (src) + (flag_state_spc(SPC_FLAG_C) ? 1 : 0); \ + \ + store_flag_h(((u32)(((dest)&0x0F) + ((src)&0x0F) + (flag_state_spc(SPC_FLAG_C) ? 1 : 0))) > 0x0F ? 1 : 0); \ + store_flag_c(result > 0xFF); \ + store_flag_v((~((dest) ^ (src))) & (((dest) ^ result) & 0x80)); \ + store_flags_nz(result); \ + (dest) = result; \ + } + +#define OP_SBC(dest, src) \ + { \ + u32 result = (dest) - (src) - (flag_state_spc(SPC_FLAG_C) ? 0 : 1); \ + \ + store_flag_h(((u32)(((dest)&0x0F) - ((src)&0x0F) - (flag_state_spc(SPC_FLAG_C) ? 0 : 1))) > 0x0F ? 0 : 1); \ + store_flag_c(result <= 0xFF); \ + store_flag_v(((dest) ^ (src)) & (((dest) ^ result) & 0x80)); \ + store_flags_nz(result); \ + (dest) = result; \ + } + +#define OP_MOV_READ_NOFLAGS(dest, src) \ + { \ + (dest) = (src); \ + } + +#define OP_MOV_READ(dest, src) \ + { \ + (dest) = (src); \ + store_flags_nz(src); \ + } + +#define OP_ADDW(dest, src) \ + { \ + u32 temp_low, carry_low, temp_high, result; \ + \ + temp_low = ((dest)&0xFF) + ((src)&0xFF); \ + carry_low = temp_low > 0xFF ? 1 : 0; \ + \ + store_flag_h(((u32)((((dest) >> 8) & 0x0F) + (((src) >> 8) & 0x0F) + carry_low)) > 0x0F ? 1 : 0); \ + \ + temp_high = ((dest) >> 8) + ((src) >> 8) + carry_low; \ + store_flag_c(temp_high > 0xFF); \ + result = ((temp_low & 0xFF) + (temp_high << 8)) & 0xFFFF; \ + \ + store_flag_v(((~((dest) ^ (src))) & (((dest) ^ result) & 0x8000)) >> 8); \ + store_flag_n(result >> 8); \ + store_flag_z(result != 0); \ + (dest) = result; \ + } + +#define OP_SUBW(dest, src) \ + { \ + u32 temp_low, carry_low, temp_high, result; \ + \ + temp_low = ((dest)&0xFF) - ((src)&0xFF); \ + carry_low = temp_low > 0xFF ? 1 : 0; \ + \ + store_flag_h(((u32)((((dest) >> 8) & 0x0F) - (((src) >> 8) & 0x0F) - carry_low)) > 0x0F ? 0 : 1); \ + \ + temp_high = ((dest) >> 8) - ((src) >> 8) - carry_low; \ + store_flag_c(temp_high <= 0xFF); \ + result = ((temp_low & 0xFF) + (temp_high << 8)) & 0xFFFF; \ + \ + store_flag_v((((dest) ^ (src)) & (((dest) ^ result) & 0x8000)) >> 8); \ + store_flag_n(result >> 8); \ + store_flag_z(result != 0); \ + (dest) = result; \ + } + +#define OP_MOVW_READ(dest, src) \ + { \ + (dest) = (src); \ + store_flag_n((dest) >> 8); \ + store_flag_z((dest) != 0); \ + } + +#define OP_ASL(var) \ + { \ + store_flag_c((var)&0x80); \ + (var) <<= 1; \ + store_flags_nz(var); \ + } + +#define OP_ROL(var) \ + { \ + s32 c = flag_state_spc(SPC_FLAG_C) ? 1 : 0; \ + \ + store_flag_c((var)&0x80); \ + (var) = ((var) << 1) + c; \ + store_flags_nz(var); \ + } + +#define OP_LSR(var) \ + { \ + store_flag_c((var)&1); \ + (var) >>= 1; \ + store_flags_nz(var); \ + } + +#define OP_ROR(var) \ + { \ + s32 c = flag_state_spc(SPC_FLAG_C) ? 0x80 : 0; \ + \ + store_flag_c((var)&1); \ + (var) = ((var) >> 1) + c; \ + store_flags_nz(var); \ + } + +#define OP_DECW(var) \ + { \ + (var)--; \ + store_flag_n(var >> 8); \ + store_flag_z((var & 0xFFFF) != 0); \ + } + +#define OP_INCW(var) \ + { \ + (var)++; \ + store_flag_n(var >> 8); \ + store_flag_z((var & 0xFFFF) != 0); \ + } + +#define OP_DEC(var) \ + { \ + (var)--; \ + store_flags_nz(var); \ + } + +#define OP_INC(var) \ + { \ + (var)++; \ + store_flags_nz(var); \ + } + +#define OP_SET1(var) \ + { \ + (var) |= offset_to_bit[_opcode >> 5]; \ + } +#define OP_CLR1(var) \ + { \ + (var) &= offset_to_not[_opcode >> 5]; \ + } + +#define WRITE_OP(OP) \ + OP_##OP(_data); \ + set_byte_spc(_address, _data); +#define WRITE_MOV(var) set_byte_spc(_address, (var)); + +#define WRITE_ASL \ + OP_ASL(_data); \ + set_byte_spc(_address, _data); +#define WRITE_LSR \ + OP_LSR(_data); \ + set_byte_spc(_address, _data); +#define WRITE_ROL \ + OP_ROL(_data); \ + set_byte_spc(_address, _data); +#define WRITE_ROR \ + OP_ROR(_data); \ + set_byte_spc(_address, _data); + +#define WRITE_DEC \ + OP_DEC(_data); \ + set_byte_spc(_address, _data); +#define WRITE_INC \ + OP_INC(_data); \ + set_byte_spc(_address, _data); + +/* xxx01011 */ +#define OP_RMW_DP(OP, NEED_OLD_DATA) \ + /* 4 cycles - opcode, address, data read, op + data write */ \ + START_CYCLE(2) \ + _address = _dp + get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + if (NEED_OLD_DATA) \ + _data = get_byte_spc(_address); \ + else \ + get_byte_spc(_address); \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + OP END_OPCODE(1) + +/* xxx01001 */ +#define OP_RMW_DP_DP(OP) \ + /* 6 cycles - opcode, src address, dest address, src read, */ \ + /* dest read + op, dest write */ \ + START_CYCLE(2) \ + _address2 = _dp + get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address = _dp + get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _data2 = get_byte_spc(_address2); \ + END_CYCLE(4, 1) \ + \ + START_CYCLE(5) \ + _data = get_byte_spc(_address); \ + OP_##OP(_data, _data2) END_CYCLE(5, 1) \ + \ + START_CYCLE(6) set_byte_spc(_address, _data); \ + END_OPCODE(1) + +/* xxx01100 */ +#define OP_RMW_ABS(OP, NEED_OLD_DATA) \ + /* 5 cycles - opcode, address low, address high, data read, */ \ + /* op + data write */ \ + START_CYCLE(2) \ + _address = get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address += get_byte_spc(_PC) << 8; \ + _PC++; \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + if (NEED_OLD_DATA) \ + _data = get_byte_spc(_address); \ + else \ + get_byte_spc(_address); \ + END_CYCLE(4, 1) \ + \ + START_CYCLE(5) \ + OP END_OPCODE(1) + +/* 0xx01101 */ +#define OP_PUSH(src) \ + { \ + /* 4 cycles - opcode, address load, data write, SP decrement */ \ + START_CYCLE(2) \ + _address = 0x0100 + _SP; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + set_byte_spc(_address, src); \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _SP--; \ + END_OPCODE(1) \ + } + +/* 1xx01110 */ +#define OP_POP(dest) \ + { \ + /* 4 cycles - opcode, SP increment, address load, data read */ \ + START_CYCLE(2) \ + _SP++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address = 0x0100 + _SP; \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + (dest) = get_byte_spc(_address); \ + END_OPCODE(1) \ + } + +#define OP_READ_DP_reg_INDEXED(OP, dest, index) \ + /* 4 cycles - opcode, address, address calc, data read + op */ \ + START_CYCLE(2) \ + _address = get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address = _dp + ((_address + index) & 0xFF); \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _data = get_byte_spc(_address); \ + OP_##OP((dest), _data) END_OPCODE(1) + +#define OP_RMW_DP_reg_INDEXED(OP, NEED_OLD_DATA, index) \ + /* 5 cycles - opcode, address, address calc, data read, */ \ + /* data write */ \ + START_CYCLE(2) \ + _address = get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address = _dp + ((_address + index) & 0xFF); \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + if (NEED_OLD_DATA) \ + _data = get_byte_spc(_address); \ + else \ + get_byte_spc(_address); \ + END_CYCLE(4, 1) \ + \ + START_CYCLE(5) \ + OP END_OPCODE(1) + +/* xxx10100 */ +#define OP_READ_DP_X_INDEXED(OP, dest) OP_READ_DP_reg_INDEXED(OP, dest, _X) +#define OP_READ_DP_X_INDEXED_A(OP) OP_READ_DP_X_INDEXED(OP, _A) +/* xxx11011 */ +#define OP_RMW_DP_X_INDEXED(OP, NEED_OLD_DATA) OP_RMW_DP_reg_INDEXED(OP, NEED_OLD_DATA, _X) + +/* xxx11001 */ +#define OP_READ_DP_Y_INDEXED(OP, dest) OP_READ_DP_reg_INDEXED(OP, dest, _Y) +#define OP_RMW_DP_Y_INDEXED(OP, NEED_OLD_DATA) OP_RMW_DP_reg_INDEXED(OP, NEED_OLD_DATA, _Y) + +#define OP_READ_ABS_reg_INDEXED(OP, dest, index) \ + /* 5 cycles - opcode, address low, address high, address calc, */ \ + /* data read + op */ \ + START_CYCLE(2) \ + _address = get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address += get_byte_spc(_PC) << 8; \ + _PC++; \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _address += index; \ + END_CYCLE(4, 1) \ + \ + START_CYCLE(5) \ + _data = get_byte_spc(_address); \ + OP_##OP((dest), _data) END_OPCODE(1) + +#define OP_RMW_ABS_reg_INDEXED(OP, NEED_OLD_DATA, index) \ + /* 6 cycles - opcode, address low, address high, address calc, */ \ + /* data read, data write */ \ + START_CYCLE(2) \ + _address = get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address += get_byte_spc(_PC) << 8; \ + _PC++; \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _address += index; \ + END_CYCLE(4, 1) \ + \ + START_CYCLE(5) \ + if (NEED_OLD_DATA) \ + _data = get_byte_spc(_address); \ + else \ + get_byte_spc(_address); \ + END_CYCLE(5, 1) \ + \ + START_CYCLE(6) \ + OP END_OPCODE(1) + +/* xxx10101 */ +#define OP_READ_ABS_X_INDEXED_A(OP) OP_READ_ABS_reg_INDEXED(OP, _A, _X) +#define OP_RMW_ABS_X_INDEXED(OP, NEED_OLD_DATA) OP_RMW_ABS_reg_INDEXED(OP, NEED_OLD_DATA, _X) + +/* xxx10110 */ +#define OP_READ_ABS_Y_INDEXED_A(OP) OP_READ_ABS_reg_INDEXED(OP, _A, _Y) +#define OP_RMW_ABS_Y_INDEXED(OP, NEED_OLD_DATA) OP_RMW_ABS_reg_INDEXED(OP, NEED_OLD_DATA, _Y) + +#define OP_READ_INDIRECT_INDEXED(OP, dest) \ + /* 6 cycles - opcode, offset, address low, address high, */ \ + /* address calc, data read + op */ \ + START_CYCLE(2) \ + _address2 = _dp + get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address = get_byte_spc(_address2); \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _address += get_byte_spc(_address2 + 1) << 8; \ + END_CYCLE(4, 1) \ + \ + START_CYCLE(5) \ + _address += _Y; \ + END_CYCLE(5, 1) \ + \ + START_CYCLE(6) \ + _data = get_byte_spc(_address); \ + OP_##OP((dest), _data) END_OPCODE(1) + +#define OP_RMW_INDIRECT_INDEXED(OP, NEED_OLD_DATA) \ + /* 7 cycles - opcode, offset, address low, address high, */ \ + /* address calc, data read + op, data write */ \ + START_CYCLE(2) \ + _address2 = _dp + get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address = get_byte_spc(_address2); \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _address += get_byte_spc(_address2 + 1) << 8; \ + END_CYCLE(4, 1) \ + \ + START_CYCLE(5) \ + _address += _Y; \ + END_CYCLE(5, 1) \ + \ + START_CYCLE(6) \ + if (NEED_OLD_DATA) \ + _data = get_byte_spc(_address); \ + else \ + get_byte_spc(_address); \ + END_CYCLE(6, 1) \ + \ + START_CYCLE(7) \ + OP END_OPCODE(1) + +/* xxx10111 */ +#define OP_READ_INDIRECT_INDEXED_A(OP) OP_READ_INDIRECT_INDEXED(OP, _A) + +/* xxx11000 */ +#define OP_RMW_DP_IMM(OP) \ + /* 5 cycles - opcode, src data, dest address, dest read + op, */ \ + /* dest write */ \ + START_CYCLE(2) \ + _data2 = get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _address = _dp + get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _data = get_byte_spc(_address); \ + OP_##OP(_data, _data2) END_CYCLE(4, 1) \ + \ + START_CYCLE(5) set_byte_spc(_address, _data); \ + END_OPCODE(1) + +/* xxx11001 */ +#define OP_RMW_INDIRECT_INDIRECT(OP) \ + /* 5 cycles - opcode, address calc, src read, dest read + op, */ \ + /* dest write */ \ + START_CYCLE(2) \ + _address = _dp + _Y; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _data2 = get_byte_spc(_address); \ + _address = _dp + _X; \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _data = get_byte_spc(_address); \ + OP_##OP(_data, _data2) END_CYCLE(4, 1) \ + \ + START_CYCLE(5) set_byte_spc(_address, _data); \ + END_OPCODE(1) + +/* note - timing on all 16-bit opcodes may be off: RMW could be instead: */ +/* data low read, write, data high read, write; or data low read, */ +/* data high read, data low write, data high write */ + +/* xxx11010 */ +#define OP_READ16_YA_DP(OP) \ + /* 5 cycles - opcode, address, data low read, data high read + op, */ \ + /* (?) */ \ + START_CYCLE(2) \ + _address = _dp + get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _data16 = get_byte_spc(_address); \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _data16 += get_byte_spc(_address + 1) << 8; \ + END_CYCLE(4, 1) \ + \ + START_CYCLE(5) \ + OP_##OP(_YA, _data16) END_OPCODE(1) + +#define OP_RMW16_DP(OP) \ + /* 6 cycles - opcode, address, data low read, data high read + op, */ \ + /* data low (?) write, data high write */ \ + START_CYCLE(2) \ + _address = _dp + get_byte_spc(_PC); \ + _PC++; \ + END_CYCLE(2, 1) \ + \ + START_CYCLE(3) \ + _data16 = get_byte_spc(_address); \ + END_CYCLE(3, 1) \ + \ + START_CYCLE(4) \ + _data16 += get_byte_spc(_address + 1) << 8; \ + END_CYCLE(4, 1) \ + \ + START_CYCLE(5) \ + OP_##OP(_data16) set_byte_spc(_address, _data16); \ + END_CYCLE(5, 1) \ + \ + START_CYCLE(6) \ + set_byte_spc(_address + 1, _data16 >> 8); \ + END_OPCODE(1) + +/* xxx11100 */ +#define OP_RMW_IMPLIED(OP, reg) \ + /* 2 cycles - opcode, op */ \ + START_CYCLE(2) \ + OP_##OP(reg); \ + END_OPCODE(1) + +/* xxx11101 */ +#define OP_MOV_IMPLIED(dest, src) \ + /* 2 cycles - opcode, op */ \ + START_CYCLE(2) \ + OP_MOV_READ(dest, src) \ + END_OPCODE(1) + +#define OP_MOV_IMPLIED_NO_FLAGS(dest, src) \ + /* 2 cycles - opcode, op */ \ + START_CYCLE(2) \ + (dest) = (src); \ + END_OPCODE(1) + +static void Execute_SPC(void) +{ + u8 was_in_cpu = In_CPU; + In_CPU = 0; + + load_cycles_spc(); + + while (_WorkCycles < 0) + { + s32 opcode_done = 1; + + START_CYCLE(1) + /* fetch opcode */ + _opcode = get_byte_spc(_PC); + _PC++; + END_FETCH_CYCLE() + + switch (_opcode) + { + /* xxx00000 */ + case 0x00: /* NOP */ + { + START_CYCLE(2) + END_OPCODE(1) + } + + case 0x20: /* CLRP */ + { + START_CYCLE(2) + clr_flag_spc(SPC_FLAG_P); + END_OPCODE(1) + } + + case 0x40: /* SETP */ + { + START_CYCLE(2) + set_flag_spc(SPC_FLAG_P); + END_OPCODE(1) + } + + case 0x60: /* CLRC */ + { + START_CYCLE(2) + clr_flag_spc(SPC_FLAG_C); + END_OPCODE(1) + } + + case 0x80: /* SETC */ + { + START_CYCLE(2) + set_flag_spc(SPC_FLAG_C); + END_OPCODE(1) + } + + case 0xA0: /* EI */ + { + START_CYCLE(2) + set_flag_spc(SPC_FLAG_I); + END_OPCODE(1) + } + + case 0xC0: /* DI */ + { + START_CYCLE(2) + clr_flag_spc(SPC_FLAG_I); + END_OPCODE(1) + } + + case 0xE0: /* CLRV */ + { + START_CYCLE(2) + clr_flag_spc(SPC_FLAG_H | SPC_FLAG_V); + END_OPCODE(1) + } + +/* xxxx0001 */ +#define opcode_TCALL(vector) (((vector) << 4) + 0x01) + case opcode_TCALL(0): + case opcode_TCALL(1): + case opcode_TCALL(2): + case opcode_TCALL(3): + case opcode_TCALL(4): + case opcode_TCALL(5): + case opcode_TCALL(6): + case opcode_TCALL(7): + case opcode_TCALL(8): + case opcode_TCALL(9): + case opcode_TCALL(10): + case opcode_TCALL(11): + case opcode_TCALL(12): + case opcode_TCALL(13): + case opcode_TCALL(14): + case opcode_TCALL(15): + { + OP_TCALL(_opcode >> 4) + } + +/* xxx00010 */ +#define opcode_SET1(bit) (((bit) << 5) + 0x02) + case opcode_SET1(0): + case opcode_SET1(1): + case opcode_SET1(2): + case opcode_SET1(3): + case opcode_SET1(4): + case opcode_SET1(5): + case opcode_SET1(6): + case opcode_SET1(7): + { + OP_RMW_DP(WRITE_OP(SET1), 1) + } + +/* xxx00011 */ +#define opcode_BBS(bit) (((bit) << 5) + 0x03) + case opcode_BBS(0): + case opcode_BBS(1): + case opcode_BBS(2): + case opcode_BBS(3): + case opcode_BBS(4): + case opcode_BBS(5): + case opcode_BBS(6): + case opcode_BBS(7): + { + COND_DP_REL(DP_REL_TEST_BBS) + } + + /* xxx00100 */ + case 0x04: /* OR A,dp */ + { + OP_READ_DP_A(OR) + } + + case 0x24: /* AND A,dp */ + { + OP_READ_DP_A(AND) + } + + case 0x44: /* EOR A,dp */ + { + OP_READ_DP_A(EOR) + } + + case 0x64: /* CMP A,dp */ + { + OP_READ_DP_A(CMP) + } + + case 0x84: /* ADC A,dp */ + { + OP_READ_DP_A(ADC) + } + + case 0xA4: /* SBC A,dp */ + { + OP_READ_DP_A(SBC) + } + + case 0xC4: /* MOV dp,A */ + { + OP_RMW_DP(WRITE_MOV(_A), 0) + } + + case 0xE4: /* MOV A,dp */ + { + OP_READ_DP_A(MOV_READ) + } + + /* xxx00101 */ + case 0x05: /* OR A,abs */ + { + OP_READ_ABS_A(OR) + } + + case 0x25: /* AND A,abs */ + { + OP_READ_ABS_A(AND) + } + + case 0x45: /* EOR A,abs */ + { + OP_READ_ABS_A(EOR) + } + + case 0x65: /* CMP A,abs */ + { + OP_READ_ABS_A(CMP) + } + + case 0x85: /* ADC A,abs */ + { + OP_READ_ABS_A(ADC) + } + + case 0xA5: /* SBC A,abs */ + { + OP_READ_ABS_A(SBC) + } + + case 0xC5: /* MOV abs,A */ + { + OP_RMW_ABS(WRITE_MOV(_A), 0) + } + + case 0xE5: /* MOV A,abs */ + { + OP_READ_ABS_A(MOV_READ) + } + + /* xxx00110 */ + case 0x06: /* OR A,(X) */ + { + OP_READ_INDIRECT_A(OR) + } + + case 0x26: /* AND A,(X) */ + { + OP_READ_INDIRECT_A(AND) + } + + case 0x46: /* EOR A,(X) */ + { + OP_READ_INDIRECT_A(EOR) + } + + case 0x66: /* CMP A,(X) */ + { + OP_READ_INDIRECT_A(CMP) + } + + case 0x86: /* ADC A,(X) */ + { + OP_READ_INDIRECT_A(ADC) + } + + case 0xA6: /* SBC A,(X) */ + { + OP_READ_INDIRECT_A(SBC) + } + + case 0xC6: /* MOV (X),A */ + { + OP_RMW_INDIRECT(WRITE_MOV(_A), 0) + } + + case 0xE6: /* MOV A,(X) */ + { + OP_READ_INDIRECT_A(MOV_READ) + } + + /* xxx00111 */ + case 0x07: /* OR A,(dp+X) */ + { + OP_READ_INDEXED_INDIRECT_A(OR) + } + + case 0x27: /* AND A,(dp+X) */ + { + OP_READ_INDEXED_INDIRECT_A(AND) + } + + case 0x47: /* EOR A,(dp+X) */ + { + OP_READ_INDEXED_INDIRECT_A(EOR) + } + + case 0x67: /* CMP A,(dp+X) */ + { + OP_READ_INDEXED_INDIRECT_A(CMP) + } + + case 0x87: /* ADC A,(dp+X) */ + { + OP_READ_INDEXED_INDIRECT_A(ADC) + } + + case 0xA7: /* SBC A,(dp+X) */ + { + OP_READ_INDEXED_INDIRECT_A(SBC) + } + + case 0xC7: /* MOV (dp+X),A */ + { + OP_RMW_INDEXED_INDIRECT(WRITE_MOV(_A), 0) + } + + case 0xE7: /* MOV A,(dp+X) */ + { + OP_READ_INDEXED_INDIRECT_A(MOV_READ) + } + + /* xxx01000 */ + case 0x08: /* OR A,#imm */ + { + OP_READ_IMM_A(OR) + } + + case 0x28: /* AND A,#imm */ + { + OP_READ_IMM_A(AND) + } + + case 0x48: /* EOR A,#imm */ + { + OP_READ_IMM_A(EOR) + } + + case 0x68: /* CMP A,#imm */ + { + OP_READ_IMM_A(CMP) + } + + case 0x88: /* ADC A,#imm */ + { + OP_READ_IMM_A(ADC) + } + + case 0xA8: /* SBC A,#imm */ + { + OP_READ_IMM_A(SBC) + } + + case 0xC8: /* CMP X,#imm */ + { + OP_READ_IMM(CMP, _X) + } + + case 0xE8: /* MOV A,#imm */ + { + OP_READ_IMM_A(MOV_READ) + } + + /* xxx01001 */ + case 0x09: /* OR dp(d),dp(s) */ + { + OP_RMW_DP_DP(OR) + } + + case 0x29: /* AND dp(d),dp(s) */ + { + OP_RMW_DP_DP(AND) + } + + case 0x49: /* EOR dp(d),dp(s) */ + { + OP_RMW_DP_DP(EOR) + } + + case 0x69: /* CMP dp(d),dp(s) */ + { + /* 6 cycles - opcode, src address, dest address, src read, */ + /* dest read + op, dummy cycle */ + START_CYCLE(2) + _address2 = _dp + get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address = _dp + get_byte_spc(_PC); + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data2 = get_byte_spc(_address2); + END_CYCLE(4, 1) + + START_CYCLE(5) + _data = get_byte_spc(_address); + OP_CMP(_data, _data2) + END_OPCODE(2) + } + + case 0x89: /* ADC dp(d),dp(s) */ + { + OP_RMW_DP_DP(ADC) + } + + case 0xA9: /* SBC dp(d),dp(s) */ + { + OP_RMW_DP_DP(SBC) + } + + case 0xC9: /* MOV abs,X */ + { + OP_RMW_ABS(WRITE_MOV(_X), 0) + } + + case 0xE9: /* MOV X,abs */ + { + OP_READ_ABS(MOV_READ, _X) + } + + /* xxx01010 */ + case 0x0A: /* OR1 C,mem.bit */ + { + /* 5 cycles - opcode, address low, address high, data read, op */ + START_CYCLE(2) + _address = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address += get_byte_spc(_PC) << 8; + /* separate bit number, address */ + _offset = _address >> 13; + _address &= 0x1FFF; + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data = get_byte_spc(_address); + END_CYCLE(4, 1) + + START_CYCLE(5) + if (_data & offset_to_bit[_offset]) + set_flag_spc(SPC_FLAG_C); + END_OPCODE(1) + } + + case 0x2A: /* OR1 C,/mem.bit */ + { + /* 5 cycles - opcode, address low, address high, data read, op */ + START_CYCLE(2) + _address = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address += get_byte_spc(_PC) << 8; + /* separate bit number, address */ + _offset = _address >> 13; + _address &= 0x1FFF; + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data = get_byte_spc(_address); + END_CYCLE(4, 1) + + START_CYCLE(5) + if (!(_data & offset_to_bit[_offset])) + set_flag_spc(SPC_FLAG_C); + END_OPCODE(1) + } + + case 0x4A: /* AND1 C,mem.bit */ + { + /* 4 cycles - opcode, address low, address high, data read + op */ + START_CYCLE(2) + _address = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address += get_byte_spc(_PC) << 8; + /* separate bit number, address */ + _offset = _address >> 13; + _address &= 0x1FFF; + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data = get_byte_spc(_address); + if (!(_data & offset_to_bit[_offset])) + clr_flag_spc(SPC_FLAG_C); + END_OPCODE(1) + } + + case 0x6A: /* AND1 C,/mem.bit */ + { + /* 4 cycles - opcode, address low, address high, data read + op */ + START_CYCLE(2) + _address = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address += get_byte_spc(_PC) << 8; + /* separate bit number, address */ + _offset = _address >> 13; + _address &= 0x1FFF; + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data = get_byte_spc(_address); + if (_data & offset_to_bit[_offset]) + clr_flag_spc(SPC_FLAG_C); + END_OPCODE(1) + } + + case 0x8A: /* EOR1 C,mem.bit */ + { + /* 5 cycles - opcode, address low, address high, data read, op */ + START_CYCLE(2) + _address = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address += get_byte_spc(_PC) << 8; + /* separate bit number, address */ + _offset = _address >> 13; + _address &= 0x1FFF; + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data = get_byte_spc(_address); + END_CYCLE(4, 1) + + START_CYCLE(5) + if (_data & offset_to_bit[_offset]) + complement_carry_spc(); + END_OPCODE(1) + } + + case 0xAA: /* MOV1 C,mem.bit */ + { + /* 4 cycles - opcode, address low, address high, data read */ + START_CYCLE(2) + _address = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address += get_byte_spc(_PC) << 8; + /* separate bit number, address */ + _offset = _address >> 13; + _address &= 0x1FFF; + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data = get_byte_spc(_address); + store_flag_c(_data & offset_to_bit[_offset]); + END_OPCODE(1) + } + + case 0xCA: /* MOV1 mem.bit,C */ + { + /* 6 cycles - opcode, address low, address high, data read, op, */ + /* data write */ + START_CYCLE(2) + _address = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address += get_byte_spc(_PC) << 8; + /* separate bit number, address */ + _offset = _address >> 13; + _address &= 0x1FFF; + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data = get_byte_spc(_address); + END_CYCLE(4, 1) + + START_CYCLE(5) + if (flag_state_spc(SPC_FLAG_C)) + _data |= offset_to_bit[_offset]; + else + _data &= offset_to_not[_offset]; + END_CYCLE(5, 1) + + START_CYCLE(6) + set_byte_spc(_address, _data); + END_OPCODE(1) + } + + case 0xEA: /* NOT1 mem.bit */ + { + /* 5 cycles - opcode, address low, address high, data read, */ + /* op + data write */ + START_CYCLE(2) + _address = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address += get_byte_spc(_PC) << 8; + /* separate bit number, address */ + _offset = _address >> 13; + _address &= 0x1FFF; + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data = get_byte_spc(_address); + END_CYCLE(4, 1) + + START_CYCLE(5) + _data ^= offset_to_bit[_offset]; + set_byte_spc(_address, _data); + END_OPCODE(1) + } + + /* xxx01011 */ + case 0x0B: /* ASL dp */ + { + OP_RMW_DP(WRITE_OP(ASL), 1) + } + + case 0x2B: /* ROL dp */ + { + OP_RMW_DP(WRITE_OP(ROL), 1) + } + + case 0x4B: /* LSR dp */ + { + OP_RMW_DP(WRITE_OP(LSR), 1) + } + + case 0x6B: /* ROR dp */ + { + OP_RMW_DP(WRITE_OP(ROR), 1) + } + + case 0x8B: /* DEC dp */ + { + OP_RMW_DP(WRITE_OP(DEC), 1) + } + + case 0xAB: /* INC dp */ + { + OP_RMW_DP(WRITE_OP(INC), 1) + } + + case 0xCB: /* MOV dp,Y */ + { + OP_RMW_DP(WRITE_MOV(_Y), 0) + } + + case 0xEB: /* MOV Y,dp */ + { + OP_READ_DP(MOV_READ, _Y) + } + + /* xxx01100 */ + case 0x0C: /* ASL abs */ + { + OP_RMW_ABS(WRITE_OP(ASL), 1) + } + + case 0x2C: /* ROL abs */ + { + OP_RMW_ABS(WRITE_OP(ROL), 1) + } + + case 0x4C: /* LSR abs */ + { + OP_RMW_ABS(WRITE_OP(LSR), 1) + } + + case 0x6C: /* ROR abs */ + { + OP_RMW_ABS(WRITE_OP(ROR), 1) + } + + case 0x8C: /* DEC abs */ + { + OP_RMW_ABS(WRITE_OP(DEC), 1) + } + + case 0xAC: /* INC abs */ + { + OP_RMW_ABS(WRITE_OP(INC), 1) + } + + case 0xCC: /* MOV abs,Y */ + { + OP_RMW_ABS(WRITE_MOV(_Y), 0) + } + + case 0xEC: /* MOV Y,abs */ + { + OP_READ_ABS(MOV_READ, _Y) + } + + /* xxx01101 */ + case 0x0D: /* PUSH PSW */ + { + /* 4 cycles - opcode, address load, data write, SP decrement */ + START_CYCLE(2) + _address = 0x0100 + _SP; + END_CYCLE(2, 1) + + START_CYCLE(3) + spc_setup_flags(_B_flag); + set_byte_spc(_address, _PSW); + END_CYCLE(3, 1) + + START_CYCLE(4) + _SP--; + END_OPCODE(1) + } + + case 0x2D: /* PUSH A */ + { + OP_PUSH(_A) + } + + case 0x4D: /* PUSH X */ + { + OP_PUSH(_X) + } + + case 0x6D: /* PUSH Y */ + { + OP_PUSH(_Y) + } + + case 0x8D: /* MOV Y,#imm */ + { + OP_READ_IMM(MOV_READ, _Y) + } + + case 0xAD: /* CMP Y,#imm */ + { + OP_READ_IMM(CMP, _Y) + } + + case 0xCD: /* MOV X,#imm */ + { + OP_READ_IMM(MOV_READ, _X) + } + + case 0xED: /* NOTC */ + { + /* 2 cycles - opcode, op */ + START_CYCLE(2) + complement_carry_spc(); + END_OPCODE(1) + } + + /* xxx01110 */ + case 0x0E: /* TSET1 abs */ + { + /* 6 cycles - opcode, address low, address high, data read, */ + /* test for flags, op + data write */ + START_CYCLE(2) + _address = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address += get_byte_spc(_PC) << 8; + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data = get_byte_spc(_address); + END_CYCLE(4, 1) + + START_CYCLE(5) + store_flags_nz(_data & _A); + END_CYCLE(5, 1) + + START_CYCLE(6) + _data |= _A; + set_byte_spc(_address, _data); + END_OPCODE(1) + } + + case 0x2E: /* CBNE dp,rel */ + { + COND_DP_REL(TEST_CBNE) + } + + case 0x4E: /* TCLR1 abs */ + { + /* 6 cycles - opcode, address low, address high, data read, */ + /* test for flags, op + data write */ + START_CYCLE(2) + _address = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address += get_byte_spc(_PC) << 8; + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data = get_byte_spc(_address); + END_CYCLE(4, 1) + + START_CYCLE(5) + store_flags_nz(_data & _A); + END_CYCLE(5, 1) + + START_CYCLE(6) + _data &= ~_A; + set_byte_spc(_address, _data); + END_OPCODE(1) + } + + case 0x6E: /* DBNZ dp,rel */ + { + COND_DP_REL(DP_REL_TEST_DBNZ) + } + + case 0x8E: /* POP PSW */ + { + /* 4 cycles - opcode, SP increment, address load, data read */ + START_CYCLE(2) + _SP++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address = 0x0100 + _SP; + END_CYCLE(3, 1) + + START_CYCLE(4) + _PSW = get_byte_spc(_address); + spc_restore_flags(); + END_OPCODE(1) + } + + case 0xAE: /* POP A */ + { + OP_POP(_A) + } + + case 0xCE: /* POP X */ + { + OP_POP(_X) + } + + case 0xEE: /* POP Y */ + { + OP_POP(_Y) + } + + /* xxx01111 */ + case 0x0F: /* BRK */ + { + /* 8 cycles - opcode, new PCL, new PCH, stack address load, */ + /* PSW write, PCH write, PCL write, SP decrement */ + /* fetch address for PC */ + START_CYCLE(2) + /* same vector as TCALL 0 */ + _address = 0xFFC0 + ((15 - (0)) * 2); + _address2 = get_byte_spc(_address); + END_CYCLE(2, 1) + + START_CYCLE(3) + _address2 += (get_byte_spc(_address + 1) << 8); + END_CYCLE(3, 1) + + START_CYCLE(4) + _address = 0x0100 + _SP; + END_CYCLE(4, 1) + + START_CYCLE(5) + set_byte_spc(_address, _PC >> 8); + _SP--; + _address = 0x0100 + _SP; + END_CYCLE(5, 1) + + START_CYCLE(6) + set_byte_spc(_address, _PC); + _SP--; + _address = 0x0100 + _SP; + END_CYCLE(6, 1) + + START_CYCLE(7) + spc_setup_flags(_B_flag); + set_byte_spc(_address, _PSW); + set_flag_spc(SPC_FLAG_B); + clr_flag_spc(SPC_FLAG_I); + END_CYCLE(7, 1) + + START_CYCLE(8) + _PC = _address2; + _SP--; + END_OPCODE(1) + } + + case 0x2F: /* BRA rel */ + { + COND_REL(REL_TEST_BRA) + } + + case 0x4F: /* PCALL upage */ + { + /* 6 cycles - opcode, new PCL, stack address load, PCH write, */ + /* PCL write, SP decrement */ + /* fetch address for PC */ + START_CYCLE(2) + _address2 = 0xFF00 + get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address = 0x0100 + _SP; + END_CYCLE(3, 1) + + START_CYCLE(4) + set_byte_spc(_address, _PC >> 8); + _SP--; + _address = 0x0100 + _SP; + END_CYCLE(4, 1) + + START_CYCLE(5) + set_byte_spc(_address, _PC); + END_CYCLE(5, 1) + + START_CYCLE(6) + _PC = _address2; + _SP--; + END_OPCODE(1) + } + + case 0x6F: /* RET */ + { + /* 5 cycles - opcode, SP increment, address load, new PCL, new PCH */ + /* pop address to PC */ + START_CYCLE(2) + _SP++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address = 0x0100 + _SP; + + END_CYCLE(3, 1) + + START_CYCLE(4) + _address2 = get_byte_spc(_address); + _SP++; + _address = 0x0100 + _SP; + END_CYCLE(4, 1) + + START_CYCLE(5) + _PC = (get_byte_spc(_address) << 8) + _address2; + END_OPCODE(1) + } + + case 0x8F: /* MOV dp,#imm */ + { + OP_RMW_DP_IMM(MOV_READ_NOFLAGS) + } + + case 0xAF: /* MOV (X)+,A */ + { + /* 4 cycles - opcode, address load, data write, X increment */ + START_CYCLE(2) + _address = _dp + _X; + END_CYCLE(2, 1) + + START_CYCLE(3) + set_byte_spc(_address, _A); + END_CYCLE(3, 1) + + START_CYCLE(4) + _X++; + END_OPCODE(1) + } + + case 0xCF: /* MUL YA */ + { + /* 9 cycles - opcode, 8(op) */ + START_CYCLE(2) + _YA = (u32)_Y * _A; + store_flags_nz(_Y); + END_OPCODE(8) + } + + /* xxx10000 */ + case 0x10: /* BPL rel */ + { + COND_REL(REL_TEST_BPL) + } + + case 0x30: /* BMI rel */ + { + COND_REL(REL_TEST_BMI) + } + + case 0x50: /* BVC rel */ + { + COND_REL(REL_TEST_BVC) + } + + case 0x70: /* BVS rel */ + { + COND_REL(REL_TEST_BVS) + } + + case 0x90: /* BCC rel */ + { + COND_REL(REL_TEST_BCC) + } + + case 0xB0: /* BCS rel */ + { + COND_REL(REL_TEST_BCS) + } + + case 0xD0: /* BNE rel */ + { + COND_REL(REL_TEST_BNE) + } + + case 0xF0: /* BEQ rel */ + { + COND_REL(REL_TEST_BEQ) + } + +/* xxx10010 */ +#define opcode_CLR1(bit) (((bit) << 5) + 0x12) + case opcode_CLR1(0): + case opcode_CLR1(1): + case opcode_CLR1(2): + case opcode_CLR1(3): + case opcode_CLR1(4): + case opcode_CLR1(5): + case opcode_CLR1(6): + case opcode_CLR1(7): + { + OP_RMW_DP(WRITE_OP(CLR1), 1) + } + +/* xxx10011 */ +#define opcode_BBC(bit) (((bit) << 5) + 0x13) + case opcode_BBC(0): + case opcode_BBC(1): + case opcode_BBC(2): + case opcode_BBC(3): + case opcode_BBC(4): + case opcode_BBC(5): + case opcode_BBC(6): + case opcode_BBC(7): + { + COND_DP_REL(DP_REL_TEST_BBC) + } + + /* xxx10100 */ + case 0x14: /* OR A,dp+X */ + { + OP_READ_DP_X_INDEXED_A(OR) + } + + case 0x34: /* AND A,dp+X */ + { + OP_READ_DP_X_INDEXED_A(AND) + } + + case 0x54: /* EOR A,dp+X */ + { + OP_READ_DP_X_INDEXED_A(EOR) + } + + case 0x74: /* CMP A,dp+X */ + { + OP_READ_DP_X_INDEXED_A(CMP) + } + + case 0x94: /* ADC A,dp+X */ + { + OP_READ_DP_X_INDEXED_A(ADC) + } + + case 0xB4: /* SBC A,dp+X */ + { + OP_READ_DP_X_INDEXED_A(SBC) + } + + case 0xD4: /* MOV dp+X,A */ + { + OP_RMW_DP_X_INDEXED(WRITE_MOV(_A), 0) + } + + case 0xF4: /* MOV A,dp+X */ + { + OP_READ_DP_X_INDEXED_A(MOV_READ) + } + + /* xxx10101 */ + case 0x15: /* OR A,abs+X */ + { + OP_READ_ABS_X_INDEXED_A(OR) + } + + case 0x35: /* AND A,abs+X */ + { + OP_READ_ABS_X_INDEXED_A(AND) + } + + case 0x55: /* EOR A,abs+X */ + { + OP_READ_ABS_X_INDEXED_A(EOR) + } + + case 0x75: /* CMP A,abs+X */ + { + OP_READ_ABS_X_INDEXED_A(CMP) + } + + case 0x95: /* ADC A,abs+X */ + { + OP_READ_ABS_X_INDEXED_A(ADC) + } + + case 0xB5: /* SBC A,abs+X */ + { + OP_READ_ABS_X_INDEXED_A(SBC) + } + + case 0xD5: /* MOV abs+X,A */ + { + OP_RMW_ABS_X_INDEXED(WRITE_MOV(_A), 0) + } + + case 0xF5: /* MOV A,abs+X */ + { + OP_READ_ABS_X_INDEXED_A(MOV_READ) + } + + /* xxx10110 */ + case 0x16: /* OR A,abs+Y */ + { + OP_READ_ABS_Y_INDEXED_A(OR) + } + + case 0x36: /* AND A,abs+Y */ + { + OP_READ_ABS_Y_INDEXED_A(AND) + } + + case 0x56: /* EOR A,abs+Y */ + { + OP_READ_ABS_Y_INDEXED_A(EOR) + } + + case 0x76: /* CMP A,abs+Y */ + { + OP_READ_ABS_Y_INDEXED_A(CMP) + } + + case 0x96: /* ADC A,abs+Y */ + { + OP_READ_ABS_Y_INDEXED_A(ADC) + } + + case 0xB6: /* SBC A,abs+Y */ + { + OP_READ_ABS_Y_INDEXED_A(SBC) + } + + case 0xD6: /* MOV abs+Y,A */ + { + OP_RMW_ABS_Y_INDEXED(WRITE_MOV(_A), 0) + } + + case 0xF6: /* MOV A,abs+Y */ + { + OP_READ_ABS_Y_INDEXED_A(MOV_READ) + } + + /* xxx10111 */ + case 0x17: /* OR A,(dp)+Y */ + { + OP_READ_INDIRECT_INDEXED_A(OR) + } + + case 0x37: /* AND A,(dp)+Y */ + { + OP_READ_INDIRECT_INDEXED_A(AND) + } + + case 0x57: /* EOR A,(dp)+Y */ + { + OP_READ_INDIRECT_INDEXED_A(EOR) + } + + case 0x77: /* CMP A,(dp)+Y */ + { + OP_READ_INDIRECT_INDEXED_A(CMP) + } + + case 0x97: /* ADC A,(dp)+Y */ + { + OP_READ_INDIRECT_INDEXED_A(ADC) + } + + case 0xB7: /* SBC A,(dp)+Y */ + { + OP_READ_INDIRECT_INDEXED_A(SBC) + } + + case 0xD7: /* MOV (dp)+Y,A */ + { + OP_RMW_INDIRECT_INDEXED(WRITE_MOV(_A), 0) + } + + case 0xF7: /* MOV A,(dp)+Y */ + { + OP_READ_INDIRECT_INDEXED_A(MOV_READ) + } + + /* xxx11000 */ + case 0x18: /* OR dp,#imm */ + { + OP_RMW_DP_IMM(OR) + } + + case 0x38: /* AND dp,#imm */ + { + OP_RMW_DP_IMM(AND) + } + + case 0x58: /* EOR dp,#imm */ + { + OP_RMW_DP_IMM(EOR) + } + + case 0x78: /* CMP dp,#imm */ + { + /* 5 cycles - opcode, src data, dest address, dest read + op, */ + /* dummy cycle */ + START_CYCLE(2) + _data2 = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address = _dp + get_byte_spc(_PC); + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data = get_byte_spc(_address); + OP_CMP(_data, _data2) + END_OPCODE(2) + } + + case 0x98: /* ADC dp,#imm */ + { + OP_RMW_DP_IMM(ADC) + } + + case 0xB8: /* SBC dp,#imm */ + { + OP_RMW_DP_IMM(SBC) + } + + case 0xD8: /* MOV dp,X */ + { + OP_RMW_DP(WRITE_MOV(_X), 0) + } + + case 0xF8: /* MOV X,dp */ + { + OP_READ_DP(MOV_READ, _X) + } + + /* xxx11001 */ + case 0x19: /* OR (X),(Y) */ + { + OP_RMW_INDIRECT_INDIRECT(OR) + } + + case 0x39: /* AND (X),(Y) */ + { + OP_RMW_INDIRECT_INDIRECT(AND) + } + + case 0x59: /* EOR (X),(Y) */ + { + OP_RMW_INDIRECT_INDIRECT(EOR) + } + + case 0x79: /* CMP (X),(Y) */ + { + /* 5 cycles - opcode, address calc, src read, dest read + op, */ + /* dummy cycle */ + START_CYCLE(2) + _address = _dp + _Y; + END_CYCLE(2, 1) + + START_CYCLE(3) + _data2 = get_byte_spc(_address); + _address = _dp + _X; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data = get_byte_spc(_address); + OP_CMP(_data, _data2) + END_OPCODE(2) + } + + case 0x99: /* ADC (X),(Y) */ + { + OP_RMW_INDIRECT_INDIRECT(ADC) + } + + case 0xB9: /* SBC (X),(Y) */ + { + OP_RMW_INDIRECT_INDIRECT(SBC) + } + + case 0xD9: /* MOV dp+Y,X */ + { + OP_RMW_DP_Y_INDEXED(WRITE_MOV(_X), 0) + } + + case 0xF9: /* MOV X,dp+Y */ + { + OP_READ_DP_Y_INDEXED(MOV_READ, _X) + } + + /* xxx11010 */ + case 0x1A: /* DECW dp */ + { + OP_RMW16_DP(DECW) + } + + case 0x3A: /* INCW dp */ + { + OP_RMW16_DP(INCW) + } + + case 0x5A: /* CMPW YA,dp */ + { + u32 temp; + + /* 4 cycles - opcode, address, data low read, data high read + op */ + START_CYCLE(2) + _address = _dp + get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _data16 = get_byte_spc(_address); + END_CYCLE(3, 1) + + START_CYCLE(4) + _data16 += get_byte_spc(_address + 1) << 8; + temp = _YA - _data16; + store_flag_c(temp <= 0xFFFF); + store_flag_n(temp >> 8); + store_flag_z(temp != 0); + END_OPCODE(1) + } + + case 0x7A: /* ADDW YA,dp */ + { + OP_READ16_YA_DP(ADDW) + } + + case 0x9A: /* SUBW YA,dp */ + { + OP_READ16_YA_DP(SUBW) + } + + case 0xBA: /* MOVW YA,dp */ + { + OP_READ16_YA_DP(MOVW_READ) + } + + case 0xDA: /* MOVW dp,YA */ + { + /* 5 cycles - opcode, address, (?), data low write, data high write, */ + START_CYCLE(2) + _address = _dp + get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + get_byte_spc(_address); + END_CYCLE(3, 1) + + START_CYCLE(4) + set_byte_spc(_address, _A); + END_CYCLE(4, 1) + + START_CYCLE(5) + set_byte_spc(_address + 1, _Y); + END_OPCODE(1) + } + + case 0xFA: /* MOV dp(d),dp(s) */ + { + /* 5 cycles - opcode, src address, dest address, src read, */ + /* dest write */ + START_CYCLE(2) + _address2 = _dp + get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address = _dp + get_byte_spc(_PC); + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data = get_byte_spc(_address2); + END_CYCLE(4, 1) + + START_CYCLE(5) + set_byte_spc(_address, _data); + END_OPCODE(1) + } + + /* xxx11011 */ + case 0x1B: /* ASL dp+X */ + { + OP_RMW_DP_X_INDEXED(WRITE_OP(ASL), 1) + } + + case 0x3B: /* ROL dp+X */ + { + OP_RMW_DP_X_INDEXED(WRITE_OP(ROL), 1) + } + + case 0x5B: /* LSR dp+X */ + { + OP_RMW_DP_X_INDEXED(WRITE_OP(LSR), 1) + } + + case 0x7B: /* ROR dp+X */ + { + OP_RMW_DP_X_INDEXED(WRITE_OP(ROR), 1) + } + + case 0x9B: /* DEC dp+X */ + { + OP_RMW_DP_X_INDEXED(WRITE_OP(DEC), 1) + } + + case 0xBB: /* INC dp+X */ + { + OP_RMW_DP_X_INDEXED(WRITE_OP(INC), 1) + } + + case 0xDB: /* MOV dp+X,Y */ + { + OP_RMW_DP_X_INDEXED(WRITE_MOV(_Y), 0) + } + + case 0xFB: /* MOV Y,dp+X */ + { + OP_READ_DP_X_INDEXED(MOV_READ, _Y) + } + + /* xxx11100 */ + case 0x1C: /* ASL A */ + { + OP_RMW_IMPLIED(ASL, _A) + } + + case 0x3C: /* ROL A */ + { + OP_RMW_IMPLIED(ROL, _A) + } + + case 0x5C: /* LSR A */ + { + OP_RMW_IMPLIED(LSR, _A) + } + + case 0x7C: /* ROR A */ + { + OP_RMW_IMPLIED(ROR, _A) + } + + case 0x9C: /* DEC A */ + { + OP_RMW_IMPLIED(DEC, _A) + } + + case 0xBC: /* INC A */ + { + OP_RMW_IMPLIED(INC, _A) + } + + case 0xDC: /* DEC Y */ + { + OP_RMW_IMPLIED(DEC, _Y) + } + + case 0xFC: /* INC Y */ + { + OP_RMW_IMPLIED(INC, _Y) + } + + /* xxx11101 */ + case 0x1D: /* DEC X */ + { + OP_RMW_IMPLIED(DEC, _X) + } + + case 0x3D: /* INC X */ + { + OP_RMW_IMPLIED(INC, _X) + } + + case 0x5D: /* MOV X,A */ + { + OP_MOV_IMPLIED(_X, _A) + } + + case 0x7D: /* MOV A,X */ + { + OP_MOV_IMPLIED(_A, _X) + } + + case 0x9D: /* MOV X,SP */ + { + OP_MOV_IMPLIED(_X, _SP) + } + + case 0xBD: /* MOV SP,X */ + { + OP_MOV_IMPLIED_NO_FLAGS(_SP, _X) + } + + case 0xDD: /* MOV A,Y */ + { + OP_MOV_IMPLIED(_A, _Y) + } + + case 0xFD: /* MOV Y,A */ + { + OP_MOV_IMPLIED(_Y, _A) + } + + /* xxx11110 */ + case 0x1E: /* CMP X,abs */ + { + OP_READ_ABS(CMP, _X) + } + + case 0x3E: /* CMP X,dp */ + { + OP_READ_DP(CMP, _X) + } + + case 0x5E: /* CMP Y,abs */ + { + OP_READ_ABS(CMP, _Y) + } + + case 0x7E: /* CMP Y,dp */ + { + OP_READ_DP(CMP, _Y) + } + + case 0x9E: /* DIV YA,X */ + { + /* 12 cycles - opcode, 11(op) */ + /* timing of operations completely wrong here, at least */ + START_CYCLE(2) + u32 yva, work_x, i; + yva = _YA; + work_x = (u32)_X << 9; + + if ((_X & 0xF) <= (_Y & 0xF)) + set_flag_spc(SPC_FLAG_H); + else + clr_flag_spc(SPC_FLAG_H); + + for (i = 0; i < 9; i++) + { + yva <<= 1; + if (yva & 0x20000) + yva = (yva & 0x1FFFF) | 1; /* 17-bit ROL */ + if (yva >= work_x) + yva ^= 1; /* Why XOR i don't know, but it's what works */ + /* and I guess this was easier than a compound if */ + if (yva & 1) + yva = (yva - work_x) & 0x1FFFF; /* enforce 17-bit register limit! */ + } + + if (yva & 0x100) + set_flag_spc(SPC_FLAG_V); + else + clr_flag_spc(SPC_FLAG_V); + + _YA = (((yva >> 9) & 0xFF) << 8) + (yva & 0xFF); + store_flags_nz(_YA); + END_OPCODE(11) + } + + case 0xBE: /* DAS */ + { + /* 3 cycles - opcode, 2(op) */ + START_CYCLE(2) + _data = _A; + if ((_data & 0x0F) > 9 || !flag_state_spc(SPC_FLAG_H)) + { + _A -= 6; + } + END_CYCLE(2, 1) + + START_CYCLE(3) + if (_data > 0x99 || !flag_state_spc(SPC_FLAG_C)) + { + _A -= 0x60; + clr_flag_spc(SPC_FLAG_C); + } + store_flags_nz(_A); + END_OPCODE(1) + } + + case 0xDE: /* CBNE dp+X,rel */ + { + /* 6 cycles - opcode, address, branch offset, address index */ + /* (add X), data read, branch logic; */ + /* +2 cycles (taken branch) add PC to offset, reload PC */ + START_CYCLE(2) + _address = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _offset = get_byte_spc(_PC); + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _address = _dp + ((_address + _X) & 0xFF); + END_CYCLE(4, 1) + + START_CYCLE(5) + _data = get_byte_spc(_address); + END_CYCLE(5, 1) + + START_CYCLE(6) + END_BRANCH_OPCODE(6, TEST_CBNE) + } + + case 0xFE: /* DBNZ Y,rel */ + { + /* 4 cycles - opcode, branch offset, decrement Y, branch logic; */ + /* +2 cycles (taken branch) add PC to offset, reload PC */ + + START_CYCLE(2) + _offset = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _Y--; + END_CYCLE(3, 1) + + START_CYCLE(4) + END_BRANCH_OPCODE(4, if (!_Y) EXIT_OPCODE(1)) + } + + /* xxx11111 */ + case 0x1F: /* JMP (abs+X) */ + { + /* 6 cycles - opcode, address low, address high */ + /* address index (add X), new PCL, new PCH */ + /* fetch base adderss */ + START_CYCLE(2) + _address = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address += get_byte_spc(_PC) << 8; + END_CYCLE(3, 1) + + START_CYCLE(4) + _address += _X; + END_CYCLE(4, 1) + + START_CYCLE(5) + _offset = get_byte_spc(_address); + END_CYCLE(5, 1) + + START_CYCLE(6) + _PC = (get_byte_spc(_address + 1) << 8) + _offset; + END_OPCODE(1) + } + + case 0x3F: /* CALL abs */ + { + /* 8 cycles - opcode, new PCL, new PCH, stack address load, PCH */ + /* write, PCL write, dummy cycle (PSW write in BRK?) */ + /* SP decrement */ + /* fetch address for PC */ + START_CYCLE(2) + _address2 = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address2 += (get_byte_spc(_PC) << 8); + _PC++; + END_CYCLE(3, 1) + + START_CYCLE(4) + _address = 0x0100 + _SP; + END_CYCLE(4, 1) + + START_CYCLE(5) + set_byte_spc(_address, _PC >> 8); + _SP--; + _address = 0x0100 + _SP; + END_CYCLE(5, 1) + + START_CYCLE(6) + set_byte_spc(_address, _PC); + _SP--; + _address = 0x0100 + _SP; + END_CYCLE(6, 1) + + START_CYCLE(7) + /* should we write PSW to stack here? */ + END_CYCLE(7, 1) + + START_CYCLE(8) + _PC = _address2; + END_OPCODE(1) + } + + case 0x5F: /* JMP abs */ + { + /* 3 cycles - opcode, new PCL, new PCH */ + /* fetch address to PC */ + START_CYCLE(2) + _address = get_byte_spc(_PC); + _PC++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _PC = (get_byte_spc(_PC) << 8) + _address; + END_OPCODE(1) + } + + case 0x7F: /* RETI */ + { + /* 6 cycles - opcode, SP increment, address load, new PSW, new PCL, */ + /* new PCH, pop address to PC */ + START_CYCLE(2) + _SP++; + END_CYCLE(2, 1) + + START_CYCLE(3) + _address = 0x0100 + _SP; + + END_CYCLE(3, 1) + + START_CYCLE(4) + _PSW = get_byte_spc(_address); + spc_restore_flags(); + _SP++; + _address = 0x0100 + _SP; + END_CYCLE(4, 1) + + START_CYCLE(5) + _address2 = get_byte_spc(_address); + _SP++; + _address = 0x0100 + _SP; + END_CYCLE(5, 1) + + START_CYCLE(6) + _PC = (get_byte_spc(_address) << 8) + _address2; + END_OPCODE(1) + } + + case 0x9F: /* XCN A */ + { + /* 5 cycles - opcode, 4(op) */ + /* timing of operations may be off here */ + START_CYCLE(2) + _data = _A; + END_CYCLE(2, 1) + + START_CYCLE(3) + _A <<= 4; + END_CYCLE(3, 1) + + START_CYCLE(4) + _data >>= 4; + END_CYCLE(4, 1) + + START_CYCLE(5) + OP_OR(_A, _data); + END_OPCODE(1) + } + + case 0xBF: /* MOV A,(X)+ */ + { + /* 4 cycles - opcode, address load, data read, X increment */ + START_CYCLE(2) + _address = _dp + _X; + END_CYCLE(2, 1) + + START_CYCLE(3) + _data = get_byte_spc(_address); + OP_MOV_READ(_A, _data) + END_CYCLE(3, 1) + + START_CYCLE(4) + _X++; + END_OPCODE(1) + } + + case 0xDF: /* DAA */ + { + /* 3 cycles - opcode, 2(op) */ + START_CYCLE(2) + _data = _A; + if ((_data & 0x0F) > 9 || flag_state_spc(SPC_FLAG_H)) + { + _A += 6; + if (_A < 6) + set_flag_spc(SPC_FLAG_C); + } + END_CYCLE(2, 1) + + START_CYCLE(3) + if (_data > 0x99 || flag_state_spc(SPC_FLAG_C)) + { + _A += 0x60; + set_flag_spc(SPC_FLAG_C); + } + store_flags_nz(_A); + END_OPCODE(1) + } + + /* handle unhandled or invalid opcodes */ + case 0xEF: /* SLEEP */ + case 0xFF: /* STOP */ + default: + { + /* set up address (PC) and opcode for display */ + Map_Byte = _opcode; + /* Adjust address to correct for increment */ + Map_Address = (_PC - 1) & 0xFFFF; + save_cycles_spc(); /* Set cycle counter */ + InvalidSPCOpcode(); /* This exits.. aviods conflict with other things! */ + load_cycles_spc(); + break; + } + } + if (opcode_done) + _cycle = 0; + } + + save_cycles_spc(); /* Set cycle counter */ + +#ifdef INDEPENDENT_SPC + /* update SPC700 timers to prevent overflow */ + Update_SPC_Timer(0); + Update_SPC_Timer(1); + Update_SPC_Timer(2); +#endif + + In_CPU = was_in_cpu; +} + +void SPC_START(u32 cycles) +{ + u64 temp = cycles; + temp = (temp * SPC_CPU_cycle_multiplicand) + SPC_CPU_cycles_mul; + + /* save remainder */ + SPC_CPU_cycles_mul = temp % SPC_CPU_cycle_divisor; + + cycles = temp / SPC_CPU_cycle_divisor; + SPC_CPU_cycles = 0; + + /* Add new balance of SPC cycles */ + _Cycles += cycles; + if (_Cycles <= _TotalCycles) + { + if ((s32)_Cycles < 0) + return; + if ((s32)_TotalCycles >= 0) + return; + Wrap_SPC_Cyclecounter(); + } + + Execute_SPC(); +}