mirror of https://github.com/bsnes-emu/bsnes.git
Update to bsnes v038 release.
- eliminated S-DD1 DMA enslavement to the S-CPU; this allows the S-DD1 to behave more like the real chip, and it also simplifies the S-CPU DMA module - eliminated S-PPU enslavement to the S-CPU; all processor cores now run independently of each other - added cycle-level S-PPU timing for OAM address reset and OBSEL; fixes scanline glitches in Mega Lo Mania and Winter Olympics - removed ppu.hack.* settings; as they are no longer needed due to above changes - corrected VRAM tiledata cache bug; fixes Super Buster Bros v1.0 reset glitch - added memory export and trace logging key bindings to user interface - removed WAV logging (to trim the emulation core) - embedded readme and license texts inside executable - simplified S-CPU, S-SMP flag register handling - source code cleanup for S-CPU timing module - GUI-Linux: added style improvements to the listbox and combo box controls - GUI-Linux: finally added filetype filter support to the file open dialog - GUI-all: shrunk configuration panel [FitzRoy] - GUI-all: modified paths panel descriptions for clarity [FitzRoy]
This commit is contained in:
parent
e370a35d7d
commit
c13ae98863
84
license.txt
84
license.txt
|
@ -1,84 +0,0 @@
|
|||
bsnes (TM) Reference License
|
||||
Copyright (C) 2004 - 2008 byuu
|
||||
All rights reserved
|
||||
|
||||
1. Definitions
|
||||
|
||||
The terms "reproduce", "reproduction", "distribute" and "distribution" have the
|
||||
same meaning here as under U.S. copyright law.
|
||||
|
||||
"The software" means this software package as a whole, including, but not
|
||||
limited to, this license, binaries, source code, documentation, and data.
|
||||
|
||||
"You" means the licensee of the software.
|
||||
|
||||
"The licensor" means the copyright holder of the software, byuu.
|
||||
|
||||
2. Grant of Rights
|
||||
|
||||
Subject to the terms of this license, the licensor grants you a
|
||||
non-transferable, non-exclusive, worldwide, royalty-free copyright license to
|
||||
reproduce the software for non-commercial use only, provided the software
|
||||
remains unmodified, and there is no charge for the software itself, its' use,
|
||||
nor for the medium upon which the software is distributed. The reproduction of
|
||||
modified or derivative works of the software is strictly prohibited, except when
|
||||
transmitted solely to the licensor.
|
||||
|
||||
3. Limitations
|
||||
|
||||
This license does not grant you any rights to use the licensor's name, logo or
|
||||
trademarks.
|
||||
|
||||
The software is provided "as is", and any express or implied warranties,
|
||||
including, but not limited to, the implied warranties of merchantability and
|
||||
fitness for a particular purpose are disclaimed. In no event shall the licensor
|
||||
be liable for any direct, indirect, incidental, special, exemplary, or
|
||||
consequential damages (including, but not limited to, procurement of substitute
|
||||
goods or services; loss of use, data, or profits; or business interruption)
|
||||
however caused and on any theory of liability, whether in contract, strict
|
||||
liability, or tort (including negligence or otherwise) arising in any way out of
|
||||
the use of the software, even if advised of the possibility of such damage.
|
||||
|
||||
In the event that this license is determined to be invalid or unenforceable, the
|
||||
Grant of Rights will become null and void, and no rights shall be granted to the
|
||||
licensee, within the scope of U.S. copyright law.
|
||||
|
||||
4. Exemptions
|
||||
|
||||
The software includes the work of other copyright holders, which is licensed
|
||||
under different agreements, and exempt from this license. Below is a complete
|
||||
list of all such software, and their respective copyright holders and licenses.
|
||||
Further, respective source code files are labeled with their correct licensing
|
||||
information in the header. The lack of such a header indicates said file falls
|
||||
under the bsnes license.
|
||||
|
||||
HQ2x filter, author: MaxST, license: LGPL
|
||||
JMA decompressor, author: NSRT Team, license: GPL*
|
||||
NTSC filter, author: blargg, license: LGPL
|
||||
zlib decompressor, license: zlib license
|
||||
|
||||
(* bsnes has received an exemption from the copyright holder to use this work.)
|
||||
|
||||
The software also includes works which have been released to the public domain,
|
||||
which are not bound to any licensing agreements. Below is a complete list of all
|
||||
such software.
|
||||
|
||||
libco, author: byuu
|
||||
S-DD1 decompressor, author: Andreas Naive
|
||||
SPC7110 decompressor, author: neviksti
|
||||
|
||||
Any software listed above as exemptions may be relicensed individually from
|
||||
bsnes under their respective terms. However, no bsnes licensed portions can be
|
||||
combined with such a derivative work.
|
||||
|
||||
The software also includes the work of other copyright holders, which is
|
||||
licensed under the terms of the bsnes license, with permission to do so from the
|
||||
respective authors. Below is a complete list of all such software.
|
||||
|
||||
Cx4 emu, authors: anomie, Overload, zsKnight, Nach
|
||||
DSP-1 emu, authors: Overload, John Weidman, Neviksti, Andreas Naive
|
||||
DSP-2 emu, author: Overload
|
||||
DSP-3 emu, authors: John Weidman, Kris Bleakley, Lancer, z80 gaiden
|
||||
DSP-4 emu, authors: Dreamer Nom, John Weidman, Kris Bleakley, Nach, z80 gaiden
|
||||
S-DSP emu, author: blargg
|
||||
ST-010 emu, authors: John Weidman, Matthew Kendora, Overload, Feather
|
108
readme.txt
108
readme.txt
|
@ -1,108 +0,0 @@
|
|||
bsnes
|
||||
Version: 0.037a
|
||||
Author: byuu
|
||||
|
||||
========
|
||||
General:
|
||||
========
|
||||
|
||||
bsnes is a Super Nintendo / Super Famicom emulator that began on
|
||||
October 14th, 2004.
|
||||
|
||||
The latest version can be downloaded from:
|
||||
http://byuu.org/
|
||||
|
||||
Please see license.txt for important licensing information.
|
||||
|
||||
==============
|
||||
Configuration:
|
||||
==============
|
||||
|
||||
bsnes has two configuration files: bsnes.cfg, for program settings; and
|
||||
locale.cfg, for localization.
|
||||
|
||||
For each file, bsnes will start by looking inside the same folder where the
|
||||
bsnes executable is located. If said file is not found, it will then check your
|
||||
user profile folder. On Windows, this is located at "%APPDATA%/.bsnes". On all
|
||||
other operating systems, this is located at "~/.bsnes". If said file is still
|
||||
not found, it will automatically be created in your user profile folder.
|
||||
|
||||
If you wish to use bsnes in single-user mode, be sure that both files exist
|
||||
inside the same folder as the bsnes executable. If they do not, you can simply
|
||||
create new blank files and bsnes will use them in the future.
|
||||
|
||||
If you wish to use bsnes in multi-user mode, simply delete these two files from
|
||||
the bsnes executable directory if they exist.
|
||||
|
||||
If you wish to have multiple configuration profiles for the same user, you will
|
||||
need to make copies of the bsnes executable, and use each one in single-user
|
||||
mode.
|
||||
|
||||
====================
|
||||
Known Limitation(s):
|
||||
====================
|
||||
|
||||
S-CPU
|
||||
- Multiply / divide register delays not implemented
|
||||
- "Glitch" when reading joypad registers during auto polling not implemented
|
||||
|
||||
S-PPU
|
||||
- Uses scanline-based renderer. This is very inaccurate, but few (if any)
|
||||
games rely on mid-scanline writes to function correctly
|
||||
- Does not support FirstSprite+Y priority
|
||||
- OAM / CGRAM accesses during active display not supported correctly
|
||||
- RTO flags are not calculated on frames that are skipped when frameskipping
|
||||
is enabled. This provides a major speedup, however it will cause in issues
|
||||
in games that test these flags, eg the SNES Test Program Electronics Test.
|
||||
Turning frameskipping off will allow RTO flag calculation on every frame
|
||||
|
||||
Hardware Bugs
|
||||
- S-CPU.r1 HDMA crashing bug not emulated
|
||||
- S-CPU<>S-SMP communication bus conflicts not emulated
|
||||
|
||||
===============
|
||||
Known Issue(s):
|
||||
===============
|
||||
|
||||
On Windows, attempting to load a ZIP, GZ or JMA compressed archive with
|
||||
non-ANSI characters in the filename will fail. This is because Windows
|
||||
requires UTF-16 encoding, but these libraries only work with UTF-8.
|
||||
Note that loading uncompressed images (SMC, SFC, etc) with non-ANSI characters
|
||||
works properly on all platforms.
|
||||
|
||||
=====================
|
||||
Unsupported Hardware:
|
||||
=====================
|
||||
|
||||
SA-1
|
||||
Coprocessor used in many popular games, including:
|
||||
- Dragon Ball Z Hyper Dimension
|
||||
- Kirby Super Star
|
||||
- Kirby's Dreamland 3
|
||||
- Marvelous
|
||||
- SD Gundam G-NEXT
|
||||
- Super Mario RPG
|
||||
|
||||
Super FX
|
||||
Coprocessor used in many popular games, including:
|
||||
- Doom
|
||||
- Star Fox
|
||||
- Star Fox 2 (unreleased beta)
|
||||
- Super Mario World 2: Yoshi's Island
|
||||
|
||||
ST-011
|
||||
SETA DSP used by Quick-move Shogi Match with Nidan Rank-holder Morita
|
||||
|
||||
ST-018
|
||||
SETA RISC CPU used by Quick-move Shogi Match with Nidan Rank-holder Morita 2
|
||||
|
||||
Super Gameboy
|
||||
Cartridge passthrough used for playing Gameboy games
|
||||
|
||||
=============
|
||||
Contributors:
|
||||
=============
|
||||
|
||||
Andreas Naive, anomie, blargg, DMV27, FitzRoy, GIGO, Jonas Quinn, kode54, krom,
|
||||
Matthew Callis, mudlord, Nach, neviksti, Overload, RedDwarf, Richard Bannister,
|
||||
tetsuo55, TRAC, zones
|
12
src/Makefile
12
src/Makefile
|
@ -6,7 +6,7 @@ prefix = /usr/local
|
|||
################
|
||||
|
||||
ifneq ($(findstring gcc,$(compiler)),) # GCC family
|
||||
flags = -O3 -fomit-frame-pointer -Ilib
|
||||
flags = -O3 -fomit-frame-pointer $(if $(call streq,$(platform),x),-mtune=native,) -Ilib
|
||||
c = $(compiler) $(flags)
|
||||
cpp = $(subst cc,++,$(compiler)) $(flags)
|
||||
obj = o
|
||||
|
@ -34,7 +34,7 @@ endif
|
|||
##########
|
||||
|
||||
ifeq ($(platform),x) # X11
|
||||
ruby = video.glx video.xv video.sdl audio.openal audio.oss audio.alsa audio.ao input.sdl input.x
|
||||
ruby = video.glx video.xv video.sdl audio.alsa audio.openal audio.oss audio.pulseaudio audio.ao input.sdl input.x
|
||||
link += `pkg-config --libs gtk+-2.0`
|
||||
link += $(call mklib,Xtst)
|
||||
delete = rm -f $1
|
||||
|
@ -70,6 +70,7 @@ link += $(if $(findstring audio.alsa,$(ruby)),$(call mklib,asound))
|
|||
link += $(if $(findstring audio.ao,$(ruby)),$(call mklib,ao))
|
||||
link += $(if $(findstring audio.directsound,$(ruby)),$(call mklib,dsound))
|
||||
link += $(if $(findstring audio.openal,$(ruby)),$(if $(call streq,$(platform),x),$(call mklib,openal),$(call mklib,openal32)))
|
||||
link += $(if $(findstring audio.pulseaudio,$(ruby)),$(call mklib,pulse-simple))
|
||||
link += $(if $(findstring input.directinput,$(ruby)),$(call mklib,dinput8) $(call mklib,dxguid))
|
||||
link += $(if $(findstring input.sdl,$(ruby)),`sdl-config --libs`)
|
||||
|
||||
|
@ -77,7 +78,8 @@ link += $(if $(findstring input.sdl,$(ruby)),`sdl-config --libs`)
|
|||
### main target and dependencies ###
|
||||
####################################
|
||||
|
||||
objects = main libco hiro ruby libfilter string reader cart cheat \
|
||||
objects = main libco hiro ruby libfilter string \
|
||||
config reader cart cheat \
|
||||
memory smemory cpu scpu smp ssmp sdsp ppu bppu snes \
|
||||
bsx srtc sdd1 spc7110 cx4 dsp1 dsp2 dsp3 dsp4 obc1 st010
|
||||
|
||||
|
@ -125,7 +127,7 @@ all: build;
|
|||
### main ###
|
||||
############
|
||||
|
||||
obj/main.$(obj): ui/main.cpp ui/* ui/base/* ui/loader/* ui/settings/*
|
||||
obj/main.$(obj): ui/main.cpp ui/* ui/base/* ui/event/* ui/loader/* ui/settings/*
|
||||
obj/bsnes.res: ui/bsnes.rc; rc /r /foobj/bsnes.res ui/bsnes.rc
|
||||
obj/bsnesrc.$(obj): ui/bsnes.rc; windres ui/bsnes.rc obj/bsnesrc.$(obj)
|
||||
|
||||
|
@ -146,6 +148,7 @@ obj/string.$(obj): lib/nall/string.cpp lib/nall/*
|
|||
### utilities ###
|
||||
#################
|
||||
|
||||
obj/config.$(obj): config/config.cpp config/*
|
||||
obj/reader.$(obj): reader/reader.cpp reader/*
|
||||
obj/cart.$(obj) : cart/cart.cpp cart/*
|
||||
obj/cheat.$(obj) : cheat/cheat.cpp cheat/*
|
||||
|
@ -176,7 +179,6 @@ obj/ssmp.$(obj): smp/ssmp/ssmp.cpp smp/ssmp/* smp/ssmp/core/* smp/ssmp/memory/*
|
|||
###########
|
||||
|
||||
obj/adsp.$(obj): dsp/adsp/adsp.cpp dsp/adsp/*
|
||||
obj/bdsp.$(obj): dsp/bdsp/bdsp.cpp dsp/bdsp/*
|
||||
obj/sdsp.$(obj): dsp/sdsp/sdsp.cpp dsp/sdsp/*
|
||||
|
||||
###########
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#define BSNES_VERSION "0.037a"
|
||||
#define BSNES_VERSION "0.038"
|
||||
#define BSNES_TITLE "bsnes v" BSNES_VERSION
|
||||
|
||||
#define BUSCORE sBus
|
||||
|
@ -19,6 +19,8 @@
|
|||
//game genie + pro action replay code support (~2% speed hit)
|
||||
#define CHEAT_SYSTEM
|
||||
|
||||
#include <libco/libco.h>
|
||||
|
||||
#include <nall/algorithm.hpp>
|
||||
#include <nall/array.hpp>
|
||||
#include <nall/bit.hpp>
|
||||
|
@ -29,6 +31,7 @@
|
|||
#include <nall/function.hpp>
|
||||
#include <nall/modulo.hpp>
|
||||
#include <nall/new.hpp>
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/sort.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
@ -36,11 +39,14 @@
|
|||
#include <nall/vector.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include <libco/libco.h>
|
||||
#include <bbase.h>
|
||||
typedef int8_t int8;
|
||||
typedef int16_t int16;
|
||||
typedef int32_t int32;
|
||||
typedef int64_t int64;
|
||||
typedef uint8_t uint8;
|
||||
typedef uint16_t uint16;
|
||||
typedef uint32_t uint32;
|
||||
typedef uint64_t uint64;
|
||||
typedef unsigned uint;
|
||||
|
||||
//platform-specific global functions
|
||||
void alert(const char*, ...);
|
||||
void dprintf(const char*, ...);
|
||||
|
||||
#include "interface.h"
|
||||
#include "interface.hpp"
|
|
@ -1,4 +1,4 @@
|
|||
#include "../base.h"
|
||||
#include <../base.hpp>
|
||||
#define CART_CPP
|
||||
|
||||
#include <nall/crc32.hpp>
|
||||
|
|
|
@ -18,17 +18,17 @@ public:
|
|||
};
|
||||
|
||||
enum HeaderField {
|
||||
CART_NAME = 0x00,
|
||||
MAPPER = 0x15,
|
||||
ROM_TYPE = 0x16,
|
||||
ROM_SIZE = 0x17,
|
||||
RAM_SIZE = 0x18,
|
||||
REGION = 0x19,
|
||||
COMPANY = 0x1a,
|
||||
VERSION = 0x1b,
|
||||
ICKSUM = 0x1c,
|
||||
CKSUM = 0x1e,
|
||||
RESETV = 0x3c,
|
||||
CartName = 0x00,
|
||||
Mapper = 0x15,
|
||||
RomType = 0x16,
|
||||
RomSize = 0x17,
|
||||
RamSize = 0x18,
|
||||
CartRegion = 0x19,
|
||||
Company = 0x1a,
|
||||
Version = 0x1b,
|
||||
Complement = 0x1c, //inverse checksum
|
||||
Checksum = 0x1e,
|
||||
ResetVector = 0x3c,
|
||||
};
|
||||
|
||||
enum Region {
|
|
@ -1,14 +1,14 @@
|
|||
#ifdef CART_CPP
|
||||
|
||||
#include "../reader/filereader.h"
|
||||
#include "../reader/filereader.hpp"
|
||||
|
||||
#if defined(GZIP_SUPPORT)
|
||||
#include "../reader/gzreader.h"
|
||||
#include "../reader/zipreader.h"
|
||||
#include "../reader/gzreader.hpp"
|
||||
#include "../reader/zipreader.hpp"
|
||||
#endif
|
||||
|
||||
#if defined(JMA_SUPPORT)
|
||||
#include "../reader/jmareader.h"
|
||||
#include "../reader/jmareader.hpp"
|
||||
#endif
|
||||
|
||||
char* Cartridge::modify_extension(char *filename, const char *extension) {
|
||||
|
@ -53,26 +53,8 @@ char* Cartridge::get_base_filename(char *filename) {
|
|||
|
||||
char* Cartridge::get_path_filename(char *filename, const char *path, const char *source, const char *extension) {
|
||||
strcpy(filename, source);
|
||||
for(char *p = filename; *p; p++) { if(*p == '\\') *p = '/'; }
|
||||
modify_extension(filename, extension);
|
||||
|
||||
//override path with user-specified folder, if one was defined
|
||||
if(*path) {
|
||||
lstring part;
|
||||
split(part, "/", filename);
|
||||
string fn = path;
|
||||
if(strend(fn, "/") == false) strcat(fn, "/");
|
||||
strcat(fn, part[count(part) - 1]);
|
||||
strcpy(filename, fn);
|
||||
|
||||
//resolve relative path, if found
|
||||
if(strbegin(fn, "./") == true) {
|
||||
ltrim(fn, "./");
|
||||
strcpy(filename, config::path.base);
|
||||
strcat(filename, fn);
|
||||
}
|
||||
}
|
||||
|
||||
strcpy(filename, config::filepath(filename, path));
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
@ -95,16 +77,10 @@ bool Cartridge::load_file(const char *fn, uint8 *&data, uint &size, CompressionM
|
|||
if(compression == CompressionInspect) filetype = Reader::detect(fn, true);
|
||||
if(compression == CompressionAuto) filetype = Reader::detect(fn, config::file.autodetect_type);
|
||||
|
||||
switch(filetype) {
|
||||
default:
|
||||
dprintf("* Warning: filetype detected as unsupported compression type.");
|
||||
dprintf("* Will attempt to load as uncompressed file -- may fail.");
|
||||
switch(filetype) { default:
|
||||
case Reader::Normal: {
|
||||
FileReader ff(fn);
|
||||
if(!ff.ready()) {
|
||||
alert("Error loading image file (%s)!", fn);
|
||||
return false;
|
||||
}
|
||||
if(!ff.ready()) return false;
|
||||
size = ff.size();
|
||||
data = ff.read();
|
||||
} break;
|
||||
|
@ -112,20 +88,14 @@ bool Cartridge::load_file(const char *fn, uint8 *&data, uint &size, CompressionM
|
|||
#ifdef GZIP_SUPPORT
|
||||
case Reader::GZIP: {
|
||||
GZReader gf(fn);
|
||||
if(!gf.ready()) {
|
||||
alert("Error loading image file (%s)!", fn);
|
||||
return false;
|
||||
}
|
||||
if(!gf.ready()) return false;
|
||||
size = gf.size();
|
||||
data = gf.read();
|
||||
} break;
|
||||
|
||||
case Reader::ZIP: {
|
||||
ZipReader zf(fn);
|
||||
if(!zf.ready()) {
|
||||
alert("Error loading image file (%s)!", fn);
|
||||
return false;
|
||||
}
|
||||
if(!zf.ready()) return false;
|
||||
size = zf.size();
|
||||
data = zf.read();
|
||||
} break;
|
||||
|
@ -138,7 +108,6 @@ bool Cartridge::load_file(const char *fn, uint8 *&data, uint &size, CompressionM
|
|||
size = jf.size();
|
||||
data = jf.read();
|
||||
} catch(JMA::jma_errors jma_error) {
|
||||
alert("Error loading image file (%s)!", fn);
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
|
|
|
@ -32,11 +32,11 @@ void Cartridge::read_header(cartinfo_t &info, const uint8_t *data, unsigned size
|
|||
}
|
||||
|
||||
//standard cart
|
||||
uint8 mapper = data[index + MAPPER];
|
||||
uint8 rom_type = data[index + ROM_TYPE];
|
||||
uint8 rom_size = data[index + ROM_SIZE];
|
||||
uint8 company = data[index + COMPANY];
|
||||
uint8 region = data[index + REGION] & 0x7f;
|
||||
uint8 mapper = data[index + Mapper];
|
||||
uint8 rom_type = data[index + RomType];
|
||||
uint8 rom_size = data[index + RomSize];
|
||||
uint8 company = data[index + Company];
|
||||
uint8 region = data[index + CartRegion] & 0x7f;
|
||||
|
||||
//detect presence of BS-X flash cartridge connector (reads extended header information)
|
||||
if(data[index - 14] == 'Z') {
|
||||
|
@ -152,8 +152,8 @@ void Cartridge::read_header(cartinfo_t &info, const uint8_t *data, unsigned size
|
|||
info.st018 = true;
|
||||
}
|
||||
|
||||
if(data[index + RAM_SIZE] & 7) {
|
||||
info.ram_size = 1024 << (data[index + RAM_SIZE] & 7);
|
||||
if(data[index + RamSize] & 7) {
|
||||
info.ram_size = 1024 << (data[index + RamSize] & 7);
|
||||
} else {
|
||||
info.ram_size = 0;
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ unsigned Cartridge::find_header(const uint8_t *data, unsigned size) {
|
|||
unsigned score_lo = score_header(data, size, 0x007fc0);
|
||||
unsigned score_hi = score_header(data, size, 0x00ffc0);
|
||||
unsigned score_ex = score_header(data, size, 0x40ffc0);
|
||||
if(score_ex) score_ex += 4; //favor ExHiROM on images > 32mbits
|
||||
if(score_ex) score_ex += 4; //favor ExHiROM on images > 32mbits
|
||||
|
||||
if(score_lo >= score_hi && score_lo >= score_ex) {
|
||||
return 0x007fc0;
|
||||
|
@ -178,15 +178,15 @@ unsigned Cartridge::find_header(const uint8_t *data, unsigned size) {
|
|||
}
|
||||
|
||||
unsigned Cartridge::score_header(const uint8_t *data, unsigned size, unsigned addr) {
|
||||
if(size < addr + 64) return 0; //image too small to contain header at this location?
|
||||
if(size < addr + 64) return 0; //image too small to contain header at this location?
|
||||
int score = 0;
|
||||
|
||||
uint16 resetvector = data[addr + RESETV] | (data[addr + RESETV + 1] << 8);
|
||||
uint16 checksum = data[addr + CKSUM] | (data[addr + CKSUM + 1] << 8);
|
||||
uint16 ichecksum = data[addr + ICKSUM] | (data[addr + ICKSUM + 1] << 8);
|
||||
uint16 resetvector = data[addr + ResetVector] | (data[addr + ResetVector + 1] << 8);
|
||||
uint16 checksum = data[addr + Checksum ] | (data[addr + Checksum + 1] << 8);
|
||||
uint16 complement = data[addr + Complement ] | (data[addr + Complement + 1] << 8);
|
||||
|
||||
uint8 resetop = data[(addr & ~0x7fff) | (resetvector & 0x7fff)]; //first opcode executed upon reset
|
||||
uint8 mapper = data[addr + MAPPER] & ~0x10; //mask off irrelevent FastROM-capable bit
|
||||
uint8 resetop = data[(addr & ~0x7fff) | (resetvector & 0x7fff)]; //first opcode executed upon reset
|
||||
uint8 mapper = data[addr + Mapper] & ~0x10; //mask off irrelevent FastROM-capable bit
|
||||
|
||||
//$00:[000-7fff] contains uninitialized RAM and MMIO.
|
||||
//reset vector must point to ROM at $00:[8000-ffff] to be considered valid.
|
||||
|
@ -198,61 +198,61 @@ unsigned Cartridge::score_header(const uint8_t *data, unsigned size, unsigned ad
|
|||
//determine the probability that this is the correct header.
|
||||
|
||||
//most likely opcodes
|
||||
if(resetop == 0x78 //sei
|
||||
|| resetop == 0x18 //clc (clc; xce)
|
||||
|| resetop == 0x38 //sec (sec; xce)
|
||||
|| resetop == 0x9c //stz $nnnn (stz $4200)
|
||||
|| resetop == 0x4c //jmp $nnnn
|
||||
|| resetop == 0x5c //jml $nnnnnn
|
||||
if(resetop == 0x78 //sei
|
||||
|| resetop == 0x18 //clc (clc; xce)
|
||||
|| resetop == 0x38 //sec (sec; xce)
|
||||
|| resetop == 0x9c //stz $nnnn (stz $4200)
|
||||
|| resetop == 0x4c //jmp $nnnn
|
||||
|| resetop == 0x5c //jml $nnnnnn
|
||||
) score += 8;
|
||||
|
||||
//plausible opcodes
|
||||
if(resetop == 0xc2 //rep #$nn
|
||||
|| resetop == 0xe2 //sep #$nn
|
||||
|| resetop == 0xad //lda $nnnn
|
||||
|| resetop == 0xae //ldx $nnnn
|
||||
|| resetop == 0xac //ldy $nnnn
|
||||
|| resetop == 0xaf //lda $nnnnnn
|
||||
|| resetop == 0xa9 //lda #$nn
|
||||
|| resetop == 0xa2 //ldx #$nn
|
||||
|| resetop == 0xa0 //ldy #$nn
|
||||
|| resetop == 0x20 //jsr $nnnn
|
||||
|| resetop == 0x22 //jsl $nnnnnn
|
||||
if(resetop == 0xc2 //rep #$nn
|
||||
|| resetop == 0xe2 //sep #$nn
|
||||
|| resetop == 0xad //lda $nnnn
|
||||
|| resetop == 0xae //ldx $nnnn
|
||||
|| resetop == 0xac //ldy $nnnn
|
||||
|| resetop == 0xaf //lda $nnnnnn
|
||||
|| resetop == 0xa9 //lda #$nn
|
||||
|| resetop == 0xa2 //ldx #$nn
|
||||
|| resetop == 0xa0 //ldy #$nn
|
||||
|| resetop == 0x20 //jsr $nnnn
|
||||
|| resetop == 0x22 //jsl $nnnnnn
|
||||
) score += 4;
|
||||
|
||||
//implausible opcodes
|
||||
if(resetop == 0x40 //rti
|
||||
|| resetop == 0x60 //rts
|
||||
|| resetop == 0x6b //rtl
|
||||
|| resetop == 0xcd //cmp $nnnn
|
||||
|| resetop == 0xec //cpx $nnnn
|
||||
|| resetop == 0xcc //cpy $nnnn
|
||||
if(resetop == 0x40 //rti
|
||||
|| resetop == 0x60 //rts
|
||||
|| resetop == 0x6b //rtl
|
||||
|| resetop == 0xcd //cmp $nnnn
|
||||
|| resetop == 0xec //cpx $nnnn
|
||||
|| resetop == 0xcc //cpy $nnnn
|
||||
) score -= 4;
|
||||
|
||||
//least likely opcodes
|
||||
if(resetop == 0x00 //brk #$nn
|
||||
|| resetop == 0x02 //cop #$nn
|
||||
|| resetop == 0xdb //stp
|
||||
|| resetop == 0x42 //wdm
|
||||
|| resetop == 0xff //sbc $nnnnnn,x
|
||||
if(resetop == 0x00 //brk #$nn
|
||||
|| resetop == 0x02 //cop #$nn
|
||||
|| resetop == 0xdb //stp
|
||||
|| resetop == 0x42 //wdm
|
||||
|| resetop == 0xff //sbc $nnnnnn,x
|
||||
) score -= 8;
|
||||
|
||||
//at times, both the header and reset vector's first opcode will match ...
|
||||
//fallback and rely on info validity in these cases to determine more likely header.
|
||||
|
||||
//a valid checksum is the biggest indicator of a valid header.
|
||||
if((checksum + ichecksum) == 0xffff && (checksum != 0) && (ichecksum != 0)) score += 4;
|
||||
if((checksum + complement) == 0xffff && (checksum != 0) && (complement != 0)) score += 4;
|
||||
|
||||
if(addr == 0x007fc0 && mapper == 0x20) score += 2; //0x20 is usually LoROM
|
||||
if(addr == 0x00ffc0 && mapper == 0x21) score += 2; //0x21 is usually HiROM
|
||||
if(addr == 0x007fc0 && mapper == 0x22) score += 2; //0x22 is usually ExLoROM
|
||||
if(addr == 0x40ffc0 && mapper == 0x25) score += 2; //0x25 is usually ExHiROM
|
||||
if(addr == 0x007fc0 && mapper == 0x20) score += 2; //0x20 is usually LoROM
|
||||
if(addr == 0x00ffc0 && mapper == 0x21) score += 2; //0x21 is usually HiROM
|
||||
if(addr == 0x007fc0 && mapper == 0x22) score += 2; //0x22 is usually ExLoROM
|
||||
if(addr == 0x40ffc0 && mapper == 0x25) score += 2; //0x25 is usually ExHiROM
|
||||
|
||||
if(data[addr + COMPANY] == 0x33) score += 2; //0x33 indicates extended header
|
||||
if(data[addr + ROM_TYPE] < 0x08) score++;
|
||||
if(data[addr + ROM_SIZE] < 0x10) score++;
|
||||
if(data[addr + RAM_SIZE] < 0x08) score++;
|
||||
if(data[addr + REGION] < 14) score++;
|
||||
if(data[addr + Company] == 0x33) score += 2; //0x33 indicates extended header
|
||||
if(data[addr + RomType] < 0x08) score++;
|
||||
if(data[addr + RomSize] < 0x10) score++;
|
||||
if(data[addr + RamSize] < 0x08) score++;
|
||||
if(data[addr + CartRegion] < 14) score++;
|
||||
|
||||
if(score < 0) score = 0;
|
||||
return score;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "../base.h"
|
||||
#include <../base.hpp>
|
||||
|
||||
Cheat cheat;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define BSX_CPP
|
||||
|
||||
#include "bsx_base.cpp"
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
#include "bsx/bsx.h"
|
||||
#include "srtc/srtc.h"
|
||||
#include "sdd1/sdd1.h"
|
||||
#include "spc7110/spc7110.h"
|
||||
#include "cx4/cx4.h"
|
||||
#include "dsp1/dsp1.h"
|
||||
#include "dsp2/dsp2.h"
|
||||
#include "dsp3/dsp3.h"
|
||||
#include "dsp4/dsp4.h"
|
||||
#include "obc1/obc1.h"
|
||||
#include "st010/st010.h"
|
|
@ -0,0 +1,11 @@
|
|||
#include "bsx/bsx.hpp"
|
||||
#include "srtc/srtc.hpp"
|
||||
#include "sdd1/sdd1.hpp"
|
||||
#include "spc7110/spc7110.hpp"
|
||||
#include "cx4/cx4.hpp"
|
||||
#include "dsp1/dsp1.hpp"
|
||||
#include "dsp2/dsp2.hpp"
|
||||
#include "dsp3/dsp3.hpp"
|
||||
#include "dsp4/dsp4.hpp"
|
||||
#include "obc1/obc1.hpp"
|
||||
#include "st010/st010.hpp"
|
|
@ -5,7 +5,7 @@
|
|||
Portions (c) anomie, Overload, zsKnight, Nach, byuu
|
||||
*/
|
||||
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define CX4_CPP
|
||||
|
||||
#include "cx4data.cpp"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define DSP1_CPP
|
||||
|
||||
#include "dsp1emu.cpp"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "dsp1emu.h"
|
||||
#include "dsp1emu.hpp"
|
||||
|
||||
class DSP1 : public Memory {
|
||||
private:
|
|
@ -1,4 +1,4 @@
|
|||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define DSP2_CPP
|
||||
|
||||
#include "dsp2_op.cpp"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define DSP3_CPP
|
||||
|
||||
namespace DSP3i {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define DSP4_CPP
|
||||
|
||||
namespace DSP4i {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
|
||||
void OBC1::init() {}
|
||||
void OBC1::enable() {}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define SDD1_CPP
|
||||
|
||||
#include "sdd1emu.cpp"
|
||||
|
@ -6,7 +6,17 @@
|
|||
void SDD1::init() {}
|
||||
|
||||
void SDD1::enable() {
|
||||
for(int i = 0x4800; i <= 0x4807; i++) memory::mmio.map(i, *this);
|
||||
//hook S-CPU DMA MMIO registers to gather information for struct dma[];
|
||||
//buffer address and transfer size information for use in SDD1::read()
|
||||
for(unsigned i = 0x4300; i <= 0x437f; i++) {
|
||||
cpu_mmio[i & 0x7f] = memory::mmio.get(i);
|
||||
memory::mmio.map(i, *this);
|
||||
}
|
||||
|
||||
//hook S-DD1 MMIO registers
|
||||
for(unsigned i = 0x4800; i <= 0x4807; i++) {
|
||||
memory::mmio.map(i, *this);
|
||||
}
|
||||
}
|
||||
|
||||
void SDD1::power() {
|
||||
|
@ -14,97 +24,133 @@ void SDD1::power() {
|
|||
}
|
||||
|
||||
void SDD1::reset() {
|
||||
sdd1.dma_active = false;
|
||||
sdd1_enable = 0x00;
|
||||
xfer_enable = 0x00;
|
||||
|
||||
regs.r4800 = 0x00;
|
||||
regs.r4801 = 0x00;
|
||||
mmc[0] = 0 << 20;
|
||||
mmc[1] = 1 << 20;
|
||||
mmc[2] = 2 << 20;
|
||||
mmc[3] = 3 << 20;
|
||||
|
||||
regs.r4804 = 0x00;
|
||||
regs.r4805 = 0x01;
|
||||
regs.r4806 = 0x02;
|
||||
regs.r4807 = 0x03;
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
dma[i].addr = 0;
|
||||
dma[i].size = 0;
|
||||
}
|
||||
|
||||
bus.map(Bus::MapLinear, 0xc0, 0xcf, 0x0000, 0xffff, memory::cartrom, (regs.r4804 & 7) << 20);
|
||||
bus.map(Bus::MapLinear, 0xd0, 0xdf, 0x0000, 0xffff, memory::cartrom, (regs.r4805 & 7) << 20);
|
||||
bus.map(Bus::MapLinear, 0xe0, 0xef, 0x0000, 0xffff, memory::cartrom, (regs.r4806 & 7) << 20);
|
||||
bus.map(Bus::MapLinear, 0xf0, 0xff, 0x0000, 0xffff, memory::cartrom, (regs.r4807 & 7) << 20);
|
||||
buffer.ready = false;
|
||||
|
||||
bus.map(Bus::MapDirect, 0xc0, 0xff, 0x0000, 0xffff, *this);
|
||||
}
|
||||
|
||||
uint8 SDD1::mmio_read(uint addr) {
|
||||
switch(addr & 0xffff) {
|
||||
case 0x4804: return regs.r4804;
|
||||
case 0x4805: return regs.r4805;
|
||||
case 0x4806: return regs.r4806;
|
||||
case 0x4807: return regs.r4807;
|
||||
addr &= 0xffff;
|
||||
|
||||
if((addr & 0x4380) == 0x4300) {
|
||||
return cpu_mmio[addr & 0x7f]->mmio_read(addr);
|
||||
}
|
||||
|
||||
switch(addr) {
|
||||
case 0x4804: return (mmc[0] >> 20) & 7;
|
||||
case 0x4805: return (mmc[1] >> 20) & 7;
|
||||
case 0x4806: return (mmc[2] >> 20) & 7;
|
||||
case 0x4807: return (mmc[3] >> 20) & 7;
|
||||
}
|
||||
|
||||
return cpu.regs.mdr;
|
||||
}
|
||||
|
||||
void SDD1::mmio_write(uint addr, uint8 data) {
|
||||
switch(addr & 0xffff) {
|
||||
case 0x4800: {
|
||||
regs.r4800 = data;
|
||||
} break;
|
||||
addr &= 0xffff;
|
||||
|
||||
case 0x4801: {
|
||||
regs.r4801 = data;
|
||||
} break;
|
||||
if((addr & 0x4380) == 0x4300) {
|
||||
unsigned channel = (addr >> 4) & 7;
|
||||
switch(addr & 15) {
|
||||
case 2: dma[channel].addr = (dma[channel].addr & 0xffff00) + (data << 0); break;
|
||||
case 3: dma[channel].addr = (dma[channel].addr & 0xff00ff) + (data << 8); break;
|
||||
case 4: dma[channel].addr = (dma[channel].addr & 0x00ffff) + (data << 16); break;
|
||||
|
||||
case 0x4804: {
|
||||
if(regs.r4804 != data) {
|
||||
regs.r4804 = data;
|
||||
bus.map(Bus::MapLinear, 0xc0, 0xcf, 0x0000, 0xffff,
|
||||
memory::cartrom, (regs.r4804 & 7) << 20);
|
||||
}
|
||||
} break;
|
||||
case 5: dma[channel].size = (dma[channel].size & 0xff00) + (data << 0); break;
|
||||
case 6: dma[channel].size = (dma[channel].size & 0x00ff) + (data << 8); break;
|
||||
}
|
||||
return cpu_mmio[addr & 0x7f]->mmio_write(addr, data);
|
||||
}
|
||||
|
||||
case 0x4805: {
|
||||
if(regs.r4805 != data) {
|
||||
regs.r4805 = data;
|
||||
bus.map(Bus::MapLinear, 0xd0, 0xdf, 0x0000, 0xffff,
|
||||
memory::cartrom, (regs.r4805 & 7) << 20);
|
||||
}
|
||||
} break;
|
||||
switch(addr) {
|
||||
case 0x4800: sdd1_enable = data; break;
|
||||
case 0x4801: xfer_enable = data; break;
|
||||
|
||||
case 0x4806: {
|
||||
if(regs.r4806 != data) {
|
||||
regs.r4806 = data;
|
||||
bus.map(Bus::MapLinear, 0xe0, 0xef, 0x0000, 0xffff,
|
||||
memory::cartrom, (regs.r4806 & 7) << 20);
|
||||
}
|
||||
} break;
|
||||
|
||||
case 0x4807: {
|
||||
if(regs.r4807 != data) {
|
||||
regs.r4807 = data;
|
||||
bus.map(Bus::MapLinear, 0xf0, 0xff, 0x0000, 0xffff,
|
||||
memory::cartrom, (regs.r4807 & 7) << 20);
|
||||
}
|
||||
} break;
|
||||
case 0x4804: mmc[0] = (data & 7) << 20; break;
|
||||
case 0x4805: mmc[1] = (data & 7) << 20; break;
|
||||
case 0x4806: mmc[2] = (data & 7) << 20; break;
|
||||
case 0x4807: mmc[3] = (data & 7) << 20; break;
|
||||
}
|
||||
}
|
||||
|
||||
void SDD1::dma_begin(uint8 channel, uint32 addr, uint16 length) {
|
||||
if(regs.r4800 & (1 << channel) && regs.r4801 & (1 << channel)) {
|
||||
regs.r4801 &= ~(1 << channel);
|
||||
sdd1.dma_active = true;
|
||||
sdd1.buffer_index = 0;
|
||||
sdd1.buffer_size = length;
|
||||
sdd1emu.decompress(addr, (length) ? length : 65536, sdd1.buffer);
|
||||
}
|
||||
//SDD1::read() is mapped to $[c0-ff]:[0000-ffff]
|
||||
//the design is meant to be as close to the hardware design as possible, thus this code
|
||||
//avoids adding S-DD1 hooks inside S-CPU::DMA emulation.
|
||||
//
|
||||
//the real S-DD1 cannot see $420b (DMA enable) writes, as they are not placed on the bus.
|
||||
//however, $43x0-$43xf writes (DMAx channel settings) most likely do appear on the bus.
|
||||
//the S-DD1 also requires fixed addresses for transfers, which wouldn't be necessary if
|
||||
//it could see $420b writes (eg it would know when the transfer should begin.)
|
||||
//
|
||||
//the hardware needs a way to distinguish program code after $4801 writes from DMA
|
||||
//decompression that follows soon after.
|
||||
//
|
||||
//the only plausible design for hardware would be for the S-DD1 to spy on DMAx settings,
|
||||
//and begin spooling decompression on writes to $4801 that activate a channel. after that,
|
||||
//it feeds decompressed data only when the ROM read address matches the DMA channel address.
|
||||
//
|
||||
//the actual S-DD1 transfer can occur on any channel, but it is most likely limited to
|
||||
//one transfer per $420b write (for spooling purposes). however, this is not known for certain.
|
||||
uint8 SDD1::read(unsigned addr) {
|
||||
if(sdd1_enable & xfer_enable) {
|
||||
//at least one channel has S-DD1 decompression enabled ...
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(sdd1_enable & xfer_enable & (1 << i)) {
|
||||
//S-DD1 always uses fixed transfer mode, so address will not change during transfer
|
||||
if(addr == dma[i].addr) {
|
||||
if(!buffer.ready) {
|
||||
//first byte read for channel performs full decompression.
|
||||
//this really should stream byte-by-byte, but it's not necessary since the size is known
|
||||
buffer.offset = 0;
|
||||
buffer.size = dma[i].size ? dma[i].size : 65536;
|
||||
|
||||
//sdd1emu calls this function; it needs to access uncompressed data;
|
||||
//so temporarily disable decompression mode for decompress() call.
|
||||
uint8 temp = sdd1_enable;
|
||||
sdd1_enable = false;
|
||||
sdd1emu.decompress(addr, buffer.size, buffer.data);
|
||||
sdd1_enable = temp;
|
||||
|
||||
buffer.ready = true;
|
||||
}
|
||||
|
||||
//fetch a decompressed byte; once buffer is depleted, disable channel and invalidate buffer
|
||||
uint8 data = buffer.data[(uint16)buffer.offset++];
|
||||
if(buffer.offset >= buffer.size) {
|
||||
buffer.ready = false;
|
||||
xfer_enable &= ~(1 << i);
|
||||
}
|
||||
|
||||
return data;
|
||||
} //address matched
|
||||
} //channel enabled
|
||||
} //channel loop
|
||||
} //S-DD1 decompressor enabled
|
||||
|
||||
//S-DD1 decompression mode inactive; return ROM data
|
||||
return memory::cartrom.read(mmc[(addr >> 20) & 3] + (addr & 0x0fffff));
|
||||
}
|
||||
|
||||
bool SDD1::dma_active() {
|
||||
return sdd1.dma_active;
|
||||
void SDD1::write(unsigned addr, uint8 data) {
|
||||
}
|
||||
|
||||
uint8 SDD1::dma_read() {
|
||||
if(--sdd1.buffer_size == 0) sdd1.dma_active = false;
|
||||
|
||||
//sdd1.buffer[] is 65536 bytes, and sdd1.buffer_index
|
||||
//is of type uint16, so no buffer overflow is possible
|
||||
return sdd1.buffer[sdd1.buffer_index++];
|
||||
SDD1::SDD1() {
|
||||
buffer.data = new uint8[65536];
|
||||
}
|
||||
|
||||
SDD1::SDD1() {}
|
||||
SDD1::~SDD1() {
|
||||
delete[] buffer.data;
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
#include "sdd1emu.h"
|
||||
|
||||
class SDD1 : public MMIO {
|
||||
public:
|
||||
void init();
|
||||
void enable();
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
uint8 mmio_read (uint addr);
|
||||
void mmio_write(uint addr, uint8 data);
|
||||
|
||||
void dma_begin(uint8 channel, uint32 addr, uint16 length);
|
||||
bool dma_active();
|
||||
uint8 dma_read();
|
||||
|
||||
SDD1();
|
||||
|
||||
private:
|
||||
SDD1emu sdd1emu;
|
||||
|
||||
struct {
|
||||
uint8 buffer[65536]; //pointer to decompressed S-DD1 data, max DMA length is 65536
|
||||
uint16 buffer_index; //DMA read index into S-DD1 decompression buffer
|
||||
uint16 buffer_size;
|
||||
bool dma_active;
|
||||
} sdd1;
|
||||
|
||||
struct {
|
||||
uint8 r4800;
|
||||
uint8 r4801;
|
||||
uint8 r4804;
|
||||
uint8 r4805;
|
||||
uint8 r4806;
|
||||
uint8 r4807;
|
||||
} regs;
|
||||
};
|
||||
|
||||
extern SDD1 sdd1;
|
|
@ -0,0 +1,40 @@
|
|||
#include "sdd1emu.hpp"
|
||||
|
||||
class SDD1 : public MMIO, public Memory {
|
||||
public:
|
||||
void init();
|
||||
void enable();
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
uint8 mmio_read(unsigned addr);
|
||||
void mmio_write(unsigned addr, uint8 data);
|
||||
|
||||
uint8 read(unsigned addr);
|
||||
void write(unsigned addr, uint8 data);
|
||||
|
||||
SDD1();
|
||||
~SDD1();
|
||||
|
||||
private:
|
||||
MMIO *cpu_mmio[0x80]; //bus spying hooks to glean information for struct dma[]
|
||||
|
||||
uint8 sdd1_enable; //channel bit-mask
|
||||
uint8 xfer_enable; //channel bit-mask
|
||||
unsigned mmc[4]; //memory map controller ROM indices
|
||||
|
||||
struct {
|
||||
unsigned addr; //$43x2-$43x4 -- DMA transfer address
|
||||
uint16 size; //$43x5-$43x6 -- DMA transfer size
|
||||
} dma[8];
|
||||
|
||||
SDD1emu sdd1emu;
|
||||
struct {
|
||||
uint8 *data; //pointer to decompressed S-DD1 data (65536 bytes)
|
||||
uint16 offset; //read index into S-DD1 decompression buffer
|
||||
unsigned size; //length of data buffer; reads decrement counter, set ready to false at 0
|
||||
bool ready; //true when data[] is valid; false to invoke sdd1emu.decompress()
|
||||
} buffer;
|
||||
};
|
||||
|
||||
extern SDD1 sdd1;
|
|
@ -30,7 +30,7 @@ understood.
|
|||
|
||||
************************************************************************/
|
||||
|
||||
#define SDD1_read(__addr) (bus.read(__addr))
|
||||
#define SDD1_read(__addr) (sdd1.read(__addr))
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define SPC7110_CPP
|
||||
|
||||
#include "decomp.cpp"
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
* or in connection with the use or performance of this software.
|
||||
*****/
|
||||
|
||||
#include "decomp.h"
|
||||
#include "decomp.hpp"
|
||||
|
||||
class SPC7110 : public MMIO, public Memory {
|
||||
public:
|
|
@ -1,4 +1,4 @@
|
|||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
|
||||
const unsigned SRTC::months[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define ST010_CPP
|
||||
|
||||
#include "st010_data.h"
|
||||
#include "st010_data.hpp"
|
||||
#include "st010_op.cpp"
|
||||
|
||||
int16 ST010::sin(int16 theta) {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#include <../base.hpp>
|
||||
|
||||
namespace config {
|
||||
|
||||
configuration& config() {
|
||||
|
@ -5,56 +7,59 @@ configuration& config() {
|
|||
return config;
|
||||
}
|
||||
|
||||
string filepath(const char *filename, const char *pathname) {
|
||||
//if no pathname, return filename as-is
|
||||
string file(filename);
|
||||
replace(file, "\\", "/");
|
||||
if(!pathname || !*pathname) return file;
|
||||
|
||||
//ensure path ends with trailing '/'
|
||||
string path(pathname);
|
||||
replace(path, "\\", "/");
|
||||
if(!strend(path, "/")) strcat(path, "/");
|
||||
|
||||
//replace relative path with absolute path
|
||||
if(strbegin(path, "./")) {
|
||||
ltrim(path, "./");
|
||||
path = string() << config::path.base << path;
|
||||
}
|
||||
|
||||
//remove folder part of filename
|
||||
lstring part;
|
||||
split(part, "/", file);
|
||||
return path << part[count(part) - 1];
|
||||
}
|
||||
|
||||
integral_setting File::autodetect_type(config(), "file.autodetect_type",
|
||||
"Auto-detect file type by inspecting file header, rather than by file extension.\n"
|
||||
"In other words, if a .zip file is renamed to .smc, it will still be correctly\n"
|
||||
"identified as a .zip file. However, there is an infinitesimal (1:~500,000,000)\n"
|
||||
"chance of a false detection when loading an uncompressed image file, if this\n"
|
||||
"In other words, if a .zip file is renamed to .smc, it will still be correctly "
|
||||
"identified as a .zip file. However, there is an infinitesimal (1:~500,000,000) "
|
||||
"chance of a false detection when loading an uncompressed image file, if this "
|
||||
"option is enabled.",
|
||||
integral_setting::boolean, false);
|
||||
|
||||
integral_setting File::bypass_patch_crc32(config(), "file.bypass_patch_crc32",
|
||||
"UPS patches contain CRC32s to validate that a patch was applied successfully.\n"
|
||||
"By default, if this validation fails, said patch will not be applied.\n"
|
||||
"Setting this option to true will bypass the validation,\n"
|
||||
"which may or may not result in a working image.\n"
|
||||
"Enabling this option is strongly discouraged.",
|
||||
"By default, if this validation fails, said patch will not be applied. "
|
||||
"Setting this option to true will bypass the validation, "
|
||||
"which may or may not result in a working image. "
|
||||
"Thus, enabling this option is strongly discouraged.",
|
||||
integral_setting::boolean, false);
|
||||
|
||||
string file_updatepath(const char *req_file, const char *req_path) {
|
||||
string file(req_file);
|
||||
replace(file, "\\", "/");
|
||||
if(!req_path || strlen(req_path) == 0) { return file; }
|
||||
|
||||
string path(req_path);
|
||||
replace(path, "\\", "/");
|
||||
if(!strend(path, "/")) { strcat(path, "/"); }
|
||||
|
||||
if(strbegin(path, "./")) {
|
||||
ltrim(path(), "./");
|
||||
string temp;
|
||||
strcpy(temp, config::path.base);
|
||||
strcat(temp, path);
|
||||
strcpy(path, temp);
|
||||
}
|
||||
|
||||
lstring part;
|
||||
split(part, "/", file);
|
||||
strcat(path, part[count(part) - 1]);
|
||||
return path;
|
||||
}
|
||||
|
||||
string_setting Path::base("path.base", "Path that bsnes resides in", "");
|
||||
string_setting Path::user("path.user", "Path to user folder", "");
|
||||
|
||||
string_setting Path::rom(config(), "path.rom",
|
||||
"Default path to look for ROM files in (\"\" = use default directory)", "");
|
||||
string_setting Path::patch(config(), "path.patch",
|
||||
"Default path for all UPS patch files (\"\" = use current directory)", "");
|
||||
string_setting Path::save(config(), "path.save",
|
||||
"Default path for all save RAM files (\"\" = use current directory)", "");
|
||||
string_setting Path::patch(config(), "path.patch",
|
||||
"Default path for all UPS patch files (\"\" = use current directory)", "");
|
||||
string_setting Path::cheat(config(), "path.cheat",
|
||||
"Default path for all cheat files (\"\" = use current directory)", "");
|
||||
string_setting Path::exportdata(config(), "path.export",
|
||||
"Default path for all exported data files\n", "");
|
||||
|
||||
string_setting Path::bsx(config(), "path.bsx", "", "");
|
||||
string_setting Path::st(config(), "path.st", "", "");
|
||||
|
||||
|
@ -65,14 +70,14 @@ integral_setting SNES::controller_port2(config(), "snes.controller_port2",
|
|||
integral_setting SNES::expansion_port(config(), "snes.expansion_port",
|
||||
"Device attached to SNES expansion port\n"
|
||||
"0 = None\n"
|
||||
"1 = Satellaview BS-X\n"
|
||||
"", integral_setting::decimal, ::SNES::ExpansionBSX);
|
||||
"1 = Satellaview BS-X",
|
||||
integral_setting::decimal, ::SNES::ExpansionBSX);
|
||||
integral_setting SNES::region(config(), "snes.region",
|
||||
"SNES regional model\n"
|
||||
"0 = Auto-detect based on cartridge\n"
|
||||
"1 = NTSC\n"
|
||||
"2 = PAL\n"
|
||||
"", integral_setting::decimal, ::SNES::Autodetect);
|
||||
"2 = PAL",
|
||||
integral_setting::decimal, ::SNES::Autodetect);
|
||||
|
||||
integral_setting CPU::ntsc_clock_rate(config(), "cpu.ntsc_clock_rate",
|
||||
"NTSC S-CPU clock rate (in hz)", integral_setting::decimal, 21477272);
|
||||
|
@ -80,16 +85,16 @@ integral_setting CPU::pal_clock_rate(config(), "cpu.pal_clock_rate",
|
|||
"PAL S-CPU clock rate (in hz)", integral_setting::decimal, 21281370);
|
||||
integral_setting CPU::wram_init_value(config(), "cpu.wram_init_value",
|
||||
"Value to initialize 128k WRAM to upon power cycle.\n"
|
||||
"Note that on real hardware, this value is undefined; meaning it can vary\n"
|
||||
"per power-on, and per SNES unit. Such randomness is undesirable for an\n"
|
||||
"emulator, so a static value is needed. There is also some form of pattern\n"
|
||||
"Note that on real hardware, this value is undefined; meaning it can vary "
|
||||
"per power-on, and per SNES unit. Such randomness is undesirable for an "
|
||||
"emulator, so a static value is needed. There is also some form of pattern "
|
||||
"to the randomness that has yet to be determined, which some games rely upon.\n"
|
||||
"A value of 0x55 is safe for all known commercial software, and should be used.\n"
|
||||
"However, some software written for SNES copiers, or backup units, relies on\n"
|
||||
"WRAM being initialized to 0x00; which was a side-effect of the BIOS program\n"
|
||||
"which executed on these copiers. Using 0x00 will therefore fix many homebrew\n"
|
||||
"programs, but *will* break some poorly programmed commercial software titles,\n"
|
||||
"which do not properly initialize WRAM upon power cycle.\n",
|
||||
"A value of 0x55 is safe for all known commercial software, and should be used. "
|
||||
"However, some software written for SNES copiers, or backup units, relies on "
|
||||
"WRAM being initialized to 0x00; which was a side-effect of the BIOS program "
|
||||
"which executed on these copiers. Using 0x00 will therefore fix many homebrew "
|
||||
"programs, but *will* break some poorly programmed commercial software titles, "
|
||||
"which do not properly initialize WRAM upon power cycle.",
|
||||
integral_setting::hex, 0x55);
|
||||
|
||||
integral_setting SMP::ntsc_clock_rate(config(), "smp.ntsc_clock_rate",
|
||||
|
@ -97,42 +102,4 @@ integral_setting SMP::ntsc_clock_rate(config(), "smp.ntsc_clock_rate",
|
|||
integral_setting SMP::pal_clock_rate(config(), "smp.pal_clock_rate",
|
||||
"PAL S-SMP clock rate (in hz)", integral_setting::decimal, 32041 * 768);
|
||||
|
||||
integral_setting PPU::Hack::render_scanline_position(config(), "ppu.hack.render_scanline_position",
|
||||
"Approximate HCLOCK position to render at for scanline-based renderers",
|
||||
integral_setting::decimal, 512);
|
||||
integral_setting PPU::Hack::obj_cache(config(), "ppu.hack.obj_cache",
|
||||
"Cache OAM OBJ attributes one scanline before rendering\n"
|
||||
"This is technically closer to the actual operation of the SNES,\n"
|
||||
"but can cause problems in some games if enabled",
|
||||
integral_setting::boolean, false);
|
||||
integral_setting PPU::Hack::oam_address_invalidation(config(), "ppu.hack.oam_address_invalidation",
|
||||
"OAM access address changes during active display, as the S-PPU reads\n"
|
||||
"data to render the display. Thusly, the address retrieved when accessing\n"
|
||||
"OAM during active display is unpredictable. Unfortunately, the exact\n"
|
||||
"algorithm for this is completely unknown at this time. It is more hardware\n"
|
||||
"accurate to enable this setting, but one must *not* rely on the actual\n"
|
||||
"address to match hardware under emulation.",
|
||||
integral_setting::boolean, true);
|
||||
integral_setting PPU::Hack::cgram_address_invalidation(config(), "ppu.hack.cgram_address_invalidation",
|
||||
"CGRAM access address changes during active display (excluding hblank), as\n"
|
||||
"the S-PPU reads data to render the display. Thusly, as with OAM, the access\n"
|
||||
"address is unpredictable. Again, enabling this setting is more hardware\n"
|
||||
"accurate, but one must *not* rely on the actual address to match hardware\n"
|
||||
"under emulation.",
|
||||
integral_setting::boolean, true);
|
||||
|
||||
integral_setting PPU::opt_enable("ppu.opt_enable", "Enable offset-per-tile effects", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg1_pri0_enable("ppu.bg1_pri0_enable", "Enable BG1 Priority 0", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg1_pri1_enable("ppu.bg1_pri1_enable", "Enable BG1 Priority 1", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg2_pri0_enable("ppu.bg2_pri0_enable", "Enable BG2 Priority 0", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg2_pri1_enable("ppu.bg2_pri1_enable", "Enable BG2 Priority 1", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg3_pri0_enable("ppu.bg3_pri0_enable", "Enable BG3 Priority 0", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg3_pri1_enable("ppu.bg3_pri1_enable", "Enable BG3 Priority 1", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg4_pri0_enable("ppu.bg4_pri0_enable", "Enable BG4 Priority 0", integral_setting::boolean, true);
|
||||
integral_setting PPU::bg4_pri1_enable("ppu.bg4_pri1_enable", "Enable BG4 Priority 1", integral_setting::boolean, true);
|
||||
integral_setting PPU::oam_pri0_enable("ppu.oam_pri0_enable", "Enable OAM Priority 0", integral_setting::boolean, true);
|
||||
integral_setting PPU::oam_pri1_enable("ppu.oam_pri1_enable", "Enable OAM Priority 1", integral_setting::boolean, true);
|
||||
integral_setting PPU::oam_pri2_enable("ppu.oam_pri2_enable", "Enable OAM Priority 2", integral_setting::boolean, true);
|
||||
integral_setting PPU::oam_pri3_enable("ppu.oam_pri3_enable", "Enable OAM Priority 3", integral_setting::boolean, true);
|
||||
|
||||
} //namespace config
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
namespace config {
|
||||
|
||||
extern configuration& config();
|
||||
|
||||
string file_updatepath(const char*, const char*);
|
||||
|
||||
extern struct File {
|
||||
static integral_setting autodetect_type;
|
||||
static integral_setting bypass_patch_crc32;
|
||||
} file;
|
||||
|
||||
extern struct Path {
|
||||
static string_setting base, user, rom, patch, save, cheat;
|
||||
static string_setting bsx, st;
|
||||
} path;
|
||||
|
||||
extern struct SNES {
|
||||
static integral_setting controller_port1;
|
||||
static integral_setting controller_port2;
|
||||
static integral_setting expansion_port;
|
||||
static integral_setting region;
|
||||
} snes;
|
||||
|
||||
extern struct CPU {
|
||||
static integral_setting ntsc_clock_rate, pal_clock_rate;
|
||||
static integral_setting wram_init_value;
|
||||
} cpu;
|
||||
|
||||
extern struct SMP {
|
||||
static integral_setting ntsc_clock_rate, pal_clock_rate;
|
||||
} smp;
|
||||
|
||||
extern struct PPU {
|
||||
struct Hack {
|
||||
static integral_setting render_scanline_position;
|
||||
static integral_setting obj_cache;
|
||||
static integral_setting oam_address_invalidation;
|
||||
static integral_setting cgram_address_invalidation;
|
||||
} hack;
|
||||
|
||||
static integral_setting opt_enable;
|
||||
static integral_setting bg1_pri0_enable, bg1_pri1_enable;
|
||||
static integral_setting bg2_pri0_enable, bg2_pri1_enable;
|
||||
static integral_setting bg3_pri0_enable, bg3_pri1_enable;
|
||||
static integral_setting bg4_pri0_enable, bg4_pri1_enable;
|
||||
static integral_setting oam_pri0_enable, oam_pri1_enable;
|
||||
static integral_setting oam_pri2_enable, oam_pri3_enable;
|
||||
} ppu;
|
||||
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
namespace config {
|
||||
|
||||
extern configuration& config();
|
||||
|
||||
string filepath(const char *filename, const char *pathname);
|
||||
|
||||
extern struct File {
|
||||
static integral_setting autodetect_type;
|
||||
static integral_setting bypass_patch_crc32;
|
||||
} file;
|
||||
|
||||
extern struct Path {
|
||||
static string_setting base, user;
|
||||
static string_setting rom, save, patch, cheat, exportdata;
|
||||
static string_setting bsx, st;
|
||||
} path;
|
||||
|
||||
extern struct SNES {
|
||||
static integral_setting controller_port1;
|
||||
static integral_setting controller_port2;
|
||||
static integral_setting expansion_port;
|
||||
static integral_setting region;
|
||||
} snes;
|
||||
|
||||
extern struct CPU {
|
||||
static integral_setting ntsc_clock_rate, pal_clock_rate;
|
||||
static integral_setting wram_init_value;
|
||||
} cpu;
|
||||
|
||||
extern struct SMP {
|
||||
static integral_setting ntsc_clock_rate, pal_clock_rate;
|
||||
} smp;
|
||||
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
#include "../base.h"
|
||||
#include <../base.hpp>
|
||||
#define CPU_CPP
|
||||
|
||||
#include "dcpu.cpp"
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#include "cpuregs.h"
|
||||
|
||||
class CPU : public MMIO {
|
||||
public:
|
||||
virtual void enter() = 0;
|
||||
|
@ -7,26 +5,16 @@ public:
|
|||
//CPU version number
|
||||
//* 1 and 2 are known
|
||||
//* reported by $4210
|
||||
//* affects DRAM refresh behavior
|
||||
//* affects timing (DRAM refresh, HDMA init, etc)
|
||||
uint8 cpu_version;
|
||||
|
||||
//timing
|
||||
virtual uint16 vcounter() = 0;
|
||||
virtual uint16 hcounter() = 0;
|
||||
virtual uint16 hdot() = 0;
|
||||
|
||||
virtual uint8 pio() = 0;
|
||||
virtual bool joylatch() = 0;
|
||||
virtual uint8 port_read(uint8 port) = 0;
|
||||
virtual void port_write(uint8 port, uint8 value) = 0;
|
||||
|
||||
CPURegs regs;
|
||||
enum {
|
||||
FLAG_N = 0x80, FLAG_V = 0x40,
|
||||
FLAG_M = 0x20, FLAG_X = 0x10,
|
||||
FLAG_D = 0x08, FLAG_I = 0x04,
|
||||
FLAG_Z = 0x02, FLAG_C = 0x01
|
||||
};
|
||||
|
||||
#include "cpuregs.hpp"
|
||||
regs_t regs;
|
||||
|
||||
virtual void scanline() = 0;
|
||||
virtual void frame() = 0;
|
|
@ -1,88 +0,0 @@
|
|||
template<int mask>
|
||||
struct CPUFlag {
|
||||
uint8 &data;
|
||||
|
||||
inline operator bool() const { return data & mask; }
|
||||
inline CPUFlag& operator=(bool i) { data = (data & ~mask) | (-i & mask); return *this; }
|
||||
|
||||
CPUFlag(uint8 &data_) : data(data_) {}
|
||||
};
|
||||
|
||||
class CPURegFlags {
|
||||
public:
|
||||
uint8 data;
|
||||
CPUFlag<0x80> n;
|
||||
CPUFlag<0x40> v;
|
||||
CPUFlag<0x20> m;
|
||||
CPUFlag<0x10> x;
|
||||
CPUFlag<0x08> d;
|
||||
CPUFlag<0x04> i;
|
||||
CPUFlag<0x02> z;
|
||||
CPUFlag<0x01> c;
|
||||
|
||||
inline operator unsigned() const { return data; }
|
||||
inline unsigned operator = (unsigned i) { data = i; return data; }
|
||||
inline unsigned operator |= (unsigned i) { data |= i; return data; }
|
||||
inline unsigned operator ^= (unsigned i) { data ^= i; return data; }
|
||||
inline unsigned operator &= (unsigned i) { data &= i; return data; }
|
||||
|
||||
CPURegFlags() : data(0), n(data), v(data), m(data), x(data), d(data), i(data), z(data), c(data) {}
|
||||
};
|
||||
|
||||
class CPUReg16 {
|
||||
public:
|
||||
union {
|
||||
uint16 w;
|
||||
struct { uint8 order_lsb2(l, h); };
|
||||
};
|
||||
|
||||
inline operator unsigned() const { return w; }
|
||||
inline unsigned operator = (unsigned i) { w = i; return w; }
|
||||
inline unsigned operator |= (unsigned i) { w |= i; return w; }
|
||||
inline unsigned operator ^= (unsigned i) { w ^= i; return w; }
|
||||
inline unsigned operator &= (unsigned i) { w &= i; return w; }
|
||||
inline unsigned operator <<= (unsigned i) { w <<= i; return w; }
|
||||
inline unsigned operator >>= (unsigned i) { w >>= i; return w; }
|
||||
inline unsigned operator += (unsigned i) { w += i; return w; }
|
||||
inline unsigned operator -= (unsigned i) { w -= i; return w; }
|
||||
inline unsigned operator *= (unsigned i) { w *= i; return w; }
|
||||
inline unsigned operator /= (unsigned i) { w /= i; return w; }
|
||||
inline unsigned operator %= (unsigned i) { w %= i; return w; }
|
||||
|
||||
CPUReg16() : w(0) {}
|
||||
};
|
||||
|
||||
class CPUReg24 {
|
||||
public:
|
||||
union {
|
||||
uint32 d;
|
||||
struct { uint16 order_lsb2(w, wh); };
|
||||
struct { uint8 order_lsb4(l, h, b, bh); };
|
||||
};
|
||||
|
||||
inline operator unsigned() const { return d; }
|
||||
inline unsigned operator = (unsigned i) { d = uclip<24>(i); return d; }
|
||||
inline unsigned operator |= (unsigned i) { d = uclip<24>(d | i); return d; }
|
||||
inline unsigned operator ^= (unsigned i) { d = uclip<24>(d ^ i); return d; }
|
||||
inline unsigned operator &= (unsigned i) { d = uclip<24>(d & i); return d; }
|
||||
inline unsigned operator <<= (unsigned i) { d = uclip<24>(d << i); return d; }
|
||||
inline unsigned operator >>= (unsigned i) { d = uclip<24>(d >> i); return d; }
|
||||
inline unsigned operator += (unsigned i) { d = uclip<24>(d + i); return d; }
|
||||
inline unsigned operator -= (unsigned i) { d = uclip<24>(d - i); return d; }
|
||||
inline unsigned operator *= (unsigned i) { d = uclip<24>(d * i); return d; }
|
||||
inline unsigned operator /= (unsigned i) { d = uclip<24>(d / i); return d; }
|
||||
inline unsigned operator %= (unsigned i) { d = uclip<24>(d % i); return d; }
|
||||
|
||||
CPUReg24() : d(0) {}
|
||||
};
|
||||
|
||||
class CPURegs {
|
||||
public:
|
||||
CPUReg24 pc;
|
||||
CPUReg16 a, x, y, s, d;
|
||||
CPURegFlags p;
|
||||
uint8 db;
|
||||
uint8 mdr;
|
||||
bool e;
|
||||
CPURegs() : db(0), mdr(0), e(false) {}
|
||||
};
|
|
@ -0,0 +1,74 @@
|
|||
struct flag_t {
|
||||
bool n, v, m, x, d, i, z, c;
|
||||
|
||||
inline operator unsigned() const {
|
||||
return (n << 7) + (v << 6) + (m << 5) + (x << 4)
|
||||
+ (d << 3) + (i << 2) + (z << 1) + (c << 0);
|
||||
}
|
||||
|
||||
inline unsigned operator=(uint8_t data) {
|
||||
n = data & 0x80; v = data & 0x40; m = data & 0x20; x = data & 0x10;
|
||||
d = data & 0x08; i = data & 0x04; z = data & 0x02; c = data & 0x01;
|
||||
return data;
|
||||
}
|
||||
|
||||
inline unsigned operator|=(unsigned data) { return operator=(operator unsigned() | data); }
|
||||
inline unsigned operator^=(unsigned data) { return operator=(operator unsigned() ^ data); }
|
||||
inline unsigned operator&=(unsigned data) { return operator=(operator unsigned() & data); }
|
||||
|
||||
flag_t() : n(0), v(0), m(0), x(0), d(0), i(0), z(0), c(0) {}
|
||||
};
|
||||
|
||||
struct reg16_t {
|
||||
union {
|
||||
uint16 w;
|
||||
struct { uint8 order_lsb2(l, h); };
|
||||
};
|
||||
|
||||
inline operator unsigned() const { return w; }
|
||||
inline unsigned operator = (unsigned i) { return w = i; }
|
||||
inline unsigned operator |= (unsigned i) { return w |= i; }
|
||||
inline unsigned operator ^= (unsigned i) { return w ^= i; }
|
||||
inline unsigned operator &= (unsigned i) { return w &= i; }
|
||||
inline unsigned operator <<= (unsigned i) { return w <<= i; }
|
||||
inline unsigned operator >>= (unsigned i) { return w >>= i; }
|
||||
inline unsigned operator += (unsigned i) { return w += i; }
|
||||
inline unsigned operator -= (unsigned i) { return w -= i; }
|
||||
inline unsigned operator *= (unsigned i) { return w *= i; }
|
||||
inline unsigned operator /= (unsigned i) { return w /= i; }
|
||||
inline unsigned operator %= (unsigned i) { return w %= i; }
|
||||
|
||||
reg16_t() : w(0) {}
|
||||
};
|
||||
|
||||
struct reg24_t {
|
||||
union {
|
||||
uint32 d;
|
||||
struct { uint16 order_lsb2(w, wh); };
|
||||
struct { uint8 order_lsb4(l, h, b, bh); };
|
||||
};
|
||||
|
||||
inline operator unsigned() const { return d; }
|
||||
inline unsigned operator = (unsigned i) { return d = uclip<24>(i); }
|
||||
inline unsigned operator |= (unsigned i) { return d = uclip<24>(d | i); }
|
||||
inline unsigned operator ^= (unsigned i) { return d = uclip<24>(d ^ i); }
|
||||
inline unsigned operator &= (unsigned i) { return d = uclip<24>(d & i); }
|
||||
inline unsigned operator <<= (unsigned i) { return d = uclip<24>(d << i); }
|
||||
inline unsigned operator >>= (unsigned i) { return d = uclip<24>(d >> i); }
|
||||
inline unsigned operator += (unsigned i) { return d = uclip<24>(d + i); }
|
||||
inline unsigned operator -= (unsigned i) { return d = uclip<24>(d - i); }
|
||||
inline unsigned operator *= (unsigned i) { return d = uclip<24>(d * i); }
|
||||
inline unsigned operator /= (unsigned i) { return d = uclip<24>(d / i); }
|
||||
inline unsigned operator %= (unsigned i) { return d = uclip<24>(d % i); }
|
||||
|
||||
reg24_t() : d(0) {}
|
||||
};
|
||||
|
||||
struct regs_t {
|
||||
reg24_t pc;
|
||||
reg16_t a, x, y, s, d;
|
||||
flag_t p;
|
||||
uint8_t db, mdr;
|
||||
bool e;
|
||||
regs_t() : db(0), mdr(0), e(false) {}
|
||||
};
|
|
@ -103,7 +103,7 @@ uint32 r = 0;
|
|||
}
|
||||
|
||||
void CPU::disassemble_opcode(char *output) {
|
||||
static CPUReg24 pc;
|
||||
static reg24_t pc;
|
||||
char t[256];
|
||||
char *s = output;
|
||||
|
||||
|
@ -425,7 +425,7 @@ uint8 op2 = dreadb(pc.d);
|
|||
strcat(s, t);
|
||||
strcat(s, " ");
|
||||
|
||||
sprintf(t, "V:%3d H:%4d", vcounter(), hcounter());
|
||||
sprintf(t, "V:%3d H:%4d", ppucounter.vcounter(), ppucounter.hcounter());
|
||||
strcat(s, t);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,14 @@
|
|||
|
||||
#include "opfn.cpp"
|
||||
|
||||
void sCPU::enter() { loop:
|
||||
void sCPU::enter() {
|
||||
initialize:
|
||||
//initial latch values for $213c/$213d
|
||||
//[x]0035 : [y]0000 (53.0 -> 212) [lda $2137]
|
||||
//[x]0038 : [y]0000 (56.5 -> 226) [nop : lda $2137]
|
||||
add_clocks(186);
|
||||
|
||||
loop:
|
||||
if(event.irq) {
|
||||
event.irq = false;
|
||||
if(status.nmi_pending == true) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
CPUReg24 aa, rd;
|
||||
reg24_t aa, rd;
|
||||
uint8_t dp, sp;
|
||||
|
||||
void op_irq();
|
|
@ -7,15 +7,15 @@ void sCPU::dma_add_clocks(uint clocks) {
|
|||
|
||||
bool sCPU::dma_addr_valid(uint32 abus) {
|
||||
//reads from B-bus or S-CPU registers are invalid
|
||||
if((abus & 0x40ff00) == 0x2100) return false; //$[00-3f|80-bf]:[2100-21ff]
|
||||
if((abus & 0x40fe00) == 0x4000) return false; //$[00-3f|80-bf]:[4000-41ff]
|
||||
if((abus & 0x40ffe0) == 0x4200) return false; //$[00-3f|80-bf]:[4200-421f]
|
||||
if((abus & 0x40ff80) == 0x4300) return false; //$[00-3f|80-bf]:[4300-437f]
|
||||
if((abus & 0x40ff00) == 0x2100) return false; //$[00-3f|80-bf]:[2100-21ff]
|
||||
if((abus & 0x40fe00) == 0x4000) return false; //$[00-3f|80-bf]:[4000-41ff]
|
||||
if((abus & 0x40ffe0) == 0x4200) return false; //$[00-3f|80-bf]:[4200-421f]
|
||||
if((abus & 0x40ff80) == 0x4300) return false; //$[00-3f|80-bf]:[4300-437f]
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8 sCPU::dma_read(uint32 abus) {
|
||||
if(dma_addr_valid(abus) == false) return 0x00; //does not return S-CPU MDR
|
||||
if(dma_addr_valid(abus) == false) return 0x00; //does not return S-CPU MDR
|
||||
return bus.read(abus);
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ void sCPU::dma_transfer(bool direction, uint8 bbus, uint32 abus) {
|
|||
//illegal WRAM->WRAM transfer (bus conflict)
|
||||
//no read occurs; write does occur
|
||||
dma_add_clocks(8);
|
||||
bus.write(abus, 0x00); //does not write S-CPU MDR
|
||||
bus.write(abus, 0x00); //does not write S-CPU MDR
|
||||
} else {
|
||||
dma_add_clocks(4);
|
||||
uint8 data = bus.read(0x2100 | bbus);
|
||||
|
@ -59,14 +59,14 @@ void sCPU::dma_transfer(bool direction, uint8 bbus, uint32 abus) {
|
|||
|
||||
uint8 sCPU::dma_bbus(uint8 i, uint8 index) {
|
||||
switch(channel[i].xfermode) { default:
|
||||
case 0: return (channel[i].destaddr); //0
|
||||
case 1: return (channel[i].destaddr + (index & 1)); //0,1
|
||||
case 2: return (channel[i].destaddr); //0,0
|
||||
case 3: return (channel[i].destaddr + ((index >> 1) & 1)); //0,0,1,1
|
||||
case 4: return (channel[i].destaddr + (index & 3)); //0,1,2,3
|
||||
case 5: return (channel[i].destaddr + (index & 1)); //0,1,0,1
|
||||
case 6: return (channel[i].destaddr); //0,0 [2]
|
||||
case 7: return (channel[i].destaddr + ((index >> 1) & 1)); //0,0,1,1 [3]
|
||||
case 0: return (channel[i].destaddr); //0
|
||||
case 1: return (channel[i].destaddr + (index & 1)); //0,1
|
||||
case 2: return (channel[i].destaddr); //0,0
|
||||
case 3: return (channel[i].destaddr + ((index >> 1) & 1)); //0,0,1,1
|
||||
case 4: return (channel[i].destaddr + (index & 3)); //0,1,2,3
|
||||
case 5: return (channel[i].destaddr + (index & 1)); //0,1,0,1
|
||||
case 6: return (channel[i].destaddr); //0,0 [2]
|
||||
case 7: return (channel[i].destaddr + ((index >> 1) & 1)); //0,0,1,1 [3]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,29 +96,6 @@ inline uint32 sCPU::hdma_iaddr(uint8 i) {
|
|||
* DMA functions
|
||||
*****/
|
||||
|
||||
void sCPU::dma_transfertobusb(uint8 i, uint8 bbus) {
|
||||
if(cartridge.info.sdd1 == true && sdd1.dma_active() == true) {
|
||||
bus.write(0x2100 | bbus, sdd1.dma_read());
|
||||
} else {
|
||||
dma_transfer(0, bbus, dma_addr(i));
|
||||
}
|
||||
channel[i].xfersize--;
|
||||
}
|
||||
|
||||
void sCPU::dma_transfertobusa(uint8 i, uint8 bbus) {
|
||||
dma_transfer(1, bbus, dma_addr(i));
|
||||
channel[i].xfersize--;
|
||||
}
|
||||
|
||||
inline void sCPU::dma_write(uint8 i, uint8 index) {
|
||||
//cannot use dma_transfer() directly, due to current S-DD1 implementation
|
||||
if(channel[i].direction == 0) {
|
||||
dma_transfertobusb(i, index);
|
||||
} else {
|
||||
dma_transfertobusa(i, index);
|
||||
}
|
||||
}
|
||||
|
||||
uint8 sCPU::dma_enabled_channels() {
|
||||
uint8 r = 0;
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
|
@ -136,23 +113,10 @@ void sCPU::dma_run() {
|
|||
dma_add_clocks(8);
|
||||
cycle_edge();
|
||||
|
||||
if(cartridge.info.sdd1 == true) {
|
||||
sdd1.dma_begin(i, (channel[i].srcbank << 16) | (channel[i].srcaddr), channel[i].xfersize);
|
||||
}
|
||||
|
||||
if(tracer.enabled() == true && tracer.cpudma_enabled() == true) {
|
||||
tprintf("[DMA] channel:%d direction:%s reverse:%c fixed:%c mode:%d b_addr:$21%0.2x "
|
||||
"a_addr:$%0.2x%0.4x length:$%0.4x (%5d)",
|
||||
i, channel[i].direction ? "b->a" : "a->b", channel[i].reversexfer ? '1' : '0',
|
||||
channel[i].fixedxfer ? '1' : '0', channel[i].xfermode, channel[i].destaddr,
|
||||
channel[i].srcbank, channel[i].srcaddr,
|
||||
channel[i].xfersize, channel[i].xfersize ? channel[i].xfersize : 65536);
|
||||
}
|
||||
|
||||
unsigned index = 0;
|
||||
do {
|
||||
dma_write(i, dma_bbus(i, index++));
|
||||
} while(channel[i].dma_enabled && channel[i].xfersize);
|
||||
dma_transfer(channel[i].direction, dma_bbus(i, index++), dma_addr(i));
|
||||
} while(channel[i].dma_enabled && --channel[i].xfersize);
|
||||
|
||||
channel[i].dma_enabled = false;
|
||||
}
|
||||
|
@ -215,7 +179,7 @@ void sCPU::hdma_run() {
|
|||
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(hdma_active(i) == false) continue;
|
||||
channel[i].dma_enabled = false; //HDMA run during DMA will stop DMA mid-transfer
|
||||
channel[i].dma_enabled = false; //HDMA run during DMA will stop DMA mid-transfer
|
||||
|
||||
if(channel[i].hdma_do_transfer) {
|
||||
static const unsigned transfer_length[8] = { 1, 2, 2, 4, 4, 4, 2, 4 };
|
||||
|
@ -254,7 +218,7 @@ void sCPU::hdma_init() {
|
|||
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(!channel[i].hdma_enabled) continue;
|
||||
channel[i].dma_enabled = false; //HDMA init during DMA will stop DMA mid-transfer
|
||||
channel[i].dma_enabled = false; //HDMA init during DMA will stop DMA mid-transfer
|
||||
|
||||
channel[i].hdma_addr = channel[i].srcaddr;
|
||||
hdma_update(i);
|
||||
|
@ -282,7 +246,7 @@ void sCPU::dma_power() {
|
|||
channel[i].srcbank = 0xff;
|
||||
|
||||
channel[i].xfersize = 0xffff;
|
||||
//channel[i].hdma_iaddr = 0xffff; //union with xfersize
|
||||
//channel[i].hdma_iaddr = 0xffff; //union with xfersize
|
||||
channel[i].hdma_ibank = 0xff;
|
||||
|
||||
channel[i].hdma_addr = 0xffff;
|
||||
|
|
|
@ -55,9 +55,6 @@
|
|||
uint32 hdma_addr(uint8 i);
|
||||
uint32 hdma_iaddr(uint8 i);
|
||||
|
||||
void dma_transfertobusb(uint8 i, uint8 bbus);
|
||||
void dma_transfertobusa(uint8 i, uint8 bbus);
|
||||
void dma_write(uint8 i, uint8 index);
|
||||
uint8 dma_enabled_channels();
|
||||
void dma_run();
|
||||
|
|
@ -190,13 +190,13 @@ uint8 sCPU::mmio_r4212() {
|
|||
uint16 vs = ppu.overscan() == false ? 225 : 240;
|
||||
|
||||
//auto joypad polling
|
||||
if(status.vcounter >= vs && status.vcounter <= (vs + 2))r |= 0x01;
|
||||
if(ppucounter.vcounter() >= vs && ppucounter.vcounter() <= (vs + 2))r |= 0x01;
|
||||
|
||||
//hblank
|
||||
if(status.hcounter <= 2 || status.hcounter >= 1096)r |= 0x40;
|
||||
if(ppucounter.hcounter() <= 2 || ppucounter.hcounter() >= 1096)r |= 0x40;
|
||||
|
||||
//vblank
|
||||
if(status.vcounter >= vs)r |= 0x80;
|
||||
if(ppucounter.vcounter() >= vs)r |= 0x80;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define SCPU_CPP
|
||||
|
||||
#include "core/core.cpp"
|
||||
|
|
|
@ -2,11 +2,11 @@ class sCPU : public CPU {
|
|||
public:
|
||||
void enter();
|
||||
|
||||
#include "core/core.h"
|
||||
#include "dma/dma.h"
|
||||
#include "memory/memory.h"
|
||||
#include "mmio/mmio.h"
|
||||
#include "timing/timing.h"
|
||||
#include "core/core.hpp"
|
||||
#include "dma/dma.hpp"
|
||||
#include "memory/memory.hpp"
|
||||
#include "mmio/mmio.hpp"
|
||||
#include "timing/timing.hpp"
|
||||
|
||||
struct {
|
||||
bool wai;
|
||||
|
@ -44,14 +44,9 @@ public:
|
|||
bool in_opcode;
|
||||
|
||||
unsigned clock_count;
|
||||
unsigned line_clocks;
|
||||
|
||||
//timing
|
||||
uint16 vcounter, hcounter;
|
||||
uint16 field_lines, line_clocks;
|
||||
|
||||
bool line_rendered;
|
||||
uint16 line_render_position;
|
||||
|
||||
bool dram_refreshed;
|
||||
uint16 dram_refresh_position;
|
||||
|
|
@ -1,13 +1,20 @@
|
|||
#ifdef SCPU_CPP
|
||||
|
||||
#ifdef SCPU_CPP
|
||||
|
||||
void sCPU::update_interrupts() {
|
||||
if(irq_pos_valid() == true) {
|
||||
status.virq_trigger_pos = status.virq_pos;
|
||||
status.hirq_trigger_pos = 4 * ((status.hirq_enabled) ? (status.hirq_pos + 1) : 0);
|
||||
} else {
|
||||
status.virq_trigger_pos = IRQ_TRIGGER_NEVER;
|
||||
status.hirq_trigger_pos = IRQ_TRIGGER_NEVER;
|
||||
unsigned vtime = status.virq_pos;
|
||||
unsigned htime = status.hirq_enabled ? status.hirq_pos : 0;
|
||||
unsigned vlimit = (snes.region() == SNES::NTSC ? 525 : 625) >> 1;
|
||||
|
||||
//an IRQ for the very last dot of a field cannot trigger an IRQ
|
||||
if((vtime == (vlimit - 1) && htime == 339 && ppu.interlace() == false)
|
||||
|| (vtime == vlimit && htime == 339)
|
||||
) {
|
||||
vtime = 0x03ff;
|
||||
htime = 0x03ff;
|
||||
}
|
||||
|
||||
status.virq_trigger_pos = vtime;
|
||||
status.hirq_trigger_pos = 4 * (status.hirq_enabled ? htime + 1 : 0);
|
||||
}
|
||||
|
||||
alwaysinline void sCPU::poll_interrupts() {
|
||||
|
@ -22,7 +29,8 @@ alwaysinline void sCPU::poll_interrupts() {
|
|||
}
|
||||
|
||||
//NMI test
|
||||
history.query(2, vpos, hpos);
|
||||
vpos = ppucounter.vcounter(2);
|
||||
hpos = ppucounter.hcounter(2);
|
||||
bool nmi_valid = (vpos >= (!ppu.overscan() ? 225 : 240));
|
||||
if(status.nmi_valid == false && nmi_valid == true) {
|
||||
//0->1 edge sensitive transition
|
||||
|
@ -41,7 +49,8 @@ alwaysinline void sCPU::poll_interrupts() {
|
|||
}
|
||||
|
||||
//IRQ test
|
||||
history.query(10, vpos, hpos);
|
||||
vpos = ppucounter.vcounter(10);
|
||||
hpos = ppucounter.hcounter(10);
|
||||
bool irq_valid = (status.virq_enabled == true || status.hirq_enabled == true);
|
||||
if(irq_valid == true) {
|
||||
if(status.virq_enabled == true && vpos != status.virq_trigger_pos) irq_valid = false;
|
||||
|
@ -103,23 +112,6 @@ bool sCPU::timeup() {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool sCPU::irq_pos_valid() {
|
||||
uint vpos = status.virq_pos;
|
||||
uint hpos = (status.hirq_enabled) ? status.hirq_pos : 0;
|
||||
uint vlimit = (snes.region() == SNES::NTSC ? 525 : 625) >> 1;
|
||||
//positions that can never be latched
|
||||
//vlimit = 262/NTSC, 312/PAL
|
||||
//PAL results are unverified on hardware
|
||||
if(vpos == 240 && hpos == 339 && ppu.interlace() == false && ppu.field() == 1) return false;
|
||||
if(vpos == (vlimit - 1) && hpos == 339 && ppu.interlace() == false) return false;
|
||||
if(vpos == vlimit && ppu.interlace() == false) return false;
|
||||
if(vpos == vlimit && hpos == 339) return false;
|
||||
if(vpos > vlimit) return false;
|
||||
if(hpos > 339) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
alwaysinline bool sCPU::nmi_test() {
|
||||
if(status.nmi_transition == false) return false;
|
||||
status.nmi_transition = false;
|
||||
|
@ -131,7 +123,7 @@ alwaysinline bool sCPU::irq_test() {
|
|||
if(status.irq_transition == false) return false;
|
||||
status.irq_transition = false;
|
||||
event.wai = false;
|
||||
return regs.p.i ? false : true;
|
||||
return !regs.p.i;
|
||||
}
|
||||
|
||||
#endif //ifdef SCPU_CPP
|
||||
#endif //ifdef SCPU_CPP
|
||||
|
|
|
@ -25,4 +25,4 @@ void sCPU::run_auto_joypad_poll() {
|
|||
status.joy4h = joy4 >> 8;
|
||||
}
|
||||
|
||||
#endif //ifdef SCPU_CPP
|
||||
#endif //ifdef SCPU_CPP
|
||||
|
|
|
@ -4,85 +4,49 @@
|
|||
#include "joypad.cpp"
|
||||
|
||||
unsigned sCPU::dma_counter() {
|
||||
return (status.dma_counter + status.hcounter) & 7;
|
||||
}
|
||||
|
||||
/*****
|
||||
* One PPU dot = 4 CPU clocks
|
||||
*
|
||||
* PPU dots 323 and 327 are 6 CPU clocks long.
|
||||
* This does not apply to NTSC non-interlace scanline 240 on odd fields. This is
|
||||
* because the PPU skips one dot to alter the color burst phase of the video signal.
|
||||
*
|
||||
* Dot 323 range = { 1292, 1294, 1296 }
|
||||
* Dot 327 range = { 1310, 1312, 1314 }
|
||||
*****/
|
||||
|
||||
#define ntsc_color_burst_phase_shift_scanline() ( \
|
||||
snes.region() == SNES::NTSC && status.vcounter == 240 && \
|
||||
ppu.interlace() == false && ppu.field() == 1 \
|
||||
)
|
||||
|
||||
uint16 sCPU::hdot() {
|
||||
if(ntsc_color_burst_phase_shift_scanline() == true) return (status.hcounter >> 2);
|
||||
return (status.hcounter - ((status.hcounter > 1292) << 1) - ((status.hcounter > 1310) << 1)) >> 2;
|
||||
return (status.dma_counter + ppucounter.hcounter()) & 7;
|
||||
}
|
||||
|
||||
void sCPU::add_clocks(unsigned clocks) {
|
||||
if(status.dram_refreshed == false) {
|
||||
if(status.hcounter + clocks >= status.dram_refresh_position) {
|
||||
if(ppucounter.hcounter() + clocks >= status.dram_refresh_position) {
|
||||
status.dram_refreshed = true;
|
||||
clocks += 40;
|
||||
}
|
||||
}
|
||||
|
||||
counter.sub(counter.irq_delay, clocks);
|
||||
scheduler.addclocks_cpu(clocks);
|
||||
|
||||
clocks >>= 1;
|
||||
while(clocks--) {
|
||||
history.enqueue(status.vcounter, status.hcounter);
|
||||
status.hcounter += 2;
|
||||
if(status.hcounter >= status.line_clocks) scanline();
|
||||
poll_interrupts();
|
||||
unsigned ticks = clocks >> 1;
|
||||
while(ticks--) {
|
||||
ppucounter.tick();
|
||||
snes.input.tick();
|
||||
poll_interrupts();
|
||||
}
|
||||
|
||||
scheduler.addclocks_cpu(clocks);
|
||||
}
|
||||
|
||||
void sCPU::scanline() {
|
||||
status.hcounter = 0;
|
||||
status.dma_counter = (status.dma_counter + status.line_clocks) & 7;
|
||||
if(++status.vcounter >= status.field_lines) frame();
|
||||
status.line_clocks = (ntsc_color_burst_phase_shift_scanline() == false) ? 1364 : 1360;
|
||||
status.line_clocks = ppucounter.lineclocks();
|
||||
if(ppucounter.vcounter() == 0) frame();
|
||||
|
||||
//dram refresh occurs once every scanline
|
||||
status.dram_refreshed = false;
|
||||
if(cpu_version == 2) status.dram_refresh_position = 530 + 8 - dma_counter();
|
||||
|
||||
//hdma triggers once every visible scanline
|
||||
status.line_rendered = false;
|
||||
status.hdma_triggered = (status.vcounter <= (ppu.overscan() == false ? 224 : 239)) ? false : true;
|
||||
|
||||
ppu.scanline();
|
||||
snes.scanline();
|
||||
status.hdma_triggered = (ppucounter.vcounter() <= (ppu.overscan() == false ? 224 : 239)) ? false : true;
|
||||
|
||||
update_interrupts();
|
||||
|
||||
if(status.auto_joypad_poll == true && status.vcounter == (ppu.overscan() == false ? 227 : 242)) {
|
||||
if(status.auto_joypad_poll == true && ppucounter.vcounter() == (ppu.overscan() == false ? 227 : 242)) {
|
||||
snes.input.poll();
|
||||
run_auto_joypad_poll();
|
||||
}
|
||||
}
|
||||
|
||||
void sCPU::frame() {
|
||||
ppu.frame();
|
||||
snes.frame();
|
||||
|
||||
status.vcounter = 0;
|
||||
status.field_lines = (snes.region() == SNES::NTSC ? 525 : 625) >> 1;
|
||||
//interlaced even fields have one extra scanline (263+262=525 NTSC, 313+312=625 PAL)
|
||||
if(ppu.interlace() == true && ppu.field() == 0) status.field_lines++;
|
||||
|
||||
status.hdmainit_triggered = false;
|
||||
if(cpu_version == 1) {
|
||||
status.hdmainit_trigger_position = 12 + 8 - dma_counter();
|
||||
|
@ -91,11 +55,7 @@ void sCPU::frame() {
|
|||
}
|
||||
}
|
||||
|
||||
/*****
|
||||
* precycle_edge()
|
||||
*
|
||||
* Used for H/DMA bus synchronization
|
||||
*****/
|
||||
//used for H/DMA bus synchronization
|
||||
void sCPU::precycle_edge() {
|
||||
if(status.dma_state == DMA_CPUsync) {
|
||||
add_clocks(status.clock_count - (status.dma_clocks % status.clock_count));
|
||||
|
@ -103,21 +63,10 @@ void sCPU::precycle_edge() {
|
|||
}
|
||||
}
|
||||
|
||||
/*****
|
||||
* cycle_edge()
|
||||
*
|
||||
* Used to test for H/DMA, which can trigger on the edge of every opcode cycle.
|
||||
*****/
|
||||
//used to test for H/DMA, which can trigger on the edge of every opcode cycle.
|
||||
void sCPU::cycle_edge() {
|
||||
if(status.line_rendered == false) {
|
||||
if(status.hcounter >= status.line_render_position) {
|
||||
status.line_rendered = true;
|
||||
ppu.render_scanline();
|
||||
}
|
||||
}
|
||||
|
||||
if(status.hdmainit_triggered == false) {
|
||||
if(status.hcounter >= status.hdmainit_trigger_position || status.vcounter) {
|
||||
if(ppucounter.hcounter() >= status.hdmainit_trigger_position || ppucounter.vcounter()) {
|
||||
status.hdmainit_triggered = true;
|
||||
hdma_init_reset();
|
||||
if(hdma_enabled_channels()) {
|
||||
|
@ -128,7 +77,7 @@ void sCPU::cycle_edge() {
|
|||
}
|
||||
|
||||
if(status.hdma_triggered == false) {
|
||||
if(status.hcounter >= 1104) {
|
||||
if(ppucounter.hcounter() >= 1104) {
|
||||
status.hdma_triggered = true;
|
||||
if(hdma_active_channels()) {
|
||||
status.hdma_pending = true;
|
||||
|
@ -173,15 +122,11 @@ void sCPU::cycle_edge() {
|
|||
}
|
||||
}
|
||||
|
||||
/*****
|
||||
* last_cycle()
|
||||
*
|
||||
* Used to test for NMI/IRQ, which can trigger on the edge of every opcode.
|
||||
* Test one cycle early to simulate two-stage pipeline of x816 CPU.
|
||||
*
|
||||
* status.irq_delay is used to simulate hardware delay before interrupts can
|
||||
* trigger during certain events (immediately after DMA, writes to $4200, etc)
|
||||
*****/
|
||||
//used to test for NMI/IRQ, which can trigger on the edge of every opcode.
|
||||
//test one cycle early to simulate two-stage pipeline of x816 CPU.
|
||||
//
|
||||
//status.irq_delay is used to simulate hardware delay before interrupts can
|
||||
//trigger during certain events (immediately after DMA, writes to $4200, etc)
|
||||
void sCPU::last_cycle() {
|
||||
if(counter.irq_delay) return;
|
||||
|
||||
|
@ -204,15 +149,7 @@ void sCPU::timing_reset() {
|
|||
counter.hw_math = 0;
|
||||
|
||||
status.clock_count = 0;
|
||||
|
||||
status.vcounter = 0;
|
||||
status.hcounter = 0;
|
||||
|
||||
status.field_lines = (snes.region() == SNES::NTSC ? 525 : 625) >> 1;
|
||||
status.line_clocks = 1364;
|
||||
|
||||
status.line_rendered = false;
|
||||
status.line_render_position = min(1112U, (unsigned)config::ppu.hack.render_scanline_position);
|
||||
status.line_clocks = ppucounter.lineclocks();
|
||||
|
||||
status.dram_refreshed = false;
|
||||
status.dram_refresh_position = (cpu_version == 1) ? 530 : 538;
|
||||
|
@ -242,15 +179,8 @@ void sCPU::timing_reset() {
|
|||
status.hdma_pending = false;
|
||||
status.hdma_mode = 0;
|
||||
status.dma_state = DMA_Inactive;
|
||||
|
||||
history.reset();
|
||||
|
||||
//initial latch values for $213c/$213d
|
||||
//[x]0035 : [y]0000 (53.0 -> 212) [lda $2137]
|
||||
//[x]0038 : [y]0000 (56.5 -> 226) [nop : lda $2137]
|
||||
add_clocks(186);
|
||||
}
|
||||
|
||||
#undef ntsc_color_burst_phase_shift_scanline
|
||||
|
||||
#endif //ifdef SCPU_CPP
|
||||
#endif //ifdef SCPU_CPP
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
alwaysinline uint16 vcounter() { return status.vcounter; }
|
||||
alwaysinline uint16 hcounter() { return status.hcounter; }
|
||||
uint16 hdot();
|
||||
unsigned dma_counter();
|
||||
|
||||
void add_clocks(unsigned clocks);
|
||||
void scanline();
|
||||
void frame();
|
||||
|
||||
void precycle_edge();
|
||||
void cycle_edge();
|
||||
void last_cycle();
|
||||
uint32 clocks_executed();
|
||||
|
||||
void timing_power();
|
||||
void timing_reset();
|
||||
|
||||
//timeshifting -- needed by NMI and IRQ timing
|
||||
struct History {
|
||||
struct Time {
|
||||
uint16 vcounter;
|
||||
uint16 hcounter;
|
||||
} time[32];
|
||||
unsigned index;
|
||||
alwaysinline void enqueue(uint16 vcounter, uint16 hcounter) {
|
||||
Time &t = time[index++];
|
||||
index &= 31;
|
||||
t.vcounter = vcounter;
|
||||
t.hcounter = hcounter;
|
||||
}
|
||||
alwaysinline void query(unsigned offset, uint16 &vcounter, uint16 &hcounter) {
|
||||
Time &t = time[(index - (offset >> 1)) & 31];
|
||||
vcounter = t.vcounter;
|
||||
hcounter = t.hcounter;
|
||||
}
|
||||
void reset() {
|
||||
index = 0;
|
||||
for(unsigned i = 0; i < 32; i++) time[i].vcounter = time[i].hcounter = 0;
|
||||
}
|
||||
History() { reset(); }
|
||||
} history;
|
||||
|
||||
//irq.cpp
|
||||
enum { IRQ_TRIGGER_NEVER = 0x3fff };
|
||||
void update_interrupts();
|
||||
void poll_interrupts();
|
||||
void nmitimen_update(uint8 data);
|
||||
void hvtime_update(uint16 addr);
|
||||
bool rdnmi();
|
||||
bool timeup();
|
||||
|
||||
bool irq_pos_valid();
|
||||
bool nmi_test();
|
||||
bool irq_test();
|
||||
|
||||
//joypad.cpp
|
||||
void run_auto_joypad_poll();
|
|
@ -0,0 +1,27 @@
|
|||
//timing.cpp
|
||||
unsigned dma_counter();
|
||||
|
||||
void add_clocks(unsigned clocks);
|
||||
void scanline();
|
||||
void frame();
|
||||
|
||||
void precycle_edge();
|
||||
void cycle_edge();
|
||||
void last_cycle();
|
||||
|
||||
void timing_power();
|
||||
void timing_reset();
|
||||
|
||||
//irq.cpp
|
||||
void update_interrupts();
|
||||
void poll_interrupts();
|
||||
void nmitimen_update(uint8 data);
|
||||
void hvtime_update(uint16 addr);
|
||||
bool rdnmi();
|
||||
bool timeup();
|
||||
|
||||
bool nmi_test();
|
||||
bool irq_test();
|
||||
|
||||
//joypad.cpp
|
||||
void run_auto_joypad_poll();
|
|
@ -1,4 +1,4 @@
|
|||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define ADSP_CPP
|
||||
|
||||
#include "adsp_tables.cpp"
|
||||
|
|
|
@ -1,681 +0,0 @@
|
|||
/* This code is heavily customized for bsnes and requires cothreads.
|
||||
Original portable snes_spc library available at http://www.slack.net/~ant/
|
||||
Copyright (C) 2007 Shay Green. See license.txt. */
|
||||
|
||||
#include "../../base.h"
|
||||
|
||||
int const brr_block_size = 9;
|
||||
|
||||
// Accesses global DSP register
|
||||
#define REG(n) m.regs [r_##n]
|
||||
|
||||
// Accesses voice DSP register
|
||||
#define VREG(r,n) r [v_##n]
|
||||
|
||||
// Volume registers and efb are signed! Easy to forget int8 cast.
|
||||
// Prefixes are to avoid accidental use of locals with same names.
|
||||
|
||||
// Gaussian interpolation
|
||||
|
||||
static short const gauss [512] =
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,
|
||||
2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5,
|
||||
6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
|
||||
11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17,
|
||||
18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27,
|
||||
28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
|
||||
58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77,
|
||||
78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102,
|
||||
104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132,
|
||||
134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168,
|
||||
171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210,
|
||||
212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257,
|
||||
260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311,
|
||||
314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370,
|
||||
374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434,
|
||||
439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504,
|
||||
508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577,
|
||||
582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654,
|
||||
659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732,
|
||||
737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811,
|
||||
816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889,
|
||||
894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965,
|
||||
969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036,
|
||||
1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102,
|
||||
1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160,
|
||||
1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210,
|
||||
1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251,
|
||||
1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280,
|
||||
1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298,
|
||||
1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305,
|
||||
};
|
||||
|
||||
|
||||
//// Counters
|
||||
|
||||
int const simple_counter_range = 2048 * 5 * 3; // 30720
|
||||
|
||||
static unsigned const counter_rates [32] =
|
||||
{
|
||||
simple_counter_range + 1, // never fires
|
||||
2048, 1536,
|
||||
1280, 1024, 768,
|
||||
640, 512, 384,
|
||||
320, 256, 192,
|
||||
160, 128, 96,
|
||||
80, 64, 48,
|
||||
40, 32, 24,
|
||||
20, 16, 12,
|
||||
10, 8, 6,
|
||||
5, 4, 3,
|
||||
2,
|
||||
1
|
||||
};
|
||||
|
||||
static unsigned const counter_offsets [32] =
|
||||
{
|
||||
1, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
0,
|
||||
0
|
||||
};
|
||||
|
||||
inline unsigned bDSP::read_counter( int rate )
|
||||
{
|
||||
return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate];
|
||||
}
|
||||
|
||||
|
||||
//// Envelope
|
||||
|
||||
inline void bDSP::run_envelope( voice_t* const v )
|
||||
{
|
||||
int env = v->env;
|
||||
if ( v->env_mode == env_release )
|
||||
{
|
||||
if ( (env -= 0x8) < 0 )
|
||||
env = 0;
|
||||
v->env = env;
|
||||
}
|
||||
else
|
||||
{
|
||||
int rate;
|
||||
int env_data = VREG(v->regs,adsr1);
|
||||
if ( m.t_adsr0 & 0x80 ) // ADSR
|
||||
{
|
||||
if ( v->env_mode >= env_decay )
|
||||
{
|
||||
env--;
|
||||
env -= asr<8>( env );
|
||||
rate = env_data & 0x1F;
|
||||
if ( v->env_mode == env_decay )
|
||||
rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10;
|
||||
}
|
||||
else // env_attack
|
||||
{
|
||||
rate = (m.t_adsr0 & 0x0F) * 2 + 1;
|
||||
env += rate < 31 ? 0x20 : 0x400;
|
||||
}
|
||||
}
|
||||
else // GAIN
|
||||
{
|
||||
env_data = VREG(v->regs,gain);
|
||||
int mode = env_data >> 5;
|
||||
if ( mode < 4 ) // direct
|
||||
{
|
||||
env = env_data * 0x10;
|
||||
rate = 31;
|
||||
}
|
||||
else
|
||||
{
|
||||
rate = env_data & 0x1F;
|
||||
if ( mode == 4 ) // 4: linear decrease
|
||||
{
|
||||
env -= 0x20;
|
||||
}
|
||||
else if ( mode < 6 ) // 5: exponential decrease
|
||||
{
|
||||
env--;
|
||||
env -= asr<8>( env );
|
||||
}
|
||||
else // 6,7: linear increase
|
||||
{
|
||||
env += 0x20;
|
||||
if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 )
|
||||
env += 0x8 - 0x20; // 7: two-slope linear increase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sustain level
|
||||
if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay )
|
||||
v->env_mode = env_sustain;
|
||||
|
||||
v->hidden_env = env;
|
||||
|
||||
// unsigned cast because linear decrease going negative also triggers this
|
||||
if ( (unsigned) env > 0x7FF )
|
||||
{
|
||||
env = (env < 0 ? 0 : 0x7FF);
|
||||
if ( v->env_mode == env_attack )
|
||||
v->env_mode = env_decay;
|
||||
}
|
||||
|
||||
if ( !read_counter( rate ) )
|
||||
v->env = env; // nothing else is controlled by the counter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// BRR Decoding
|
||||
|
||||
inline void bDSP::decode_brr( voice_t* v )
|
||||
{
|
||||
// Arrange the four input nybbles in 0xABCD order for easy decoding
|
||||
int nybbles = m.t_brr_byte * 0x100 + ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF];
|
||||
|
||||
// Write to next four samples in circular buffer
|
||||
int* pos = &v->buf [v->buf_pos];
|
||||
if ( (v->buf_pos += 4) >= brr_buf_size )
|
||||
v->buf_pos = 0;
|
||||
|
||||
// Decode four samples
|
||||
for ( int* end = pos + 4; pos < end; pos++ )
|
||||
{
|
||||
// Extract nybble and sign-extend
|
||||
int s = asr<12>( sclip<16>( nybbles ) );
|
||||
nybbles <<= 4;
|
||||
|
||||
// Shift sample based on header
|
||||
int const shift = m.t_brr_header >> 4;
|
||||
s = asr<1>( s << shift );
|
||||
if ( shift >= 0xD ) // handle invalid range
|
||||
s = (s < 0 ? -0x800 : 0);
|
||||
|
||||
// Apply IIR filter (8 is the most commonly used)
|
||||
int const p1 = pos [brr_buf_size - 1];
|
||||
int const p2 = asr<1>( pos [brr_buf_size - 2] );
|
||||
switch ( m.t_brr_header >> 2 & 3 )
|
||||
{
|
||||
case 1: s += asr<1>( p1 ) + asr<5>( -p1 ); break; // s += p1 * 0.4687500
|
||||
case 2: s += p1 + asr<6>( p1 * -3 ) - p2 + asr<4>( p2 ); break; // s += p1 * 0.9531250 - p2 * 0.46875
|
||||
case 3: s += p1 + asr<7>( p1 * -13 ) - p2 + asr<4>( p2 * 3 ); break; // s += p1 * 0.8984375 - p2 * 0.40625
|
||||
}
|
||||
|
||||
// Adjust and write sample
|
||||
s = sclip<16>( sclamp<16>( s ) * 2 );
|
||||
pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Voices
|
||||
|
||||
#define VOICE_CLOCK( n ) void bDSP::voice_##n( voice_t* const v )
|
||||
|
||||
inline VOICE_CLOCK( V1 )
|
||||
{
|
||||
m.t_dir_addr = m.t_dir * 0x100 + m.t_srcn * 4;
|
||||
m.t_srcn = VREG(v->regs,srcn);
|
||||
}
|
||||
inline VOICE_CLOCK( V2 )
|
||||
{
|
||||
// Read sample pointer (ignored if not needed)
|
||||
uint8 const* entry = &ram [m.t_dir_addr];
|
||||
if ( !v->kon_delay )
|
||||
entry += 2;
|
||||
m.t_brr_next_addr = entry [0] | entry [1] << 8;
|
||||
|
||||
m.t_adsr0 = VREG(v->regs,adsr0);
|
||||
|
||||
// Read pitch, spread over two clocks
|
||||
m.t_pitch = VREG(v->regs,pitchl);
|
||||
}
|
||||
inline VOICE_CLOCK( V3a )
|
||||
{
|
||||
m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8;
|
||||
}
|
||||
inline VOICE_CLOCK( V3b )
|
||||
{
|
||||
// Read BRR header and byte
|
||||
m.t_brr_byte = ram [(v->brr_addr + v->brr_offset) & 0xFFFF];
|
||||
m.t_brr_header = ram [v->brr_addr]; // brr_addr doesn't need masking
|
||||
}
|
||||
VOICE_CLOCK( V3c )
|
||||
{
|
||||
// Pitch modulation using previous voice's output
|
||||
if ( m.t_pmon & v->vbit )
|
||||
m.t_pitch += asr<10>( asr<5>( m.t_output ) * m.t_pitch );
|
||||
|
||||
if ( v->kon_delay )
|
||||
{
|
||||
// Get ready to start BRR decoding on next sample
|
||||
if ( v->kon_delay == 5 )
|
||||
{
|
||||
v->brr_addr = m.t_brr_next_addr;
|
||||
v->brr_offset = 1;
|
||||
v->buf_pos = 0;
|
||||
m.t_brr_header = 0; // header is ignored on this sample
|
||||
}
|
||||
|
||||
// Envelope is never run during KON
|
||||
v->env = 0;
|
||||
v->hidden_env = 0;
|
||||
|
||||
// Disable BRR decoding until last three samples
|
||||
v->interp_pos = 0;
|
||||
if ( --v->kon_delay & 3 )
|
||||
v->interp_pos = 0x4000;
|
||||
|
||||
// Pitch is never added during KON
|
||||
m.t_pitch = 0;
|
||||
}
|
||||
|
||||
// Gaussian interpolation
|
||||
{
|
||||
// Make pointers into gaussian based on fractional position between samples
|
||||
int offset = v->interp_pos >> 4 & 0xFF;
|
||||
short const* fwd = gauss + 255 - offset;
|
||||
short const* rev = gauss + offset; // mirror left half of gaussian
|
||||
|
||||
int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos];
|
||||
int out;
|
||||
out = asr<11>( fwd [ 0] * in [0] );
|
||||
out += asr<11>( fwd [256] * in [1] );
|
||||
out += asr<11>( rev [256] * in [2] );
|
||||
out = sclip<16>( out );
|
||||
out += asr<11>( rev [ 0] * in [3] );
|
||||
|
||||
out = sclamp<16>( out ) & ~1;
|
||||
|
||||
// Noise
|
||||
if ( m.t_non & v->vbit )
|
||||
out = sclip<16>( m.noise * 2 );
|
||||
|
||||
// Apply envelope
|
||||
m.t_output = asr<11>( out * v->env ) & ~1;
|
||||
v->t_envx_out = (uint8) (v->env >> 4);
|
||||
}
|
||||
|
||||
// Immediate silence due to end of sample or soft reset
|
||||
if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 )
|
||||
{
|
||||
v->env_mode = env_release;
|
||||
v->env = 0;
|
||||
}
|
||||
|
||||
if ( m.every_other_sample )
|
||||
{
|
||||
// KOFF
|
||||
if ( m.t_koff & v->vbit )
|
||||
v->env_mode = env_release;
|
||||
|
||||
// KON
|
||||
if ( m.kon & v->vbit )
|
||||
{
|
||||
v->kon_delay = 5;
|
||||
v->env_mode = env_attack;
|
||||
}
|
||||
}
|
||||
|
||||
// Run envelope for next sample
|
||||
if ( !v->kon_delay )
|
||||
run_envelope( v );
|
||||
}
|
||||
inline void bDSP::voice_output( voice_t const* v, int ch )
|
||||
{
|
||||
// Apply left/right volume
|
||||
int amp = asr<7>( m.t_output * (int8) VREG(v->regs,voll + ch) );
|
||||
|
||||
// Add to output total
|
||||
m.t_main_out [ch] = sclamp<16>( m.t_main_out [ch] + amp );
|
||||
|
||||
// Optionally add to echo total
|
||||
if ( m.t_eon & v->vbit )
|
||||
m.t_echo_out [ch] = sclamp<16>( m.t_echo_out [ch] + amp );
|
||||
}
|
||||
VOICE_CLOCK( V4 )
|
||||
{
|
||||
// Decode BRR
|
||||
m.t_looped = 0;
|
||||
if ( v->interp_pos >= 0x4000 )
|
||||
{
|
||||
decode_brr( v );
|
||||
|
||||
if ( (v->brr_offset += 2) >= brr_block_size )
|
||||
{
|
||||
// Start decoding next BRR block
|
||||
assert( v->brr_offset == brr_block_size );
|
||||
v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF;
|
||||
if ( m.t_brr_header & 1 )
|
||||
{
|
||||
v->brr_addr = m.t_brr_next_addr;
|
||||
m.t_looped = v->vbit;
|
||||
}
|
||||
v->brr_offset = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply pitch
|
||||
v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch;
|
||||
|
||||
// Keep from getting too far ahead (when using pitch modulation)
|
||||
if ( v->interp_pos > 0x7FFF )
|
||||
v->interp_pos = 0x7FFF;
|
||||
|
||||
// Output left
|
||||
voice_output( v, 0 );
|
||||
}
|
||||
inline VOICE_CLOCK( V5 )
|
||||
{
|
||||
// Output right
|
||||
voice_output( v, 1 );
|
||||
|
||||
// ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier
|
||||
int endx_buf = REG(endx) | m.t_looped;
|
||||
|
||||
// Clear bit in ENDX if KON just began
|
||||
if ( v->kon_delay == 5 )
|
||||
endx_buf &= ~v->vbit;
|
||||
m.endx_buf = (uint8) endx_buf;
|
||||
}
|
||||
inline VOICE_CLOCK( V6 )
|
||||
{
|
||||
(void) v; // avoid compiler warning about unused v
|
||||
m.outx_buf = (uint8) (m.t_output >> 8);
|
||||
}
|
||||
inline VOICE_CLOCK( V7 )
|
||||
{
|
||||
// Update ENDX
|
||||
REG(endx) = m.endx_buf;
|
||||
|
||||
m.envx_buf = v->t_envx_out;
|
||||
}
|
||||
inline VOICE_CLOCK( V8 )
|
||||
{
|
||||
// Update OUTX
|
||||
VREG(v->regs,outx) = m.outx_buf;
|
||||
}
|
||||
inline VOICE_CLOCK( V9 )
|
||||
{
|
||||
// Update ENVX
|
||||
VREG(v->regs,envx) = m.envx_buf;
|
||||
}
|
||||
|
||||
// Most voices do all these in one clock, so make a handy composite
|
||||
inline VOICE_CLOCK( V3 )
|
||||
{
|
||||
voice_V3a( v );
|
||||
voice_V3b( v );
|
||||
voice_V3c( v );
|
||||
}
|
||||
|
||||
// Common combinations of voice steps on different voices. This greatly reduces
|
||||
// code size and allows everything to be inlined in these functions.
|
||||
VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); }
|
||||
VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); }
|
||||
VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); }
|
||||
|
||||
|
||||
//// Echo
|
||||
|
||||
// Current echo buffer pointer for left/right channel
|
||||
#define ECHO_PTR( ch ) (&ram [echo_ptr + ch * 2])
|
||||
|
||||
// Sample in echo history buffer, where 0 is the oldest
|
||||
#define ECHO_FIR( i ) (m.echo_hist [(m.echo_hist_pos + (i)) & (echo_hist_size - 1)])
|
||||
|
||||
// Calculate FIR point for left/right channel
|
||||
#define CALC_FIR( i, ch ) asr<6>( ECHO_FIR( i + 1 ) [ch] * (int8) REG(fir + i * 0x10) )
|
||||
|
||||
inline int get_echo_sample( void const* p )
|
||||
{
|
||||
return ((uint8 const*) p) [0] |
|
||||
(( int8 const*) p) [1] << 8;
|
||||
}
|
||||
|
||||
inline void set_echo_sample( void* p, unsigned n )
|
||||
{
|
||||
((uint8*) p) [0] = (uint8) n;
|
||||
((uint8*) p) [1] = (uint8) (n >> 8);
|
||||
}
|
||||
|
||||
inline int bDSP::calc_echo_output( int ch, int echo_in )
|
||||
{
|
||||
return sclamp<16>(
|
||||
sclip<16>( asr<7>( m.t_main_out [ch] * (int8) REG(mvoll + ch * 0x10) ) ) +
|
||||
sclip<16>( asr<7>( echo_in * (int8) REG(evoll + ch * 0x10) ) ) );
|
||||
}
|
||||
|
||||
|
||||
//// Timing
|
||||
|
||||
void bDSP::enter()
|
||||
{
|
||||
int t_esa = REG(esa);
|
||||
|
||||
while ( 1 )
|
||||
{
|
||||
// n is currently ignored
|
||||
#define NEXT_CLOCK( n ) \
|
||||
scheduler.addclocks_dsp( 3 * 8 );
|
||||
|
||||
// Execute clock for a particular voice
|
||||
#define V( clock, voice ) voice_##clock( &m.voices [voice] );
|
||||
|
||||
/* The most common sequence of clocks uses composite operations
|
||||
for efficiency. For example, the following are equivalent to the
|
||||
individual steps on the right:
|
||||
|
||||
V(V7_V4_V1,2) -> V(V7,2) V(V4,3) V(V1,5)
|
||||
V(V8_V5_V2,2) -> V(V8,2) V(V5,3) V(V2,4)
|
||||
V(V9_V6_V3,2) -> V(V9,2) V(V6,3) V(V3,4) */
|
||||
|
||||
NEXT_CLOCK( 0) V(V5,0)V(V2,1)
|
||||
NEXT_CLOCK( 1) V(V6,0)V(V3,1)
|
||||
NEXT_CLOCK( 2) V(V7_V4_V1,0)
|
||||
NEXT_CLOCK( 3) V(V8_V5_V2,0)
|
||||
NEXT_CLOCK( 4) V(V9_V6_V3,0)
|
||||
NEXT_CLOCK( 5) V(V7_V4_V1,1)
|
||||
NEXT_CLOCK( 6) V(V8_V5_V2,1)
|
||||
NEXT_CLOCK( 7) V(V9_V6_V3,1)
|
||||
NEXT_CLOCK( 8) V(V7_V4_V1,2)
|
||||
NEXT_CLOCK( 9) V(V8_V5_V2,2)
|
||||
NEXT_CLOCK(10) V(V9_V6_V3,2)
|
||||
NEXT_CLOCK(11) V(V7_V4_V1,3)
|
||||
NEXT_CLOCK(12) V(V8_V5_V2,3)
|
||||
NEXT_CLOCK(13) V(V9_V6_V3,3)
|
||||
NEXT_CLOCK(14) V(V7_V4_V1,4)
|
||||
NEXT_CLOCK(15) V(V8_V5_V2,4)
|
||||
NEXT_CLOCK(16) V(V9_V6_V3,4)
|
||||
NEXT_CLOCK(17) V(V1,0) V(V7,5)V(V4,6)
|
||||
NEXT_CLOCK(18) V(V8_V5_V2,5)
|
||||
NEXT_CLOCK(19) V(V9_V6_V3,5)
|
||||
NEXT_CLOCK(20) V(V1,1) V(V7,6)V(V4,7)
|
||||
NEXT_CLOCK(21) V(V8,6)V(V5,7) V(V2,0) /* t_brr_next_addr order dependency */
|
||||
NEXT_CLOCK(22) V(V3a,0) V(V9,6)V(V6,7)
|
||||
|
||||
// History
|
||||
if ( ++m.echo_hist_pos >= echo_hist_size )
|
||||
m.echo_hist_pos = 0;
|
||||
|
||||
int const echo_ptr = (t_esa * 0x100 + m.echo_offset) & 0xFFFF;
|
||||
|
||||
// FIR
|
||||
int echo_in_l = CALC_FIR( 0, 0 );
|
||||
int echo_in_r = CALC_FIR( 0, 1 );
|
||||
|
||||
ECHO_FIR( 0 ) [0] = asr<1>( get_echo_sample( ECHO_PTR( 0 ) ) );
|
||||
|
||||
NEXT_CLOCK(23) V(V7,7)
|
||||
|
||||
echo_in_l += CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 );
|
||||
echo_in_r += CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 );
|
||||
|
||||
ECHO_FIR( 0 ) [1] = asr<1>( get_echo_sample( ECHO_PTR( 1 ) ) );
|
||||
|
||||
NEXT_CLOCK(24) V(V8,7)
|
||||
|
||||
echo_in_l += CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 );
|
||||
echo_in_r += CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 );
|
||||
|
||||
NEXT_CLOCK(25) V(V3b,0) V(V9,7)
|
||||
|
||||
echo_in_l = sclip<16>( echo_in_l + CALC_FIR( 6, 0 ) ) + sclip<16>( CALC_FIR( 7, 0 ) );
|
||||
echo_in_r = sclip<16>( echo_in_r + CALC_FIR( 6, 1 ) ) + sclip<16>( CALC_FIR( 7, 1 ) );
|
||||
|
||||
echo_in_l = sclamp<16>( echo_in_l ) & ~1;
|
||||
echo_in_r = sclamp<16>( echo_in_r ) & ~1;
|
||||
|
||||
NEXT_CLOCK(26)
|
||||
|
||||
// Echo feedback
|
||||
int echo_out_l = m.t_echo_out [0] + sclip<16>( asr<7>( echo_in_l * (int8) REG(efb) ) );
|
||||
int echo_out_r = m.t_echo_out [1] + sclip<16>( asr<7>( echo_in_r * (int8) REG(efb) ) );
|
||||
|
||||
echo_out_l = sclamp<16>( echo_out_l ) & ~1;
|
||||
echo_out_r = sclamp<16>( echo_out_r ) & ~1;
|
||||
|
||||
// Output
|
||||
int main_out_l = calc_echo_output( 0, echo_in_l );
|
||||
|
||||
NEXT_CLOCK(27)
|
||||
|
||||
int main_out_r = calc_echo_output( 1, echo_in_r );
|
||||
|
||||
// TODO: global muting isn't this simple (turns DAC on and off
|
||||
// or something, causing small ~37-sample pulse when first muted)
|
||||
if ( REG(flg) & 0x40 )
|
||||
{
|
||||
main_out_l = 0;
|
||||
main_out_r = 0;
|
||||
}
|
||||
|
||||
// Output sample to DAC
|
||||
snes.audio.update( main_out_l, main_out_r );
|
||||
|
||||
m.t_main_out [0] = 0;
|
||||
m.t_main_out [1] = 0;
|
||||
|
||||
m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON
|
||||
|
||||
NEXT_CLOCK(28)
|
||||
|
||||
m.t_non = REG(non);
|
||||
m.t_eon = REG(eon);
|
||||
m.t_dir = REG(dir);
|
||||
|
||||
int echo_disabled = REG(flg);
|
||||
|
||||
NEXT_CLOCK(29)
|
||||
|
||||
// Write left echo
|
||||
if ( !(echo_disabled & 0x20) )
|
||||
set_echo_sample( ECHO_PTR( 0 ), echo_out_l );
|
||||
m.t_echo_out [0] = 0;
|
||||
|
||||
t_esa = REG(esa);
|
||||
|
||||
if ( !m.echo_offset )
|
||||
m.echo_length = (REG(edl) & 0x0F) * 0x800;
|
||||
|
||||
m.echo_offset += 4;
|
||||
if ( m.echo_offset >= m.echo_length )
|
||||
m.echo_offset = 0;
|
||||
|
||||
if ( (m.every_other_sample ^= 1) != 0 )
|
||||
m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read
|
||||
|
||||
echo_disabled = REG(flg);
|
||||
|
||||
NEXT_CLOCK(30)
|
||||
|
||||
// Write right echo
|
||||
if ( !(echo_disabled & 0x20) )
|
||||
set_echo_sample( ECHO_PTR( 1 ), echo_out_r );
|
||||
m.t_echo_out [1] = 0;
|
||||
|
||||
if ( m.every_other_sample )
|
||||
{
|
||||
m.kon = m.new_kon;
|
||||
m.t_koff = REG(koff);
|
||||
}
|
||||
|
||||
if ( --m.counter < 0 )
|
||||
m.counter = simple_counter_range - 1;
|
||||
|
||||
// Noise
|
||||
if ( !read_counter( REG(flg) & 0x1F ) )
|
||||
{
|
||||
int feedback = (m.noise << 13) ^ (m.noise << 14);
|
||||
m.noise = (feedback & 0x4000) ^ (m.noise >> 1);
|
||||
}
|
||||
|
||||
V(V3c,0)
|
||||
|
||||
NEXT_CLOCK(31) V(V4,0) V(V1,2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Setup
|
||||
|
||||
bDSP::bDSP() { }
|
||||
|
||||
bDSP::~bDSP() { }
|
||||
|
||||
void bDSP::reset()
|
||||
{
|
||||
REG(flg) = 0xE0;
|
||||
|
||||
m.noise = 0x4000;
|
||||
m.echo_hist_pos = 0;
|
||||
m.every_other_sample = 1;
|
||||
m.echo_offset = 0;
|
||||
m.counter = 0;
|
||||
}
|
||||
|
||||
static uint8 const initial_regs [bDSP::register_count] =
|
||||
{
|
||||
0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80,
|
||||
0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF,
|
||||
0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A,
|
||||
0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF,
|
||||
0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67,
|
||||
0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF,
|
||||
0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F,
|
||||
0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF
|
||||
};
|
||||
|
||||
void bDSP::power()
|
||||
{
|
||||
ram = (uint8*) smp.get_spcram_handle();
|
||||
memset( &m, 0, sizeof m );
|
||||
//memcpy( m.regs, initial_regs, sizeof m.regs );
|
||||
memset(m.regs, 0, sizeof m.regs);
|
||||
REG(flg) = 0xe0;
|
||||
|
||||
// Internal state
|
||||
for ( int i = voice_count; --i >= 0; )
|
||||
{
|
||||
voice_t* v = &m.voices [i];
|
||||
v->brr_offset = 1;
|
||||
v->vbit = 1 << i;
|
||||
v->regs = &m.regs [i * 0x10];
|
||||
}
|
||||
m.new_kon = REG(kon);
|
||||
m.t_dir = REG(dir);
|
||||
|
||||
reset();
|
||||
}
|
|
@ -1,172 +0,0 @@
|
|||
class bDSP : public DSP {
|
||||
public:
|
||||
void enter();
|
||||
|
||||
uint8 read( uint8 addr );
|
||||
void write( uint8 addr, uint8 data );
|
||||
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
bDSP();
|
||||
~bDSP();
|
||||
|
||||
template<int n, typename T> inline T asr(const T x) {
|
||||
enum { bits = (sizeof(T) << 3) - n };
|
||||
return sclip<bits>(x >> n);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
enum { echo_hist_size = 8 };
|
||||
enum { register_count = 128 };
|
||||
enum { voice_count = 8 };
|
||||
|
||||
enum env_mode_t { env_release, env_attack, env_decay, env_sustain };
|
||||
enum { brr_buf_size = 12 };
|
||||
struct voice_t
|
||||
{
|
||||
int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling)
|
||||
int buf_pos; // place in buffer where next samples will be decoded
|
||||
int interp_pos; // relative fractional position in sample (0x1000 = 1.0)
|
||||
int brr_addr; // address of current BRR block
|
||||
int brr_offset; // current decoding offset in BRR block
|
||||
uint8* regs; // pointer to voice's DSP registers
|
||||
int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc.
|
||||
int kon_delay; // KON delay/current setup phase
|
||||
env_mode_t env_mode;
|
||||
int env; // current envelope level
|
||||
int hidden_env; // used by GAIN mode 7, very obscure quirk
|
||||
uint8 t_envx_out;
|
||||
};
|
||||
private:
|
||||
|
||||
struct state_t
|
||||
{
|
||||
uint8 regs [register_count];
|
||||
|
||||
// Echo history keeps most recent 8 samples
|
||||
int echo_hist [echo_hist_size] [2];
|
||||
int echo_hist_pos;
|
||||
|
||||
int every_other_sample; // toggles every sample
|
||||
int kon; // KON value when last checked
|
||||
int noise;
|
||||
int counter;
|
||||
int echo_offset; // offset from ESA in echo buffer
|
||||
int echo_length; // number of bytes that echo_offset will stop at
|
||||
|
||||
// Hidden registers also written to when main register is written to
|
||||
int new_kon;
|
||||
uint8 endx_buf;
|
||||
uint8 envx_buf;
|
||||
uint8 outx_buf;
|
||||
|
||||
// Temporary state between clocks
|
||||
|
||||
// read once per sample
|
||||
int t_pmon;
|
||||
int t_non;
|
||||
int t_eon;
|
||||
int t_dir;
|
||||
int t_koff;
|
||||
|
||||
// read a few clocks ahead then used
|
||||
int t_brr_next_addr;
|
||||
int t_adsr0;
|
||||
int t_brr_header;
|
||||
int t_brr_byte;
|
||||
int t_srcn;
|
||||
|
||||
// internal state that is recalculated every sample
|
||||
int t_dir_addr;
|
||||
int t_pitch;
|
||||
int t_output;
|
||||
int t_looped;
|
||||
|
||||
// left/right sums
|
||||
int t_main_out [2];
|
||||
int t_echo_out [2];
|
||||
|
||||
voice_t voices [voice_count];
|
||||
};
|
||||
state_t m;
|
||||
uint8* ram;
|
||||
|
||||
unsigned read_counter( int rate );
|
||||
|
||||
void run_envelope( voice_t* const v );
|
||||
void decode_brr( voice_t* v );
|
||||
|
||||
void voice_output( voice_t const* v, int ch );
|
||||
void voice_V1( voice_t* const );
|
||||
void voice_V2( voice_t* const );
|
||||
void voice_V3( voice_t* const );
|
||||
void voice_V3a( voice_t* const );
|
||||
void voice_V3b( voice_t* const );
|
||||
void voice_V3c( voice_t* const );
|
||||
void voice_V4( voice_t* const );
|
||||
void voice_V5( voice_t* const );
|
||||
void voice_V6( voice_t* const );
|
||||
void voice_V7( voice_t* const );
|
||||
void voice_V8( voice_t* const );
|
||||
void voice_V9( voice_t* const );
|
||||
void voice_V7_V4_V1( voice_t* const );
|
||||
void voice_V8_V5_V2( voice_t* const );
|
||||
void voice_V9_V6_V3( voice_t* const );
|
||||
|
||||
int calc_echo_output( int ch, int sample );
|
||||
|
||||
// Global registers
|
||||
enum {
|
||||
r_mvoll = 0x0C, r_mvolr = 0x1C,
|
||||
r_evoll = 0x2C, r_evolr = 0x3C,
|
||||
r_kon = 0x4C, r_koff = 0x5C,
|
||||
r_flg = 0x6C, r_endx = 0x7C,
|
||||
r_efb = 0x0D, r_pmon = 0x2D,
|
||||
r_non = 0x3D, r_eon = 0x4D,
|
||||
r_dir = 0x5D, r_esa = 0x6D,
|
||||
r_edl = 0x7D,
|
||||
r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F
|
||||
};
|
||||
|
||||
// Voice registers
|
||||
enum {
|
||||
v_voll = 0x00, v_volr = 0x01,
|
||||
v_pitchl = 0x02, v_pitchh = 0x03,
|
||||
v_srcn = 0x04, v_adsr0 = 0x05,
|
||||
v_adsr1 = 0x06, v_gain = 0x07,
|
||||
v_envx = 0x08, v_outx = 0x09
|
||||
};
|
||||
};
|
||||
|
||||
inline uint8 bDSP::read( uint8 addr )
|
||||
{
|
||||
return m.regs [addr];
|
||||
}
|
||||
|
||||
inline void bDSP::write( uint8 addr, uint8 data )
|
||||
{
|
||||
m.regs [addr] = data;
|
||||
switch ( addr & 0x0F )
|
||||
{
|
||||
case v_envx:
|
||||
m.envx_buf = data;
|
||||
break;
|
||||
|
||||
case v_outx:
|
||||
m.outx_buf = data;
|
||||
break;
|
||||
|
||||
case 0x0C:
|
||||
if ( addr == r_kon )
|
||||
m.new_kon = data;
|
||||
|
||||
if ( addr == r_endx ) // always cleared, regardless of data written
|
||||
{
|
||||
m.endx_buf = 0;
|
||||
m.regs [r_endx] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
#include "../../base.h"
|
||||
#include "spc_dsp.h"
|
||||
|
||||
void bDSP::power() {
|
||||
spc_dsp_init(r_smp->get_spcram_handle());
|
||||
spc_dsp_reset();
|
||||
}
|
||||
|
||||
void bDSP::reset() {
|
||||
spc_dsp_soft_reset();
|
||||
}
|
||||
|
||||
uint8 bDSP::read(uint8 addr) {
|
||||
return spc_dsp_read(addr);
|
||||
}
|
||||
|
||||
void bDSP::write(uint8 addr, uint8 data) {
|
||||
spc_dsp_write(addr, data);
|
||||
}
|
||||
|
||||
#define SPC_DSP_CUSTOM_RUN 1 //causes spc_dsp_run() to not be defined since it's huge and we don't need it
|
||||
#define SPC_DSP_OUT_HOOK(left, right) snes.audio_update(left, right);
|
||||
#include "spc_dsp.cpp"
|
||||
|
||||
void bDSP::enter() { loop:
|
||||
#define PHASE(n) scheduler.addclocks_dsp(3);
|
||||
#include "spc_dsp_timing.h"
|
||||
goto loop;
|
||||
}
|
||||
|
||||
bDSP::bDSP() {}
|
||||
bDSP::~bDSP() {}
|
|
@ -1,10 +0,0 @@
|
|||
class bDSP : public DSP { public:
|
||||
void enter();
|
||||
uint8 read(uint8 addr);
|
||||
void write(uint8 addr, uint8 data);
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
bDSP();
|
||||
~bDSP();
|
||||
};
|
|
@ -1,64 +0,0 @@
|
|||
Overall operation
|
||||
-----------------
|
||||
This DSP emulator fundamentally emulates the different options the DSP
|
||||
performs on each clock. The pattern of operations repeats every 32
|
||||
clocks (except one minor detail, which repeats every 64 clocks instead).
|
||||
There are three main types of operations:
|
||||
|
||||
- Miscellaneous processing
|
||||
- Voice processing
|
||||
- Echo processing
|
||||
|
||||
Each is done over several clocks, and several operations are done on
|
||||
each clock. Each clock is defined as a separate function, then called
|
||||
from a large switch block in a loop.
|
||||
|
||||
Many times a value is read on one clock but not used until a later
|
||||
clock, so many non-local temporary variables are used in the code to
|
||||
store these values. These are named with t_ to make it clear that they
|
||||
don't store long-term state.
|
||||
|
||||
|
||||
Circular buffers
|
||||
----------------
|
||||
Two circular buffers are used in the code (echo history and BRR decode).
|
||||
Both need efficient index-based access with wrap-around. Things are
|
||||
greatly simplified by repeating the contents of buffer twice, so instead
|
||||
of
|
||||
|
||||
0 1 2 3 4 5 6 7
|
||||
|
||||
it stores
|
||||
|
||||
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
|
||||
|
||||
The position in this case would always be 0 to 7, so reading up to +8
|
||||
won't go outside buffer. This duplication is maintained by simply
|
||||
writing data twice when filling buffer:
|
||||
|
||||
0 1 2 3 4 # 6 7 0 1 2 3 4 # 6 7
|
||||
new data -----^---------------^
|
||||
|
||||
No wrap checking needs to be done when writing either, since the above
|
||||
reasoning holds. When making a state snapshot, only the first copy needs
|
||||
to be saved. When restoring, simply duplicate the data twice.
|
||||
|
||||
|
||||
Code
|
||||
----
|
||||
- Currently all state is in static variables. They have either a t_ or
|
||||
m_ prefix to allow easy migration to a structure.
|
||||
|
||||
- Static state that persists over several samples or more is prefixed
|
||||
with m_.
|
||||
|
||||
- State which is temporary to the current sample is prefixed with t_.
|
||||
These are usually just overwritten with new data on the next sample.
|
||||
These generally correspond to temporaries/registers in actual DSP
|
||||
itself.
|
||||
|
||||
- Minimal stdint.h included in case your system doesn't have one.
|
||||
|
||||
|
||||
--
|
||||
Shay Green <gblargg@gmail.com>
|
|
@ -1,828 +0,0 @@
|
|||
// http://www.slack.net/~ant/
|
||||
|
||||
#include "spc_dsp.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
// Volume registers and efb are signed! Easy to forget int8_t cast.
|
||||
// Prefixes are to avoid accidental use of locals with same names.
|
||||
|
||||
// Global registers
|
||||
enum {
|
||||
r_mvoll = 0x0C, r_mvolr = 0x1C,
|
||||
r_evoll = 0x2C, r_evolr = 0x3C,
|
||||
r_kon = 0x4C, r_koff = 0x5C,
|
||||
r_flg = 0x6C, r_endx = 0x7C,
|
||||
r_efb = 0x0D, r_pmon = 0x2D,
|
||||
r_non = 0x3D, r_eon = 0x4D,
|
||||
r_dir = 0x5D, r_esa = 0x6D,
|
||||
r_edl = 0x7D,
|
||||
r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F
|
||||
};
|
||||
|
||||
// Voice registers
|
||||
enum {
|
||||
v_voll = 0x00, v_volr = 0x01,
|
||||
v_pitchl = 0x02, v_pitchh = 0x03,
|
||||
v_srcn = 0x04, v_adsr0 = 0x05,
|
||||
v_adsr1 = 0x06, v_gain = 0x07,
|
||||
v_envx = 0x08, v_outx = 0x09
|
||||
};
|
||||
|
||||
// Internal envelope modes
|
||||
enum env_mode_t { env_release, env_attack, env_decay, env_sustain };
|
||||
|
||||
// Internal voice state
|
||||
enum { brr_buf_size = 12 };
|
||||
enum { brr_block_size = 9 };
|
||||
typedef struct voice_t
|
||||
{
|
||||
int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling)
|
||||
int buf_pos; // place in buffer where next samples will be decoded
|
||||
int interp_pos; // relative fractional position in sample (0x1000 = 1.0)
|
||||
int brr_addr; // address of current BRR block
|
||||
int brr_offset; // current decoding offset in BRR block
|
||||
uint8_t* regs; // pointer to voice's DSP registers
|
||||
int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc.
|
||||
int kon_delay; // KON delay/current setup phase
|
||||
enum env_mode_t env_mode;
|
||||
int env; // current envelope level
|
||||
int t_envx_out;
|
||||
int hidden_env; // used by GAIN mode 7, very obscure quirk
|
||||
} voice_t;
|
||||
|
||||
static voice_t m_voice_state [spc_dsp_voice_count];
|
||||
static uint8_t* m_ram; // 64K shared RAM between DSP and SMP
|
||||
spc_dsp_t m_spc_dsp;
|
||||
spc_dsp_sample_t* m_spc_dsp_out_begin;
|
||||
spc_dsp_sample_t* m_spc_dsp_out;
|
||||
spc_dsp_sample_t* m_spc_dsp_out_end;
|
||||
|
||||
// "Member" access
|
||||
#define m m_spc_dsp
|
||||
|
||||
// Access global DSP register
|
||||
#define REG(n) m.regs [r_##n]
|
||||
|
||||
// Access voice DSP register
|
||||
#define VREG(r,n) r [v_##n]
|
||||
|
||||
// if ( io < -32768 ) io = -32768;
|
||||
// if ( io > 32767 ) io = 32767;
|
||||
#define CLAMP16( io )\
|
||||
{\
|
||||
if ( (int16_t) io != io )\
|
||||
io = 0x7FFF ^ (io >> 31);\
|
||||
}
|
||||
|
||||
// Gaussian interpolation
|
||||
|
||||
static short const gauss [512] =
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,
|
||||
2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5,
|
||||
6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
|
||||
11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17,
|
||||
18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27,
|
||||
28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
|
||||
58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77,
|
||||
78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102,
|
||||
104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132,
|
||||
134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168,
|
||||
171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210,
|
||||
212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257,
|
||||
260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311,
|
||||
314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370,
|
||||
374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434,
|
||||
439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504,
|
||||
508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577,
|
||||
582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654,
|
||||
659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732,
|
||||
737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811,
|
||||
816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889,
|
||||
894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965,
|
||||
969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036,
|
||||
1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102,
|
||||
1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160,
|
||||
1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210,
|
||||
1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251,
|
||||
1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280,
|
||||
1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298,
|
||||
1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305,
|
||||
};
|
||||
|
||||
static inline int interpolate( voice_t const* const v )
|
||||
{
|
||||
// Make pointers into gaussian based on fractional position between samples
|
||||
int offset = v->interp_pos >> 4 & 0xFF;
|
||||
short const* fwd = gauss + 255 - offset;
|
||||
short const* rev = gauss + offset; // mirror left half of gaussian
|
||||
|
||||
int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos];
|
||||
int out;
|
||||
out = (fwd [ 0] * in [0]) >> 11;
|
||||
out += (fwd [256] * in [1]) >> 11;
|
||||
out += (rev [256] * in [2]) >> 11;
|
||||
out = (int16_t) out;
|
||||
out += (rev [ 0] * in [3]) >> 11;
|
||||
|
||||
CLAMP16( out );
|
||||
out &= ~1;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
//// Counters
|
||||
|
||||
enum { simple_counter_range = 2048 * 5 * 3 }; // 30720
|
||||
|
||||
static unsigned short const counter_rates [32] =
|
||||
{
|
||||
simple_counter_range + 1, // never fires
|
||||
2048, 1536,
|
||||
1280, 1024, 768,
|
||||
640, 512, 384,
|
||||
320, 256, 192,
|
||||
160, 128, 96,
|
||||
80, 64, 48,
|
||||
40, 32, 24,
|
||||
20, 16, 12,
|
||||
10, 8, 6,
|
||||
5, 4, 3,
|
||||
2,
|
||||
1
|
||||
};
|
||||
|
||||
static unsigned short const counter_offsets [32] =
|
||||
{
|
||||
1, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
536, 0, 1040,
|
||||
0,
|
||||
0
|
||||
};
|
||||
|
||||
static inline void init_counters( void ) { }
|
||||
|
||||
static inline void run_counters( void )
|
||||
{
|
||||
if ( --m.counter < 0 )
|
||||
m.counter = simple_counter_range - 1;
|
||||
}
|
||||
|
||||
static inline unsigned read_counter( int rate )
|
||||
{
|
||||
return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate];
|
||||
}
|
||||
|
||||
|
||||
//// Envelope
|
||||
|
||||
static inline void run_envelope( voice_t* const v )
|
||||
{
|
||||
int env = v->env;
|
||||
if ( v->env_mode == env_release ) // 60%
|
||||
{
|
||||
if ( (env -= 0x8) < 0 )
|
||||
env = 0;
|
||||
v->env = env;
|
||||
}
|
||||
else
|
||||
{
|
||||
int rate;
|
||||
int env_data = VREG(v->regs,adsr1);
|
||||
if ( m.t_adsr0 & 0x80 ) // 99% ADSR
|
||||
{
|
||||
if ( v->env_mode >= env_decay ) // 99%
|
||||
{
|
||||
env--;
|
||||
env -= env >> 8;
|
||||
rate = env_data & 0x1F;
|
||||
if ( v->env_mode == env_decay ) // 1%
|
||||
rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10;
|
||||
}
|
||||
else // env_attack
|
||||
{
|
||||
rate = (m.t_adsr0 & 0x0F) * 2 + 1;
|
||||
env += rate < 31 ? 0x20 : 0x400;
|
||||
}
|
||||
}
|
||||
else // GAIN
|
||||
{
|
||||
env_data = VREG(v->regs,gain);
|
||||
int mode = env_data >> 5;
|
||||
if ( mode < 4 ) // direct
|
||||
{
|
||||
env = env_data * 0x10;
|
||||
rate = 31;
|
||||
}
|
||||
else
|
||||
{
|
||||
rate = env_data & 0x1F;
|
||||
if ( mode == 4 ) // 4: linear decrease
|
||||
{
|
||||
env -= 0x20;
|
||||
}
|
||||
else if ( mode < 6 ) // 5: exponential decrease
|
||||
{
|
||||
env--;
|
||||
env -= env >> 8;
|
||||
}
|
||||
else // 6,7: linear increase
|
||||
{
|
||||
env += 0x20;
|
||||
if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 )
|
||||
env += 0x8 - 0x20; // 7: two-slope linear increase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sustain level
|
||||
if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay )
|
||||
v->env_mode = env_sustain;
|
||||
|
||||
v->hidden_env = env;
|
||||
|
||||
// unsigned cast because linear decrease going negative also triggers this
|
||||
if ( (unsigned) env > 0x7FF )
|
||||
{
|
||||
env = (env < 0 ? 0 : 0x7FF);
|
||||
if ( v->env_mode == env_attack )
|
||||
v->env_mode = env_decay;
|
||||
}
|
||||
|
||||
if ( !read_counter( rate ) )
|
||||
v->env = env; // nothing else is controlled by the counter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// BRR Decoding
|
||||
|
||||
static inline void decode_brr( voice_t* v )
|
||||
{
|
||||
// Arrange the four input nybbles in 0xABCD order for easy decoding
|
||||
int nybbles = m.t_brr_byte * 0x100 + m_ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF];
|
||||
|
||||
int const header = m.t_brr_header;
|
||||
|
||||
// 0: >>1 1: <<0 2: <<1 ... 12: <<11 13-15: >>4 <<11
|
||||
static unsigned char const shifts [16 * 2] = {
|
||||
13,12,12,12,12,12,12,12,12,12,12, 12, 12, 16, 16, 16,
|
||||
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 11, 11
|
||||
};
|
||||
int const scale = header >> 4;
|
||||
int const right_shift = shifts [scale];
|
||||
int const left_shift = shifts [scale + 16];
|
||||
|
||||
// Write to next four samples in circular buffer
|
||||
int* pos = &v->buf [v->buf_pos];
|
||||
if ( (v->buf_pos += 4) >= brr_buf_size )
|
||||
v->buf_pos = 0;
|
||||
|
||||
// Decode four samples
|
||||
for ( int* end = pos + 4; pos < end; pos++ )
|
||||
{
|
||||
// Extract upper nybble and scale appropriately
|
||||
int s = ((int16_t) nybbles >> right_shift) << left_shift;
|
||||
nybbles <<= 4;
|
||||
|
||||
// Apply IIR filter (8 is the most commonly used)
|
||||
int const filter = header & 0x0C;
|
||||
int const p1 = pos [brr_buf_size - 1];
|
||||
int const p2 = pos [brr_buf_size - 2] >> 1;
|
||||
if ( filter >= 8 )
|
||||
{
|
||||
s += p1;
|
||||
s -= p2;
|
||||
if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875
|
||||
{
|
||||
s += p2 >> 4;
|
||||
s += (p1 * -3) >> 6;
|
||||
}
|
||||
else // s += p1 * 0.8984375 - p2 * 0.40625
|
||||
{
|
||||
s += (p1 * -13) >> 7;
|
||||
s += (p2 * 3) >> 4;
|
||||
}
|
||||
}
|
||||
else if ( filter ) // s += p1 * 0.46875
|
||||
{
|
||||
s += p1 >> 1;
|
||||
s += (-p1) >> 5;
|
||||
}
|
||||
|
||||
// Adjust and write sample
|
||||
CLAMP16( s );
|
||||
s = (int16_t) (s * 2);
|
||||
pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Misc
|
||||
|
||||
#define MISC_CLOCK( n ) inline static void misc_##n( void )
|
||||
|
||||
MISC_CLOCK( 27 )
|
||||
{
|
||||
m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON
|
||||
}
|
||||
MISC_CLOCK( 28 )
|
||||
{
|
||||
m.t_non = REG(non);
|
||||
m.t_eon = REG(eon);
|
||||
m.t_dir = REG(dir);
|
||||
}
|
||||
MISC_CLOCK( 29 )
|
||||
{
|
||||
if ( (m.every_other_sample ^= 1) != 0 )
|
||||
m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read
|
||||
}
|
||||
MISC_CLOCK( 30 )
|
||||
{
|
||||
if ( m.every_other_sample )
|
||||
{
|
||||
m.kon = m.new_kon;
|
||||
m.t_koff = REG(koff) | m.mute_mask;
|
||||
}
|
||||
|
||||
run_counters();
|
||||
|
||||
// Noise
|
||||
if ( !read_counter( REG(flg) & 0x1F ) )
|
||||
{
|
||||
int feedback = (m.noise << 13) ^ (m.noise << 14);
|
||||
m.noise = (feedback & 0x4000) ^ (m.noise >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Voices
|
||||
|
||||
#define VOICE_CLOCK( n ) static void voice_##n( voice_t* const v )
|
||||
|
||||
inline VOICE_CLOCK( V1 )
|
||||
{
|
||||
m.t_dir_addr = m.t_dir * 0x100 + m.t_srcn * 4;
|
||||
m.t_srcn = VREG(v->regs,srcn);
|
||||
}
|
||||
inline VOICE_CLOCK( V2 )
|
||||
{
|
||||
// Read sample pointer (ignored if not needed)
|
||||
uint8_t const* entry = &m_ram [m.t_dir_addr];
|
||||
if ( !v->kon_delay )
|
||||
entry += 2;
|
||||
m.t_brr_next_addr = entry [1] * 0x100 + entry [0];
|
||||
|
||||
m.t_adsr0 = VREG(v->regs,adsr0);
|
||||
|
||||
// Read pitch, spread over two clocks
|
||||
m.t_pitch = VREG(v->regs,pitchl);
|
||||
}
|
||||
inline VOICE_CLOCK( V3a )
|
||||
{
|
||||
m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8;
|
||||
}
|
||||
inline VOICE_CLOCK( V3b )
|
||||
{
|
||||
// Read BRR header and byte
|
||||
m.t_brr_byte = m_ram [(v->brr_addr + v->brr_offset) & 0xFFFF];
|
||||
m.t_brr_header = m_ram [v->brr_addr]; // brr_addr doesn't need masking
|
||||
}
|
||||
VOICE_CLOCK( V3c )
|
||||
{
|
||||
// Pitch modulation using previous voice's output
|
||||
if ( m.t_pmon & v->vbit )
|
||||
m.t_pitch += ((m.t_output >> 5) * m.t_pitch) >> 10;
|
||||
|
||||
if ( v->kon_delay )
|
||||
{
|
||||
// Get ready to start BRR decoding on next sample
|
||||
if ( v->kon_delay == 5 )
|
||||
{
|
||||
v->brr_addr = m.t_brr_next_addr;
|
||||
v->brr_offset = 1;
|
||||
v->buf_pos = 0;
|
||||
m.t_brr_header = 0; // header is ignored on this sample
|
||||
}
|
||||
|
||||
// Envelope is never run during KON
|
||||
v->env = 0;
|
||||
v->hidden_env = 0;
|
||||
|
||||
// Disable BRR decoding until last three samples
|
||||
v->interp_pos = 0;
|
||||
if ( --v->kon_delay & 3 )
|
||||
v->interp_pos = 0x4000;
|
||||
|
||||
// Pitch is never added during KON
|
||||
m.t_pitch = 0;
|
||||
}
|
||||
|
||||
// Gaussian interpolation
|
||||
int output = interpolate( v );
|
||||
|
||||
// Noise
|
||||
if ( m.t_non & v->vbit )
|
||||
output = (int16_t) (m.noise * 2);
|
||||
|
||||
// Apply envelope
|
||||
m.t_output = (output * v->env) >> 11 & ~1;
|
||||
v->t_envx_out = v->env >> 4;
|
||||
|
||||
// Immediate silence due to end of sample or soft reset
|
||||
if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 )
|
||||
{
|
||||
v->env_mode = env_release;
|
||||
v->env = 0;
|
||||
}
|
||||
|
||||
if ( m.every_other_sample )
|
||||
{
|
||||
// KOFF
|
||||
if ( m.t_koff & v->vbit )
|
||||
v->env_mode = env_release;
|
||||
|
||||
// KON
|
||||
if ( m.kon & v->vbit )
|
||||
{
|
||||
v->kon_delay = 5;
|
||||
v->env_mode = env_attack;
|
||||
}
|
||||
}
|
||||
|
||||
// Run envelope for next sample
|
||||
if ( !v->kon_delay )
|
||||
run_envelope( v );
|
||||
}
|
||||
static inline void voice_output( voice_t const* v, int ch )
|
||||
{
|
||||
// Apply left/right volume
|
||||
int amp = (m.t_output * (int8_t) VREG(v->regs,voll + ch)) >> 7;
|
||||
|
||||
// Add to output total
|
||||
m.t_main_out [ch] += amp;
|
||||
CLAMP16( m.t_main_out [ch] );
|
||||
|
||||
// Optionally add to echo total
|
||||
if ( m.t_eon & v->vbit )
|
||||
{
|
||||
m.t_echo_out [ch] += amp;
|
||||
CLAMP16( m.t_echo_out [ch] );
|
||||
}
|
||||
}
|
||||
VOICE_CLOCK( V4 )
|
||||
{
|
||||
// Decode BRR
|
||||
m.t_looped = 0;
|
||||
if ( v->interp_pos >= 0x4000 )
|
||||
{
|
||||
decode_brr( v );
|
||||
|
||||
if ( (v->brr_offset += 2) >= brr_block_size )
|
||||
{
|
||||
// Start decoding next BRR block
|
||||
assert( v->brr_offset == brr_block_size );
|
||||
v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF;
|
||||
if ( m.t_brr_header & 1 )
|
||||
{
|
||||
v->brr_addr = m.t_brr_next_addr;
|
||||
m.t_looped = v->vbit;
|
||||
}
|
||||
v->brr_offset = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply pitch
|
||||
v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch;
|
||||
|
||||
// Keep from getting too far ahead (when using pitch modulation)
|
||||
if ( v->interp_pos > 0x7FFF )
|
||||
v->interp_pos = 0x7FFF;
|
||||
|
||||
// Output left
|
||||
voice_output( v, 0 );
|
||||
}
|
||||
inline VOICE_CLOCK( V5 )
|
||||
{
|
||||
// Output right
|
||||
voice_output( v, 1 );
|
||||
|
||||
// ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier
|
||||
|
||||
m.endx_buf = REG(endx) | m.t_looped;
|
||||
|
||||
// Clear bit in ENDX if KON just began
|
||||
if ( v->kon_delay == 5 )
|
||||
m.endx_buf &= ~v->vbit;
|
||||
}
|
||||
inline VOICE_CLOCK( V6 )
|
||||
{
|
||||
m.outx_buf = m.t_output >> 8;
|
||||
}
|
||||
inline VOICE_CLOCK( V7 )
|
||||
{
|
||||
// Update ENDX
|
||||
REG(endx) = (uint8_t) m.endx_buf;
|
||||
|
||||
m.envx_buf = v->t_envx_out;
|
||||
}
|
||||
inline VOICE_CLOCK( V8 )
|
||||
{
|
||||
// Update OUTX
|
||||
VREG(v->regs,outx) = (uint8_t) m.outx_buf;
|
||||
}
|
||||
inline VOICE_CLOCK( V9 )
|
||||
{
|
||||
// Update ENVX
|
||||
VREG(v->regs,envx) = (uint8_t) m.envx_buf;
|
||||
}
|
||||
|
||||
// Most voices do all these in one clock, so make a handy composite
|
||||
inline VOICE_CLOCK( V3 )
|
||||
{
|
||||
voice_V3a( v );
|
||||
voice_V3b( v );
|
||||
voice_V3c( v );
|
||||
}
|
||||
|
||||
// Common combinations of voice steps on different voices. This greatly reduces
|
||||
// code size and allows everything to be inlined in these functions.
|
||||
VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); }
|
||||
VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); }
|
||||
VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); }
|
||||
|
||||
|
||||
//// Echo
|
||||
|
||||
// Current echo buffer pointer for left/right channel
|
||||
#define ECHO_PTR( ch ) (&m_ram [m.t_echo_ptr + ch * 2])
|
||||
|
||||
// Sample in echo history buffer, where 0 is the oldest
|
||||
#define ECHO_FIR( i ) (m.echo_hist_pos [i])
|
||||
|
||||
// Calculate FIR point for left/right channel
|
||||
#define CALC_FIR( i, ch ) ((ECHO_FIR( i + 1 ) [ch] * (int8_t) REG(fir + i * 0x10)) >> 6)
|
||||
|
||||
#define ECHO_CLOCK( n ) inline static void echo_##n( void )
|
||||
|
||||
static inline void echo_read( int ch )
|
||||
{
|
||||
uint8_t const* in = ECHO_PTR( ch );
|
||||
int s = (int8_t) in [1] * 0x100 + in [0];
|
||||
// second copy simplifies wrap-around handling
|
||||
ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1;
|
||||
}
|
||||
|
||||
ECHO_CLOCK( 22 )
|
||||
{
|
||||
// History
|
||||
if ( ++m.echo_hist_pos >= &m.echo_hist [spc_dsp_echo_hist_size] )
|
||||
m.echo_hist_pos = m.echo_hist;
|
||||
|
||||
m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF;
|
||||
echo_read( 0 );
|
||||
|
||||
// FIR (using l and r temporaries below helps compiler optimize)
|
||||
int l = CALC_FIR( 0, 0 );
|
||||
int r = CALC_FIR( 0, 1 );
|
||||
|
||||
m.t_echo_in [0] = l;
|
||||
m.t_echo_in [1] = r;
|
||||
}
|
||||
ECHO_CLOCK( 23 )
|
||||
{
|
||||
int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 );
|
||||
int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 );
|
||||
|
||||
m.t_echo_in [0] += l;
|
||||
m.t_echo_in [1] += r;
|
||||
|
||||
echo_read( 1 );
|
||||
}
|
||||
ECHO_CLOCK( 24 )
|
||||
{
|
||||
int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 );
|
||||
int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 );
|
||||
|
||||
m.t_echo_in [0] += l;
|
||||
m.t_echo_in [1] += r;
|
||||
}
|
||||
ECHO_CLOCK( 25 )
|
||||
{
|
||||
int l = m.t_echo_in [0] + CALC_FIR( 6, 0 );
|
||||
int r = m.t_echo_in [1] + CALC_FIR( 6, 1 );
|
||||
|
||||
l = (int16_t) l;
|
||||
r = (int16_t) r;
|
||||
|
||||
l += (int16_t) CALC_FIR( 7, 0 );
|
||||
r += (int16_t) CALC_FIR( 7, 1 );
|
||||
|
||||
CLAMP16( l );
|
||||
CLAMP16( r );
|
||||
|
||||
m.t_echo_in [0] = l & ~1;
|
||||
m.t_echo_in [1] = r & ~1;
|
||||
}
|
||||
static inline int echo_output( int ch )
|
||||
{
|
||||
int out = (int16_t) ((m.t_main_out [ch] * (int8_t) REG(mvoll + ch * 0x10)) >> 7) +
|
||||
(int16_t) ((m.t_echo_in [ch] * (int8_t) REG(evoll + ch * 0x10)) >> 7);
|
||||
CLAMP16( out );
|
||||
return out;
|
||||
}
|
||||
ECHO_CLOCK( 26 )
|
||||
{
|
||||
// Left output volumes
|
||||
// (save sample for next clock so we can output both together)
|
||||
m.t_main_out [0] = echo_output( 0 );
|
||||
|
||||
// Echo feedback
|
||||
int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7);
|
||||
int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7);
|
||||
|
||||
CLAMP16( l );
|
||||
CLAMP16( r );
|
||||
|
||||
m.t_echo_out [0] = l & ~1;
|
||||
m.t_echo_out [1] = r & ~1;
|
||||
}
|
||||
ECHO_CLOCK( 27 )
|
||||
{
|
||||
// Output
|
||||
int outl = m.t_main_out [0];
|
||||
int outr = echo_output( 1 );
|
||||
m.t_main_out [0] = 0;
|
||||
m.t_main_out [1] = 0;
|
||||
|
||||
// TODO: global muting isn't this simple (turns DAC on and off
|
||||
// or something, causing small ~37-sample pulse when first muted)
|
||||
if ( REG(flg) & 0x40 )
|
||||
{
|
||||
outl = 0;
|
||||
outr = 0;
|
||||
}
|
||||
|
||||
// Output sample to DAC
|
||||
#ifdef SPC_DSP_OUT_HOOK
|
||||
SPC_DSP_OUT_HOOK( outl, outr );
|
||||
#else
|
||||
spc_dsp_sample_t* out = m_spc_dsp_out;
|
||||
assert( !out || out < m_spc_dsp_out_end ); // fails if output buffer is too small
|
||||
if ( out != m_spc_dsp_out_end )
|
||||
{
|
||||
out [0] = outl;
|
||||
out [1] = outr;
|
||||
m_spc_dsp_out = out + 2;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
ECHO_CLOCK( 28 )
|
||||
{
|
||||
m.t_echo_enabled = REG(flg);
|
||||
}
|
||||
static inline void echo_write( int ch )
|
||||
{
|
||||
if ( !(m.t_echo_enabled & 0x20) )
|
||||
{
|
||||
uint8_t* out = ECHO_PTR( ch );
|
||||
int s = m.t_echo_out [ch];
|
||||
out [0] = (uint8_t) s;
|
||||
out [1] = (uint8_t) (s >> 8);
|
||||
}
|
||||
m.t_echo_out [ch] = 0;
|
||||
}
|
||||
ECHO_CLOCK( 29 )
|
||||
{
|
||||
m.t_esa = REG(esa);
|
||||
|
||||
if ( !m.echo_offset )
|
||||
m.echo_length = (REG(edl) & 0x0F) * 0x800;
|
||||
|
||||
m.echo_offset += 4;
|
||||
if ( m.echo_offset >= m.echo_length )
|
||||
m.echo_offset = 0;
|
||||
|
||||
// Write left echo
|
||||
echo_write( 0 );
|
||||
|
||||
m.t_echo_enabled = REG(flg);
|
||||
}
|
||||
ECHO_CLOCK( 30 )
|
||||
{
|
||||
// Write right echo
|
||||
echo_write( 1 );
|
||||
}
|
||||
|
||||
|
||||
//// Timing
|
||||
|
||||
#if !SPC_DSP_CUSTOM_RUN
|
||||
|
||||
void spc_dsp_run( int clocks_remain )
|
||||
{
|
||||
assert( clocks_remain > 0 );
|
||||
|
||||
int const phase = m.phase;
|
||||
m.phase = (phase + clocks_remain) & 31;
|
||||
switch ( phase )
|
||||
{
|
||||
loop:
|
||||
|
||||
#define PHASE( n ) if ( n && !--clocks_remain ) break; case n:
|
||||
#include "spc_dsp_timing.h"
|
||||
#undef PHASE
|
||||
|
||||
if ( --clocks_remain )
|
||||
goto loop;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
//// Setup
|
||||
|
||||
void spc_dsp_reset( void )
|
||||
{
|
||||
// Clear everything to zero, then set things which must be non-zero
|
||||
memset( m_voice_state, 0, sizeof m_voice_state );
|
||||
memset( &m_spc_dsp, 0, sizeof m_spc_dsp );
|
||||
|
||||
m.noise = 1;
|
||||
m.echo_hist_pos = m.echo_hist;
|
||||
m.every_other_sample = 1;
|
||||
init_counters();
|
||||
|
||||
int i;
|
||||
for ( i = spc_dsp_voice_count; --i >= 0; )
|
||||
{
|
||||
voice_t* v = &m_voice_state [i];
|
||||
v->regs = &m.regs [i * 0x10];
|
||||
v->vbit = 1 << i;
|
||||
v->brr_offset = 1;
|
||||
}
|
||||
|
||||
REG(flg) = 0xE0;
|
||||
}
|
||||
|
||||
void spc_dsp_soft_reset( void )
|
||||
{
|
||||
// TODO: doesn't reset everything
|
||||
spc_dsp_reset();
|
||||
}
|
||||
|
||||
void spc_dsp_init( void* ram_64k )
|
||||
{
|
||||
m_ram = (uint8_t*) ram_64k;
|
||||
spc_dsp_reset();
|
||||
|
||||
#if INT_MAX < 0x7FFFFFFF
|
||||
#error "Requires that int have at least 32 bits"
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
// be sure this sign-extends
|
||||
assert( (int16_t) 0x8000 == -0x8000 );
|
||||
|
||||
// be sure right shift preserves sign
|
||||
assert( (-1 >> 1) == -1 );
|
||||
|
||||
// check clamp macro
|
||||
int i;
|
||||
i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF );
|
||||
i = -0x8001; CLAMP16( i ); assert( i == -0x8000 );
|
||||
#endif
|
||||
}
|
||||
|
||||
void spc_dsp_load( uint8_t const regs [spc_dsp_register_count] )
|
||||
{
|
||||
int i;
|
||||
for ( i = 0; i < 0x80; i++ )
|
||||
spc_dsp_write( i, regs [i] );
|
||||
m.t_esa = regs [r_esa];
|
||||
m.t_dir = regs [r_dir];
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
// SNES SPC-700 DSP emulator
|
||||
|
||||
#ifndef SPC_DSP_H
|
||||
#define SPC_DSP_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//// Setup
|
||||
|
||||
// Initializes DSP and has it use the 64K RAM provided
|
||||
void spc_dsp_init( void* ram_64k );
|
||||
|
||||
// Restores DSP registers using supplied values
|
||||
enum { spc_dsp_register_count = 128 };
|
||||
void spc_dsp_load( uint8_t const regs [spc_dsp_register_count] );
|
||||
|
||||
// Mutes voice n if bit 1<<b is set
|
||||
enum { spc_dsp_voice_count = 8 };
|
||||
static void spc_dsp_mute_voices( int mask );
|
||||
|
||||
// Sets destination for output samples. If out is NULL or out_size is 0,
|
||||
// doesn't generate any.
|
||||
typedef short spc_dsp_sample_t;
|
||||
static void spc_dsp_set_output( spc_dsp_sample_t* out, int out_size );
|
||||
|
||||
// Number of samples written to output since it was last set, always
|
||||
// a multiple of 2
|
||||
static int spc_dsp_sample_count( void );
|
||||
|
||||
|
||||
//// Emulation
|
||||
|
||||
// Resets DSP to power-on state. Does not affect anything set by above functions.
|
||||
void spc_dsp_reset( void );
|
||||
|
||||
// Emulates pressing reset switch on SNES
|
||||
void spc_dsp_soft_reset( void );
|
||||
|
||||
// Reads/writes DSP registers. For accuracy, you must first call spc_run_dsp()
|
||||
// to catch the DSP up to present.
|
||||
static int spc_dsp_read( int addr );
|
||||
static void spc_dsp_write( int addr, int data );
|
||||
|
||||
// Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks
|
||||
// a pair of samples will be generated.
|
||||
void spc_dsp_run( int clock_count );
|
||||
|
||||
|
||||
//// private
|
||||
|
||||
enum { spc_dsp_echo_hist_size = 8 };
|
||||
|
||||
extern spc_dsp_sample_t* m_spc_dsp_out_begin;
|
||||
extern spc_dsp_sample_t* m_spc_dsp_out;
|
||||
extern spc_dsp_sample_t* m_spc_dsp_out_end;
|
||||
|
||||
typedef struct spc_dsp_t
|
||||
{
|
||||
uint8_t regs [spc_dsp_register_count];
|
||||
|
||||
// Echo history keeps most recent 8 samples (twice the size to simplify wrap handling)
|
||||
int echo_hist [spc_dsp_echo_hist_size * 2] [2];
|
||||
int (*echo_hist_pos) [2]; // &echo_hist [0 to 7]
|
||||
int mute_mask;
|
||||
|
||||
int every_other_sample; // toggles every sample
|
||||
int kon; // KON value when last checked
|
||||
int noise;
|
||||
int counter;
|
||||
int echo_offset; // offset from ESA in echo buffer
|
||||
int echo_length; // number of bytes that echo_offset will stop at
|
||||
int phase; // next clock cycle to run (0-31)
|
||||
|
||||
// Hidden registers also written to when main register is written to
|
||||
int new_kon;
|
||||
int endx_buf;
|
||||
int envx_buf;
|
||||
int outx_buf;
|
||||
|
||||
// Temporary state between clocks
|
||||
|
||||
// read once per sample
|
||||
int t_pmon;
|
||||
int t_non;
|
||||
int t_eon;
|
||||
int t_dir;
|
||||
int t_koff;
|
||||
|
||||
// read a few clocks ahead then used
|
||||
int t_brr_next_addr;
|
||||
int t_adsr0;
|
||||
int t_brr_header;
|
||||
int t_brr_byte;
|
||||
int t_srcn;
|
||||
int t_esa;
|
||||
int t_echo_enabled;
|
||||
|
||||
// internal state that is recalculated every sample
|
||||
int t_dir_addr;
|
||||
int t_pitch;
|
||||
int t_output;
|
||||
int t_looped;
|
||||
int t_echo_ptr;
|
||||
|
||||
// left/right sums
|
||||
int t_main_out [2];
|
||||
int t_echo_out [2];
|
||||
int t_echo_in [2];
|
||||
|
||||
} spc_dsp_t;
|
||||
extern spc_dsp_t m_spc_dsp;
|
||||
|
||||
static inline int spc_dsp_read( int addr )
|
||||
{
|
||||
assert( (unsigned) addr < spc_dsp_register_count );
|
||||
return m_spc_dsp.regs [addr];
|
||||
}
|
||||
|
||||
static inline void spc_dsp_write( int addr, int data )
|
||||
{
|
||||
assert( (unsigned) addr < spc_dsp_register_count );
|
||||
|
||||
m_spc_dsp.regs [addr] = data;
|
||||
switch ( addr & 0x0F )
|
||||
{
|
||||
case 0x08:
|
||||
m_spc_dsp.envx_buf = data;
|
||||
break;
|
||||
|
||||
case 0x09:
|
||||
m_spc_dsp.outx_buf = data;
|
||||
break;
|
||||
|
||||
case 0x0C:
|
||||
if ( addr == 0x4C ) // KON
|
||||
m_spc_dsp.new_kon = data;
|
||||
|
||||
if ( addr == 0x7C ) // ENDX, write always clears it regardless of data
|
||||
{
|
||||
m_spc_dsp.endx_buf = 0;
|
||||
m_spc_dsp.regs [0x7C] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void spc_dsp_mute_voices( int mask ) { m_spc_dsp.mute_mask = mask; }
|
||||
|
||||
static inline void spc_dsp_set_output( spc_dsp_sample_t* out, int out_size )
|
||||
{
|
||||
assert( out_size % 2 == 0 ); // must be even
|
||||
m_spc_dsp_out_begin = out;
|
||||
m_spc_dsp_out = out;
|
||||
if ( out )
|
||||
out += out_size;
|
||||
m_spc_dsp_out_end = out;
|
||||
}
|
||||
|
||||
static inline int spc_dsp_sample_count( void ) { return m_spc_dsp_out - m_spc_dsp_out_begin; }
|
||||
|
||||
#endif
|
|
@ -1,46 +0,0 @@
|
|||
// Execute clock for a particular voice
|
||||
#define V( clock, voice ) voice_##clock( &m_voice_state [voice] );
|
||||
|
||||
/* The most common sequence of clocks uses composite operations
|
||||
for efficiency. For example, the following are equivalent to the
|
||||
individual steps on the right:
|
||||
|
||||
V(2_31 ,2) -> V( 2,2) V(31,3)
|
||||
V(3_0_29,2) -> V( 3,2) V( 0,3) V(29,4)
|
||||
V(4_1_30,2) -> V( 4,2) V( 1,3) V(30,4) */
|
||||
|
||||
// Voice 0 1 2 3 4 5 6 7
|
||||
PHASE( 0) V(V5,0)V(V2,1)
|
||||
PHASE( 1) V(V6,0)V(V3,1)
|
||||
PHASE( 2) V(V7_V4_V1,0)
|
||||
PHASE( 3) V(V8_V5_V2,0)
|
||||
PHASE( 4) V(V9_V6_V3,0)
|
||||
PHASE( 5) V(V7_V4_V1,1)
|
||||
PHASE( 6) V(V8_V5_V2,1)
|
||||
PHASE( 7) V(V9_V6_V3,1)
|
||||
PHASE( 8) V(V7_V4_V1,2)
|
||||
PHASE( 9) V(V8_V5_V2,2)
|
||||
PHASE(10) V(V9_V6_V3,2)
|
||||
PHASE(11) V(V7_V4_V1,3)
|
||||
PHASE(12) V(V8_V5_V2,3)
|
||||
PHASE(13) V(V9_V6_V3,3)
|
||||
PHASE(14) V(V7_V4_V1,4)
|
||||
PHASE(15) V(V8_V5_V2,4)
|
||||
PHASE(16) V(V9_V6_V3,4)
|
||||
PHASE(17) V(V1,0) V(V7,5)V(V4,6)
|
||||
PHASE(18) V(V8_V5_V2,5)
|
||||
PHASE(19) V(V9_V6_V3,5)
|
||||
PHASE(20) V(V1,1) V(V7,6)V(V4,7)
|
||||
PHASE(21) V(V8,6)V(V5,7) V(V2,0) // t_brr_next_addr order dependency
|
||||
PHASE(22) V(V3a,0) V(V9,6)V(V6,7) echo_22();
|
||||
PHASE(23) V(V7,7) echo_23();
|
||||
PHASE(24) V(V8,7) echo_24();
|
||||
PHASE(25) V(V3b,0) V(V9,7) echo_25();
|
||||
PHASE(26) echo_26();
|
||||
PHASE(27) misc_27(); echo_27();
|
||||
PHASE(28) misc_28(); echo_28();
|
||||
PHASE(29) misc_29(); echo_29();
|
||||
PHASE(30) misc_30();V(V3c,0) echo_30();
|
||||
PHASE(31) V(V4,0) V(V1,2)
|
||||
|
||||
#undef V
|
|
@ -1,43 +0,0 @@
|
|||
// Byte order handling
|
||||
|
||||
#ifndef SPC_ENDIAN_H
|
||||
#define SPC_ENDIAN_H
|
||||
|
||||
//#include <stdint.h>
|
||||
|
||||
static inline unsigned get_le16( void const* p )
|
||||
{
|
||||
return ((uint8_t const*) p) [1] * 0x100u +
|
||||
((uint8_t const*) p) [0];
|
||||
}
|
||||
|
||||
static inline int get_le16s( void const* p )
|
||||
{
|
||||
return ((int8_t const*) p) [1] * 0x100 +
|
||||
((uint8_t const*) p) [0];
|
||||
}
|
||||
|
||||
static inline void set_le16( void* p, unsigned n )
|
||||
{
|
||||
((uint8_t*) p) [1] = (uint8_t) (n >> 8);
|
||||
((uint8_t*) p) [0] = (uint8_t) n;
|
||||
}
|
||||
|
||||
// *A versions are used where data is aligned
|
||||
// Sometimes BIG_ENDIAN is defined to 0x1234 or something, so treat values
|
||||
// other than 1 as false.
|
||||
#if BIG_ENDIAN != 1
|
||||
#define GET_LE16A( addr ) (*(uint16_t const*) (addr))
|
||||
#define GET_LE16SA( addr ) (*( int16_t const*) (addr))
|
||||
#define SET_LE16A( addr, data ) (void) (*(uint16_t*) (addr) = (data))
|
||||
|
||||
#else
|
||||
#define GET_LE16A( addr ) get_le16 ( addr )
|
||||
#define GET_LE16SA( addr ) get_le16s( addr )
|
||||
#define SET_LE16A( addr, data ) set_le16 ( addr, data )
|
||||
#endif
|
||||
|
||||
#define GET_LE16( addr ) GET_LE16A( addr )
|
||||
#define SET_LE16( addr, data ) SET_LE16A( addr, data )
|
||||
|
||||
#endif
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
/*
|
||||
S-DSP emulator
|
||||
license: LGPLv2
|
||||
|
@ -7,7 +6,7 @@
|
|||
The actual algorithms, timing information, tables, variable names, etc were all from him.
|
||||
*/
|
||||
|
||||
#include "../../base.h"
|
||||
#include <../base.hpp>
|
||||
#define SDSP_CPP
|
||||
|
||||
#define REG(n) state.regs[r_##n]
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
#include "reader/reader.h"
|
||||
#include "cheat/cheat.h"
|
||||
#include "config/config.h"
|
||||
|
||||
#include "memory/memory.h"
|
||||
#include "memory/smemory/smemory.h"
|
||||
|
||||
#include "cart/cart.h"
|
||||
|
||||
#include "cpu/cpu.h"
|
||||
#include "cpu/scpu/scpu.h"
|
||||
|
||||
#include "smp/smp.h"
|
||||
#include "smp/ssmp/ssmp.h"
|
||||
|
||||
#include "dsp/dsp.h"
|
||||
#include "dsp/sdsp/sdsp.h"
|
||||
|
||||
#include "ppu/ppu.h"
|
||||
#include "ppu/bppu/bppu.h"
|
||||
|
||||
#ifdef INTERFACE_MAIN
|
||||
#define extern
|
||||
#endif
|
||||
|
||||
extern BUSCORE bus;
|
||||
extern CPUCORE cpu;
|
||||
extern SMPCORE smp;
|
||||
extern DSPCORE dsp;
|
||||
extern PPUCORE ppu;
|
||||
|
||||
#undef extern
|
||||
|
||||
#include "snes/snes.h"
|
||||
#include "chip/chip.h"
|
||||
|
||||
#ifdef INTERFACE_MAIN
|
||||
#include "config/config.cpp"
|
||||
#endif
|
|
@ -0,0 +1,29 @@
|
|||
#include "reader/reader.hpp"
|
||||
#include "cheat/cheat.hpp"
|
||||
#include "config/config.hpp"
|
||||
|
||||
#include "memory/memory.hpp"
|
||||
#include "memory/smemory/smemory.hpp"
|
||||
|
||||
#include "cart/cart.hpp"
|
||||
|
||||
#include "cpu/cpu.hpp"
|
||||
#include "cpu/scpu/scpu.hpp"
|
||||
|
||||
#include "smp/smp.hpp"
|
||||
#include "smp/ssmp/ssmp.hpp"
|
||||
|
||||
#include "dsp/dsp.hpp"
|
||||
#include "dsp/sdsp/sdsp.hpp"
|
||||
|
||||
#include "ppu/ppu.hpp"
|
||||
#include "ppu/bppu/bppu.hpp"
|
||||
|
||||
extern BUSCORE bus;
|
||||
extern CPUCORE cpu;
|
||||
extern SMPCORE smp;
|
||||
extern DSPCORE dsp;
|
||||
extern PPUCORE ppu;
|
||||
|
||||
#include "snes/snes.hpp"
|
||||
#include "chip/chip.hpp"
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
bbase : version 0.17 ~byuu (2008-10-19)
|
||||
license: public domain
|
||||
*/
|
||||
|
||||
#ifndef BBASE_H
|
||||
#define BBASE_H
|
||||
|
||||
#include <nall/stdint.hpp>
|
||||
typedef int8_t int8;
|
||||
typedef int16_t int16;
|
||||
typedef int32_t int32;
|
||||
typedef int64_t int64;
|
||||
typedef uint8_t uint8;
|
||||
typedef uint16_t uint16;
|
||||
typedef uint32_t uint32;
|
||||
typedef uint64_t uint64;
|
||||
typedef unsigned uint;
|
||||
|
||||
#include <algorithm>
|
||||
using std::min;
|
||||
using std::max;
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
#include <io.h>
|
||||
#include <direct.h>
|
||||
#include <shlobj.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
//disable libc deprecation warnings in MSVC 2k5+
|
||||
#pragma warning(disable:4996)
|
||||
|
||||
#define NOMINMAX
|
||||
#define PATH_MAX _MAX_PATH
|
||||
#define va_copy(dst, src) ((dst) = (src))
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
#define getcwd _getcwd
|
||||
#define ftruncate _chsize
|
||||
#define putenv _putenv
|
||||
#define rmdir _rmdir
|
||||
#define vsnprintf _vsnprintf
|
||||
#define usleep(n) Sleep(n / 1000)
|
||||
#endif
|
||||
|
||||
/*****
|
||||
* inline expansion
|
||||
*****/
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define noinline __declspec(noinline)
|
||||
#define inline inline
|
||||
#define alwaysinline __forceinline
|
||||
#elif defined(__GNUC__)
|
||||
#define noinline __attribute__((noinline))
|
||||
#define inline inline
|
||||
#define alwaysinline __attribute__((always_inline))
|
||||
#else
|
||||
#define noinline
|
||||
#define inline inline
|
||||
#define alwaysinline inline
|
||||
#endif
|
||||
|
||||
#endif //ifndef BBASE_H
|
|
@ -1,4 +1,4 @@
|
|||
void hiro_peditbox_change(pEditbox *p) {
|
||||
static void hiro_peditbox_change(pEditbox *p) {
|
||||
if(p->self.on_change) {
|
||||
p->self.on_change(event_t(event_t::Change, 0, &p->self));
|
||||
}
|
||||
|
@ -16,16 +16,18 @@ void pEditbox::create(unsigned style, unsigned width, unsigned height, const cha
|
|||
g_signal_connect_swapped(G_OBJECT(editbox), "changed", G_CALLBACK(hiro_peditbox_change), (gpointer)this);
|
||||
} else {
|
||||
GtkPolicyType hscroll = (style & Editbox::HorizontalScrollAlways) ? GTK_POLICY_ALWAYS :
|
||||
(style & Editbox::HorizontalScrollNever) ? GTK_POLICY_NEVER :
|
||||
(style & Editbox::HorizontalScrollNever ) ? GTK_POLICY_NEVER :
|
||||
GTK_POLICY_AUTOMATIC;
|
||||
GtkPolicyType vscroll = (style & Editbox::VerticalScrollAlways) ? GTK_POLICY_ALWAYS :
|
||||
(style & Editbox::VerticalScrollNever) ? GTK_POLICY_NEVER :
|
||||
(style & Editbox::VerticalScrollNever ) ? GTK_POLICY_NEVER :
|
||||
GTK_POLICY_AUTOMATIC;
|
||||
scrollbox = gtk_scrolled_window_new(0, 0);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollbox), hscroll, vscroll);
|
||||
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollbox), GTK_SHADOW_ETCHED_IN);
|
||||
gtk_widget_set_size_request(scrollbox, width, height);
|
||||
editbox = gtk_text_view_new();
|
||||
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(editbox),
|
||||
(hscroll == GTK_POLICY_NEVER ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE));
|
||||
gtk_container_add(GTK_CONTAINER(scrollbox), editbox);
|
||||
buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editbox));
|
||||
if(style & Editbox::Readonly) { gtk_text_view_set_editable(GTK_TEXT_VIEW(editbox), false); }
|
||||
|
|
|
@ -56,6 +56,23 @@ void pHiro::init() {
|
|||
pango_font_description_set_family(font, "Sans");
|
||||
pango_font_description_set_absolute_size(font, 11.0 * PANGO_SCALE);
|
||||
pango_font_description_set_style(font, PANGO_STYLE_NORMAL);
|
||||
|
||||
//apply custom GTK+-2.0 stylesheet.
|
||||
//it's obviously not ideal to override the global GTK+ theme settings;
|
||||
//however it is necessary to ensure consistency between ports of hiro.
|
||||
//without this, it would be impossible to develop a hiro application
|
||||
//on one platform, and be assured text wouldn't clipped off, etc on
|
||||
//another platform.
|
||||
gtk_rc_parse_string(
|
||||
"style \"ruby-gtk\"\n"
|
||||
"{\n"
|
||||
" GtkComboBox::appears-as-list = 1\n" //text tends to get cut off in some themes otherwise
|
||||
" GtkTreeView::vertical-separator = 0\n" //GTK+ lists tend to have way more space than on Windows
|
||||
"}\n"
|
||||
"\n"
|
||||
"class \"GtkComboBox\" style \"ruby-gtk\"\n"
|
||||
"class \"GtkTreeView\" style \"ruby-gtk\"\n"
|
||||
);
|
||||
}
|
||||
|
||||
void pHiro::term() {
|
||||
|
@ -112,6 +129,23 @@ bool pHiro::file_open(Window *focus, char *filename, const char *path, const cha
|
|||
if(path && *path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path);
|
||||
else if(*default_path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), default_path);
|
||||
|
||||
if(filter && *filter) {
|
||||
lstring filterlist;
|
||||
split(filterlist, "\n", filter);
|
||||
for(unsigned i = 0; i < count(filterlist); i++) {
|
||||
GtkFileFilter *filter = gtk_file_filter_new();
|
||||
lstring filterpart;
|
||||
split(filterpart, "\t", filterlist[i]);
|
||||
gtk_file_filter_set_name(filter, string() << filterpart[0] << " (" << filterpart[1] << ")");
|
||||
lstring patternlist;
|
||||
split(patternlist, ",", filterpart[1]);
|
||||
for(unsigned l = 0; l < count(patternlist); l++) {
|
||||
gtk_file_filter_add_pattern(filter, patternlist[l]);
|
||||
}
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
|
||||
}
|
||||
}
|
||||
|
||||
if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
|
||||
char *fn = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
|
||||
strcpy(filename, fn);
|
||||
|
@ -138,6 +172,23 @@ bool pHiro::file_save(Window *focus, char *filename, const char *path, const cha
|
|||
else if(*default_path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), default_path);
|
||||
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
|
||||
|
||||
if(filter && *filter) {
|
||||
lstring filterlist;
|
||||
split(filterlist, "\n", filter);
|
||||
for(unsigned i = 0; i < count(filterlist); i++) {
|
||||
GtkFileFilter *filter = gtk_file_filter_new();
|
||||
lstring filterpart;
|
||||
split(filterpart, "\t", filterlist[i]);
|
||||
gtk_file_filter_set_name(filter, string() << filterpart[0] << " (" << filterpart[1] << ")");
|
||||
lstring patternlist;
|
||||
split(patternlist, ",", filterpart[1]);
|
||||
for(unsigned l = 0; l < count(patternlist); l++) {
|
||||
gtk_file_filter_add_pattern(filter, patternlist[l]);
|
||||
}
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
|
||||
}
|
||||
}
|
||||
|
||||
if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
|
||||
char *fn = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
|
||||
strcpy(filename, fn);
|
||||
|
|
|
@ -46,10 +46,18 @@ void pListbox::create(unsigned style, unsigned width, unsigned height, const cha
|
|||
//alternate colors for each listbox entry if there are multiple columns
|
||||
gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(listbox), count(list) >= 2 ? true : false);
|
||||
for(unsigned i = 0; i < count(list); i++) {
|
||||
renderer = gtk_cell_renderer_text_new();
|
||||
column = gtk_tree_view_column_new_with_attributes(list[i], renderer, "text", i, (void*)0);
|
||||
column_list[column_list.size()] = column;
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(listbox), column);
|
||||
unsigned i = column.size();
|
||||
column[i].renderer = gtk_cell_renderer_text_new();
|
||||
column[i].column = gtk_tree_view_column_new_with_attributes(
|
||||
list[i], column[i].renderer, "text", i, (void*)0
|
||||
);
|
||||
//default header widget is GtkLabel with stock font size;
|
||||
//only way to assign a custom font to header widget is to create custom GtkLabel widget.
|
||||
column[i].label = gtk_label_new(list[i]);
|
||||
set_default_font(column[i].label);
|
||||
gtk_widget_show(column[i].label);
|
||||
gtk_tree_view_column_set_widget(GTK_TREE_VIEW_COLUMN(column[i].column), column[i].label);
|
||||
gtk_tree_view_append_column(GTK_TREE_VIEW(listbox), column[i].column);
|
||||
}
|
||||
|
||||
if(text && *text) {
|
||||
|
@ -70,9 +78,9 @@ void pListbox::autosize_columns() {
|
|||
gtk_tree_view_columns_autosize(GTK_TREE_VIEW(listbox));
|
||||
}
|
||||
|
||||
void pListbox::set_column_width(unsigned column, unsigned width) {
|
||||
gtk_tree_view_column_set_min_width(column_list[column], width);
|
||||
gtk_tree_view_column_set_max_width(column_list[column], width);
|
||||
void pListbox::set_column_width(unsigned index, unsigned width) {
|
||||
gtk_tree_view_column_set_min_width(column[index].column, width);
|
||||
gtk_tree_view_column_set_max_width(column[index].column, width);
|
||||
}
|
||||
|
||||
void pListbox::add_item(const char *text) {
|
||||
|
|
|
@ -16,9 +16,12 @@ public:
|
|||
GtkWidget *scrollbox;
|
||||
GtkWidget *listbox;
|
||||
GtkListStore *store;
|
||||
GtkCellRenderer *renderer;
|
||||
GtkTreeViewColumn *column;
|
||||
array<GtkTreeViewColumn*> column_list;
|
||||
struct GtkColumn {
|
||||
GtkCellRenderer *renderer;
|
||||
GtkTreeViewColumn *column;
|
||||
GtkWidget *label;
|
||||
};
|
||||
vector<GtkColumn> column;
|
||||
GtkTreeIter iter;
|
||||
int listbox_selection;
|
||||
GtkWidget* gtk_handle();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
hiro
|
||||
version: 0.008 (2008-10-27)
|
||||
version: 0.008.1 (2008-11-02)
|
||||
author: byuu
|
||||
license: public domain
|
||||
*/
|
||||
|
|
|
@ -2,10 +2,10 @@ void pEditbox::create(unsigned style, unsigned width, unsigned height, const cha
|
|||
bool multiline = style & Editbox::Multiline;
|
||||
bool readonly = style & Editbox::Readonly;
|
||||
unsigned vscroll = (style & Editbox::VerticalScrollAlways) ? WS_VSCROLL :
|
||||
(style & Editbox::VerticalScrollNever) ? 0 :
|
||||
(style & Editbox::VerticalScrollNever ) ? 0 :
|
||||
ES_AUTOVSCROLL;
|
||||
unsigned hscroll = (style & Editbox::HorizontalScrollAlways) ? WS_HSCROLL :
|
||||
(style & Editbox::HorizontalScrollNever) ? 0 :
|
||||
(style & Editbox::HorizontalScrollNever ) ? 0 :
|
||||
ES_AUTOHSCROLL;
|
||||
|
||||
hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", L"",
|
||||
|
|
|
@ -106,7 +106,7 @@ void HQ2xFilter::render(
|
|||
pattern |= hdiff(center, W9) ? 0x80 : 0x00;
|
||||
|
||||
switch(pattern) {
|
||||
#include "hq2x_table.h"
|
||||
#include "hq2x_table.hpp"
|
||||
}
|
||||
|
||||
input++;
|
||||
|
|
|
@ -11,4 +11,4 @@ namespace libfilter {
|
|||
#include "scale2x.cpp"
|
||||
#include "hq2x.cpp"
|
||||
#include "ntsc.cpp"
|
||||
} //namespace libfilter
|
||||
} //namespace libfilter
|
||||
|
|
|
@ -9,14 +9,14 @@
|
|||
#include <nall/stdint.hpp>
|
||||
|
||||
namespace libfilter {
|
||||
#include "colortable.h"
|
||||
#include "filter.h"
|
||||
#include "colortable.hpp"
|
||||
#include "filter.hpp"
|
||||
|
||||
#include "direct.h"
|
||||
#include "scanline.h"
|
||||
#include "scale2x.h"
|
||||
#include "hq2x.h"
|
||||
#include "ntsc.h"
|
||||
} //namespace libfilter
|
||||
#include "direct.hpp"
|
||||
#include "scanline.hpp"
|
||||
#include "scale2x.hpp"
|
||||
#include "hq2x.hpp"
|
||||
#include "ntsc.hpp"
|
||||
} //namespace libfilter
|
||||
|
||||
#endif //ifndef LIBFILTER_H
|
||||
#endif //ifndef LIBFILTER_H
|
||||
|
|
|
@ -54,12 +54,12 @@ void NTSCFilter::adjust(
|
|||
if(!ntsc) {
|
||||
ntsc = (snes_ntsc_t*)malloc(sizeof *ntsc);
|
||||
if(!ntsc) {
|
||||
return; //to do: report out of memory error
|
||||
return; //to do: report out of memory error
|
||||
}
|
||||
}
|
||||
|
||||
burst = 0;
|
||||
burst_toggle = (merge_fields ? 0 : 1); // don't toggle burst when fields are merged
|
||||
burst_toggle = (merge_fields ? 0 : 1); //don't toggle burst when fields are merged
|
||||
snes_ntsc_init(ntsc, &setup);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,14 @@
|
|||
#define NALL_ARRAY_HPP
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <nall/algorithm.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
//dynamic vector array
|
||||
//neither constructor nor destructor is ever invoked;
|
||||
//this this should only be used for POD objects.
|
||||
|
||||
template<typename T> class array {
|
||||
protected:
|
||||
T *pool;
|
||||
|
@ -27,40 +32,33 @@ public:
|
|||
unsigned capacity() const { return poolsize; }
|
||||
|
||||
void reset() {
|
||||
if(pool) {
|
||||
free(pool);
|
||||
pool = 0;
|
||||
}
|
||||
if(pool) free(pool);
|
||||
pool = 0;
|
||||
poolsize = 0;
|
||||
buffersize = 0;
|
||||
}
|
||||
|
||||
void reserve(unsigned size) {
|
||||
if(size == poolsize) return;
|
||||
if(size < poolsize) buffersize = size;
|
||||
void reserve(unsigned newsize) {
|
||||
if(newsize == poolsize) return;
|
||||
|
||||
pool = (T*)realloc(pool, sizeof(T) * size);
|
||||
poolsize = size;
|
||||
pool = (T*)realloc(pool, newsize * sizeof(T));
|
||||
poolsize = newsize;
|
||||
buffersize = min(buffersize, newsize);
|
||||
}
|
||||
|
||||
T* get(unsigned size = 0) {
|
||||
if(size > buffersize) resize(size);
|
||||
if(size > buffersize) throw "array[] out of bounds";
|
||||
void resize(unsigned newsize) {
|
||||
if(newsize > poolsize) reserve(findsize(newsize));
|
||||
buffersize = newsize;
|
||||
}
|
||||
|
||||
T* get(unsigned minsize = 0) {
|
||||
if(minsize > buffersize) resize(minsize);
|
||||
if(minsize > buffersize) throw "array[] out of bounds";
|
||||
return pool;
|
||||
}
|
||||
|
||||
void add(T data) {
|
||||
operator[](buffersize) = data;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
memset(pool, 0, sizeof(T) * buffersize);
|
||||
}
|
||||
|
||||
void resize(unsigned size) {
|
||||
if(size > poolsize) reserve(findsize(size));
|
||||
if(size > buffersize) buffersize = size;
|
||||
}
|
||||
void add(const T data) { operator[](buffersize) = data; }
|
||||
void clear() { memset(pool, 0, buffersize * sizeof(T)); }
|
||||
|
||||
array() {
|
||||
pool = 0;
|
||||
|
@ -70,22 +68,22 @@ public:
|
|||
|
||||
~array() { reset(); }
|
||||
|
||||
array& operator=(array &source) {
|
||||
array& operator=(const array &source) {
|
||||
if(pool) free(pool);
|
||||
buffersize = source.buffersize;
|
||||
poolsize = source.poolsize;
|
||||
pool = (T*)realloc(pool, sizeof(T) * poolsize); //allocate entire pool size ...
|
||||
memcpy(pool, source.pool, sizeof(T) * buffersize); //... but only copy used pool objects
|
||||
pool = (T*)realloc(pool, sizeof(T) * poolsize); //allocate entire pool size,
|
||||
memcpy(pool, source.pool, sizeof(T) * buffersize); //... but only copy used pool objects
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline T& operator[](int index) {
|
||||
inline T& operator[](unsigned index) {
|
||||
if(index >= buffersize) resize(index + 1);
|
||||
if(index >= buffersize) throw "array[] out of bounds";
|
||||
return pool[index];
|
||||
}
|
||||
|
||||
inline const T& operator[](int index) const {
|
||||
inline const T& operator[](unsigned index) const {
|
||||
if(index >= buffersize) throw "array[] out of bounds";
|
||||
return pool[index];
|
||||
}
|
||||
|
|
|
@ -52,6 +52,8 @@ public:
|
|||
index_input[n] = part[0];
|
||||
index_output[n] = part[1];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
#ifndef NALL_PLATFORM_HPP
|
||||
#define NALL_PLATFORM_HPP
|
||||
|
||||
//=========================
|
||||
//standard platform headers
|
||||
//=========================
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <io.h>
|
||||
#include <direct.h>
|
||||
#include <shlobj.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
//==================
|
||||
//warning supression
|
||||
//==================
|
||||
|
||||
//Visual C++
|
||||
#if defined(_MSC_VER)
|
||||
//disable libc "deprecation" warnings
|
||||
#pragma warning(disable:4996)
|
||||
#endif
|
||||
|
||||
//================
|
||||
//POSIX compliance
|
||||
//================
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define PATH_MAX _MAX_PATH
|
||||
#define va_copy(dest, src) ((dest) = (src))
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define getcwd _getcwd
|
||||
#define ftruncate _chsize
|
||||
#define putenv _putenv
|
||||
#define rmdir _rmdir
|
||||
#define vsnprintf _vsnprintf
|
||||
#define usleep(n) Sleep(n / 1000)
|
||||
#endif
|
||||
|
||||
//================
|
||||
//inline expansion
|
||||
//================
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#define noinline __attribute__((noinline))
|
||||
#define inline inline
|
||||
#define alwaysinline __attribute__((always_inline))
|
||||
#elif defined(_MSC_VER)
|
||||
#define noinline __declspec(noinline)
|
||||
#define inline inline
|
||||
#define alwaysinline __forceinline
|
||||
#else
|
||||
#define noinline
|
||||
#define inline inline
|
||||
#define alwaysinline inline
|
||||
#endif
|
||||
|
||||
#endif //ifndef NALL_PLATFORM_HPP
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue