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:
byuu 2008-12-15 16:19:04 +00:00
parent e370a35d7d
commit c13ae98863
203 changed files with 2424 additions and 4667 deletions

View File

@ -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

View File

@ -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

View File

@ -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/*
###########

View File

@ -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"

View File

@ -1,4 +1,4 @@
#include "../base.h"
#include <../base.hpp>
#define CART_CPP
#include <nall/crc32.hpp>

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -1,4 +1,4 @@
#include "../base.h"
#include <../base.hpp>
Cheat cheat;

View File

@ -1,4 +1,4 @@
#include "../../base.h"
#include <../base.hpp>
#define BSX_CPP
#include "bsx_base.cpp"

View File

@ -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"

11
src/chip/chip.hpp Normal file
View File

@ -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"

View File

@ -5,7 +5,7 @@
Portions (c) anomie, Overload, zsKnight, Nach, byuu
*/
#include "../../base.h"
#include <../base.hpp>
#define CX4_CPP
#include "cx4data.cpp"

View File

@ -1,4 +1,4 @@
#include "../../base.h"
#include <../base.hpp>
#define DSP1_CPP
#include "dsp1emu.cpp"

View File

@ -1,4 +1,4 @@
#include "dsp1emu.h"
#include "dsp1emu.hpp"
class DSP1 : public Memory {
private:

View File

@ -1,4 +1,4 @@
#include "../../base.h"
#include <../base.hpp>
#define DSP2_CPP
#include "dsp2_op.cpp"

View File

@ -1,4 +1,4 @@
#include "../../base.h"
#include <../base.hpp>
#define DSP3_CPP
namespace DSP3i {

View File

@ -1,4 +1,4 @@
#include "../../base.h"
#include <../base.hpp>
#define DSP4_CPP
namespace DSP4i {

View File

@ -1,4 +1,4 @@
#include "../../base.h"
#include <../base.hpp>
void OBC1::init() {}
void OBC1::enable() {}

View File

@ -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;
}

View File

@ -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;

40
src/chip/sdd1/sdd1.hpp Normal file
View File

@ -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;

View File

@ -30,7 +30,7 @@ understood.
************************************************************************/
#define SDD1_read(__addr) (bus.read(__addr))
#define SDD1_read(__addr) (sdd1.read(__addr))
////////////////////////////////////////////////////

View File

@ -1,4 +1,4 @@
#include "../../base.h"
#include <../base.hpp>
#define SPC7110_CPP
#include "decomp.cpp"

View File

@ -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:

View File

@ -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 };

View File

@ -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) {

View File

@ -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

View File

@ -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;
};

34
src/config/config.hpp Normal file
View File

@ -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;
};

View File

@ -1,4 +1,4 @@
#include "../base.h"
#include <../base.hpp>
#define CPU_CPP
#include "dcpu.cpp"

View File

@ -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;

View File

@ -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) {}
};

74
src/cpu/cpuregs.hpp Normal file
View File

@ -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) {}
};

View File

@ -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);
}

View File

@ -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) {

View File

@ -1,4 +1,4 @@
CPUReg24 aa, rd;
reg24_t aa, rd;
uint8_t dp, sp;
void op_irq();

View File

@ -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;

View File

@ -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();

View File

@ -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;
}

View File

@ -1,4 +1,4 @@
#include "../../base.h"
#include <../base.hpp>
#define SCPU_CPP
#include "core/core.cpp"

View File

@ -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;

View File

@ -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

View File

@ -25,4 +25,4 @@ void sCPU::run_auto_joypad_poll() {
status.joy4h = joy4 >> 8;
}
#endif //ifdef SCPU_CPP
#endif //ifdef SCPU_CPP

View File

@ -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

View File

@ -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();

View File

@ -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();

View File

@ -1,4 +1,4 @@
#include "../../base.h"
#include <../base.hpp>
#define ADSP_CPP
#include "adsp_tables.cpp"

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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() {}

View File

@ -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();
};

View File

@ -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>

View File

@ -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];
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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

29
src/interface.hpp Normal file
View File

@ -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"

View File

@ -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

View File

@ -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); }

View File

@ -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);

View File

@ -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) {

View File

@ -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();

View File

@ -1,6 +1,6 @@
/*
hiro
version: 0.008 (2008-10-27)
version: 0.008.1 (2008-11-02)
author: byuu
license: public domain
*/

View File

@ -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"",

View File

@ -106,7 +106,7 @@ void HQ2xFilter::render(
pattern |= hdiff(center, W9) ? 0x80 : 0x00;
switch(pattern) {
#include "hq2x_table.h"
#include "hq2x_table.hpp"
}
input++;

View File

@ -11,4 +11,4 @@ namespace libfilter {
#include "scale2x.cpp"
#include "hq2x.cpp"
#include "ntsc.cpp"
} //namespace libfilter
} //namespace libfilter

View File

@ -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

View File

@ -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);
}

View File

@ -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];
}

View File

@ -52,6 +52,8 @@ public:
index_input[n] = part[0];
index_output[n] = part[1];
}
return true;
}
void reset() {

73
src/lib/nall/platform.hpp Normal file
View File

@ -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