pizza: first bits of sound. bios starting sound plays. bomb sound plays in Bomberman GB.
This commit is contained in:
parent
21c70d1d65
commit
26063b35d0
|
@ -30,6 +30,8 @@ namespace BizHawk.Emulation.Common
|
|||
FirmwareAndOption("A3AF676991391A6DD716C79022D4947206B78164", 4096, "A78", "Bios_HSC", "7800highscore.bin", "Highscore Bios");
|
||||
FirmwareAndOption("45BEDC4CBDEAC66C7DF59E9E599195C778D86A92", 8192, "Coleco", "Bios", "ColecoBios.bin", "Bios");
|
||||
|
||||
FirmwareAndOption("079a7ce93f3fd7d35e444b2fab16b1867c95e2c1", 66084, "SGB", "SPC", "sgb.spc", "Super Gameboy SPC");
|
||||
|
||||
var gbaNormal = File("300C20DF6731A33952DED8C436F7F186D25D3492", 16384, "gbabios.rom", "Bios (World)");
|
||||
var gbaJDebug = File("AA98A2AD32B86106340665D1222D7D973A1361C7", 16384, "gbabios.rom", "Bios (J Debug)");
|
||||
Firmware("GBA", "Bios", "Bios");
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
|||
public Buttons Keys;
|
||||
}
|
||||
[BizImport(CC)]
|
||||
public abstract bool Init(byte[] rom, int romlen, bool sgb);
|
||||
public abstract bool Init(byte[] rom, int romlen, bool sgb, byte[] spc, int spclen);
|
||||
[BizImport(CC)]
|
||||
public abstract bool IsCGB();
|
||||
}
|
||||
|
|
|
@ -31,8 +31,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
|||
DefaultFpsDenominator = TICKSPERFRAME
|
||||
})
|
||||
{
|
||||
ControllerDefinition = BizHawk.Emulation.Cores.Nintendo.Gameboy.Gameboy.GbController;
|
||||
|
||||
_pizza = PreInit<LibPizza>(new PeRunnerOptions
|
||||
{
|
||||
Filename = "pizza.wbx",
|
||||
|
@ -43,8 +41,9 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
|||
MmapHeapSizeKB = 32 * 1024
|
||||
});
|
||||
|
||||
var spc = comm.CoreFileProvider.GetFirmware("SGB", "SPC", true);
|
||||
_sgb = true;
|
||||
if (!_pizza.Init(rom, rom.Length, _sgb))
|
||||
if (!_pizza.Init(rom, rom.Length, _sgb, spc, spc.Length))
|
||||
{
|
||||
throw new InvalidOperationException("Core rejected the rom!");
|
||||
}
|
||||
|
|
|
@ -1,33 +1,45 @@
|
|||
CC = x86_64-nt64-midipix-gcc
|
||||
CPP = x86_64-nt64-midipix-g++
|
||||
|
||||
CCFLAGS:=-Ilib \
|
||||
-I../emulibc \
|
||||
-Wall -Werror=pointer-to-int-cast -Werror=int-to-pointer-cast -Werror=implicit-function-declaration \
|
||||
-std=c99 -fomit-frame-pointer -fvisibility=hidden \
|
||||
-DLSB_FIRST -D_GNU_SOURCE \
|
||||
FLAGS:=-Wall -Werror=pointer-to-int-cast -Werror=int-to-pointer-cast -Werror=implicit-function-declaration \
|
||||
-fomit-frame-pointer -fvisibility=hidden \
|
||||
-O0 -g
|
||||
|
||||
CCFLAGS:=$(FLAGS) -Ilib \
|
||||
-I../emulibc \
|
||||
-std=c99 \
|
||||
-DLSB_FIRST -D_GNU_SOURCE
|
||||
|
||||
CPPFLAGS:=$(FLAGS)
|
||||
|
||||
TARGET = pizza.wbx
|
||||
|
||||
LDFLAGS = -Wl,--dynamicbase,--export-all-symbols
|
||||
|
||||
ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
SRCS:=$(shell find $(ROOT_DIR) -type f -name '*.c')
|
||||
C_SRCS:=$(shell find $(ROOT_DIR) -type f -name '*.c')
|
||||
CPP_SRCS:=$(shell find $(ROOT_DIR) -type f -name '*.cpp')
|
||||
SRCS:=$(C_SRCS) $(CPP_SRCS)
|
||||
OBJ_DIR:=$(ROOT_DIR)/obj
|
||||
|
||||
_OBJS:=$(SRCS:.c=.o)
|
||||
__OBJS:=$(SRCS:.c=.o)
|
||||
_OBJS:=$(__OBJS:.cpp=.opp)
|
||||
OBJS:=$(patsubst $(ROOT_DIR)%,$(OBJ_DIR)%,$(_OBJS))
|
||||
|
||||
$(OBJ_DIR)/%.o: %.c
|
||||
@mkdir -p $(@D)
|
||||
@$(CC) -c -o $@ $< $(CCFLAGS)
|
||||
|
||||
$(OBJ_DIR)/%.opp: %.cpp
|
||||
@mkdir -p $(@D)
|
||||
@$(CPP) -c -o $@ $< $(CPPFLAGS)
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
.PHONY: clean all
|
||||
|
||||
$(TARGET).in: $(OBJS)
|
||||
@$(CC) -o $@ $(LDFLAGS) $(CCFLAGS) $(OBJS) ../emulibc/libemuhost.so
|
||||
@$(CPP) -o $@ $(LDFLAGS) $(FLAGS) $(OBJS) ../emulibc/libemuhost.so
|
||||
|
||||
$(TARGET): $(TARGET).in
|
||||
# strip $< -o $@ -R /4 -R /14 -R /29 -R /41 -R /55 -R /67 -R /78 -R /89 -R /104
|
||||
|
|
|
@ -2,11 +2,53 @@
|
|||
#include "utils.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "snes_spc/spc.h"
|
||||
|
||||
const uint8_t iplrom[64] = {
|
||||
/*ffc0*/ 0xcd, 0xef, //mov x,#$ef
|
||||
/*ffc2*/ 0xbd, //mov sp,x
|
||||
/*ffc3*/ 0xe8, 0x00, //mov a,#$00
|
||||
/*ffc5*/ 0xc6, //mov (x),a
|
||||
/*ffc6*/ 0x1d, //dec x
|
||||
/*ffc7*/ 0xd0, 0xfc, //bne $ffc5
|
||||
/*ffc9*/ 0x8f, 0xaa, 0xf4, //mov $f4,#$aa
|
||||
/*ffcc*/ 0x8f, 0xbb, 0xf5, //mov $f5,#$bb
|
||||
/*ffcf*/ 0x78, 0xcc, 0xf4, //cmp $f4,#$cc
|
||||
/*ffd2*/ 0xd0, 0xfb, //bne $ffcf
|
||||
/*ffd4*/ 0x2f, 0x19, //bra $ffef
|
||||
/*ffd6*/ 0xeb, 0xf4, //mov y,$f4
|
||||
/*ffd8*/ 0xd0, 0xfc, //bne $ffd6
|
||||
/*ffda*/ 0x7e, 0xf4, //cmp y,$f4
|
||||
/*ffdc*/ 0xd0, 0x0b, //bne $ffe9
|
||||
/*ffde*/ 0xe4, 0xf5, //mov a,$f5
|
||||
/*ffe0*/ 0xcb, 0xf4, //mov $f4,y
|
||||
/*ffe2*/ 0xd7, 0x00, //mov ($00)+y,a
|
||||
/*ffe4*/ 0xfc, //inc y
|
||||
/*ffe5*/ 0xd0, 0xf3, //bne $ffda
|
||||
/*ffe7*/ 0xab, 0x01, //inc $01
|
||||
/*ffe9*/ 0x10, 0xef, //bpl $ffda
|
||||
/*ffeb*/ 0x7e, 0xf4, //cmp y,$f4
|
||||
/*ffed*/ 0x10, 0xeb, //bpl $ffda
|
||||
/*ffef*/ 0xba, 0xf6, //movw ya,$f6
|
||||
/*fff1*/ 0xda, 0x00, //movw $00,ya
|
||||
/*fff3*/ 0xba, 0xf4, //movw ya,$f4
|
||||
/*fff5*/ 0xc4, 0xf4, //mov $f4,a
|
||||
/*fff7*/ 0xdd, //mov a,y
|
||||
/*fff8*/ 0x5d, //mov x,a
|
||||
/*fff9*/ 0xd0, 0xdb, //bne $ffd6
|
||||
/*fffb*/ 0x1f, 0x00, 0x00, //jmp ($0000+x)
|
||||
/*fffe*/ 0xc0, 0xff //reset vector location ($ffc0)
|
||||
};
|
||||
|
||||
// the "reference clock" is tied to the GB cpu. 35112 of these should equal one GB LCD frame.
|
||||
// it is always increasing and never resets/rebases
|
||||
|
||||
const int refclocks_per_spc_sample = 67; // ~32055hz
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// writes to FF00
|
||||
uint64_t last_write_time;
|
||||
uint64_t last_write_time; // last write time relative to reference clock
|
||||
uint8_t last_write_value;
|
||||
|
||||
// recv packets
|
||||
|
@ -41,6 +83,12 @@ typedef struct
|
|||
uint8_t waiting_mask; // true if waiting to capture a mask
|
||||
uint8_t active_mask; // true if mask is currently being used
|
||||
|
||||
// audio
|
||||
SNES_SPC *spc;
|
||||
uint64_t frame_start; // when the current audio frame started relative to reference clock
|
||||
uint32_t clock_remainder; // number of reference clocks not sent to the SPC last frame
|
||||
uint8_t sound_control[4]; // TODO...
|
||||
|
||||
// transfers
|
||||
uint32_t waiting_transfer;
|
||||
#define TRN_NONE 0
|
||||
|
@ -174,7 +222,7 @@ static void cmd_attr_blk()
|
|||
ctrl = 6;
|
||||
linepal = outsidepal;
|
||||
}
|
||||
uint8_t* dst = sgb.attr;
|
||||
uint8_t *dst = sgb.attr;
|
||||
for (int y = 0; y < 18; y++)
|
||||
{
|
||||
for (int x = 0; x < 20; x++)
|
||||
|
@ -400,6 +448,20 @@ static void cmd_mask(void)
|
|||
}
|
||||
}
|
||||
|
||||
static void cmd_sound(void)
|
||||
{
|
||||
if ((sgb.command[0] & 7) == 1)
|
||||
{
|
||||
sgb.sound_control[1] = sgb.command[1];
|
||||
sgb.sound_control[2] = sgb.command[2];
|
||||
sgb.sound_control[3] = sgb.command[3];
|
||||
}
|
||||
else
|
||||
{
|
||||
utils_log("SGB: cmd_sound bad length");
|
||||
}
|
||||
}
|
||||
|
||||
static void do_command(void)
|
||||
{
|
||||
const int command = sgb.command[0] >> 3;
|
||||
|
@ -491,6 +553,12 @@ static void do_command(void)
|
|||
cmd_mlt_req();
|
||||
break;
|
||||
|
||||
// sound
|
||||
case 0x08: // SOUND
|
||||
utils_log("SGB: SOUND %02x %02x %02x %02x", sgb.command[1], sgb.command[2], sgb.command[3], sgb.command[4]);
|
||||
cmd_sound();
|
||||
break;
|
||||
|
||||
// all vram transfers
|
||||
case 0x09: // SOU_TRN
|
||||
utils_log("SGB: SOU_TRN");
|
||||
|
@ -537,11 +605,26 @@ static void do_packet(void)
|
|||
}
|
||||
}
|
||||
|
||||
void sgb_init(void)
|
||||
int sgb_init(const uint8_t *spc, int length)
|
||||
{
|
||||
memset(&sgb, 0, sizeof(sgb));
|
||||
sgb.read_index = 255;
|
||||
sgb.num_joypads = 1;
|
||||
sgb.palette[0][0] = 0xffffffff;
|
||||
sgb.palette[0][1] = 0xffaaaaaa;
|
||||
sgb.palette[0][2] = 0xff555555;
|
||||
sgb.palette[0][3] = 0xff000000;
|
||||
|
||||
sgb.spc = spc_new();
|
||||
spc_init_rom(sgb.spc, iplrom);
|
||||
spc_reset(sgb.spc);
|
||||
if (spc_load_spc(sgb.spc, spc, length) != NULL)
|
||||
{
|
||||
utils_log("SGB: Failed to load SPC");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void sgb_write_ff00(uint8_t val, uint64_t time)
|
||||
|
@ -670,18 +753,18 @@ static void trn_attr(const uint8_t *data)
|
|||
}
|
||||
}
|
||||
|
||||
static void trn_pct(const uint8_t* data)
|
||||
static void trn_pct(const uint8_t *data)
|
||||
{
|
||||
memcpy(sgb.tilemap, data, sizeof(sgb.tilemap));
|
||||
const uint16_t* palettes = (const uint16_t*)(data + sizeof(sgb.tilemap));
|
||||
uint32_t* dst = sgb.palette[4];
|
||||
const uint16_t *palettes = (const uint16_t *)(data + sizeof(sgb.tilemap));
|
||||
uint32_t *dst = sgb.palette[4];
|
||||
for (int i = 0; i < 64; i++)
|
||||
dst[i] = makecol(palettes[i]);
|
||||
}
|
||||
|
||||
static void trn_chr(const uint8_t* data, int bank)
|
||||
static void trn_chr(const uint8_t *data, int bank)
|
||||
{
|
||||
uint8_t* dst = sgb.tiles[128 * bank];
|
||||
uint8_t *dst = sgb.tiles[128 * bank];
|
||||
for (int n = 0; n < 128; n++)
|
||||
{
|
||||
for (int y = 0; y < 8; y++)
|
||||
|
@ -804,10 +887,10 @@ static void sgb_render_frame_gb(uint32_t *vbuff)
|
|||
}
|
||||
}
|
||||
|
||||
static void draw_tile(uint16_t entry, uint32_t* dest)
|
||||
static void draw_tile(uint16_t entry, uint32_t *dest)
|
||||
{
|
||||
const uint8_t* tile = sgb.tiles[entry & 0xff];
|
||||
const uint32_t* palette = sgb.palette[entry >> 10 & 7];
|
||||
const uint8_t *tile = sgb.tiles[entry & 0xff];
|
||||
const uint32_t *palette = sgb.palette[entry >> 10 & 7];
|
||||
int hflip = entry & 0x4000;
|
||||
int vflip = entry & 0x8000;
|
||||
int hinc, vinc;
|
||||
|
@ -841,9 +924,9 @@ static void draw_tile(uint16_t entry, uint32_t* dest)
|
|||
}
|
||||
}
|
||||
|
||||
static void sgb_render_border(uint32_t*vbuff)
|
||||
static void sgb_render_border(uint32_t *vbuff)
|
||||
{
|
||||
const uint16_t* tilemap = sgb.tilemap;
|
||||
const uint16_t *tilemap = sgb.tilemap;
|
||||
for (int n = 0; n < 32 * 28; n++)
|
||||
{
|
||||
draw_tile(*tilemap++, vbuff);
|
||||
|
@ -860,3 +943,39 @@ void sgb_render_frame(uint32_t *vbuff)
|
|||
sgb_render_frame_gb(vbuff);
|
||||
sgb_render_border(vbuff);
|
||||
}
|
||||
|
||||
void sgb_render_audio(uint64_t time, void (*callback)(int16_t l, int16_t r, uint64_t time))
|
||||
{
|
||||
int16_t sound_buffer[4096];
|
||||
uint32_t diff = time - sgb.frame_start + sgb.clock_remainder;
|
||||
//utils_log("%ul", diff);
|
||||
|
||||
uint32_t samples = diff / refclocks_per_spc_sample;
|
||||
uint32_t new_remainder = diff % refclocks_per_spc_sample;
|
||||
|
||||
spc_set_output(sgb.spc, sound_buffer, sizeof(sound_buffer) / sizeof(sound_buffer[0]));
|
||||
int p;
|
||||
for (p = 0; p < 4; p++)
|
||||
{
|
||||
if (spc_read_port(sgb.spc, 0, p) != sgb.sound_control[p])
|
||||
break;
|
||||
}
|
||||
if (p == 4) // recived
|
||||
{
|
||||
sgb.sound_control[1] = 0;
|
||||
sgb.sound_control[2] = 0;
|
||||
}
|
||||
for (p = 0; p < 4; p++)
|
||||
{
|
||||
spc_write_port(sgb.spc, 0, p, sgb.sound_control[p]);
|
||||
}
|
||||
|
||||
spc_end_frame(sgb.spc, samples * 32);
|
||||
|
||||
uint64_t t = sgb.frame_start + refclocks_per_spc_sample - sgb.clock_remainder;
|
||||
for (int i = 0; i < samples; i++, t += refclocks_per_spc_sample)
|
||||
callback(sound_buffer[i * 2], sound_buffer[i * 2] + 1, t);
|
||||
|
||||
sgb.frame_start = time;
|
||||
sgb.clock_remainder = new_remainder;
|
||||
}
|
||||
|
|
|
@ -7,8 +7,10 @@ uint8_t sgb_read_ff00(uint64_t time);
|
|||
|
||||
void sgb_set_controller_data(const uint8_t* buttons);
|
||||
|
||||
void sgb_init(void);
|
||||
int sgb_init(const uint8_t* spc, int length);
|
||||
|
||||
void sgb_take_frame(uint32_t* vbuff);
|
||||
|
||||
void sgb_render_frame(uint32_t* vbuff);
|
||||
|
||||
void sgb_render_audio(uint64_t time, void(*callback)(int16_t l, int16_t r, uint64_t time));
|
||||
|
|
|
@ -0,0 +1,564 @@
|
|||
// Core SPC emulation: CPU, timers, SMP registers, memory
|
||||
|
||||
// snes_spc 0.9.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "SNES_SPC.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#define RAM (m.ram.ram)
|
||||
#define REGS (m.smp_regs [0])
|
||||
#define REGS_IN (m.smp_regs [1])
|
||||
|
||||
// (n ? n : 256)
|
||||
#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
|
||||
|
||||
// Note: SPC_MORE_ACCURACY exists mainly so I can run my validation tests, which
|
||||
// do crazy echo buffer accesses.
|
||||
#ifndef SPC_MORE_ACCURACY
|
||||
#define SPC_MORE_ACCURACY 0
|
||||
#endif
|
||||
|
||||
#ifdef BLARGG_ENABLE_OPTIMIZER
|
||||
#include BLARGG_ENABLE_OPTIMIZER
|
||||
#endif
|
||||
|
||||
|
||||
//// Timers
|
||||
|
||||
#if SPC_DISABLE_TEMPO
|
||||
#define TIMER_DIV( t, n ) ((n) >> t->prescaler)
|
||||
#define TIMER_MUL( t, n ) ((n) << t->prescaler)
|
||||
#else
|
||||
#define TIMER_DIV( t, n ) ((n) / t->prescaler)
|
||||
#define TIMER_MUL( t, n ) ((n) * t->prescaler)
|
||||
#endif
|
||||
|
||||
SNES_SPC::Timer* SNES_SPC::run_timer_( Timer* t, rel_time_t time )
|
||||
{
|
||||
int elapsed = TIMER_DIV( t, time - t->next_time ) + 1;
|
||||
t->next_time += TIMER_MUL( t, elapsed );
|
||||
|
||||
if ( t->enabled )
|
||||
{
|
||||
int remain = IF_0_THEN_256( t->period - t->divider );
|
||||
int divider = t->divider + elapsed;
|
||||
int over = elapsed - remain;
|
||||
if ( over >= 0 )
|
||||
{
|
||||
int n = over / t->period;
|
||||
t->counter = (t->counter + 1 + n) & 0x0F;
|
||||
divider = over - n * t->period;
|
||||
}
|
||||
t->divider = (uint8_t) divider;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
inline SNES_SPC::Timer* SNES_SPC::run_timer( Timer* t, rel_time_t time )
|
||||
{
|
||||
if ( time >= t->next_time )
|
||||
t = run_timer_( t, time );
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
//// ROM
|
||||
|
||||
void SNES_SPC::enable_rom( int enable )
|
||||
{
|
||||
if ( m.rom_enabled != enable )
|
||||
{
|
||||
m.rom_enabled = enable;
|
||||
if ( enable )
|
||||
memcpy( m.hi_ram, &RAM [rom_addr], sizeof m.hi_ram );
|
||||
memcpy( &RAM [rom_addr], (enable ? m.rom : m.hi_ram), rom_size );
|
||||
// TODO: ROM can still get overwritten when DSP writes to echo buffer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// DSP
|
||||
|
||||
#if SPC_LESS_ACCURATE
|
||||
int const max_reg_time = 29;
|
||||
|
||||
signed char const SNES_SPC::reg_times_ [256] =
|
||||
{
|
||||
-1, 0,-11,-10,-15,-11, -2, -2, 4, 3, 14, 14, 26, 26, 14, 22,
|
||||
2, 3, 0, 1,-12, 0, 1, 1, 7, 6, 14, 14, 27, 14, 14, 23,
|
||||
5, 6, 3, 4, -1, 3, 4, 4, 10, 9, 14, 14, 26, -5, 14, 23,
|
||||
8, 9, 6, 7, 2, 6, 7, 7, 13, 12, 14, 14, 27, -4, 14, 24,
|
||||
11, 12, 9, 10, 5, 9, 10, 10, 16, 15, 14, 14, -2, -4, 14, 24,
|
||||
14, 15, 12, 13, 8, 12, 13, 13, 19, 18, 14, 14, -2,-36, 14, 24,
|
||||
17, 18, 15, 16, 11, 15, 16, 16, 22, 21, 14, 14, 28, -3, 14, 25,
|
||||
20, 21, 18, 19, 14, 18, 19, 19, 25, 24, 14, 14, 14, 29, 14, 25,
|
||||
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
|
||||
};
|
||||
|
||||
#define RUN_DSP( time, offset ) \
|
||||
int count = (time) - (offset) - m.dsp_time;\
|
||||
if ( count >= 0 )\
|
||||
{\
|
||||
int clock_count = (count & ~(clocks_per_sample - 1)) + clocks_per_sample;\
|
||||
m.dsp_time += clock_count;\
|
||||
dsp.run( clock_count );\
|
||||
}
|
||||
#else
|
||||
#define RUN_DSP( time, offset ) \
|
||||
{\
|
||||
int count = (time) - m.dsp_time;\
|
||||
if ( !SPC_MORE_ACCURACY || count )\
|
||||
{\
|
||||
assert( count > 0 );\
|
||||
m.dsp_time = (time);\
|
||||
dsp.run( count );\
|
||||
}\
|
||||
}
|
||||
#endif
|
||||
|
||||
int SNES_SPC::dsp_read( rel_time_t time )
|
||||
{
|
||||
RUN_DSP( time, reg_times [REGS [r_dspaddr] & 0x7F] );
|
||||
|
||||
int result = dsp.read( REGS [r_dspaddr] & 0x7F );
|
||||
|
||||
#ifdef SPC_DSP_READ_HOOK
|
||||
SPC_DSP_READ_HOOK( spc_time + time, (REGS [r_dspaddr] & 0x7F), result );
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void SNES_SPC::dsp_write( int data, rel_time_t time )
|
||||
{
|
||||
RUN_DSP( time, reg_times [REGS [r_dspaddr]] )
|
||||
#if SPC_LESS_ACCURATE
|
||||
else if ( m.dsp_time == skipping_time )
|
||||
{
|
||||
int r = REGS [r_dspaddr];
|
||||
if ( r == SPC_DSP::r_kon )
|
||||
m.skipped_kon |= data & ~dsp.read( SPC_DSP::r_koff );
|
||||
|
||||
if ( r == SPC_DSP::r_koff )
|
||||
{
|
||||
m.skipped_koff |= data;
|
||||
m.skipped_kon &= ~data;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SPC_DSP_WRITE_HOOK
|
||||
SPC_DSP_WRITE_HOOK( m.spc_time + time, REGS [r_dspaddr], (uint8_t) data );
|
||||
#endif
|
||||
|
||||
if ( REGS [r_dspaddr] <= 0x7F )
|
||||
dsp.write( REGS [r_dspaddr], data );
|
||||
else if ( !SPC_MORE_ACCURACY )
|
||||
dprintf( "SPC wrote to DSP register > $7F\n" );
|
||||
}
|
||||
|
||||
|
||||
//// Memory access extras
|
||||
|
||||
#if SPC_MORE_ACCURACY
|
||||
#define MEM_ACCESS( time, addr ) \
|
||||
{\
|
||||
if ( time >= m.dsp_time )\
|
||||
{\
|
||||
RUN_DSP( time, max_reg_time );\
|
||||
}\
|
||||
}
|
||||
#elif !defined (NDEBUG)
|
||||
// Debug-only check for read/write within echo buffer, since this might result in
|
||||
// inaccurate emulation due to the DSP not being caught up to the present.
|
||||
|
||||
bool SNES_SPC::check_echo_access( int addr )
|
||||
{
|
||||
if ( !(dsp.read( SPC_DSP::r_flg ) & 0x20) )
|
||||
{
|
||||
int start = 0x100 * dsp.read( SPC_DSP::r_esa );
|
||||
int size = 0x800 * (dsp.read( SPC_DSP::r_edl ) & 0x0F);
|
||||
int end = start + (size ? size : 4);
|
||||
if ( start <= addr && addr < end )
|
||||
{
|
||||
if ( !m.echo_accessed )
|
||||
{
|
||||
m.echo_accessed = 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#define MEM_ACCESS( time, addr ) check( !check_echo_access( (uint16_t) addr ) );
|
||||
#else
|
||||
#define MEM_ACCESS( time, addr )
|
||||
#endif
|
||||
|
||||
|
||||
//// CPU write
|
||||
|
||||
#if SPC_MORE_ACCURACY
|
||||
static unsigned char const glitch_probs [3] [256] =
|
||||
{
|
||||
0xC3,0x92,0x5B,0x1C,0xD1,0x92,0x5B,0x1C,0xDB,0x9C,0x72,0x18,0xCD,0x5C,0x38,0x0B,
|
||||
0xE1,0x9C,0x74,0x17,0xCF,0x75,0x45,0x0C,0xCF,0x6E,0x4A,0x0D,0xA3,0x3A,0x1D,0x08,
|
||||
0xDB,0xA0,0x82,0x19,0xD9,0x73,0x3C,0x0E,0xCB,0x76,0x52,0x0B,0xA5,0x46,0x1D,0x09,
|
||||
0xDA,0x74,0x55,0x0F,0xA2,0x3F,0x21,0x05,0x9A,0x40,0x20,0x07,0x63,0x1E,0x10,0x01,
|
||||
0xDF,0xA9,0x85,0x1D,0xD3,0x84,0x4B,0x0E,0xCF,0x6F,0x49,0x0F,0xB3,0x48,0x1E,0x05,
|
||||
0xD8,0x77,0x52,0x12,0xB7,0x49,0x23,0x06,0xAA,0x45,0x28,0x07,0x7D,0x28,0x0F,0x07,
|
||||
0xCC,0x7B,0x4A,0x0E,0xB2,0x4F,0x24,0x07,0xAD,0x43,0x2C,0x06,0x86,0x29,0x11,0x07,
|
||||
0xAE,0x48,0x1F,0x0A,0x76,0x21,0x19,0x05,0x76,0x21,0x14,0x05,0x44,0x11,0x0B,0x01,
|
||||
0xE7,0xAD,0x96,0x23,0xDC,0x86,0x59,0x0E,0xDC,0x7C,0x5F,0x15,0xBB,0x53,0x2E,0x09,
|
||||
0xD6,0x7C,0x4A,0x16,0xBB,0x4A,0x25,0x08,0xB3,0x4F,0x28,0x0B,0x8E,0x23,0x15,0x08,
|
||||
0xCF,0x7F,0x57,0x11,0xB5,0x4A,0x23,0x0A,0xAA,0x42,0x28,0x05,0x7D,0x22,0x12,0x03,
|
||||
0xA6,0x49,0x28,0x09,0x82,0x2B,0x0D,0x04,0x7A,0x20,0x0F,0x04,0x3D,0x0F,0x09,0x03,
|
||||
0xD1,0x7C,0x4C,0x0F,0xAF,0x4E,0x21,0x09,0xA8,0x46,0x2A,0x07,0x85,0x1F,0x0E,0x07,
|
||||
0xA6,0x3F,0x26,0x07,0x7C,0x24,0x14,0x07,0x78,0x22,0x16,0x04,0x46,0x12,0x0A,0x02,
|
||||
0xA6,0x41,0x2C,0x0A,0x7E,0x28,0x11,0x05,0x73,0x1B,0x14,0x05,0x3D,0x11,0x0A,0x02,
|
||||
0x70,0x22,0x17,0x05,0x48,0x13,0x08,0x03,0x3C,0x07,0x0D,0x07,0x26,0x07,0x06,0x01,
|
||||
|
||||
0xE0,0x9F,0xDA,0x7C,0x4F,0x18,0x28,0x0D,0xE9,0x9F,0xDA,0x7C,0x4F,0x18,0x1F,0x07,
|
||||
0xE6,0x97,0xD8,0x72,0x64,0x13,0x26,0x09,0xDC,0x67,0xA9,0x38,0x21,0x07,0x15,0x06,
|
||||
0xE9,0x91,0xD2,0x6B,0x63,0x14,0x2B,0x0E,0xD6,0x61,0xB7,0x41,0x2B,0x0E,0x10,0x09,
|
||||
0xCF,0x59,0xB0,0x2F,0x35,0x08,0x0F,0x07,0xB6,0x30,0x7A,0x21,0x17,0x07,0x09,0x03,
|
||||
0xE7,0xA3,0xE5,0x6B,0x65,0x1F,0x34,0x09,0xD8,0x6B,0xBE,0x45,0x27,0x07,0x10,0x07,
|
||||
0xDA,0x54,0xB1,0x39,0x2E,0x0E,0x17,0x08,0xA9,0x3C,0x86,0x22,0x16,0x06,0x07,0x03,
|
||||
0xD4,0x51,0xBC,0x3D,0x38,0x0A,0x13,0x06,0xB2,0x37,0x79,0x1C,0x17,0x05,0x0E,0x06,
|
||||
0xA7,0x31,0x74,0x1C,0x11,0x06,0x0C,0x02,0x6D,0x1A,0x38,0x10,0x0B,0x05,0x06,0x03,
|
||||
0xEB,0x9A,0xE1,0x7A,0x6F,0x13,0x34,0x0E,0xE6,0x75,0xC5,0x45,0x3E,0x0B,0x1A,0x05,
|
||||
0xD8,0x63,0xC1,0x40,0x3C,0x1B,0x19,0x06,0xB3,0x42,0x83,0x29,0x18,0x0A,0x08,0x04,
|
||||
0xD4,0x58,0xBA,0x43,0x3F,0x0A,0x1F,0x09,0xB1,0x33,0x8A,0x1F,0x1F,0x06,0x0D,0x05,
|
||||
0xAF,0x3C,0x7A,0x1F,0x16,0x08,0x0A,0x01,0x72,0x1B,0x52,0x0D,0x0B,0x09,0x06,0x01,
|
||||
0xCF,0x63,0xB7,0x47,0x40,0x10,0x14,0x06,0xC0,0x41,0x96,0x20,0x1C,0x09,0x10,0x05,
|
||||
0xA6,0x35,0x82,0x1A,0x20,0x0C,0x0E,0x04,0x80,0x1F,0x53,0x0F,0x0B,0x02,0x06,0x01,
|
||||
0xA6,0x31,0x81,0x1B,0x1D,0x01,0x08,0x08,0x7B,0x20,0x4D,0x19,0x0E,0x05,0x07,0x03,
|
||||
0x6B,0x17,0x49,0x07,0x0E,0x03,0x0A,0x05,0x37,0x0B,0x1F,0x06,0x04,0x02,0x07,0x01,
|
||||
|
||||
0xF0,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x47,0x1E,0x6E,0x1B,0x32,0x0A,
|
||||
0xF0,0xD6,0xEA,0xA4,0xED,0xC4,0xDE,0x82,0x98,0x1F,0x50,0x13,0x52,0x15,0x2A,0x0A,
|
||||
0xF1,0xD1,0xEB,0xA2,0xEB,0xB7,0xD8,0x69,0xA2,0x1F,0x5B,0x18,0x55,0x18,0x2C,0x0A,
|
||||
0xED,0xB5,0xDE,0x7E,0xE6,0x85,0xD3,0x59,0x59,0x0F,0x2C,0x09,0x24,0x07,0x15,0x09,
|
||||
0xF1,0xD6,0xEA,0xA0,0xEC,0xBB,0xDA,0x77,0xA9,0x23,0x58,0x14,0x5D,0x12,0x2F,0x09,
|
||||
0xF1,0xC1,0xE3,0x86,0xE4,0x87,0xD2,0x4E,0x68,0x15,0x26,0x0B,0x27,0x09,0x15,0x02,
|
||||
0xEE,0xA6,0xE0,0x5C,0xE0,0x77,0xC3,0x41,0x67,0x1B,0x3C,0x07,0x2A,0x06,0x19,0x07,
|
||||
0xE4,0x75,0xC6,0x43,0xCC,0x50,0x95,0x23,0x35,0x09,0x14,0x04,0x15,0x05,0x0B,0x04,
|
||||
0xEE,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x56,0x14,0x5A,0x12,0x26,0x0A,
|
||||
0xEE,0xBB,0xE7,0x7E,0xE9,0x8D,0xCB,0x49,0x67,0x11,0x34,0x07,0x2B,0x0B,0x14,0x07,
|
||||
0xED,0xA7,0xE5,0x76,0xE3,0x7E,0xC4,0x4B,0x77,0x14,0x34,0x08,0x27,0x07,0x14,0x04,
|
||||
0xE7,0x8B,0xD2,0x4C,0xCA,0x56,0x9E,0x31,0x36,0x0C,0x11,0x07,0x14,0x04,0x0A,0x02,
|
||||
0xF0,0x9B,0xEA,0x6F,0xE5,0x81,0xC4,0x43,0x74,0x10,0x30,0x0B,0x2D,0x08,0x1B,0x06,
|
||||
0xE6,0x83,0xCA,0x48,0xD9,0x56,0xA7,0x23,0x3B,0x09,0x12,0x09,0x15,0x07,0x0A,0x03,
|
||||
0xE5,0x5F,0xCB,0x3C,0xCF,0x48,0x91,0x22,0x31,0x0A,0x17,0x08,0x15,0x04,0x0D,0x02,
|
||||
0xD1,0x43,0x91,0x20,0xA9,0x2D,0x54,0x12,0x17,0x07,0x09,0x02,0x0C,0x04,0x05,0x03,
|
||||
};
|
||||
#endif
|
||||
|
||||
// divided into multiple functions to keep rarely-used functionality separate
|
||||
// so often-used functionality can be optimized better by compiler
|
||||
|
||||
// If write isn't preceded by read, data has this added to it
|
||||
int const no_read_before_write = 0x2000;
|
||||
|
||||
void SNES_SPC::cpu_write_smp_reg_( int data, rel_time_t time, int addr )
|
||||
{
|
||||
switch ( addr )
|
||||
{
|
||||
case r_t0target:
|
||||
case r_t1target:
|
||||
case r_t2target: {
|
||||
Timer* t = &m.timers [addr - r_t0target];
|
||||
int period = IF_0_THEN_256( data );
|
||||
if ( t->period != period )
|
||||
{
|
||||
t = run_timer( t, time );
|
||||
#if SPC_MORE_ACCURACY
|
||||
// Insane behavior when target is written just after counter is
|
||||
// clocked and counter matches new period and new period isn't 1, 2, 4, or 8
|
||||
if ( t->divider == (period & 0xFF) &&
|
||||
t->next_time == time + TIMER_MUL( t, 1 ) &&
|
||||
((period - 1) | ~0x0F) & period )
|
||||
{
|
||||
//dprintf( "SPC pathological timer target write\n" );
|
||||
|
||||
// If the period is 3, 5, or 9, there's a probability this behavior won't occur,
|
||||
// based on the previous period
|
||||
int prob = 0xFF;
|
||||
int old_period = t->period & 0xFF;
|
||||
if ( period == 3 ) prob = glitch_probs [0] [old_period];
|
||||
if ( period == 5 ) prob = glitch_probs [1] [old_period];
|
||||
if ( period == 9 ) prob = glitch_probs [2] [old_period];
|
||||
|
||||
// The glitch suppresses incrementing of one of the counter bits, based on
|
||||
// the lowest set bit in the new period
|
||||
int b = 1;
|
||||
while ( !(period & b) )
|
||||
b <<= 1;
|
||||
|
||||
if ( (rand() >> 4 & 0xFF) <= prob )
|
||||
t->divider = (t->divider - b) & 0xFF;
|
||||
}
|
||||
#endif
|
||||
t->period = period;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case r_t0out:
|
||||
case r_t1out:
|
||||
case r_t2out:
|
||||
if ( !SPC_MORE_ACCURACY )
|
||||
dprintf( "SPC wrote to counter %d\n", (int) addr - r_t0out );
|
||||
|
||||
if ( data < no_read_before_write / 2 )
|
||||
run_timer( &m.timers [addr - r_t0out], time - 1 )->counter = 0;
|
||||
break;
|
||||
|
||||
// Registers that act like RAM
|
||||
case 0x8:
|
||||
case 0x9:
|
||||
REGS_IN [addr] = (uint8_t) data;
|
||||
break;
|
||||
|
||||
case r_test:
|
||||
if ( (uint8_t) data != 0x0A )
|
||||
dprintf( "SPC wrote to test register\n" );
|
||||
break;
|
||||
|
||||
case r_control:
|
||||
// port clears
|
||||
if ( data & 0x10 )
|
||||
{
|
||||
REGS_IN [r_cpuio0] = 0;
|
||||
REGS_IN [r_cpuio1] = 0;
|
||||
}
|
||||
if ( data & 0x20 )
|
||||
{
|
||||
REGS_IN [r_cpuio2] = 0;
|
||||
REGS_IN [r_cpuio3] = 0;
|
||||
}
|
||||
|
||||
// timers
|
||||
{
|
||||
for ( int i = 0; i < timer_count; i++ )
|
||||
{
|
||||
Timer* t = &m.timers [i];
|
||||
int enabled = data >> i & 1;
|
||||
if ( t->enabled != enabled )
|
||||
{
|
||||
t = run_timer( t, time );
|
||||
t->enabled = enabled;
|
||||
if ( enabled )
|
||||
{
|
||||
t->divider = 0;
|
||||
t->counter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
enable_rom( data & 0x80 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SNES_SPC::cpu_write_smp_reg( int data, rel_time_t time, int addr )
|
||||
{
|
||||
if ( addr == r_dspdata ) // 99%
|
||||
dsp_write( data, time );
|
||||
else
|
||||
cpu_write_smp_reg_( data, time, addr );
|
||||
}
|
||||
|
||||
void SNES_SPC::cpu_write_high( int data, int i, rel_time_t time )
|
||||
{
|
||||
if ( i < rom_size )
|
||||
{
|
||||
m.hi_ram [i] = (uint8_t) data;
|
||||
if ( m.rom_enabled )
|
||||
RAM [i + rom_addr] = m.rom [i]; // restore overwritten ROM
|
||||
}
|
||||
else
|
||||
{
|
||||
assert( RAM [i + rom_addr] == (uint8_t) data );
|
||||
RAM [i + rom_addr] = cpu_pad_fill; // restore overwritten padding
|
||||
cpu_write( data, i + rom_addr - 0x10000, time );
|
||||
}
|
||||
}
|
||||
|
||||
int const bits_in_int = CHAR_BIT * sizeof (int);
|
||||
|
||||
void SNES_SPC::cpu_write( int data, int addr, rel_time_t time )
|
||||
{
|
||||
MEM_ACCESS( time, addr )
|
||||
|
||||
// RAM
|
||||
RAM [addr] = (uint8_t) data;
|
||||
int reg = addr - 0xF0;
|
||||
if ( reg >= 0 ) // 64%
|
||||
{
|
||||
// $F0-$FF
|
||||
if ( reg < reg_count ) // 87%
|
||||
{
|
||||
REGS [reg] = (uint8_t) data;
|
||||
|
||||
// Ports
|
||||
#ifdef SPC_PORT_WRITE_HOOK
|
||||
if ( (unsigned) (reg - r_cpuio0) < port_count )
|
||||
SPC_PORT_WRITE_HOOK( m.spc_time + time, (reg - r_cpuio0),
|
||||
(uint8_t) data, ®S [r_cpuio0] );
|
||||
#endif
|
||||
|
||||
// Registers other than $F2 and $F4-$F7
|
||||
//if ( reg != 2 && reg != 4 && reg != 5 && reg != 6 && reg != 7 )
|
||||
// TODO: this is a bit on the fragile side
|
||||
if ( ((~0x2F00 << (bits_in_int - 16)) << reg) < 0 ) // 36%
|
||||
cpu_write_smp_reg( data, time, reg );
|
||||
}
|
||||
// High mem/address wrap-around
|
||||
else
|
||||
{
|
||||
reg -= rom_addr - 0xF0;
|
||||
if ( reg >= 0 ) // 1% in IPL ROM area or address wrapped around
|
||||
cpu_write_high( data, reg, time );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// CPU read
|
||||
|
||||
inline int SNES_SPC::cpu_read_smp_reg( int reg, rel_time_t time )
|
||||
{
|
||||
int result = REGS_IN [reg];
|
||||
reg -= r_dspaddr;
|
||||
// DSP addr and data
|
||||
if ( (unsigned) reg <= 1 ) // 4% 0xF2 and 0xF3
|
||||
{
|
||||
result = REGS [r_dspaddr];
|
||||
if ( (unsigned) reg == 1 )
|
||||
result = dsp_read( time ); // 0xF3
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int SNES_SPC::cpu_read( int addr, rel_time_t time )
|
||||
{
|
||||
MEM_ACCESS( time, addr )
|
||||
|
||||
// RAM
|
||||
int result = RAM [addr];
|
||||
int reg = addr - 0xF0;
|
||||
if ( reg >= 0 ) // 40%
|
||||
{
|
||||
reg -= 0x10;
|
||||
if ( (unsigned) reg >= 0xFF00 ) // 21%
|
||||
{
|
||||
reg += 0x10 - r_t0out;
|
||||
|
||||
// Timers
|
||||
if ( (unsigned) reg < timer_count ) // 90%
|
||||
{
|
||||
Timer* t = &m.timers [reg];
|
||||
if ( time >= t->next_time )
|
||||
t = run_timer_( t, time );
|
||||
result = t->counter;
|
||||
t->counter = 0;
|
||||
}
|
||||
// Other registers
|
||||
else if ( reg < 0 ) // 10%
|
||||
{
|
||||
result = cpu_read_smp_reg( reg + r_t0out, time );
|
||||
}
|
||||
else // 1%
|
||||
{
|
||||
assert( reg + (r_t0out + 0xF0 - 0x10000) < 0x100 );
|
||||
result = cpu_read( reg + (r_t0out + 0xF0 - 0x10000), time );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//// Run
|
||||
|
||||
// Prefix and suffix for CPU emulator function
|
||||
#define SPC_CPU_RUN_FUNC \
|
||||
BOOST::uint8_t* SNES_SPC::run_until_( time_t end_time )\
|
||||
{\
|
||||
rel_time_t rel_time = m.spc_time - end_time;\
|
||||
assert( rel_time <= 0 );\
|
||||
m.spc_time = end_time;\
|
||||
m.dsp_time += rel_time;\
|
||||
m.timers [0].next_time += rel_time;\
|
||||
m.timers [1].next_time += rel_time;\
|
||||
m.timers [2].next_time += rel_time;
|
||||
|
||||
#define SPC_CPU_RUN_FUNC_END \
|
||||
m.spc_time += rel_time;\
|
||||
m.dsp_time -= rel_time;\
|
||||
m.timers [0].next_time -= rel_time;\
|
||||
m.timers [1].next_time -= rel_time;\
|
||||
m.timers [2].next_time -= rel_time;\
|
||||
assert( m.spc_time <= end_time );\
|
||||
return ®S [r_cpuio0];\
|
||||
}
|
||||
|
||||
int const cpu_lag_max = 12 - 1; // DIV YA,X takes 12 clocks
|
||||
|
||||
void SNES_SPC::end_frame( time_t end_time )
|
||||
{
|
||||
// Catch CPU up to as close to end as possible. If final instruction
|
||||
// would exceed end, does NOT execute it and leaves m.spc_time < end.
|
||||
if ( end_time > m.spc_time )
|
||||
run_until_( end_time );
|
||||
|
||||
m.spc_time -= end_time;
|
||||
m.extra_clocks += end_time;
|
||||
|
||||
// Greatest number of clocks early that emulation can stop early due to
|
||||
// not being able to execute current instruction without going over
|
||||
// allowed time.
|
||||
assert( -cpu_lag_max <= m.spc_time && m.spc_time <= 0 );
|
||||
|
||||
// Catch timers up to CPU
|
||||
for ( int i = 0; i < timer_count; i++ )
|
||||
run_timer( &m.timers [i], 0 );
|
||||
|
||||
// Catch DSP up to CPU
|
||||
if ( m.dsp_time < 0 )
|
||||
{
|
||||
RUN_DSP( 0, max_reg_time );
|
||||
}
|
||||
|
||||
// Save any extra samples beyond what should be generated
|
||||
if ( m.buf_begin )
|
||||
save_extra();
|
||||
}
|
||||
|
||||
// Inclusion here allows static memory access functions and better optimization
|
||||
#include "SPC_CPU.h"
|
|
@ -0,0 +1,279 @@
|
|||
// SNES SPC-700 APU emulator
|
||||
|
||||
// snes_spc 0.9.0
|
||||
#ifndef SNES_SPC_H
|
||||
#define SNES_SPC_H
|
||||
|
||||
#include "SPC_DSP.h"
|
||||
#include "blargg_endian.h"
|
||||
|
||||
struct SNES_SPC {
|
||||
public:
|
||||
typedef BOOST::uint8_t uint8_t;
|
||||
|
||||
// Must be called once before using
|
||||
blargg_err_t init();
|
||||
|
||||
// Sample pairs generated per second
|
||||
enum { sample_rate = 32000 };
|
||||
|
||||
// Emulator use
|
||||
|
||||
// Sets IPL ROM data. Library does not include ROM data. Most SPC music files
|
||||
// don't need ROM, but a full emulator must provide this.
|
||||
enum { rom_size = 0x40 };
|
||||
void init_rom( uint8_t const rom [rom_size] );
|
||||
|
||||
// Sets destination for output samples
|
||||
typedef short sample_t;
|
||||
void set_output( sample_t* out, int out_size );
|
||||
|
||||
// Number of samples written to output since last set
|
||||
int sample_count() const;
|
||||
|
||||
// Resets SPC to power-on state. This resets your output buffer, so you must
|
||||
// call set_output() after this.
|
||||
void reset();
|
||||
|
||||
// Emulates pressing reset switch on SNES. This resets your output buffer, so
|
||||
// you must call set_output() after this.
|
||||
void soft_reset();
|
||||
|
||||
// 1024000 SPC clocks per second, sample pair every 32 clocks
|
||||
typedef int time_t;
|
||||
enum { clock_rate = 1024000 };
|
||||
enum { clocks_per_sample = 32 };
|
||||
|
||||
// Emulated port read/write at specified time
|
||||
enum { port_count = 4 };
|
||||
int read_port ( time_t, int port );
|
||||
void write_port( time_t, int port, int data );
|
||||
|
||||
// Runs SPC to end_time and starts a new time frame at 0
|
||||
void end_frame( time_t end_time );
|
||||
|
||||
// Sound control
|
||||
|
||||
// Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events).
|
||||
// Reduces emulation accuracy.
|
||||
enum { voice_count = 8 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// If true, prevents channels and global volumes from being phase-negated.
|
||||
// Only supported by fast DSP.
|
||||
void disable_surround( bool disable = true );
|
||||
|
||||
// Sets tempo, where tempo_unit = normal, tempo_unit / 2 = half speed, etc.
|
||||
enum { tempo_unit = 0x100 };
|
||||
void set_tempo( int );
|
||||
|
||||
// SPC music files
|
||||
|
||||
// Loads SPC data into emulator
|
||||
enum { spc_min_file_size = 0x10180 };
|
||||
enum { spc_file_size = 0x10200 };
|
||||
blargg_err_t load_spc( void const* in, long size );
|
||||
|
||||
// Clears echo region. Useful after loading an SPC as many have garbage in echo.
|
||||
void clear_echo();
|
||||
|
||||
// Plays for count samples and write samples to out. Discards samples if out
|
||||
// is NULL. Count must be a multiple of 2 since output is stereo.
|
||||
blargg_err_t play( int count, sample_t* out );
|
||||
|
||||
// Skips count samples. Several times faster than play() when using fast DSP.
|
||||
blargg_err_t skip( int count );
|
||||
|
||||
// State save/load (only available with accurate DSP)
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
// Saves/loads state
|
||||
enum { state_size = 67 * 1024L }; // maximum space needed when saving
|
||||
typedef SPC_DSP::copy_func_t copy_func_t;
|
||||
void copy_state( unsigned char** io, copy_func_t );
|
||||
|
||||
// Writes minimal header to spc_out
|
||||
static void init_header( void* spc_out );
|
||||
|
||||
// Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out.
|
||||
// Does not set up SPC header; use init_header() for that.
|
||||
void save_spc( void* spc_out );
|
||||
|
||||
// Returns true if new key-on events occurred since last check. Useful for
|
||||
// trimming silence while saving an SPC.
|
||||
bool check_kon();
|
||||
#endif
|
||||
|
||||
public:
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
|
||||
typedef BOOST::uint16_t uint16_t;
|
||||
|
||||
// Time relative to m_spc_time. Speeds up code a bit by eliminating need to
|
||||
// constantly add m_spc_time to time from CPU. CPU uses time that ends at
|
||||
// 0 to eliminate reloading end time every instruction. It pays off.
|
||||
typedef int rel_time_t;
|
||||
|
||||
struct Timer
|
||||
{
|
||||
rel_time_t next_time; // time of next event
|
||||
int prescaler;
|
||||
int period;
|
||||
int divider;
|
||||
int enabled;
|
||||
int counter;
|
||||
};
|
||||
enum { reg_count = 0x10 };
|
||||
enum { timer_count = 3 };
|
||||
enum { extra_size = SPC_DSP::extra_size };
|
||||
|
||||
enum { signature_size = 35 };
|
||||
|
||||
private:
|
||||
SPC_DSP dsp;
|
||||
|
||||
#if SPC_LESS_ACCURATE
|
||||
static signed char const reg_times_ [256];
|
||||
signed char reg_times [256];
|
||||
#endif
|
||||
|
||||
struct state_t
|
||||
{
|
||||
Timer timers [timer_count];
|
||||
|
||||
uint8_t smp_regs [2] [reg_count];
|
||||
|
||||
struct
|
||||
{
|
||||
int pc;
|
||||
int a;
|
||||
int x;
|
||||
int y;
|
||||
int psw;
|
||||
int sp;
|
||||
} cpu_regs;
|
||||
|
||||
rel_time_t dsp_time;
|
||||
time_t spc_time;
|
||||
bool echo_accessed;
|
||||
|
||||
int tempo;
|
||||
int skipped_kon;
|
||||
int skipped_koff;
|
||||
const char* cpu_error;
|
||||
|
||||
int extra_clocks;
|
||||
sample_t* buf_begin;
|
||||
sample_t const* buf_end;
|
||||
sample_t* extra_pos;
|
||||
sample_t extra_buf [extra_size];
|
||||
|
||||
int rom_enabled;
|
||||
uint8_t rom [rom_size];
|
||||
uint8_t hi_ram [rom_size];
|
||||
|
||||
unsigned char cycle_table [256];
|
||||
|
||||
struct
|
||||
{
|
||||
// padding to neutralize address overflow
|
||||
union {
|
||||
uint8_t padding1 [0x100];
|
||||
uint16_t align; // makes compiler align data for 16-bit access
|
||||
} padding1 [1];
|
||||
uint8_t ram [0x10000];
|
||||
uint8_t padding2 [0x100];
|
||||
} ram;
|
||||
};
|
||||
state_t m;
|
||||
|
||||
enum { rom_addr = 0xFFC0 };
|
||||
|
||||
enum { skipping_time = 127 };
|
||||
|
||||
// Value that padding should be filled with
|
||||
enum { cpu_pad_fill = 0xFF };
|
||||
|
||||
enum {
|
||||
r_test = 0x0, r_control = 0x1,
|
||||
r_dspaddr = 0x2, r_dspdata = 0x3,
|
||||
r_cpuio0 = 0x4, r_cpuio1 = 0x5,
|
||||
r_cpuio2 = 0x6, r_cpuio3 = 0x7,
|
||||
r_f8 = 0x8, r_f9 = 0x9,
|
||||
r_t0target = 0xA, r_t1target = 0xB, r_t2target = 0xC,
|
||||
r_t0out = 0xD, r_t1out = 0xE, r_t2out = 0xF
|
||||
};
|
||||
|
||||
void timers_loaded();
|
||||
void enable_rom( int enable );
|
||||
void reset_buf();
|
||||
void save_extra();
|
||||
void load_regs( uint8_t const in [reg_count] );
|
||||
void ram_loaded();
|
||||
void regs_loaded();
|
||||
void reset_time_regs();
|
||||
void reset_common( int timer_counter_init );
|
||||
|
||||
Timer* run_timer_ ( Timer* t, rel_time_t );
|
||||
Timer* run_timer ( Timer* t, rel_time_t );
|
||||
int dsp_read ( rel_time_t );
|
||||
void dsp_write ( int data, rel_time_t );
|
||||
void cpu_write_smp_reg_( int data, rel_time_t, int addr );
|
||||
void cpu_write_smp_reg ( int data, rel_time_t, int addr );
|
||||
void cpu_write_high ( int data, int i, rel_time_t );
|
||||
void cpu_write ( int data, int addr, rel_time_t );
|
||||
int cpu_read_smp_reg ( int i, rel_time_t );
|
||||
int cpu_read ( int addr, rel_time_t );
|
||||
unsigned CPU_mem_bit ( uint8_t const* pc, rel_time_t );
|
||||
|
||||
bool check_echo_access ( int addr );
|
||||
uint8_t* run_until_( time_t end_time );
|
||||
|
||||
struct spc_file_t
|
||||
{
|
||||
char signature [signature_size];
|
||||
uint8_t has_id666;
|
||||
uint8_t version;
|
||||
uint8_t pcl, pch;
|
||||
uint8_t a;
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t psw;
|
||||
uint8_t sp;
|
||||
char text [212];
|
||||
uint8_t ram [0x10000];
|
||||
uint8_t dsp [128];
|
||||
uint8_t unused [0x40];
|
||||
uint8_t ipl_rom [0x40];
|
||||
};
|
||||
|
||||
static char const signature [signature_size + 1];
|
||||
|
||||
void save_regs( uint8_t out [reg_count] );
|
||||
};
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
inline int SNES_SPC::sample_count() const { return (m.extra_clocks >> 5) * 2; }
|
||||
|
||||
inline int SNES_SPC::read_port( time_t t, int port )
|
||||
{
|
||||
assert( (unsigned) port < port_count );
|
||||
return run_until_( t ) [port];
|
||||
}
|
||||
|
||||
inline void SNES_SPC::write_port( time_t t, int port, int data )
|
||||
{
|
||||
assert( (unsigned) port < port_count );
|
||||
run_until_( t ) [0x10 + port] = data;
|
||||
}
|
||||
|
||||
inline void SNES_SPC::mute_voices( int mask ) { dsp.mute_voices( mask ); }
|
||||
|
||||
inline void SNES_SPC::disable_surround( bool disable ) { dsp.disable_surround( disable ); }
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
inline bool SNES_SPC::check_kon() { return dsp.check_kon(); }
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,380 @@
|
|||
// SPC emulation support: init, sample buffering, reset, SPC loading
|
||||
|
||||
// snes_spc 0.9.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "SNES_SPC.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#define RAM (m.ram.ram)
|
||||
#define REGS (m.smp_regs [0])
|
||||
#define REGS_IN (m.smp_regs [1])
|
||||
|
||||
// (n ? n : 256)
|
||||
#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
|
||||
|
||||
|
||||
//// Init
|
||||
|
||||
blargg_err_t SNES_SPC::init()
|
||||
{
|
||||
memset( &m, 0, sizeof m );
|
||||
dsp.init( RAM );
|
||||
|
||||
m.tempo = tempo_unit;
|
||||
|
||||
// Most SPC music doesn't need ROM, and almost all the rest only rely
|
||||
// on these two bytes
|
||||
m.rom [0x3E] = 0xFF;
|
||||
m.rom [0x3F] = 0xC0;
|
||||
|
||||
static unsigned char const cycle_table [128] =
|
||||
{// 01 23 45 67 89 AB CD EF
|
||||
0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x68, // 0
|
||||
0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x46, // 1
|
||||
0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x74, // 2
|
||||
0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x38, // 3
|
||||
0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x66, // 4
|
||||
0x48,0x47,0x45,0x56,0x55,0x45,0x22,0x43, // 5
|
||||
0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x75, // 6
|
||||
0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x36, // 7
|
||||
0x28,0x47,0x34,0x36,0x26,0x54,0x52,0x45, // 8
|
||||
0x48,0x47,0x45,0x56,0x55,0x55,0x22,0xC5, // 9
|
||||
0x38,0x47,0x34,0x36,0x26,0x44,0x52,0x44, // A
|
||||
0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x34, // B
|
||||
0x38,0x47,0x45,0x47,0x25,0x64,0x52,0x49, // C
|
||||
0x48,0x47,0x56,0x67,0x45,0x55,0x22,0x83, // D
|
||||
0x28,0x47,0x34,0x36,0x24,0x53,0x43,0x40, // E
|
||||
0x48,0x47,0x45,0x56,0x34,0x54,0x22,0x60, // F
|
||||
};
|
||||
|
||||
// unpack cycle table
|
||||
for ( int i = 0; i < 128; i++ )
|
||||
{
|
||||
int n = cycle_table [i];
|
||||
m.cycle_table [i * 2 + 0] = n >> 4;
|
||||
m.cycle_table [i * 2 + 1] = n & 0x0F;
|
||||
}
|
||||
|
||||
#if SPC_LESS_ACCURATE
|
||||
memcpy( reg_times, reg_times_, sizeof reg_times );
|
||||
#endif
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SNES_SPC::init_rom( uint8_t const in [rom_size] )
|
||||
{
|
||||
memcpy( m.rom, in, sizeof m.rom );
|
||||
}
|
||||
|
||||
void SNES_SPC::set_tempo( int t )
|
||||
{
|
||||
m.tempo = t;
|
||||
int const timer2_shift = 4; // 64 kHz
|
||||
int const other_shift = 3; // 8 kHz
|
||||
|
||||
#if SPC_DISABLE_TEMPO
|
||||
m.timers [2].prescaler = timer2_shift;
|
||||
m.timers [1].prescaler = timer2_shift + other_shift;
|
||||
m.timers [0].prescaler = timer2_shift + other_shift;
|
||||
#else
|
||||
if ( !t )
|
||||
t = 1;
|
||||
int const timer2_rate = 1 << timer2_shift;
|
||||
int rate = (timer2_rate * tempo_unit + (t >> 1)) / t;
|
||||
if ( rate < timer2_rate / 4 )
|
||||
rate = timer2_rate / 4; // max 4x tempo
|
||||
m.timers [2].prescaler = rate;
|
||||
m.timers [1].prescaler = rate << other_shift;
|
||||
m.timers [0].prescaler = rate << other_shift;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Timer registers have been loaded. Applies these to the timers. Does not
|
||||
// reset timer prescalers or dividers.
|
||||
void SNES_SPC::timers_loaded()
|
||||
{
|
||||
int i;
|
||||
for ( i = 0; i < timer_count; i++ )
|
||||
{
|
||||
Timer* t = &m.timers [i];
|
||||
t->period = IF_0_THEN_256( REGS [r_t0target + i] );
|
||||
t->enabled = REGS [r_control] >> i & 1;
|
||||
t->counter = REGS_IN [r_t0out + i] & 0x0F;
|
||||
}
|
||||
|
||||
set_tempo( m.tempo );
|
||||
}
|
||||
|
||||
// Loads registers from unified 16-byte format
|
||||
void SNES_SPC::load_regs( uint8_t const in [reg_count] )
|
||||
{
|
||||
memcpy( REGS, in, reg_count );
|
||||
memcpy( REGS_IN, REGS, reg_count );
|
||||
|
||||
// These always read back as 0
|
||||
REGS_IN [r_test ] = 0;
|
||||
REGS_IN [r_control ] = 0;
|
||||
REGS_IN [r_t0target] = 0;
|
||||
REGS_IN [r_t1target] = 0;
|
||||
REGS_IN [r_t2target] = 0;
|
||||
}
|
||||
|
||||
// RAM was just loaded from SPC, with $F0-$FF containing SMP registers
|
||||
// and timer counts. Copies these to proper registers.
|
||||
void SNES_SPC::ram_loaded()
|
||||
{
|
||||
m.rom_enabled = 0;
|
||||
load_regs( &RAM [0xF0] );
|
||||
|
||||
// Put STOP instruction around memory to catch PC underflow/overflow
|
||||
memset( m.ram.padding1, cpu_pad_fill, sizeof m.ram.padding1 );
|
||||
memset( m.ram.padding2, cpu_pad_fill, sizeof m.ram.padding2 );
|
||||
}
|
||||
|
||||
// Registers were just loaded. Applies these new values.
|
||||
void SNES_SPC::regs_loaded()
|
||||
{
|
||||
enable_rom( REGS [r_control] & 0x80 );
|
||||
timers_loaded();
|
||||
}
|
||||
|
||||
void SNES_SPC::reset_time_regs()
|
||||
{
|
||||
m.cpu_error = 0;
|
||||
m.echo_accessed = 0;
|
||||
m.spc_time = 0;
|
||||
m.dsp_time = 0;
|
||||
#if SPC_LESS_ACCURATE
|
||||
m.dsp_time = clocks_per_sample + 1;
|
||||
#endif
|
||||
|
||||
for ( int i = 0; i < timer_count; i++ )
|
||||
{
|
||||
Timer* t = &m.timers [i];
|
||||
t->next_time = 1;
|
||||
t->divider = 0;
|
||||
}
|
||||
|
||||
regs_loaded();
|
||||
|
||||
m.extra_clocks = 0;
|
||||
reset_buf();
|
||||
}
|
||||
|
||||
void SNES_SPC::reset_common( int timer_counter_init )
|
||||
{
|
||||
int i;
|
||||
for ( i = 0; i < timer_count; i++ )
|
||||
REGS_IN [r_t0out + i] = timer_counter_init;
|
||||
|
||||
// Run IPL ROM
|
||||
memset( &m.cpu_regs, 0, sizeof m.cpu_regs );
|
||||
m.cpu_regs.pc = rom_addr;
|
||||
|
||||
REGS [r_test ] = 0x0A;
|
||||
REGS [r_control] = 0xB0; // ROM enabled, clear ports
|
||||
for ( i = 0; i < port_count; i++ )
|
||||
REGS_IN [r_cpuio0 + i] = 0;
|
||||
|
||||
reset_time_regs();
|
||||
}
|
||||
|
||||
void SNES_SPC::soft_reset()
|
||||
{
|
||||
reset_common( 0 );
|
||||
dsp.soft_reset();
|
||||
}
|
||||
|
||||
void SNES_SPC::reset()
|
||||
{
|
||||
memset( RAM, 0xFF, 0x10000 );
|
||||
ram_loaded();
|
||||
reset_common( 0x0F );
|
||||
dsp.reset();
|
||||
}
|
||||
|
||||
char const SNES_SPC::signature [signature_size + 1] =
|
||||
"SNES-SPC700 Sound File Data v0.30\x1A\x1A";
|
||||
|
||||
blargg_err_t SNES_SPC::load_spc( void const* data, long size )
|
||||
{
|
||||
spc_file_t const* const spc = (spc_file_t const*) data;
|
||||
|
||||
// be sure compiler didn't insert any padding into fle_t
|
||||
assert( sizeof (spc_file_t) == spc_min_file_size + 0x80 );
|
||||
|
||||
// Check signature and file size
|
||||
if ( size < signature_size || memcmp( spc, signature, 27 ) )
|
||||
return "Not an SPC file";
|
||||
|
||||
if ( size < spc_min_file_size )
|
||||
return "Corrupt SPC file";
|
||||
|
||||
// CPU registers
|
||||
m.cpu_regs.pc = spc->pch * 0x100 + spc->pcl;
|
||||
m.cpu_regs.a = spc->a;
|
||||
m.cpu_regs.x = spc->x;
|
||||
m.cpu_regs.y = spc->y;
|
||||
m.cpu_regs.psw = spc->psw;
|
||||
m.cpu_regs.sp = spc->sp;
|
||||
|
||||
// RAM and registers
|
||||
memcpy( RAM, spc->ram, 0x10000 );
|
||||
ram_loaded();
|
||||
|
||||
// DSP registers
|
||||
dsp.load( spc->dsp );
|
||||
|
||||
reset_time_regs();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SNES_SPC::clear_echo()
|
||||
{
|
||||
if ( !(dsp.read( SPC_DSP::r_flg ) & 0x20) )
|
||||
{
|
||||
int addr = 0x100 * dsp.read( SPC_DSP::r_esa );
|
||||
int end = addr + 0x800 * (dsp.read( SPC_DSP::r_edl ) & 0x0F);
|
||||
if ( end > 0x10000 )
|
||||
end = 0x10000;
|
||||
memset( &RAM [addr], 0xFF, end - addr );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Sample output
|
||||
|
||||
void SNES_SPC::reset_buf()
|
||||
{
|
||||
// Start with half extra buffer of silence
|
||||
sample_t* out = m.extra_buf;
|
||||
while ( out < &m.extra_buf [extra_size / 2] )
|
||||
*out++ = 0;
|
||||
|
||||
m.extra_pos = out;
|
||||
m.buf_begin = 0;
|
||||
|
||||
dsp.set_output( 0, 0 );
|
||||
}
|
||||
|
||||
void SNES_SPC::set_output( sample_t* out, int size )
|
||||
{
|
||||
require( (size & 1) == 0 ); // size must be even
|
||||
|
||||
m.extra_clocks &= clocks_per_sample - 1;
|
||||
if ( out )
|
||||
{
|
||||
sample_t const* out_end = out + size;
|
||||
m.buf_begin = out;
|
||||
m.buf_end = out_end;
|
||||
|
||||
// Copy extra to output
|
||||
sample_t const* in = m.extra_buf;
|
||||
while ( in < m.extra_pos && out < out_end )
|
||||
*out++ = *in++;
|
||||
|
||||
// Handle output being full already
|
||||
if ( out >= out_end )
|
||||
{
|
||||
// Have DSP write to remaining extra space
|
||||
out = dsp.extra();
|
||||
out_end = &dsp.extra() [extra_size];
|
||||
|
||||
// Copy any remaining extra samples as if DSP wrote them
|
||||
while ( in < m.extra_pos )
|
||||
*out++ = *in++;
|
||||
assert( out <= out_end );
|
||||
}
|
||||
|
||||
dsp.set_output( out, out_end - out );
|
||||
}
|
||||
else
|
||||
{
|
||||
reset_buf();
|
||||
}
|
||||
}
|
||||
|
||||
void SNES_SPC::save_extra()
|
||||
{
|
||||
// Get end pointers
|
||||
sample_t const* main_end = m.buf_end; // end of data written to buf
|
||||
sample_t const* dsp_end = dsp.out_pos(); // end of data written to dsp.extra()
|
||||
if ( m.buf_begin <= dsp_end && dsp_end <= main_end )
|
||||
{
|
||||
main_end = dsp_end;
|
||||
dsp_end = dsp.extra(); // nothing in DSP's extra
|
||||
}
|
||||
|
||||
// Copy any extra samples at these ends into extra_buf
|
||||
sample_t* out = m.extra_buf;
|
||||
sample_t const* in;
|
||||
for ( in = m.buf_begin + sample_count(); in < main_end; in++ )
|
||||
*out++ = *in;
|
||||
for ( in = dsp.extra(); in < dsp_end ; in++ )
|
||||
*out++ = *in;
|
||||
|
||||
m.extra_pos = out;
|
||||
assert( out <= &m.extra_buf [extra_size] );
|
||||
}
|
||||
|
||||
blargg_err_t SNES_SPC::play( int count, sample_t* out )
|
||||
{
|
||||
require( (count & 1) == 0 ); // must be even
|
||||
if ( count )
|
||||
{
|
||||
set_output( out, count );
|
||||
end_frame( count * (clocks_per_sample / 2) );
|
||||
}
|
||||
|
||||
const char* err = m.cpu_error;
|
||||
m.cpu_error = 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
blargg_err_t SNES_SPC::skip( int count )
|
||||
{
|
||||
#if SPC_LESS_ACCURATE
|
||||
if ( count > 2 * sample_rate * 2 )
|
||||
{
|
||||
set_output( 0, 0 );
|
||||
|
||||
// Skip a multiple of 4 samples
|
||||
time_t end = count;
|
||||
count = (count & 3) + 1 * sample_rate * 2;
|
||||
end = (end - count) * (clocks_per_sample / 2);
|
||||
|
||||
m.skipped_kon = 0;
|
||||
m.skipped_koff = 0;
|
||||
|
||||
// Preserve DSP and timer synchronization
|
||||
// TODO: verify that this really preserves it
|
||||
int old_dsp_time = m.dsp_time + m.spc_time;
|
||||
m.dsp_time = end - m.spc_time + skipping_time;
|
||||
end_frame( end );
|
||||
m.dsp_time = m.dsp_time - skipping_time + old_dsp_time;
|
||||
|
||||
dsp.write( SPC_DSP::r_koff, m.skipped_koff & ~m.skipped_kon );
|
||||
dsp.write( SPC_DSP::r_kon , m.skipped_kon );
|
||||
clear_echo();
|
||||
}
|
||||
#endif
|
||||
|
||||
return play( count, 0 );
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
// SPC emulation state save/load: copy_state(), save_spc()
|
||||
// Separate file to avoid linking in unless needed
|
||||
|
||||
// snes_spc 0.9.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "SNES_SPC.h"
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2004-2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#define RAM (m.ram.ram)
|
||||
#define REGS (m.smp_regs [0])
|
||||
#define REGS_IN (m.smp_regs [1])
|
||||
|
||||
void SNES_SPC::save_regs( uint8_t out [reg_count] )
|
||||
{
|
||||
// Use current timer counter values
|
||||
for ( int i = 0; i < timer_count; i++ )
|
||||
out [r_t0out + i] = m.timers [i].counter;
|
||||
|
||||
// Last written values
|
||||
memcpy( out, REGS, r_t0out );
|
||||
}
|
||||
|
||||
void SNES_SPC::init_header( void* spc_out )
|
||||
{
|
||||
spc_file_t* const spc = (spc_file_t*) spc_out;
|
||||
|
||||
spc->has_id666 = 26; // has none
|
||||
spc->version = 30;
|
||||
memcpy( spc, signature, sizeof spc->signature );
|
||||
memset( spc->text, 0, sizeof spc->text );
|
||||
}
|
||||
|
||||
void SNES_SPC::save_spc( void* spc_out )
|
||||
{
|
||||
spc_file_t* const spc = (spc_file_t*) spc_out;
|
||||
|
||||
// CPU
|
||||
spc->pcl = (uint8_t) (m.cpu_regs.pc >> 0);
|
||||
spc->pch = (uint8_t) (m.cpu_regs.pc >> 8);
|
||||
spc->a = m.cpu_regs.a;
|
||||
spc->x = m.cpu_regs.x;
|
||||
spc->y = m.cpu_regs.y;
|
||||
spc->psw = m.cpu_regs.psw;
|
||||
spc->sp = m.cpu_regs.sp;
|
||||
|
||||
// RAM, ROM
|
||||
memcpy( spc->ram, RAM, sizeof spc->ram );
|
||||
if ( m.rom_enabled )
|
||||
memcpy( spc->ram + rom_addr, m.hi_ram, sizeof m.hi_ram );
|
||||
memset( spc->unused, 0, sizeof spc->unused );
|
||||
memcpy( spc->ipl_rom, m.rom, sizeof spc->ipl_rom );
|
||||
|
||||
// SMP registers
|
||||
save_regs( &spc->ram [0xF0] );
|
||||
int i;
|
||||
for ( i = 0; i < port_count; i++ )
|
||||
spc->ram [0xF0 + r_cpuio0 + i] = REGS_IN [r_cpuio0 + i];
|
||||
|
||||
// DSP registers
|
||||
for ( i = 0; i < SPC_DSP::register_count; i++ )
|
||||
spc->dsp [i] = dsp.read( i );
|
||||
}
|
||||
|
||||
void SNES_SPC::copy_state( unsigned char** io, copy_func_t copy )
|
||||
{
|
||||
SPC_State_Copier copier( io, copy );
|
||||
|
||||
// Make state data more readable by putting 64K RAM, 16 SMP registers,
|
||||
// then DSP (with its 128 registers) first
|
||||
|
||||
// RAM
|
||||
enable_rom( 0 ); // will get re-enabled if necessary in regs_loaded() below
|
||||
copier.copy( RAM, 0x10000 );
|
||||
|
||||
{
|
||||
// SMP registers
|
||||
uint8_t out_ports [port_count];
|
||||
uint8_t regs [reg_count];
|
||||
memcpy( out_ports, ®S [r_cpuio0], sizeof out_ports );
|
||||
save_regs( regs );
|
||||
copier.copy( regs, sizeof regs );
|
||||
copier.copy( out_ports, sizeof out_ports );
|
||||
load_regs( regs );
|
||||
regs_loaded();
|
||||
memcpy( ®S [r_cpuio0], out_ports, sizeof out_ports );
|
||||
}
|
||||
|
||||
// CPU registers
|
||||
SPC_COPY( uint16_t, m.cpu_regs.pc );
|
||||
SPC_COPY( uint8_t, m.cpu_regs.a );
|
||||
SPC_COPY( uint8_t, m.cpu_regs.x );
|
||||
SPC_COPY( uint8_t, m.cpu_regs.y );
|
||||
SPC_COPY( uint8_t, m.cpu_regs.psw );
|
||||
SPC_COPY( uint8_t, m.cpu_regs.sp );
|
||||
copier.extra();
|
||||
|
||||
SPC_COPY( int16_t, m.spc_time );
|
||||
SPC_COPY( int16_t, m.dsp_time );
|
||||
|
||||
// DSP
|
||||
dsp.copy_state( io, copy );
|
||||
|
||||
// Timers
|
||||
for ( int i = 0; i < timer_count; i++ )
|
||||
{
|
||||
Timer* t = &m.timers [i];
|
||||
SPC_COPY( int16_t, t->next_time );
|
||||
SPC_COPY( uint8_t, t->divider );
|
||||
copier.extra();
|
||||
}
|
||||
copier.extra();
|
||||
}
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,304 @@
|
|||
// Highly accurate SNES SPC-700 DSP emulator
|
||||
|
||||
// snes_spc 0.9.0
|
||||
#ifndef SPC_DSP_H
|
||||
#define SPC_DSP_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
extern "C" { typedef void (*dsp_copy_func_t)( unsigned char** io, void* state, size_t ); }
|
||||
|
||||
class SPC_DSP {
|
||||
public:
|
||||
typedef BOOST::uint8_t uint8_t;
|
||||
|
||||
// Setup
|
||||
|
||||
// Initializes DSP and has it use the 64K RAM provided
|
||||
void init( void* ram_64k );
|
||||
|
||||
// Sets destination for output samples. If out is NULL or out_size is 0,
|
||||
// doesn't generate any.
|
||||
typedef short sample_t;
|
||||
void set_output( sample_t* out, int out_size );
|
||||
|
||||
// Number of samples written to output since it was last set, always
|
||||
// a multiple of 2. Undefined if more samples were generated than
|
||||
// output buffer could hold.
|
||||
int sample_count() const;
|
||||
|
||||
// Emulation
|
||||
|
||||
// Resets DSP to power-on state
|
||||
void reset();
|
||||
|
||||
// Emulates pressing reset switch on SNES
|
||||
void soft_reset();
|
||||
|
||||
// Reads/writes DSP registers. For accuracy, you must first call run()
|
||||
// to catch the DSP up to present.
|
||||
int read ( int addr ) const;
|
||||
void write( int addr, int data );
|
||||
|
||||
// Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks
|
||||
// a pair of samples is be generated.
|
||||
void run( int clock_count );
|
||||
|
||||
// Sound control
|
||||
|
||||
// Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events).
|
||||
// Reduces emulation accuracy.
|
||||
enum { voice_count = 8 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// State
|
||||
|
||||
// Resets DSP and uses supplied values to initialize registers
|
||||
enum { register_count = 128 };
|
||||
void load( uint8_t const regs [register_count] );
|
||||
|
||||
// Saves/loads exact emulator state
|
||||
enum { state_size = 640 }; // maximum space needed when saving
|
||||
typedef dsp_copy_func_t copy_func_t;
|
||||
void copy_state( unsigned char** io, copy_func_t );
|
||||
|
||||
// Returns non-zero if new key-on events occurred since last call
|
||||
bool check_kon();
|
||||
|
||||
// DSP register addresses
|
||||
|
||||
// Global registers
|
||||
enum {
|
||||
r_mvoll = 0x0C, r_mvolr = 0x1C,
|
||||
r_evoll = 0x2C, r_evolr = 0x3C,
|
||||
r_kon = 0x4C, r_koff = 0x5C,
|
||||
r_flg = 0x6C, r_endx = 0x7C,
|
||||
r_efb = 0x0D, r_pmon = 0x2D,
|
||||
r_non = 0x3D, r_eon = 0x4D,
|
||||
r_dir = 0x5D, r_esa = 0x6D,
|
||||
r_edl = 0x7D,
|
||||
r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F
|
||||
};
|
||||
|
||||
// Voice registers
|
||||
enum {
|
||||
v_voll = 0x00, v_volr = 0x01,
|
||||
v_pitchl = 0x02, v_pitchh = 0x03,
|
||||
v_srcn = 0x04, v_adsr0 = 0x05,
|
||||
v_adsr1 = 0x06, v_gain = 0x07,
|
||||
v_envx = 0x08, v_outx = 0x09
|
||||
};
|
||||
|
||||
public:
|
||||
enum { extra_size = 16 };
|
||||
sample_t* extra() { return m.extra; }
|
||||
sample_t const* out_pos() const { return m.out; }
|
||||
void disable_surround( bool ) { } // not supported
|
||||
public:
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
|
||||
typedef BOOST::int8_t int8_t;
|
||||
typedef BOOST::int16_t int16_t;
|
||||
|
||||
enum { echo_hist_size = 8 };
|
||||
|
||||
enum env_mode_t { env_release, env_attack, env_decay, env_sustain };
|
||||
enum { brr_buf_size = 12 };
|
||||
struct voice_t
|
||||
{
|
||||
int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling)
|
||||
int buf_pos; // place in buffer where next samples will be decoded
|
||||
int interp_pos; // relative fractional position in sample (0x1000 = 1.0)
|
||||
int brr_addr; // address of current BRR block
|
||||
int brr_offset; // current decoding offset in BRR block
|
||||
uint8_t* regs; // pointer to voice's DSP registers
|
||||
int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc.
|
||||
int kon_delay; // KON delay/current setup phase
|
||||
env_mode_t env_mode;
|
||||
int env; // current envelope level
|
||||
int hidden_env; // used by GAIN mode 7, very obscure quirk
|
||||
uint8_t t_envx_out;
|
||||
};
|
||||
private:
|
||||
enum { brr_block_size = 9 };
|
||||
|
||||
struct state_t
|
||||
{
|
||||
uint8_t regs [register_count];
|
||||
|
||||
// Echo history keeps most recent 8 samples (twice the size to simplify wrap handling)
|
||||
int echo_hist [echo_hist_size * 2] [2];
|
||||
int (*echo_hist_pos) [2]; // &echo_hist [0 to 7]
|
||||
|
||||
int every_other_sample; // toggles every sample
|
||||
int kon; // KON value when last checked
|
||||
int noise;
|
||||
int counter;
|
||||
int echo_offset; // offset from ESA in echo buffer
|
||||
int echo_length; // number of bytes that echo_offset will stop at
|
||||
int phase; // next clock cycle to run (0-31)
|
||||
bool kon_check; // set when a new KON occurs
|
||||
|
||||
// Hidden registers also written to when main register is written to
|
||||
int new_kon;
|
||||
uint8_t endx_buf;
|
||||
uint8_t envx_buf;
|
||||
uint8_t outx_buf;
|
||||
|
||||
// Temporary state between clocks
|
||||
|
||||
// read once per sample
|
||||
int t_pmon;
|
||||
int t_non;
|
||||
int t_eon;
|
||||
int t_dir;
|
||||
int t_koff;
|
||||
|
||||
// read a few clocks ahead then used
|
||||
int t_brr_next_addr;
|
||||
int t_adsr0;
|
||||
int t_brr_header;
|
||||
int t_brr_byte;
|
||||
int t_srcn;
|
||||
int t_esa;
|
||||
int t_echo_enabled;
|
||||
|
||||
// internal state that is recalculated every sample
|
||||
int t_dir_addr;
|
||||
int t_pitch;
|
||||
int t_output;
|
||||
int t_looped;
|
||||
int t_echo_ptr;
|
||||
|
||||
// left/right sums
|
||||
int t_main_out [2];
|
||||
int t_echo_out [2];
|
||||
int t_echo_in [2];
|
||||
|
||||
voice_t voices [voice_count];
|
||||
|
||||
// non-emulation state
|
||||
uint8_t* ram; // 64K shared RAM between DSP and SMP
|
||||
int mute_mask;
|
||||
sample_t* out;
|
||||
sample_t* out_end;
|
||||
sample_t* out_begin;
|
||||
sample_t extra [extra_size];
|
||||
};
|
||||
state_t m;
|
||||
|
||||
void init_counter();
|
||||
void run_counters();
|
||||
unsigned read_counter( int rate );
|
||||
|
||||
int interpolate( voice_t const* v );
|
||||
void run_envelope( voice_t* const v );
|
||||
void decode_brr( voice_t* v );
|
||||
|
||||
void misc_27();
|
||||
void misc_28();
|
||||
void misc_29();
|
||||
void misc_30();
|
||||
|
||||
void voice_output( voice_t const* v, int ch );
|
||||
void voice_V1( voice_t* const );
|
||||
void voice_V2( voice_t* const );
|
||||
void voice_V3( voice_t* const );
|
||||
void voice_V3a( voice_t* const );
|
||||
void voice_V3b( voice_t* const );
|
||||
void voice_V3c( voice_t* const );
|
||||
void voice_V4( voice_t* const );
|
||||
void voice_V5( voice_t* const );
|
||||
void voice_V6( voice_t* const );
|
||||
void voice_V7( voice_t* const );
|
||||
void voice_V8( voice_t* const );
|
||||
void voice_V9( voice_t* const );
|
||||
void voice_V7_V4_V1( voice_t* const );
|
||||
void voice_V8_V5_V2( voice_t* const );
|
||||
void voice_V9_V6_V3( voice_t* const );
|
||||
|
||||
void echo_read( int ch );
|
||||
int echo_output( int ch );
|
||||
void echo_write( int ch );
|
||||
void echo_22();
|
||||
void echo_23();
|
||||
void echo_24();
|
||||
void echo_25();
|
||||
void echo_26();
|
||||
void echo_27();
|
||||
void echo_28();
|
||||
void echo_29();
|
||||
void echo_30();
|
||||
|
||||
void soft_reset_common();
|
||||
};
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
inline int SPC_DSP::sample_count() const { return m.out - m.out_begin; }
|
||||
|
||||
inline int SPC_DSP::read( int addr ) const
|
||||
{
|
||||
assert( (unsigned) addr < register_count );
|
||||
return m.regs [addr];
|
||||
}
|
||||
|
||||
inline void SPC_DSP::write( int addr, int data )
|
||||
{
|
||||
assert( (unsigned) addr < register_count );
|
||||
|
||||
m.regs [addr] = (uint8_t) data;
|
||||
switch ( addr & 0x0F )
|
||||
{
|
||||
case v_envx:
|
||||
m.envx_buf = (uint8_t) data;
|
||||
break;
|
||||
|
||||
case v_outx:
|
||||
m.outx_buf = (uint8_t) data;
|
||||
break;
|
||||
|
||||
case 0x0C:
|
||||
if ( addr == r_kon )
|
||||
m.new_kon = (uint8_t) data;
|
||||
|
||||
if ( addr == r_endx ) // always cleared, regardless of data written
|
||||
{
|
||||
m.endx_buf = 0;
|
||||
m.regs [r_endx] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline void SPC_DSP::mute_voices( int mask ) { m.mute_mask = mask; }
|
||||
|
||||
inline bool SPC_DSP::check_kon()
|
||||
{
|
||||
bool old = m.kon_check;
|
||||
m.kon_check = 0;
|
||||
return old;
|
||||
}
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
|
||||
class SPC_State_Copier {
|
||||
SPC_DSP::copy_func_t func;
|
||||
unsigned char** buf;
|
||||
public:
|
||||
SPC_State_Copier( unsigned char** p, SPC_DSP::copy_func_t f ) { func = f; buf = p; }
|
||||
void copy( void* state, size_t size );
|
||||
int copy_int( int state, int size );
|
||||
void skip( int count );
|
||||
void extra();
|
||||
};
|
||||
|
||||
#define SPC_COPY( type, state )\
|
||||
{\
|
||||
state = (BOOST::type) copier.copy_int( state, sizeof (BOOST::type) );\
|
||||
assert( (BOOST::type) state == state );\
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,68 @@
|
|||
// snes_spc 0.9.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "SPC_Filter.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copyright (C) 2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
void SPC_Filter::clear() { memset( ch, 0, sizeof ch ); }
|
||||
|
||||
SPC_Filter::SPC_Filter()
|
||||
{
|
||||
gain = gain_unit;
|
||||
bass = bass_norm;
|
||||
clear();
|
||||
}
|
||||
|
||||
void SPC_Filter::run( short* io, int count )
|
||||
{
|
||||
require( (count & 1) == 0 ); // must be even
|
||||
|
||||
int const gain = this->gain;
|
||||
int const bass = this->bass;
|
||||
chan_t* c = &ch [2];
|
||||
do
|
||||
{
|
||||
// cache in registers
|
||||
int sum = (--c)->sum;
|
||||
int pp1 = c->pp1;
|
||||
int p1 = c->p1;
|
||||
|
||||
for ( int i = 0; i < count; i += 2 )
|
||||
{
|
||||
// Low-pass filter (two point FIR with coeffs 0.25, 0.75)
|
||||
int f = io [i] + p1;
|
||||
p1 = io [i] * 3;
|
||||
|
||||
// High-pass filter ("leaky integrator")
|
||||
int delta = f - pp1;
|
||||
pp1 = f;
|
||||
int s = sum >> (gain_bits + 2);
|
||||
sum += (delta * gain) - (sum >> bass);
|
||||
|
||||
// Clamp to 16 bits
|
||||
if ( (short) s != s )
|
||||
s = (s >> 31) ^ 0x7FFF;
|
||||
|
||||
io [i] = (short) s;
|
||||
}
|
||||
|
||||
c->p1 = p1;
|
||||
c->pp1 = pp1;
|
||||
c->sum = sum;
|
||||
++io;
|
||||
}
|
||||
while ( c != ch );
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// Simple low-pass and high-pass filter to better match sound output of a SNES
|
||||
|
||||
// snes_spc 0.9.0
|
||||
#ifndef SPC_FILTER_H
|
||||
#define SPC_FILTER_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
struct SPC_Filter {
|
||||
public:
|
||||
|
||||
// Filters count samples of stereo sound in place. Count must be a multiple of 2.
|
||||
typedef short sample_t;
|
||||
void run( sample_t* io, int count );
|
||||
|
||||
// Optional features
|
||||
|
||||
// Clears filter to silence
|
||||
void clear();
|
||||
|
||||
// Sets gain (volume), where gain_unit is normal. Gains greater than gain_unit
|
||||
// are fine, since output is clamped to 16-bit sample range.
|
||||
enum { gain_unit = 0x100 };
|
||||
void set_gain( int gain );
|
||||
|
||||
// Sets amount of bass (logarithmic scale)
|
||||
enum { bass_none = 0 };
|
||||
enum { bass_norm = 8 }; // normal amount
|
||||
enum { bass_max = 31 };
|
||||
void set_bass( int bass );
|
||||
|
||||
public:
|
||||
SPC_Filter();
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
private:
|
||||
enum { gain_bits = 8 };
|
||||
int gain;
|
||||
int bass;
|
||||
struct chan_t { int p1, pp1, sum; };
|
||||
chan_t ch [2];
|
||||
};
|
||||
|
||||
inline void SPC_Filter::set_gain( int g ) { gain = g; }
|
||||
|
||||
inline void SPC_Filter::set_bass( int b ) { bass = b; }
|
||||
|
||||
#endif
|
|
@ -0,0 +1,186 @@
|
|||
// Sets up common environment for Shay Green's libraries.
|
||||
// To change configuration options, modify blargg_config.h, not this file.
|
||||
|
||||
// snes_spc 0.9.0
|
||||
#ifndef BLARGG_COMMON_H
|
||||
#define BLARGG_COMMON_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
#undef BLARGG_COMMON_H
|
||||
// allow blargg_config.h to #include blargg_common.h
|
||||
#include "blargg_config.h"
|
||||
#ifndef BLARGG_COMMON_H
|
||||
#define BLARGG_COMMON_H
|
||||
|
||||
// BLARGG_RESTRICT: equivalent to restrict, where supported
|
||||
#if defined (__GNUC__) || _MSC_VER >= 1100
|
||||
#define BLARGG_RESTRICT __restrict
|
||||
#else
|
||||
#define BLARGG_RESTRICT
|
||||
#endif
|
||||
|
||||
// STATIC_CAST(T,expr): Used in place of static_cast<T> (expr)
|
||||
#ifndef STATIC_CAST
|
||||
#define STATIC_CAST(T,expr) ((T) (expr))
|
||||
#endif
|
||||
|
||||
// blargg_err_t (0 on success, otherwise error string)
|
||||
#ifndef blargg_err_t
|
||||
typedef const char* blargg_err_t;
|
||||
#endif
|
||||
|
||||
// blargg_vector - very lightweight vector of POD types (no constructor/destructor)
|
||||
template<class T>
|
||||
class blargg_vector {
|
||||
T* begin_;
|
||||
size_t size_;
|
||||
public:
|
||||
blargg_vector() : begin_( 0 ), size_( 0 ) { }
|
||||
~blargg_vector() { free( begin_ ); }
|
||||
size_t size() const { return size_; }
|
||||
T* begin() const { return begin_; }
|
||||
T* end() const { return begin_ + size_; }
|
||||
blargg_err_t resize( size_t n )
|
||||
{
|
||||
// TODO: blargg_common.cpp to hold this as an outline function, ugh
|
||||
void* p = realloc( begin_, n * sizeof (T) );
|
||||
if ( p )
|
||||
begin_ = (T*) p;
|
||||
else if ( n > size_ ) // realloc failure only a problem if expanding
|
||||
return "Out of memory";
|
||||
size_ = n;
|
||||
return 0;
|
||||
}
|
||||
void clear() { void* p = begin_; begin_ = 0; size_ = 0; free( p ); }
|
||||
T& operator [] ( size_t n ) const
|
||||
{
|
||||
assert( n <= size_ ); // <= to allow past-the-end value
|
||||
return begin_ [n];
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef BLARGG_DISABLE_NOTHROW
|
||||
// throw spec mandatory in ISO C++ if operator new can return NULL
|
||||
#if __cplusplus >= 199711 || defined (__GNUC__)
|
||||
#define BLARGG_THROWS( spec ) throw spec
|
||||
#else
|
||||
#define BLARGG_THROWS( spec )
|
||||
#endif
|
||||
#define BLARGG_DISABLE_NOTHROW \
|
||||
void* operator new ( size_t s ) BLARGG_THROWS(()) { return malloc( s ); }\
|
||||
void operator delete ( void* p ) { free( p ); }
|
||||
#define BLARGG_NEW new
|
||||
#else
|
||||
#include <new>
|
||||
#define BLARGG_NEW new (std::nothrow)
|
||||
#endif
|
||||
|
||||
// BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant)
|
||||
#define BLARGG_4CHAR( a, b, c, d ) \
|
||||
((a&0xFF)*0x1000000L + (b&0xFF)*0x10000L + (c&0xFF)*0x100L + (d&0xFF))
|
||||
|
||||
// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0.
|
||||
#ifndef BOOST_STATIC_ASSERT
|
||||
#ifdef _MSC_VER
|
||||
// MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified
|
||||
#define BOOST_STATIC_ASSERT( expr ) \
|
||||
void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] )
|
||||
#else
|
||||
// Some other compilers fail when declaring same function multiple times in class,
|
||||
// so differentiate them by line
|
||||
#define BOOST_STATIC_ASSERT( expr ) \
|
||||
void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] )
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compiler. If 1,
|
||||
// compiler is assumed to support bool. If undefined, availability is determined.
|
||||
#ifndef BLARGG_COMPILER_HAS_BOOL
|
||||
#if defined (__MWERKS__)
|
||||
#if !__option(bool)
|
||||
#define BLARGG_COMPILER_HAS_BOOL 0
|
||||
#endif
|
||||
#elif defined (_MSC_VER)
|
||||
#if _MSC_VER < 1100
|
||||
#define BLARGG_COMPILER_HAS_BOOL 0
|
||||
#endif
|
||||
#elif defined (__GNUC__)
|
||||
// supports bool
|
||||
#elif __cplusplus < 199711
|
||||
#define BLARGG_COMPILER_HAS_BOOL 0
|
||||
#endif
|
||||
#endif
|
||||
#if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL
|
||||
// If you get errors here, modify your blargg_config.h file
|
||||
typedef int bool;
|
||||
const bool true = 1;
|
||||
const bool false = 0;
|
||||
#endif
|
||||
|
||||
// blargg_long/blargg_ulong = at least 32 bits, int if it's big enough
|
||||
|
||||
#if INT_MAX < 0x7FFFFFFF || LONG_MAX == 0x7FFFFFFF
|
||||
typedef long blargg_long;
|
||||
#else
|
||||
typedef int blargg_long;
|
||||
#endif
|
||||
|
||||
#if UINT_MAX < 0xFFFFFFFF || ULONG_MAX == 0xFFFFFFFF
|
||||
typedef unsigned long blargg_ulong;
|
||||
#else
|
||||
typedef unsigned blargg_ulong;
|
||||
#endif
|
||||
|
||||
// BOOST::int8_t etc.
|
||||
|
||||
// HAVE_STDINT_H: If defined, use <stdint.h> for int8_t etc.
|
||||
#if defined (HAVE_STDINT_H)
|
||||
#include <stdint.h>
|
||||
#define BOOST
|
||||
|
||||
// HAVE_INTTYPES_H: If defined, use <stdint.h> for int8_t etc.
|
||||
#elif defined (HAVE_INTTYPES_H)
|
||||
#include <inttypes.h>
|
||||
#define BOOST
|
||||
|
||||
#else
|
||||
struct BOOST
|
||||
{
|
||||
#if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F
|
||||
typedef signed char int8_t;
|
||||
typedef unsigned char uint8_t;
|
||||
#else
|
||||
// No suitable 8-bit type available
|
||||
typedef struct see_blargg_common_h int8_t;
|
||||
typedef struct see_blargg_common_h uint8_t;
|
||||
#endif
|
||||
|
||||
#if USHRT_MAX == 0xFFFF
|
||||
typedef short int16_t;
|
||||
typedef unsigned short uint16_t;
|
||||
#else
|
||||
// No suitable 16-bit type available
|
||||
typedef struct see_blargg_common_h int16_t;
|
||||
typedef struct see_blargg_common_h uint16_t;
|
||||
#endif
|
||||
|
||||
#if ULONG_MAX == 0xFFFFFFFF
|
||||
typedef long int32_t;
|
||||
typedef unsigned long uint32_t;
|
||||
#elif UINT_MAX == 0xFFFFFFFF
|
||||
typedef int int32_t;
|
||||
typedef unsigned int uint32_t;
|
||||
#else
|
||||
// No suitable 32-bit type available
|
||||
typedef struct see_blargg_common_h int32_t;
|
||||
typedef struct see_blargg_common_h uint32_t;
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -0,0 +1,24 @@
|
|||
// snes_spc 0.9.0 user configuration file. Don't replace when updating library.
|
||||
|
||||
// snes_spc 0.9.0
|
||||
#ifndef BLARGG_CONFIG_H
|
||||
#define BLARGG_CONFIG_H
|
||||
|
||||
// Uncomment to disable debugging checks
|
||||
//#define NDEBUG 1
|
||||
|
||||
// Uncomment to enable platform-specific (and possibly non-portable) optimizations
|
||||
//#define BLARGG_NONPORTABLE 1
|
||||
|
||||
// Uncomment if automatic byte-order determination doesn't work
|
||||
//#define BLARGG_BIG_ENDIAN 1
|
||||
|
||||
// Uncomment if you get errors in the bool section of blargg_common.h
|
||||
//#define BLARGG_COMPILER_HAS_BOOL 1
|
||||
|
||||
// Use standard config.h if present
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,185 @@
|
|||
// CPU Byte Order Utilities
|
||||
|
||||
// snes_spc 0.9.0
|
||||
#ifndef BLARGG_ENDIAN
|
||||
#define BLARGG_ENDIAN
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16)
|
||||
#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \
|
||||
defined (__x86_64__) || defined (__ia64__) || defined (__i386__)
|
||||
#define BLARGG_CPU_X86 1
|
||||
#define BLARGG_CPU_CISC 1
|
||||
#endif
|
||||
|
||||
#if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc)
|
||||
#define BLARGG_CPU_POWERPC 1
|
||||
#define BLARGG_CPU_RISC 1
|
||||
#endif
|
||||
|
||||
// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only
|
||||
// one may be #defined to 1. Only needed if something actually depends on byte order.
|
||||
#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN)
|
||||
#ifdef __GLIBC__
|
||||
// GCC handles this for us
|
||||
#include <endian.h>
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#define BLARGG_LITTLE_ENDIAN 1
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
#define BLARGG_BIG_ENDIAN 1
|
||||
#endif
|
||||
#else
|
||||
|
||||
#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \
|
||||
(defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234)
|
||||
#define BLARGG_LITTLE_ENDIAN 1
|
||||
#endif
|
||||
|
||||
#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \
|
||||
defined (__sparc__) || BLARGG_CPU_POWERPC || \
|
||||
(defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321)
|
||||
#define BLARGG_BIG_ENDIAN 1
|
||||
#elif !defined (__mips__)
|
||||
// No endian specified; assume little-endian, since it's most common
|
||||
#define BLARGG_LITTLE_ENDIAN 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN
|
||||
#undef BLARGG_LITTLE_ENDIAN
|
||||
#undef BLARGG_BIG_ENDIAN
|
||||
#endif
|
||||
|
||||
inline void blargg_verify_byte_order()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
#if BLARGG_BIG_ENDIAN
|
||||
volatile int i = 1;
|
||||
assert( *(volatile char*) &i == 0 );
|
||||
#elif BLARGG_LITTLE_ENDIAN
|
||||
volatile int i = 1;
|
||||
assert( *(volatile char*) &i != 0 );
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
inline unsigned get_le16( void const* p )
|
||||
{
|
||||
return (unsigned) ((unsigned char const*) p) [1] << 8 |
|
||||
(unsigned) ((unsigned char const*) p) [0];
|
||||
}
|
||||
|
||||
inline unsigned get_be16( void const* p )
|
||||
{
|
||||
return (unsigned) ((unsigned char const*) p) [0] << 8 |
|
||||
(unsigned) ((unsigned char const*) p) [1];
|
||||
}
|
||||
|
||||
inline blargg_ulong get_le32( void const* p )
|
||||
{
|
||||
return (blargg_ulong) ((unsigned char const*) p) [3] << 24 |
|
||||
(blargg_ulong) ((unsigned char const*) p) [2] << 16 |
|
||||
(blargg_ulong) ((unsigned char const*) p) [1] << 8 |
|
||||
(blargg_ulong) ((unsigned char const*) p) [0];
|
||||
}
|
||||
|
||||
inline blargg_ulong get_be32( void const* p )
|
||||
{
|
||||
return (blargg_ulong) ((unsigned char const*) p) [0] << 24 |
|
||||
(blargg_ulong) ((unsigned char const*) p) [1] << 16 |
|
||||
(blargg_ulong) ((unsigned char const*) p) [2] << 8 |
|
||||
(blargg_ulong) ((unsigned char const*) p) [3];
|
||||
}
|
||||
|
||||
inline void set_le16( void* p, unsigned n )
|
||||
{
|
||||
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
|
||||
((unsigned char*) p) [0] = (unsigned char) n;
|
||||
}
|
||||
|
||||
inline void set_be16( void* p, unsigned n )
|
||||
{
|
||||
((unsigned char*) p) [0] = (unsigned char) (n >> 8);
|
||||
((unsigned char*) p) [1] = (unsigned char) n;
|
||||
}
|
||||
|
||||
inline void set_le32( void* p, blargg_ulong n )
|
||||
{
|
||||
((unsigned char*) p) [0] = (unsigned char) n;
|
||||
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
|
||||
((unsigned char*) p) [2] = (unsigned char) (n >> 16);
|
||||
((unsigned char*) p) [3] = (unsigned char) (n >> 24);
|
||||
}
|
||||
|
||||
inline void set_be32( void* p, blargg_ulong n )
|
||||
{
|
||||
((unsigned char*) p) [3] = (unsigned char) n;
|
||||
((unsigned char*) p) [2] = (unsigned char) (n >> 8);
|
||||
((unsigned char*) p) [1] = (unsigned char) (n >> 16);
|
||||
((unsigned char*) p) [0] = (unsigned char) (n >> 24);
|
||||
}
|
||||
|
||||
#if BLARGG_NONPORTABLE
|
||||
// Optimized implementation if byte order is known
|
||||
#if BLARGG_LITTLE_ENDIAN
|
||||
#define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr))
|
||||
#define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr))
|
||||
#define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data))
|
||||
#define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data))
|
||||
#elif BLARGG_BIG_ENDIAN
|
||||
#define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr))
|
||||
#define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr))
|
||||
#define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data))
|
||||
#define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data))
|
||||
|
||||
#if BLARGG_CPU_POWERPC
|
||||
// PowerPC has special byte-reversed instructions
|
||||
#if defined (__MWERKS__)
|
||||
#define GET_LE16( addr ) (__lhbrx( addr, 0 ))
|
||||
#define GET_LE32( addr ) (__lwbrx( addr, 0 ))
|
||||
#define SET_LE16( addr, in ) (__sthbrx( in, addr, 0 ))
|
||||
#define SET_LE32( addr, in ) (__stwbrx( in, addr, 0 ))
|
||||
#elif defined (__GNUC__)
|
||||
#define GET_LE16( addr ) ({unsigned ppc_lhbrx_; asm( "lhbrx %0,0,%1" : "=r" (ppc_lhbrx_) : "r" (addr), "0" (ppc_lhbrx_) ); ppc_lhbrx_;})
|
||||
#define GET_LE32( addr ) ({unsigned ppc_lwbrx_; asm( "lwbrx %0,0,%1" : "=r" (ppc_lwbrx_) : "r" (addr), "0" (ppc_lwbrx_) ); ppc_lwbrx_;})
|
||||
#define SET_LE16( addr, in ) ({asm( "sthbrx %0,0,%1" : : "r" (in), "r" (addr) );})
|
||||
#define SET_LE32( addr, in ) ({asm( "stwbrx %0,0,%1" : : "r" (in), "r" (addr) );})
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef GET_LE16
|
||||
#define GET_LE16( addr ) get_le16( addr )
|
||||
#define SET_LE16( addr, data ) set_le16( addr, data )
|
||||
#endif
|
||||
|
||||
#ifndef GET_LE32
|
||||
#define GET_LE32( addr ) get_le32( addr )
|
||||
#define SET_LE32( addr, data ) set_le32( addr, data )
|
||||
#endif
|
||||
|
||||
#ifndef GET_BE16
|
||||
#define GET_BE16( addr ) get_be16( addr )
|
||||
#define SET_BE16( addr, data ) set_be16( addr, data )
|
||||
#endif
|
||||
|
||||
#ifndef GET_BE32
|
||||
#define GET_BE32( addr ) get_be32( addr )
|
||||
#define SET_BE32( addr, data ) set_be32( addr, data )
|
||||
#endif
|
||||
|
||||
// auto-selecting versions
|
||||
|
||||
inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); }
|
||||
inline void set_le( BOOST::uint32_t* p, blargg_ulong n ) { SET_LE32( p, n ); }
|
||||
inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); }
|
||||
inline void set_be( BOOST::uint32_t* p, blargg_ulong n ) { SET_BE32( p, n ); }
|
||||
inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); }
|
||||
inline blargg_ulong get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); }
|
||||
inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); }
|
||||
inline blargg_ulong get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); }
|
||||
|
||||
#endif
|
|
@ -0,0 +1,100 @@
|
|||
/* Included at the beginning of library source files, after all other #include lines.
|
||||
Sets up helpful macros and services used in my source code. They don't need
|
||||
module an annoying module prefix on their names since they are defined after
|
||||
all other #include lines. */
|
||||
|
||||
// snes_spc 0.9.0
|
||||
#ifndef BLARGG_SOURCE_H
|
||||
#define BLARGG_SOURCE_H
|
||||
|
||||
// If debugging is enabled, abort program if expr is false. Meant for checking
|
||||
// internal state and consistency. A failed assertion indicates a bug in the module.
|
||||
// void assert( bool expr );
|
||||
#include <assert.h>
|
||||
|
||||
// If debugging is enabled and expr is false, abort program. Meant for checking
|
||||
// caller-supplied parameters and operations that are outside the control of the
|
||||
// module. A failed requirement indicates a bug outside the module.
|
||||
// void require( bool expr );
|
||||
#undef require
|
||||
#define require( expr ) assert( expr )
|
||||
|
||||
// Like printf() except output goes to debug log file. Might be defined to do
|
||||
// nothing (not even evaluate its arguments).
|
||||
// void dprintf( const char* format, ... );
|
||||
static inline void blargg_dprintf_( const char*, ... ) { }
|
||||
#undef dprintf
|
||||
#define dprintf (1) ? (void) 0 : blargg_dprintf_
|
||||
|
||||
// If enabled, evaluate expr and if false, make debug log entry with source file
|
||||
// and line. Meant for finding situations that should be examined further, but that
|
||||
// don't indicate a problem. In all cases, execution continues normally.
|
||||
#undef check
|
||||
#define check( expr ) ((void) 0)
|
||||
|
||||
// If expr yields error string, return it from current function, otherwise continue.
|
||||
#undef RETURN_ERR
|
||||
#define RETURN_ERR( expr ) do { \
|
||||
blargg_err_t blargg_return_err_ = (expr); \
|
||||
if ( blargg_return_err_ ) return blargg_return_err_; \
|
||||
} while ( 0 )
|
||||
|
||||
// If ptr is 0, return out of memory error string.
|
||||
#undef CHECK_ALLOC
|
||||
#define CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 )
|
||||
|
||||
// Avoid any macros which evaluate their arguments multiple times
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
#define DEF_MIN_MAX( type ) \
|
||||
static inline type min( type x, type y ) { if ( x < y ) return x; return y; }\
|
||||
static inline type max( type x, type y ) { if ( y < x ) return x; return y; }
|
||||
|
||||
DEF_MIN_MAX( int )
|
||||
DEF_MIN_MAX( unsigned )
|
||||
DEF_MIN_MAX( long )
|
||||
DEF_MIN_MAX( unsigned long )
|
||||
DEF_MIN_MAX( float )
|
||||
DEF_MIN_MAX( double )
|
||||
|
||||
#undef DEF_MIN_MAX
|
||||
|
||||
/*
|
||||
// using const references generates crappy code, and I am currenly only using these
|
||||
// for built-in types, so they take arguments by value
|
||||
|
||||
// TODO: remove
|
||||
inline int min( int x, int y )
|
||||
template<class T>
|
||||
inline T min( T x, T y )
|
||||
{
|
||||
if ( x < y )
|
||||
return x;
|
||||
return y;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline T max( T x, T y )
|
||||
{
|
||||
if ( x < y )
|
||||
return y;
|
||||
return x;
|
||||
}
|
||||
*/
|
||||
|
||||
// TODO: good idea? bad idea?
|
||||
#undef byte
|
||||
#define byte byte_
|
||||
typedef unsigned char byte;
|
||||
|
||||
// deprecated
|
||||
#define BLARGG_CHECK_ALLOC CHECK_ALLOC
|
||||
#define BLARGG_RETURN_ERR RETURN_ERR
|
||||
|
||||
// BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf and check
|
||||
#ifdef BLARGG_SOURCE_BEGIN
|
||||
#include BLARGG_SOURCE_BEGIN
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,107 @@
|
|||
snes_spc Change Log
|
||||
-------------------
|
||||
|
||||
snes_spc 0.9.0
|
||||
--------------
|
||||
- Improved documentation
|
||||
|
||||
- SPC: Added spc_skip() function for quickly seeking in an SPC music
|
||||
file. Runs 3-4x faster than normal playback using the fast DSP (or about
|
||||
43-60X real-time on my 400 MHz Mac).
|
||||
|
||||
- SPC: Added spc_set_tempo() to change tempo of SPC music playback.
|
||||
|
||||
- SPC: Sample generation is now corrected to generate exactly one pair
|
||||
of samples every 32 clocks without exception. Before it could generate a
|
||||
few samples more or less depending on how far ahead or behind DSP was at
|
||||
the moment.
|
||||
|
||||
- SPC: Changed spc_reset() and spc_soft_reset() to also reset output
|
||||
buffer (see spc.h).
|
||||
|
||||
- SPC: Fixed minor timer counting bug.
|
||||
|
||||
- SPC: Stack pointer wrap-around is now emulated (and without any
|
||||
noticeable performance hit).
|
||||
|
||||
- SPC: Runs about 5% faster due to various optimizations.
|
||||
|
||||
- SPC: Found way to make fast DSP register accesses cycle-accurate in
|
||||
most cases, without reducing performance. Allows fast DSP to pass most
|
||||
of my validation tests.
|
||||
|
||||
- DSP: Added surround disable support to fast DSP again.
|
||||
|
||||
- DSP: Improved voice un-muting to take effect immediately on fast DSP.
|
||||
|
||||
- DSP: Noise shift register now starts at 0x4000 instead of 0x4001 as it
|
||||
incorrectly did before.
|
||||
|
||||
- Converted library to C++ code internally. A C interface is still
|
||||
included in spc.h and dsp.h. Note that these are different than the
|
||||
previous interface, so your code will require minor changes:
|
||||
|
||||
Old SPC code New SPC code
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
#include "spc/spc.h" #include "snes_spc/spc.h"
|
||||
|
||||
snes_spc_t* spc; SNES_SPC* spc;
|
||||
spc = malloc( sizeof (snes_spc_t) ); spc = spc_new();
|
||||
spc_init( spc );
|
||||
|
||||
spc_end_frame( time ); spc_end_frame( spc, time );
|
||||
/* etc. */
|
||||
|
||||
/* done using SPC */ spc_delete( spc );
|
||||
|
||||
|
||||
Old DSP code New DSP code
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
#include "spc/spc_dsp.h" #include "snes_spc/dsp.h"
|
||||
|
||||
spc_dsp_init( ram ); SPC_DSP* dsp;
|
||||
dsp = spc_dsp_new();
|
||||
spc_dsp_init( dsp, ram );
|
||||
|
||||
spc_dsp_run( count ); spc_dsp_run( dsp, count );
|
||||
/* etc. */
|
||||
|
||||
/* done using DSP */ spc_dsp_delete( dsp );
|
||||
|
||||
|
||||
snes_spc 0.8.0
|
||||
--------------
|
||||
- Added several demos
|
||||
|
||||
- Added high-pass/low-pass filter to better match SNES sound
|
||||
|
||||
- Added save state functionality for SPC and accurate DSP (but not fast
|
||||
DSP)
|
||||
|
||||
- Added emulation of reset switch on NES (soft reset)
|
||||
|
||||
- Made source more compatible with pre-C99 compilers by eliminating
|
||||
mid-block declarations
|
||||
|
||||
- SPC: Many S-SMP accuracy improvements, mostly in memory access times
|
||||
|
||||
- SPC: S-SMP speed improvements
|
||||
|
||||
- SPC: Added SPC load/save functions and KON checking to help trim
|
||||
silence from beginning
|
||||
|
||||
- SPC: Changed spc_init() to have you allocate most of the memory used
|
||||
by the library so you have more control over it
|
||||
|
||||
- DSP: New highly accurate DSP and faster version derived from same code
|
||||
|
||||
- DSP: Changed prefix from dsp_ to spc_dsp_. Your DSP code will require
|
||||
changes.
|
||||
|
||||
- DSP: Removed surround disable and gain. Gain can now be done with the
|
||||
dsp_filter module, and surround disable will probably only be
|
||||
implemented in the fast DSP at some point.
|
||||
|
||||
- DSP: Changed interface to work in clocks rather than samples,
|
||||
necessary for the new accurate DSP. Sample output is now done with
|
||||
separate functions. Your DSP code will require changes.
|
|
@ -0,0 +1,48 @@
|
|||
// snes_spc 0.9.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "dsp.h"
|
||||
|
||||
#include "SPC_DSP.h"
|
||||
|
||||
/* Copyright (C) 2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
SPC_DSP* spc_dsp_new( void )
|
||||
{
|
||||
// be sure constants match
|
||||
assert( spc_dsp_voice_count == (int) SPC_DSP::voice_count );
|
||||
assert( spc_dsp_register_count == (int) SPC_DSP::register_count );
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
assert( spc_dsp_state_size == (int) SPC_DSP::state_size );
|
||||
#endif
|
||||
|
||||
return new SPC_DSP;
|
||||
}
|
||||
|
||||
void spc_dsp_delete ( SPC_DSP* s ) { delete s; }
|
||||
void spc_dsp_init ( SPC_DSP* s, void* ram_64k ) { s->init( ram_64k ); }
|
||||
void spc_dsp_set_output ( SPC_DSP* s, spc_dsp_sample_t* p, int n ) { s->set_output( p, n ); }
|
||||
int spc_dsp_sample_count( SPC_DSP const* s ) { return s->sample_count(); }
|
||||
void spc_dsp_reset ( SPC_DSP* s ) { s->reset(); }
|
||||
void spc_dsp_soft_reset ( SPC_DSP* s ) { s->soft_reset(); }
|
||||
int spc_dsp_read ( SPC_DSP const* s, int addr ) { return s->read( addr ); }
|
||||
void spc_dsp_write ( SPC_DSP* s, int addr, int data ) { s->write( addr, data ); }
|
||||
void spc_dsp_run ( SPC_DSP* s, int clock_count ) { s->run( clock_count ); }
|
||||
void spc_dsp_mute_voices ( SPC_DSP* s, int mask ) { s->mute_voices( mask ); }
|
||||
void spc_dsp_disable_surround( SPC_DSP* s, int disable ) { s->disable_surround( disable ); }
|
||||
void spc_dsp_load ( SPC_DSP* s, unsigned char const regs [spc_dsp_register_count] ) { s->load( regs ); }
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
void spc_dsp_copy_state ( SPC_DSP* s, unsigned char** p, spc_dsp_copy_func_t f ) { s->copy_state( p, f ); }
|
||||
int spc_dsp_check_kon ( SPC_DSP* s ) { return s->check_kon(); }
|
||||
#endif
|
|
@ -0,0 +1,83 @@
|
|||
/* SNES SPC-700 DSP emulator C interface (also usable from C++) */
|
||||
|
||||
/* snes_spc 0.9.0 */
|
||||
#ifndef DSP_H
|
||||
#define DSP_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct SPC_DSP SPC_DSP;
|
||||
|
||||
/* Creates new DSP emulator. NULL if out of memory. */
|
||||
SPC_DSP* spc_dsp_new( void );
|
||||
|
||||
/* Frees DSP emulator */
|
||||
void spc_dsp_delete( SPC_DSP* );
|
||||
|
||||
/* Initializes DSP and has it use the 64K RAM provided */
|
||||
void spc_dsp_init( SPC_DSP*, void* ram_64k );
|
||||
|
||||
/* Sets destination for output samples. If out is NULL or out_size is 0,
|
||||
doesn't generate any. */
|
||||
typedef short spc_dsp_sample_t;
|
||||
void spc_dsp_set_output( SPC_DSP*, spc_dsp_sample_t* out, int out_size );
|
||||
|
||||
/* Number of samples written to output since it was last set, always
|
||||
a multiple of 2. Undefined if more samples were generated than
|
||||
output buffer could hold. */
|
||||
int spc_dsp_sample_count( SPC_DSP const* );
|
||||
|
||||
|
||||
/**** Emulation *****/
|
||||
|
||||
/* Resets DSP to power-on state */
|
||||
void spc_dsp_reset( SPC_DSP* );
|
||||
|
||||
/* Emulates pressing reset switch on SNES */
|
||||
void spc_dsp_soft_reset( SPC_DSP* );
|
||||
|
||||
/* Reads/writes DSP registers. For accuracy, you must first call spc_dsp_run() */
|
||||
/* to catch the DSP up to present. */
|
||||
int spc_dsp_read ( SPC_DSP const*, int addr );
|
||||
void spc_dsp_write( SPC_DSP*, int addr, int data );
|
||||
|
||||
/* Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks */
|
||||
/* a pair of samples is be generated. */
|
||||
void spc_dsp_run( SPC_DSP*, int clock_count );
|
||||
|
||||
|
||||
/**** Sound control *****/
|
||||
|
||||
/* Mutes voices corresponding to non-zero bits in mask. Reduces emulation accuracy. */
|
||||
enum { spc_dsp_voice_count = 8 };
|
||||
void spc_dsp_mute_voices( SPC_DSP*, int mask );
|
||||
|
||||
/* If true, prevents channels and global volumes from being phase-negated.
|
||||
Only supported by fast DSP; has no effect on accurate DSP. */
|
||||
void spc_dsp_disable_surround( SPC_DSP*, int disable );
|
||||
|
||||
|
||||
/**** State save/load *****/
|
||||
|
||||
/* Resets DSP and uses supplied values to initialize registers */
|
||||
enum { spc_dsp_register_count = 128 };
|
||||
void spc_dsp_load( SPC_DSP*, unsigned char const regs [spc_dsp_register_count] );
|
||||
|
||||
/* Saves/loads exact emulator state (accurate DSP only) */
|
||||
enum { spc_dsp_state_size = 640 }; /* maximum space needed when saving */
|
||||
typedef void (*spc_dsp_copy_func_t)( unsigned char** io, void* state, size_t );
|
||||
void spc_dsp_copy_state( SPC_DSP*, unsigned char** io, spc_dsp_copy_func_t );
|
||||
|
||||
/* Returns non-zero if new key-on events occurred since last call (accurate DSP only) */
|
||||
int spc_dsp_check_kon( SPC_DSP* );
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,504 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
snes_spc 0.9.0: SNES SPC-700 APU Emulator
|
||||
-----------------------------------------
|
||||
This library includes a full SPC emulator and an S-DSP emulator that can
|
||||
be used on its own. Two S-DSP emulators are available: a highly accurate
|
||||
one for use in a SNES emulator, and a 3x faster one for use in an SPC
|
||||
music player or a resource-limited SNES emulator.
|
||||
|
||||
* Can be used from C and C++ code
|
||||
* Full SPC-700 APU emulator with cycle accuracy in most cases
|
||||
* Loads, plays, and saves SPC music files
|
||||
* Can save and load exact full emulator state
|
||||
* DSP voice muting, surround sound disable, and song tempo adjustment
|
||||
* Uses 7% CPU average on 400 MHz Mac to play an SPC using fast DSP
|
||||
|
||||
The accurate DSP emulator is based on past research by others and
|
||||
hundreds of hours of recent research by me. It passes over a hundred
|
||||
strenuous timing and behavior validation tests that were also run on the
|
||||
SNES. As far as I know, it's the first DSP emulator with cycle accuracy,
|
||||
properly emulating every DSP register and memory access at the exact SPC
|
||||
cycle it occurs at, whereas previous DSP emulators emulated these only
|
||||
to the nearest sample (which occurs every 32 clocks).
|
||||
|
||||
Author : Shay Green <gblargg@gmail.com>
|
||||
Website: http://www.slack.net/~ant/
|
||||
Forum : http://groups.google.com/group/blargg-sound-libs
|
||||
License: GNU Lesser General Public License (LGPL)
|
||||
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
Build a program consisting of demo/play_spc.c, demo/demo_util.c,
|
||||
demo/wave_writer.c, and all source files in snes_spc/. Put an SPC music
|
||||
file in the same directory and name it "test.spc". Running the program
|
||||
should generate the recording "out.wav".
|
||||
|
||||
Read snes_spc.txt for more information. Post to the discussion forum for
|
||||
assistance.
|
||||
|
||||
|
||||
Files
|
||||
-----
|
||||
snes_spc.txt Documentation
|
||||
changes.txt Change log
|
||||
license.txt GNU LGPL license
|
||||
|
||||
demo/
|
||||
play_spc.c Records SPC file to wave sound file
|
||||
benchmark.c Finds how fast emulator runs on your computer
|
||||
trim_spc.c Trims silence off beginning of an SPC file
|
||||
save_state.c Saves/loads exact emulator state to/from file
|
||||
comm.c Communicates with SPC how SNES would
|
||||
demo_util.h General utility functions used by demos
|
||||
demo_util.c
|
||||
wave_writer.h WAVE sound file writer used for demo output
|
||||
wave_writer.c
|
||||
|
||||
fast_dsp/ Optional standalone fast DSP emulator
|
||||
SPC_DSP.h To use with full SPC emulator, move into
|
||||
SPC_DSP.cpp snes_spc/ and replace original files
|
||||
|
||||
snes_spc/ Library sources
|
||||
blargg_config.h Configuration (modify as necessary)
|
||||
|
||||
spc.h C interface to SPC emulator and sound filter
|
||||
spc.cpp
|
||||
|
||||
SPC_Filter.h Optional filter to make sound more authentic
|
||||
SPC_Filter.cpp
|
||||
|
||||
SNES_SPC.h Full SPC emulator
|
||||
SNES_SPC.cpp
|
||||
SNES_SPC_misc.cpp
|
||||
SNES_SPC_state.cpp
|
||||
SPC_CPU.h
|
||||
|
||||
dsp.h C interface to DSP emulator
|
||||
dsp.cpp
|
||||
|
||||
SPC_DSP.h Standalone accurate DSP emulator
|
||||
SPC_DSP.cpp
|
||||
blargg_common.h
|
||||
blargg_endian.h
|
||||
blargg_source.h
|
||||
|
||||
--
|
||||
Shay Green <gblargg@gmail.com>
|
|
@ -0,0 +1,318 @@
|
|||
snes_spc 0.9.0: SNES SPC-700 APU Emulator
|
||||
-----------------------------------------
|
||||
Author : Shay Green <gblargg@gmail.com>
|
||||
Website: http://www.slack.net/~ant/
|
||||
Forum : http://groups.google.com/group/blargg-sound-libs
|
||||
License: GNU Lesser General Public License (LGPL)
|
||||
|
||||
|
||||
Contents
|
||||
--------
|
||||
* C and C++ Interfaces
|
||||
* Overview
|
||||
* Full SPC Emulation
|
||||
* DSP Emulation
|
||||
* SPC Music Playback
|
||||
* State Copying
|
||||
* Library Compilation
|
||||
* Error handling
|
||||
* Solving Problems
|
||||
* Accurate S-DSP Limitations
|
||||
* Fast S-DSP Limitations
|
||||
* S-SMP Limitations
|
||||
* To Do
|
||||
* Thanks
|
||||
|
||||
|
||||
C and C++ Interfaces
|
||||
--------------------
|
||||
The library includes a C interface in spc.h and dsp.h, which can be used
|
||||
from C and C++. This C interface is referred to in the following
|
||||
documentation. If you're building this as a shared library (rather than
|
||||
linking statically), you should use the C interface since it will change
|
||||
less in future versions.
|
||||
|
||||
The native C++ interface is in the header files SNES_SPC.h, SPC_DSP.h,
|
||||
and SPC_Filter.h, and the two interfaces can be freely mixed in C++
|
||||
code. Conversion between the two interfaces generally follows a pattern:
|
||||
|
||||
C interface C++ interface
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
SNES_SPC* spc; SNES_SPC* spc;
|
||||
|
||||
spc = spc_new(); spc = new SNES_SPC;
|
||||
|
||||
spc_play( spc, count, buf ); spc->play( count, buf );
|
||||
|
||||
spc_sample_rate SNES_SPC::sample_rate
|
||||
|
||||
spc_delete( spc ); delete spc;
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
There are three main roles for this library:
|
||||
* Full SPC emulation in a SNES emulator
|
||||
* DSP emulation in a SNES emulator (where you emulate the SMP CPU)
|
||||
* SPC playback in an SPC music file player
|
||||
|
||||
Each of these uses are described separately below.
|
||||
|
||||
|
||||
Full SPC Emulation
|
||||
------------------
|
||||
See spc.h for full function reference (SNES_SPC.h if using C++).
|
||||
|
||||
* Create SPC emulator with spc_new() and check for NULL.
|
||||
|
||||
* Call spc_init_rom() with a pointer to the 64-byte IPL ROM dump (not
|
||||
included with library).
|
||||
|
||||
* When your emulated SNES is powered on, call spc_reset(). When the
|
||||
reset switch is pressed, call spc_soft_reset().
|
||||
|
||||
* Call spc_set_output() with your output buffer, then do emulation.
|
||||
|
||||
* When the SNES CPU accesses APU ports, call spc_read_port() and
|
||||
spc_write_port().
|
||||
|
||||
* When your emulator's timebase is going back to 0, call
|
||||
spc_end_frame(), usually at the end of a video frame or scanline.
|
||||
|
||||
* Periodically play samples from your buffer. Use spc_sample_count() to
|
||||
find out how many samples have been written, then spc_set_output() after
|
||||
you've made more space in your buffer.
|
||||
|
||||
* Save/load full emulator state with spc_copy_state().
|
||||
|
||||
* You can save as an SPC music file with spc_save_spc().
|
||||
|
||||
* When done, use spc_delete() to free memory.
|
||||
|
||||
|
||||
DSP Emulation
|
||||
-------------
|
||||
See dsp.h for full function reference (SPC_DSP.h if using C++).
|
||||
|
||||
* Create DSP emulator with spc_dsp_new() and check for NULL.
|
||||
|
||||
* Let the DSP know where your 64K RAM is with spc_dsp_init().
|
||||
|
||||
* When your emulated SNES is powered on, call spc_dsp_reset(). When the
|
||||
reset switch is pressed, call spc_dsp_soft_reset().
|
||||
|
||||
* Call spc_dsp_set_output() with your output buffer, then do emulation.
|
||||
|
||||
* Use spc_dsp_run() to run DSP for specified number of clocks (1024000
|
||||
per second). Every 32 clocks a pair of samples is added to your output
|
||||
buffer.
|
||||
|
||||
* Use spc_dsp_read() and spc_dsp_write() to handle DSP reads/writes from
|
||||
the S-SMP. Before calling these always catch the DSP up to present time
|
||||
with spc_dsp_run().
|
||||
|
||||
* Periodically play samples from your buffer. Use spc_dsp_sample_count()
|
||||
to find out how many samples have been written, then
|
||||
spc_dsp_set_output() after you've made more space in your buffer.
|
||||
|
||||
* Use spc_dsp_copy_state() to save/load full DSP state.
|
||||
|
||||
* When done, use spc_delete() to free memory.
|
||||
|
||||
|
||||
SPC Music Playback
|
||||
------------------
|
||||
See spc.h for full function reference (SNES_SPC.h if using C++).
|
||||
|
||||
* Create SPC emulator with spc_new() and check for NULL.
|
||||
|
||||
* Load SPC with spc_load_spc() and check for error.
|
||||
|
||||
* Optionally cear echo buffer with spc_clear_echo(). Many SPCs have
|
||||
garbage in echo buffer, which causes noise at the beginning.
|
||||
|
||||
* Generate samples as needed with spc_play().
|
||||
|
||||
* When done, use spc_delete() to free memory.
|
||||
|
||||
* For a more complete game music playback library, use Game_Music_Emu
|
||||
|
||||
|
||||
State Copying
|
||||
-------------
|
||||
The SPC and DSP modules include state save/load functions. They take a
|
||||
pointer to a pointer to a buffer to store state, and a copy function.
|
||||
This copy function can either copy data to the buffer or from it,
|
||||
allowing state save and restore with the same library function. The
|
||||
internal save state format allows for future expansion without making
|
||||
previous save states unusable.
|
||||
|
||||
The SPC save state format puts the most important parts first to make it
|
||||
easier to manually examine. It's organized as follows:
|
||||
|
||||
Offset Size Data
|
||||
- - - - - - - - - - - - - - - - - -
|
||||
0 $10000 SPC RAM
|
||||
$10000 $10 SMP $F0-$FF registers
|
||||
$10010 4 SMP $F4-$F8 output registers
|
||||
$10014 2 PC
|
||||
$10016 1 A
|
||||
$10017 1 X
|
||||
$10018 1 Y
|
||||
$10019 1 PSW
|
||||
$1001A 1 SP
|
||||
$1001B 5 internal
|
||||
$10020 $80 DSP registers
|
||||
$100A0 ... internal
|
||||
|
||||
|
||||
Library Compilation
|
||||
-------------------
|
||||
While this library is in C++, it has been written to easily link in a C
|
||||
program *without* needing the standard C++ library. It doesn't use
|
||||
exception handling or run-time type information (RTTI), so you can
|
||||
disable these in your C++ compiler to increase efficiency.
|
||||
|
||||
If you're building a shared library (DLL), I recommend only exporting
|
||||
the C interfaces in spc.h and dsp.h, as the C++ interfaces expose
|
||||
implementation details that will break link compatibility across
|
||||
versions.
|
||||
|
||||
If you're using C and compiling with GCC, I recommend the following
|
||||
command-line options when compiling the library source, otherwise GCC
|
||||
will insert calls to the standard C++ library and require that it be
|
||||
linked in:
|
||||
|
||||
-fno-rtti -fno-exceptions
|
||||
|
||||
For maximum optimization, see the NDEBUG and BLARGG_NONPORTABLE options
|
||||
in blargg_config. If using GCC, you can enable these by adding the
|
||||
following command-line options when you invoke gcc. If you encounter
|
||||
problems, try without -DBLARGG_NONPORTABLE; if that works, contact me so
|
||||
I can figure out why BLARGG_NONPORTABLE was causing it to fail.
|
||||
|
||||
-O3 -DNDEBUG -DBLARGG_NONPORTABLE -fno-rtti -fno-exceptions
|
||||
|
||||
|
||||
|
||||
Error handling
|
||||
--------------
|
||||
Functions which can fail have a return type of spc_err_t (blargg_err_t
|
||||
in the C++ interfaces), which is a pointer to an error string (const
|
||||
char*). If a function is successful it returns NULL. Errors that you can
|
||||
easily avoid are checked with debug assertions; spc_err_t return values
|
||||
are only used for genuine run-time errors that can't be easily predicted
|
||||
in advance (out of memory, I/O errors, incompatible file data). Your
|
||||
code should check all error values.
|
||||
|
||||
To improve usability for C programmers, C++ programmers unfamiliar with
|
||||
exceptions, and compatibility with older C++ compilers, the library does
|
||||
*not* throw any C++ exceptions and uses malloc() instead of the standard
|
||||
operator new. This means that you *must* check for NULL when creating a
|
||||
library object with the new operator.
|
||||
|
||||
|
||||
Solving Problems
|
||||
----------------
|
||||
If you're having problems, try the following:
|
||||
|
||||
* If you're getting garbled sound, try this simple siren generator in
|
||||
place of your call to play(). This will quickly tell whether the problem
|
||||
is in the library or in your code.
|
||||
|
||||
static void play_siren( long count, short* out )
|
||||
{
|
||||
static double a, a2;
|
||||
while ( count-- )
|
||||
*out++ = 0x2000 * sin( a += .1 + .05*sin( a2+=.00005 ) );
|
||||
}
|
||||
|
||||
* Enable debugging support in your environment. This enables assertions
|
||||
and other run-time checks.
|
||||
|
||||
* Turn the compiler's optimizer is off. Sometimes an optimizer generates
|
||||
bad code.
|
||||
|
||||
* If multiple threads are being used, ensure that only one at a time is
|
||||
accessing a given set of objects from the library. This library is not
|
||||
in general thread-safe, though independent objects can be used in
|
||||
separate threads.
|
||||
|
||||
* If all else fails, see if the demos work.
|
||||
|
||||
|
||||
Accurate S-DSP Limitations
|
||||
--------------------------
|
||||
* Power-up and soft reset behavior might have slight inaccuracies.
|
||||
|
||||
* Muting (FLG bit 6) behavior when toggling bit very rapidly is not
|
||||
emulated properly.
|
||||
|
||||
* No other known inaccuracies. Has passed 100+ strenuous tests.
|
||||
|
||||
|
||||
Fast S-DSP Limitations
|
||||
----------------------
|
||||
* Uses faster sample calculations except in cases where exact value is
|
||||
actually important (BRR decoding, and gaussian interpolation combined
|
||||
with pitch modulation).
|
||||
|
||||
* Stops decoding BRR data when a voice's envelope has released to
|
||||
silence.
|
||||
|
||||
* Emulates 32 clocks at a time, so DSP register and memory accesses are
|
||||
all done in a bunch rather than spread out. Even though, some clever
|
||||
code makes register accesses separated by 40 or so clocks occur with
|
||||
cycle-accurate timing.
|
||||
|
||||
|
||||
S-SMP Limitations
|
||||
-----------------
|
||||
* Opcode fetches and indirect pointers are always read directly from
|
||||
memory, even for the $F0-$FF region, and the DSP is not caught up for
|
||||
these fetches.
|
||||
|
||||
* Attempts to perversely execute data in registers or an area being
|
||||
modified by echo will not be emulated properly.
|
||||
|
||||
* Has not been thoroughly tested.
|
||||
|
||||
* Test register ($F0) is not implemented.
|
||||
|
||||
* Echo buffer can overwrite IPL ROM area, and does not correctly update
|
||||
extra RAM there.
|
||||
|
||||
|
||||
To Do
|
||||
-----
|
||||
* I'd like feedback on the interface and any ways to improve it. In
|
||||
particular, the differing features between the accurate and fast DSP
|
||||
emulators might make it harder to cleanly switch between them without
|
||||
modifying source code.
|
||||
|
||||
* Finish thorough tests on SMP memory access times.
|
||||
|
||||
* Finish thorough tests on SMP instruction behavior (flags, registers).
|
||||
|
||||
* Finish thorough tests on SMP timers.
|
||||
|
||||
* Finish power-up and reset behavior testing.
|
||||
|
||||
* Come up with best starting conditions to play an SPC and implement in
|
||||
hardware SNES SPC player for verification.
|
||||
|
||||
|
||||
Thanks
|
||||
------
|
||||
Thanks to Anti-Resonance's SPC2ROM and help getting SPCs playing on my
|
||||
SNES in the first place, then Brad Martin's openspc and Chris Moeller's
|
||||
openspc++ C++ adaptation, giving me a good SPC emulator to start with
|
||||
several years ago. Thanks to Richard Bannister, Mahendra Tallur, Shazz,
|
||||
nenolod, theHobbit, Johan Samuelsson, nes6502, and Micket for helping
|
||||
test my Game_Music_Emu library. Thanks to hcs for help in converting to
|
||||
C for the Rockbox port. Thanks to byuu (bsnes author) and pagefault and
|
||||
Nach (zsnes team) for testing and using my new rewritten DSP in their
|
||||
emulators. Thanks to anomie for his good SNES documentation and
|
||||
discussions with me to keep it up to date with my latest findings.
|
||||
--
|
||||
Shay Green <gblargg@gmail.com>
|
|
@ -0,0 +1,73 @@
|
|||
// snes_spc 0.9.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "spc.h"
|
||||
|
||||
#include "SNES_SPC.h"
|
||||
#include "SPC_Filter.h"
|
||||
|
||||
/* Copyright (C) 2004-2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
SNES_SPC* spc_new( void )
|
||||
{
|
||||
// be sure constants match
|
||||
assert( spc_sample_rate == (int) SNES_SPC::sample_rate );
|
||||
assert( spc_rom_size == (int) SNES_SPC::rom_size );
|
||||
assert( spc_clock_rate == (int) SNES_SPC::clock_rate );
|
||||
assert( spc_clocks_per_sample == (int) SNES_SPC::clocks_per_sample );
|
||||
assert( spc_port_count == (int) SNES_SPC::port_count );
|
||||
assert( spc_voice_count == (int) SNES_SPC::voice_count );
|
||||
assert( spc_tempo_unit == (int) SNES_SPC::tempo_unit );
|
||||
assert( spc_file_size == (int) SNES_SPC::spc_file_size );
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
assert( spc_state_size == (int) SNES_SPC::state_size );
|
||||
#endif
|
||||
|
||||
SNES_SPC* s = new SNES_SPC;
|
||||
if ( s && s->init() )
|
||||
{
|
||||
delete s;
|
||||
s = 0;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void spc_delete ( SNES_SPC* s ) { delete s; }
|
||||
void spc_init_rom ( SNES_SPC* s, unsigned char const r [64] ) { s->init_rom( r ); }
|
||||
void spc_set_output ( SNES_SPC* s, spc_sample_t* p, int n ) { s->set_output( p, n ); }
|
||||
int spc_sample_count ( SNES_SPC const* s ) { return s->sample_count(); }
|
||||
void spc_reset ( SNES_SPC* s ) { s->reset(); }
|
||||
void spc_soft_reset ( SNES_SPC* s ) { s->soft_reset(); }
|
||||
int spc_read_port ( SNES_SPC* s, spc_time_t t, int p ) { return s->read_port( t, p ); }
|
||||
void spc_write_port ( SNES_SPC* s, spc_time_t t, int p, int d ) { s->write_port( t, p, d ); }
|
||||
void spc_end_frame ( SNES_SPC* s, spc_time_t t ) { s->end_frame( t ); }
|
||||
void spc_mute_voices ( SNES_SPC* s, int mask ) { s->mute_voices( mask ); }
|
||||
void spc_disable_surround( SNES_SPC* s, int disable ) { s->disable_surround( disable ); }
|
||||
void spc_set_tempo ( SNES_SPC* s, int tempo ) { s->set_tempo( tempo ); }
|
||||
spc_err_t spc_load_spc ( SNES_SPC* s, void const* p, long n ) { return s->load_spc( p, n ); }
|
||||
void spc_clear_echo ( SNES_SPC* s ) { s->clear_echo(); }
|
||||
spc_err_t spc_play ( SNES_SPC* s, int count, short* out ) { return s->play( count, out ); }
|
||||
spc_err_t spc_skip ( SNES_SPC* s, int count ) { return s->skip( count ); }
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
void spc_copy_state ( SNES_SPC* s, unsigned char** p, spc_copy_func_t f ) { s->copy_state( p, f ); }
|
||||
void spc_init_header ( void* spc_out ) { SNES_SPC::init_header( spc_out ); }
|
||||
void spc_save_spc ( SNES_SPC* s, void* spc_out ) { s->save_spc( spc_out ); }
|
||||
int spc_check_kon ( SNES_SPC* s ) { return s->check_kon(); }
|
||||
#endif
|
||||
|
||||
SPC_Filter* spc_filter_new( void ) { return new SPC_Filter; }
|
||||
void spc_filter_delete( SPC_Filter* f ) { delete f; }
|
||||
void spc_filter_run( SPC_Filter* f, spc_sample_t* p, int s ) { f->run( p, s ); }
|
||||
void spc_filter_clear( SPC_Filter* f ) { f->clear(); }
|
||||
void spc_filter_set_gain( SPC_Filter* f, int gain ) { f->set_gain( gain ); }
|
||||
void spc_filter_set_bass( SPC_Filter* f, int bass ) { f->set_bass( bass ); }
|
|
@ -0,0 +1,147 @@
|
|||
/* SNES SPC-700 APU emulator C interface (also usable from C++) */
|
||||
|
||||
/* snes_spc 0.9.0 */
|
||||
#ifndef SPC_H
|
||||
#define SPC_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Error string return. NULL if success, otherwise error message. */
|
||||
typedef const char* spc_err_t;
|
||||
|
||||
typedef struct SNES_SPC SNES_SPC;
|
||||
|
||||
/* Creates new SPC emulator. NULL if out of memory. */
|
||||
SNES_SPC* spc_new( void );
|
||||
|
||||
/* Frees SPC emulator */
|
||||
void spc_delete( SNES_SPC* );
|
||||
|
||||
/* Sample pairs generated per second */
|
||||
enum { spc_sample_rate = 32000 };
|
||||
|
||||
|
||||
/**** Emulator use ****/
|
||||
|
||||
/* Sets IPL ROM data. Library does not include ROM data. Most SPC music files
|
||||
don't need ROM, but a full emulator must provide this. */
|
||||
enum { spc_rom_size = 0x40 };
|
||||
void spc_init_rom( SNES_SPC*, unsigned char const rom [spc_rom_size] );
|
||||
|
||||
/* Sets destination for output samples */
|
||||
typedef short spc_sample_t;
|
||||
void spc_set_output( SNES_SPC*, spc_sample_t* out, int out_size );
|
||||
|
||||
/* Number of samples written to output since last set */
|
||||
int spc_sample_count( SNES_SPC const* );
|
||||
|
||||
/* Resets SPC to power-on state. This resets your output buffer, so you must
|
||||
call spc_set_output() after this. */
|
||||
void spc_reset( SNES_SPC* );
|
||||
|
||||
/* Emulates pressing reset switch on SNES. This resets your output buffer, so
|
||||
you must call spc_set_output() after this. */
|
||||
void spc_soft_reset( SNES_SPC* );
|
||||
|
||||
/* 1024000 SPC clocks per second, sample pair every 32 clocks */
|
||||
typedef int spc_time_t;
|
||||
enum { spc_clock_rate = 1024000 };
|
||||
enum { spc_clocks_per_sample = 32 };
|
||||
|
||||
/* Reads/writes port at specified time */
|
||||
enum { spc_port_count = 4 };
|
||||
int spc_read_port ( SNES_SPC*, spc_time_t, int port );
|
||||
void spc_write_port( SNES_SPC*, spc_time_t, int port, int data );
|
||||
|
||||
/* Runs SPC to end_time and starts a new time frame at 0 */
|
||||
void spc_end_frame( SNES_SPC*, spc_time_t end_time );
|
||||
|
||||
|
||||
/**** Sound control ****/
|
||||
|
||||
/*Mutes voices corresponding to non-zero bits in mask. Reduces emulation accuracy. */
|
||||
enum { spc_voice_count = 8 };
|
||||
void spc_mute_voices( SNES_SPC*, int mask );
|
||||
|
||||
/* If true, prevents channels and global volumes from being phase-negated.
|
||||
Only supported by fast DSP; has no effect on accurate DSP. */
|
||||
void spc_disable_surround( SNES_SPC*, int disable );
|
||||
|
||||
/* Sets tempo, where spc_tempo_unit = normal, spc_tempo_unit / 2 = half speed, etc. */
|
||||
enum { spc_tempo_unit = 0x100 };
|
||||
void spc_set_tempo( SNES_SPC*, int );
|
||||
|
||||
|
||||
/**** SPC music playback *****/
|
||||
|
||||
/* Loads SPC data into emulator. Returns NULL on success, otherwise error string. */
|
||||
spc_err_t spc_load_spc( SNES_SPC*, void const* spc_in, long size );
|
||||
|
||||
/* Clears echo region. Useful after loading an SPC as many have garbage in echo. */
|
||||
void spc_clear_echo( SNES_SPC* );
|
||||
|
||||
/* Plays for count samples and write samples to out. Discards samples if out
|
||||
is NULL. Count must be a multiple of 2 since output is stereo. */
|
||||
spc_err_t spc_play( SNES_SPC*, int count, short* out );
|
||||
|
||||
/* Skips count samples. Several times faster than spc_play(). */
|
||||
spc_err_t spc_skip( SNES_SPC*, int count );
|
||||
|
||||
|
||||
/**** State save/load (only available with accurate DSP) ****/
|
||||
|
||||
/* Saves/loads exact emulator state */
|
||||
enum { spc_state_size = 67 * 1024L }; /* maximum space needed when saving */
|
||||
typedef void (*spc_copy_func_t)( unsigned char** io, void* state, size_t );
|
||||
void spc_copy_state( SNES_SPC*, unsigned char** io, spc_copy_func_t );
|
||||
|
||||
/* Writes minimal SPC file header to spc_out */
|
||||
void spc_init_header( void* spc_out );
|
||||
|
||||
/* Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out.
|
||||
Does not set up SPC header; use spc_init_header() for that. */
|
||||
enum { spc_file_size = 0x10200 }; /* spc_out must have this many bytes allocated */
|
||||
void spc_save_spc( SNES_SPC*, void* spc_out );
|
||||
|
||||
/* Returns non-zero if new key-on events occurred since last check. Useful for
|
||||
trimming silence while saving an SPC. */
|
||||
int spc_check_kon( SNES_SPC* );
|
||||
|
||||
|
||||
/**** SPC_Filter ****/
|
||||
|
||||
typedef struct SPC_Filter SPC_Filter;
|
||||
|
||||
/* Creates new filter. NULL if out of memory. */
|
||||
SPC_Filter* spc_filter_new( void );
|
||||
|
||||
/* Frees filter */
|
||||
void spc_filter_delete( SPC_Filter* );
|
||||
|
||||
/* Filters count samples of stereo sound in place. Count must be a multiple of 2. */
|
||||
void spc_filter_run( SPC_Filter*, spc_sample_t* io, int count );
|
||||
|
||||
/* Clears filter to silence */
|
||||
void spc_filter_clear( SPC_Filter* );
|
||||
|
||||
/* Sets gain (volume), where spc_filter_gain_unit is normal. Gains greater than
|
||||
spc_filter_gain_unit are fine, since output is clamped to 16-bit sample range. */
|
||||
enum { spc_filter_gain_unit = 0x100 };
|
||||
void spc_filter_set_gain( SPC_Filter*, int gain );
|
||||
|
||||
/* Sets amount of bass (logarithmic scale) */
|
||||
enum { spc_filter_bass_none = 0 };
|
||||
enum { spc_filter_bass_norm = 8 }; /* normal amount */
|
||||
enum { spc_filter_bass_max = 31 };
|
||||
void spc_filter_set_bass( SPC_Filter*, int bass );
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -1,6 +1,8 @@
|
|||
#include "../blip_buf/blip_buf.h"
|
||||
#include "sound_output.h"
|
||||
#include "cycles.h"
|
||||
#include "sgb.h"
|
||||
#include "global.h"
|
||||
|
||||
static blip_t* lb;
|
||||
static blip_t* rb;
|
||||
|
@ -26,8 +28,26 @@ void sound_output_init(double clock_rate, double sample_rate)
|
|||
blip_set_rates(lb, clock_rate, sample_rate);
|
||||
blip_set_rates(rb, clock_rate, sample_rate);
|
||||
}
|
||||
|
||||
static int32_t sgb_last_l;
|
||||
static int32_t sgb_last_r;
|
||||
|
||||
static void sgb_audio_callback(int16_t l, int16_t r, uint64_t time)
|
||||
{
|
||||
uint64_t t = time - startclock;
|
||||
int32_t ld = l - sgb_last_l;
|
||||
int32_t rd = r - sgb_last_r;
|
||||
blip_add_delta(lb, t, ld);
|
||||
blip_add_delta(rb, t, rd);
|
||||
sgb_last_l = l;
|
||||
sgb_last_r = r;
|
||||
}
|
||||
|
||||
int sound_output_read(int16_t* output)
|
||||
{
|
||||
if (global_sgb)
|
||||
sgb_render_audio(cycles.sampleclock, sgb_audio_callback);
|
||||
|
||||
blip_end_frame(lb, RELATIVECLOCK);
|
||||
blip_end_frame(rb, RELATIVECLOCK);
|
||||
startclock = cycles.sampleclock;
|
||||
|
|
|
@ -55,7 +55,7 @@ int main(void)
|
|||
{
|
||||
}
|
||||
|
||||
EXPORT int Init(const void *rom, int romlen, int sgb)
|
||||
EXPORT int Init(const void *rom, int romlen, int sgb, const void *spc, int spclen)
|
||||
{
|
||||
/* init global variables */
|
||||
global_init();
|
||||
|
@ -68,8 +68,8 @@ EXPORT int Init(const void *rom, int romlen, int sgb)
|
|||
global_sgb = !!sgb;
|
||||
if (global_sgb && global_cgb)
|
||||
utils_log("Warn: CGB game in SGB mode");
|
||||
if (sgb)
|
||||
sgb_init();
|
||||
if (sgb && !sgb_init((const uint8_t*)spc, spclen))
|
||||
return 0;
|
||||
|
||||
gameboy_init();
|
||||
|
||||
|
|
Loading…
Reference in New Issue