Update to v095r01 release (open beta).

byuu says:

Changelog:
- added MSU1 resume support
- updated sfc/dsp, sfc/controller to match my coding style
- fixed hiro/Windows Button and ListView::CheckButton in Windows Classic
  mode
This commit is contained in:
Tim Allen 2015-10-10 13:16:12 +11:00
parent b0e862613b
commit 8476a12deb
35 changed files with 1013 additions and 991 deletions

View File

@ -8,7 +8,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "095";
static const string Version = "095.01";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

@ -5,7 +5,7 @@ static const unsigned WindowsXP = 0x0501;
static const unsigned WindowsVista = 0x0600;
static const unsigned Windows7 = 0x0601;
static auto Button_CustomDraw(HWND, PAINTSTRUCT&, unsigned, const Font&, const Image&, Orientation, const string&) -> void;
static auto Button_CustomDraw(HWND, PAINTSTRUCT&, bool, bool, bool, unsigned, const Font&, const Image&, Orientation, const string&) -> void;
static auto OsVersion() -> unsigned {
OSVERSIONINFO versionInfo{0};

View File

@ -9,12 +9,7 @@ static auto Button_paintProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam,
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
auto state = Button_GetState(hwnd);
Button_CustomDraw(hwnd, ps,
(state & BST_PUSHED || checked) ? PBS_PRESSED
: (state & BST_HOT) ? PBS_HOT
: bordered ? (enabled ? PBS_NORMAL : PBS_DISABLED)
: 0, font, image, orientation, text
);
Button_CustomDraw(hwnd, ps, bordered, checked, enabled, state, font, image, orientation, text);
EndPaint(hwnd, &ps);
}
return DefWindowProc(hwnd, msg, wparam, lparam);
@ -113,7 +108,7 @@ auto pButton::_setState() -> void {
}
//this function is designed to be used with Button, CheckButton, and RadioButton
auto Button_CustomDraw(HWND hwnd, PAINTSTRUCT& ps, unsigned state, const Font& font, const Image& image, Orientation orientation, const string& text) -> void {
auto Button_CustomDraw(HWND hwnd, PAINTSTRUCT& ps, bool bordered, bool checked, bool enabled, unsigned state, const Font& font, const Image& image, Orientation orientation, const string& text) -> void {
RECT rc;
GetClientRect(hwnd, &rc);
Geometry geometry{rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top}, imageGeometry, textGeometry;
@ -138,23 +133,29 @@ auto Button_CustomDraw(HWND hwnd, PAINTSTRUCT& ps, unsigned state, const Font& f
break;
}
HDC hdcSource = CreateCompatibleDC(ps.hdc);
DrawThemeParentBackground(hwnd, ps.hdc, &rc);
if(state) {
if(auto theme = OpenThemeData(hwnd, L"BUTTON")) {
DrawThemeBackground(theme, ps.hdc, BP_PUSHBUTTON, state, &rc, &ps.rcPaint);
CloseThemeData(theme);
}
if(auto theme = OpenThemeData(hwnd, L"BUTTON")) {
DrawThemeParentBackground(hwnd, ps.hdc, &rc);
unsigned flags = 0;
if(state & BST_PUSHED || checked) flags = PBS_PRESSED;
else if(state & BST_HOT) flags = PBS_HOT;
else if(bordered) flags = enabled ? PBS_NORMAL : PBS_DISABLED;
if(bordered || flags) DrawThemeBackground(theme, ps.hdc, BP_PUSHBUTTON, flags, &rc, &ps.rcPaint);
CloseThemeData(theme);
} else {
//Windows Classic
FillRect(ps.hdc, &rc, GetSysColorBrush(COLOR_3DFACE));
unsigned flags = (state & BST_PUSHED || checked) ? DFCS_PUSHED : 0;
if(bordered || flags) DrawFrameControl(ps.hdc, &rc, DFC_BUTTON, DFCS_BUTTONPUSH | flags | (enabled ? 0 : DFCS_INACTIVE));
}
if(GetFocus() == hwnd) {
signed offset = state ? 4 : 1;
RECT rcFocus{rc.left + offset, rc.top + offset, rc.right - offset, rc.bottom - offset};
if(!state || state == PBS_NORMAL) DrawFocusRect(ps.hdc, &rcFocus);
if(!(state & BST_PUSHED) && !(state & BST_HOT)) DrawFocusRect(ps.hdc, &rcFocus);
}
if(image) {
HDC hdcSource = CreateCompatibleDC(ps.hdc);
auto bitmap = CreateBitmap(image);
SelectBitmap(hdcSource, bitmap);
BLENDFUNCTION blend{AC_SRC_OVER, 0, (BYTE)(IsWindowEnabled(hwnd) ? 255 : 128), AC_SRC_ALPHA};
@ -163,6 +164,7 @@ auto Button_CustomDraw(HWND hwnd, PAINTSTRUCT& ps, unsigned state, const Font& f
hdcSource, 0, 0, image.width(), image.height(), blend
);
DeleteObject(bitmap);
DeleteDC(hdcSource);
}
if(text) {
@ -175,8 +177,6 @@ auto Button_CustomDraw(HWND hwnd, PAINTSTRUCT& ps, unsigned state, const Font& f
DrawText(ps.hdc, wText, -1, &rcText, DT_NOPREFIX | DT_END_ELLIPSIS);
DeleteObject(hFont);
}
DeleteDC(hdcSource);
}
}

View File

@ -131,12 +131,6 @@ auto pListView::setGeometry(Geometry geometry) -> void {
}
}
/*auto pListView::setHeaderVisible(bool visible) -> void {
auto style = GetWindowLong(hwnd, GWL_STYLE);
!visible ? style |= LVS_NOCOLUMNHEADER : style &=~ LVS_NOCOLUMNHEADER;
SetWindowLong(hwnd, GWL_STYLE, style);
}*/
auto pListView::onActivate(LPARAM lparam) -> void {
auto nmlistview = (LPNMLISTVIEW)lparam;
if(ListView_GetSelectedCount(hwnd) == 0) return;
@ -213,6 +207,11 @@ auto pListView::onCustomDraw(LPARAM lparam) -> LRESULT {
RECT rd{rc.left + center, rc.top + center, rc.left + center + size.cx, rc.top + center + size.cy};
DrawThemeBackground(htheme, hdc, BP_CHECKBOX, state, &rd, nullptr);
CloseThemeData(htheme);
} else {
//Windows Classic
rc.left += 2;
RECT rd{rc.left, rc.top, rc.left + iconSize, rc.top + iconSize};
DrawFrameControl(hdc, &rd, DFC_BUTTON, DFCS_BUTTONCHECK | (cell->state.checked ? DFCS_CHECKED : 0));
}
rc.left += iconSize + 2;
} else {

View File

@ -542,7 +542,7 @@ auto Cartridge::parseMarkupMSU1(Markup::Node root) -> void {
for(auto node : root.find("map")) {
if(node["id"].text() == "io") {
Mapping m({&MSU1::mmio_read, &msu1}, {&MSU1::mmio_write, &msu1});
Mapping m({&MSU1::mmioRead, &msu1}, {&MSU1::mmioWrite, &msu1});
parseMarkupMap(m, node);
mapping.append(m);
}

View File

@ -7,9 +7,9 @@ MSU1 msu1;
#include "serialization.cpp"
void MSU1::Enter() { msu1.enter(); }
auto MSU1::Enter() -> void { msu1.enter(); }
void MSU1::enter() {
auto MSU1::enter() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
@ -17,27 +17,27 @@ void MSU1::enter() {
int16 left = 0, right = 0;
if(mmio.audio_play) {
if(audiofile.open()) {
if(audiofile.end()) {
if(!mmio.audio_repeat) {
mmio.audio_play = false;
audiofile.seek(mmio.audio_play_offset = 8);
if(mmio.audioPlay) {
if(audioFile.open()) {
if(audioFile.end()) {
if(!mmio.audioRepeat) {
mmio.audioPlay = false;
audioFile.seek(mmio.audioPlayOffset = 8);
} else {
audiofile.seek(mmio.audio_play_offset = mmio.audio_loop_offset);
audioFile.seek(mmio.audioPlayOffset = mmio.audioLoopOffset);
}
} else {
mmio.audio_play_offset += 4;
left = audiofile.readl(2);
right = audiofile.readl(2);
mmio.audioPlayOffset += 4;
left = audioFile.readl(2);
right = audioFile.readl(2);
}
} else {
mmio.audio_play = false;
mmio.audioPlay = false;
}
}
signed lchannel = (double)left * (double)mmio.audio_volume / 255.0;
signed rchannel = (double)right * (double)mmio.audio_volume / 255.0;
signed lchannel = (double)left * (double)mmio.audioVolume / 255.0;
signed rchannel = (double)right * (double)mmio.audioVolume / 255.0;
left = sclamp<16>(lchannel);
right = sclamp<16>(rchannel);
if(dsp.mute()) left = 0, right = 0;
@ -48,96 +48,99 @@ void MSU1::enter() {
}
}
void MSU1::init() {
auto MSU1::init() -> void {
}
void MSU1::load() {
auto MSU1::load() -> void {
}
void MSU1::unload() {
if(datafile.open()) datafile.close();
if(audiofile.open()) audiofile.close();
auto MSU1::unload() -> void {
if(dataFile.open()) dataFile.close();
if(audioFile.open()) audioFile.close();
}
void MSU1::power() {
auto MSU1::power() -> void {
audio.coprocessor_enable(true);
audio.coprocessor_frequency(44100.0);
}
void MSU1::reset() {
auto MSU1::reset() -> void {
create(MSU1::Enter, 44100);
mmio.data_seek_offset = 0;
mmio.data_read_offset = 0;
mmio.dataSeekOffset = 0;
mmio.dataReadOffset = 0;
mmio.audio_play_offset = 0;
mmio.audio_loop_offset = 0;
mmio.audioPlayOffset = 0;
mmio.audioLoopOffset = 0;
mmio.audio_track = 0;
mmio.audio_volume = 0;
mmio.audioTrack = 0;
mmio.audioVolume = 0;
mmio.data_busy = false;
mmio.audio_busy = false;
mmio.audio_repeat = false;
mmio.audio_play = false;
mmio.audio_error = false;
mmio.audioResumeTrack = ~0; //no resume
mmio.audioResumeOffset = 0;
data_open();
audio_open();
mmio.dataBusy = false;
mmio.audioBusy = false;
mmio.audioRepeat = false;
mmio.audioPlay = false;
mmio.audioError = false;
dataOpen();
audioOpen();
}
void MSU1::data_open() {
if(datafile.open()) datafile.close();
auto MSU1::dataOpen() -> void {
if(dataFile.open()) dataFile.close();
auto document = BML::unserialize(cartridge.information.markup.cartridge);
string name = document["cartridge/msu1/rom/name"].text();
if(!name) name = "msu1.rom";
if(datafile.open({interface->path(ID::SuperFamicom), name}, file::mode::read)) {
datafile.seek(mmio.data_read_offset);
if(dataFile.open({interface->path(ID::SuperFamicom), name}, file::mode::read)) {
dataFile.seek(mmio.dataReadOffset);
}
}
void MSU1::audio_open() {
if(audiofile.open()) audiofile.close();
auto MSU1::audioOpen() -> void {
if(audioFile.open()) audioFile.close();
auto document = BML::unserialize(cartridge.information.markup.cartridge);
string name = {"track-", mmio.audio_track, ".pcm"};
string name = {"track-", mmio.audioTrack, ".pcm"};
for(auto track : document.find("cartridge/msu1/track")) {
if(track["number"].decimal() != mmio.audio_track) continue;
if(track["number"].decimal() != mmio.audioTrack) continue;
name = track["name"].text();
break;
}
if(audiofile.open({interface->path(ID::SuperFamicom), name}, file::mode::read)) {
if(audiofile.size() >= 8) {
uint32 header = audiofile.readm(4);
if(audioFile.open({interface->path(ID::SuperFamicom), name}, file::mode::read)) {
if(audioFile.size() >= 8) {
uint32 header = audioFile.readm(4);
if(header == 0x4d535531) { //"MSU1"
mmio.audio_loop_offset = 8 + audiofile.readl(4) * 4;
if(mmio.audio_loop_offset > audiofile.size()) mmio.audio_loop_offset = 8;
mmio.audio_error = false;
audiofile.seek(mmio.audio_play_offset);
mmio.audioLoopOffset = 8 + audioFile.readl(4) * 4;
if(mmio.audioLoopOffset > audioFile.size()) mmio.audioLoopOffset = 8;
mmio.audioError = false;
audioFile.seek(mmio.audioPlayOffset);
return;
}
}
audiofile.close();
audioFile.close();
}
mmio.audio_error = true;
mmio.audioError = true;
}
uint8 MSU1::mmio_read(unsigned addr) {
auto MSU1::mmioRead(unsigned addr) -> uint8 {
cpu.synchronize_coprocessors();
addr = 0x2000 | (addr & 7);
switch(addr) {
case 0x2000:
return (mmio.data_busy << 7)
| (mmio.audio_busy << 6)
| (mmio.audio_repeat << 5)
| (mmio.audio_play << 4)
| (mmio.audio_error << 3)
| (Revision << 0);
return (mmio.dataBusy << 7)
| (mmio.audioBusy << 6)
| (mmio.audioRepeat << 5)
| (mmio.audioPlay << 4)
| (mmio.audioError << 3)
| (Revision << 0);
case 0x2001:
if(mmio.data_busy) return 0x00;
if(datafile.end()) return 0x00;
mmio.data_read_offset++;
return datafile.read();
if(mmio.dataBusy) return 0x00;
if(dataFile.end()) return 0x00;
mmio.dataReadOffset++;
return dataFile.read();
case 0x2002: return 'S';
case 0x2003: return '-';
case 0x2004: return 'M';
@ -147,29 +150,41 @@ uint8 MSU1::mmio_read(unsigned addr) {
}
}
void MSU1::mmio_write(unsigned addr, uint8 data) {
auto MSU1::mmioWrite(unsigned addr, uint8 data) -> void {
cpu.synchronize_coprocessors();
addr = 0x2000 | (addr & 7);
switch(addr) {
case 0x2000: mmio.data_seek_offset = (mmio.data_seek_offset & 0xffffff00) | (data << 0); break;
case 0x2001: mmio.data_seek_offset = (mmio.data_seek_offset & 0xffff00ff) | (data << 8); break;
case 0x2002: mmio.data_seek_offset = (mmio.data_seek_offset & 0xff00ffff) | (data << 16); break;
case 0x2003: mmio.data_seek_offset = (mmio.data_seek_offset & 0x00ffffff) | (data << 24);
mmio.data_read_offset = mmio.data_seek_offset;
data_open();
case 0x2000: mmio.dataSeekOffset = (mmio.dataSeekOffset & 0xffffff00) | (data << 0); break;
case 0x2001: mmio.dataSeekOffset = (mmio.dataSeekOffset & 0xffff00ff) | (data << 8); break;
case 0x2002: mmio.dataSeekOffset = (mmio.dataSeekOffset & 0xff00ffff) | (data << 16); break;
case 0x2003: mmio.dataSeekOffset = (mmio.dataSeekOffset & 0x00ffffff) | (data << 24);
mmio.dataReadOffset = mmio.dataSeekOffset;
dataOpen();
break;
case 0x2004: mmio.audio_track = (mmio.audio_track & 0xff00) | (data << 0); break;
case 0x2005: mmio.audio_track = (mmio.audio_track & 0x00ff) | (data << 8);
mmio.audio_play_offset = 8;
audio_open();
case 0x2004: mmio.audioTrack = (mmio.audioTrack & 0xff00) | (data << 0); break;
case 0x2005: mmio.audioTrack = (mmio.audioTrack & 0x00ff) | (data << 8);
mmio.audioPlayOffset = 8;
if(mmio.audioTrack == mmio.audioResumeTrack) {
mmio.audioPlayOffset = mmio.audioResumeOffset;
mmio.audioResumeTrack = ~0; //erase resume track
mmio.audioResumeOffset = 0;
}
audioOpen();
break;
case 0x2006:
mmio.audioVolume = data;
break;
case 0x2006: mmio.audio_volume = data; break;
case 0x2007:
if(mmio.audio_busy) break;
if(mmio.audio_error) break;
mmio.audio_repeat = data & 2;
mmio.audio_play = data & 1;
if(mmio.audioBusy) break;
if(mmio.audioError) break;
bool audioResume = data & 4;
mmio.audioRepeat = data & 2;
mmio.audioPlay = data & 1;
if(!mmio.audioPlay && audioResume) {
mmio.audioResumeTrack = mmio.audioTrack;
mmio.audioResumeOffset = mmio.audioPlayOffset;
}
break;
}
}

View File

@ -1,23 +1,24 @@
struct MSU1 : Coprocessor {
static void Enter();
void enter();
void init();
void load();
void unload();
void power();
void reset();
static auto Enter() -> void;
void data_open();
void audio_open();
auto enter() -> void;
auto init() -> void;
auto load() -> void;
auto unload() -> void;
auto power() -> void;
auto reset() -> void;
uint8 mmio_read(unsigned addr);
void mmio_write(unsigned addr, uint8 data);
auto dataOpen() -> void;
auto audioOpen() -> void;
void serialize(serializer&);
auto mmioRead(unsigned addr) -> uint8;
auto mmioWrite(unsigned addr, uint8 data) -> void;
auto serialize(serializer&) -> void;
private:
file datafile;
file audiofile;
file dataFile;
file audioFile;
enum Flag : unsigned {
DataBusy = 0x80,
@ -25,24 +26,27 @@ private:
AudioRepeating = 0x20,
AudioPlaying = 0x10,
AudioError = 0x08,
Revision = 0x01,
Revision = 0x02,
};
struct MMIO {
uint32 data_seek_offset;
uint32 data_read_offset;
uint32 dataSeekOffset;
uint32 dataReadOffset;
uint32 audio_play_offset;
uint32 audio_loop_offset;
uint32 audioPlayOffset;
uint32 audioLoopOffset;
uint16 audio_track;
uint8 audio_volume;
uint16 audioTrack;
uint8 audioVolume;
bool data_busy;
bool audio_busy;
bool audio_repeat;
bool audio_play;
bool audio_error;
uint32 audioResumeTrack;
uint32 audioResumeOffset;
bool dataBusy;
bool audioBusy;
bool audioRepeat;
bool audioPlay;
bool audioError;
} mmio;
};

View File

@ -1,25 +1,28 @@
#ifdef MSU1_CPP
void MSU1::serialize(serializer& s) {
auto MSU1::serialize(serializer& s) -> void {
Thread::serialize(s);
s.integer(mmio.data_seek_offset);
s.integer(mmio.data_read_offset);
s.integer(mmio.dataSeekOffset);
s.integer(mmio.dataReadOffset);
s.integer(mmio.audio_play_offset);
s.integer(mmio.audio_loop_offset);
s.integer(mmio.audioPlayOffset);
s.integer(mmio.audioLoopOffset);
s.integer(mmio.audio_track);
s.integer(mmio.audio_volume);
s.integer(mmio.audioTrack);
s.integer(mmio.audioVolume);
s.integer(mmio.data_busy);
s.integer(mmio.audio_busy);
s.integer(mmio.audio_repeat);
s.integer(mmio.audio_play);
s.integer(mmio.audio_error);
s.integer(mmio.audioResumeTrack);
s.integer(mmio.audioResumeOffset);
data_open();
audio_open();
s.integer(mmio.dataBusy);
s.integer(mmio.audioBusy);
s.integer(mmio.audioRepeat);
s.integer(mmio.audioPlay);
s.integer(mmio.audioError);
dataOpen();
audioOpen();
}
#endif

View File

@ -25,10 +25,10 @@ auto Controller::enter() -> void {
auto Controller::step(unsigned clocks) -> void {
clock += clocks * (uint64)cpu.frequency;
synchronize_cpu();
synchronizeCPU();
}
auto Controller::synchronize_cpu() -> void {
auto Controller::synchronizeCPU() -> void {
if(CPU::Threaded == true) {
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
} else {

View File

@ -20,7 +20,7 @@ struct Controller : Thread {
virtual auto enter() -> void;
auto step(unsigned clocks) -> void;
auto synchronize_cpu() -> void;
auto synchronizeCPU() -> void;
auto iobit() -> bool;
auto iobit(bool data) -> void;

View File

@ -1,6 +1,15 @@
#ifdef CONTROLLER_CPP
uint2 Gamepad::data() {
Gamepad::Gamepad(bool port) : Controller(port) {
latched = 0;
counter = 0;
b = y = select = start = 0;
up = down = left = right = 0;
a = x = l = r = 0;
}
auto Gamepad::data() -> uint2 {
if(counter >= 16) return 1;
if(latched == 1) return interface->inputPoll(port, (unsigned)Input::Device::Joypad, (unsigned)Input::JoypadID::B);
@ -23,7 +32,7 @@ uint2 Gamepad::data() {
return 0; //12-15: signature
}
void Gamepad::latch(bool data) {
auto Gamepad::latch(bool data) -> void {
if(latched == data) return;
latched = data;
counter = 0;
@ -45,13 +54,4 @@ void Gamepad::latch(bool data) {
}
}
Gamepad::Gamepad(bool port) : Controller(port) {
latched = 0;
counter = 0;
b = y = select = start = 0;
up = down = left = right = 0;
a = x = l = r = 0;
}
#endif

View File

@ -1,8 +1,9 @@
struct Gamepad : Controller {
uint2 data();
void latch(bool data);
Gamepad(bool port);
auto data() -> uint2;
auto latch(bool data) -> void;
private:
bool latched;
unsigned counter;

View File

@ -1,6 +1,35 @@
#ifdef CONTROLLER_CPP
void Justifier::enter() {
Justifier::Justifier(bool port, bool chained):
Controller(port),
chained(chained),
device(chained == false ? (unsigned)Input::Device::Justifier : (unsigned)Input::Device::Justifiers)
{
create(Controller::Enter, 21477272);
latched = 0;
counter = 0;
active = 0;
player1.x = 256 / 2;
player1.y = 240 / 2;
player1.trigger = false;
player2.start = false;
player2.x = 256 / 2;
player2.y = 240 / 2;
player2.trigger = false;
player2.start = false;
if(chained == false) {
player2.x = -1;
player2.y = -1;
} else {
player1.x -= 16;
player2.x += 16;
}
}
auto Justifier::enter() -> void {
unsigned prev = 0;
while(true) {
unsigned next = cpu.vcounter() * 1364 + cpu.hcounter();
@ -40,7 +69,7 @@ void Justifier::enter() {
}
}
uint2 Justifier::data() {
auto Justifier::data() -> uint2 {
if(counter >= 32) return 1;
if(counter == 0) {
@ -93,40 +122,11 @@ uint2 Justifier::data() {
}
}
void Justifier::latch(bool data) {
auto Justifier::latch(bool data) -> void {
if(latched == data) return;
latched = data;
counter = 0;
if(latched == 0) active = !active; //toggle between both controllers, even when unchained
}
Justifier::Justifier(bool port, bool chained):
Controller(port),
chained(chained),
device(chained == false ? (unsigned)Input::Device::Justifier : (unsigned)Input::Device::Justifiers)
{
create(Controller::Enter, 21477272);
latched = 0;
counter = 0;
active = 0;
player1.x = 256 / 2;
player1.y = 240 / 2;
player1.trigger = false;
player2.start = false;
player2.x = 256 / 2;
player2.y = 240 / 2;
player2.trigger = false;
player2.start = false;
if(chained == false) {
player2.x = -1;
player2.y = -1;
} else {
player1.x -= 16;
player2.x += 16;
}
}
#endif

View File

@ -1,9 +1,10 @@
struct Justifier : Controller {
void enter();
uint2 data();
void latch(bool data);
Justifier(bool port, bool chained);
auto enter() -> void;
auto data() -> uint2;
auto latch(bool data) -> void;
//private:
const bool chained; //true if the second justifier is attached to the first
const unsigned device;

View File

@ -1,6 +1,19 @@
#ifdef CONTROLLER_CPP
uint2 Mouse::data() {
Mouse::Mouse(bool port) : Controller(port) {
latched = 0;
counter = 0;
speed = 0;
x = 0;
y = 0;
dx = 0;
dy = 0;
l = 0;
r = 0;
}
auto Mouse::data() -> uint2 {
if(latched == 1) {
speed = (speed + 1) % 3;
return 0;
@ -48,7 +61,7 @@ uint2 Mouse::data() {
}
}
void Mouse::latch(bool data) {
auto Mouse::latch(bool data) -> void {
if(latched == data) return;
latched = data;
counter = 0;
@ -74,17 +87,4 @@ void Mouse::latch(bool data) {
y = min(127, y);
}
Mouse::Mouse(bool port) : Controller(port) {
latched = 0;
counter = 0;
speed = 0;
x = 0;
y = 0;
dx = 0;
dy = 0;
l = 0;
r = 0;
}
#endif

View File

@ -1,8 +1,9 @@
struct Mouse : Controller {
uint2 data();
void latch(bool data);
Mouse(bool port);
auto data() -> uint2;
auto latch(bool data) -> void;
private:
bool latched;
unsigned counter;

View File

@ -1,6 +1,12 @@
#ifdef CONTROLLER_CPP
uint2 Multitap::data() {
Multitap::Multitap(bool port) : Controller(port) {
latched = 0;
counter1 = 0;
counter2 = 0;
}
auto Multitap::data() -> uint2 {
if(latched) return 2; //multitap detection
unsigned index, port1, port2;
@ -25,17 +31,11 @@ uint2 Multitap::data() {
return (data2 << 1) | (data1 << 0);
}
void Multitap::latch(bool data) {
auto Multitap::latch(bool data) -> void {
if(latched == data) return;
latched = data;
counter1 = 0;
counter2 = 0;
}
Multitap::Multitap(bool port) : Controller(port) {
latched = 0;
counter1 = 0;
counter2 = 0;
}
#endif

View File

@ -1,8 +1,9 @@
struct Multitap : Controller {
uint2 data();
void latch(bool data);
Multitap(bool port);
auto data() -> uint2;
auto latch(bool data) -> void;
private:
bool latched;
unsigned counter1;

View File

@ -12,7 +12,27 @@
//require manual polling of PIO ($4201.d6) to determine when iobit was written.
//Note that no commercial game ever utilizes a Super Scope in port 1.
void SuperScope::enter() {
SuperScope::SuperScope(bool port) : Controller(port) {
create(Controller::Enter, 21477272);
latched = 0;
counter = 0;
//center cursor onscreen
x = 256 / 2;
y = 240 / 2;
trigger = false;
cursor = false;
turbo = false;
pause = false;
offscreen = false;
turbolock = false;
triggerlock = false;
pauselock = false;
}
auto SuperScope::enter() -> void {
unsigned prev = 0;
while(true) {
unsigned next = cpu.vcounter() * 1364 + cpu.hcounter();
@ -42,7 +62,7 @@ void SuperScope::enter() {
}
}
uint2 SuperScope::data() {
auto SuperScope::data() -> uint2 {
if(counter >= 8) return 1;
if(counter == 0) {
@ -94,30 +114,10 @@ uint2 SuperScope::data() {
}
}
void SuperScope::latch(bool data) {
auto SuperScope::latch(bool data) -> void {
if(latched == data) return;
latched = data;
counter = 0;
}
SuperScope::SuperScope(bool port) : Controller(port) {
create(Controller::Enter, 21477272);
latched = 0;
counter = 0;
//center cursor onscreen
x = 256 / 2;
y = 240 / 2;
trigger = false;
cursor = false;
turbo = false;
pause = false;
offscreen = false;
turbolock = false;
triggerlock = false;
pauselock = false;
}
#endif

View File

@ -1,9 +1,10 @@
struct SuperScope : Controller {
void enter();
uint2 data();
void latch(bool data);
SuperScope(bool port);
auto enter() -> void;
auto data() -> uint2;
auto latch(bool data) -> void;
//private:
bool latched;
unsigned counter;

View File

@ -14,7 +14,31 @@
//SNES Clock <> 1Kohm Resistor <> Teensy D5
//Teensy D5 <> Teensy D7
void USART::enter() {
USART::USART(bool port) : Controller(port) {
latched = 0;
data1 = 0;
data2 = 0;
counter = 0;
rxlength = 0;
rxdata = 0;
txlength = 0;
txdata = 0;
string filename = {interface->path(ID::SuperFamicom), "usart.so"};
if(openAbsolute(filename)) {
init = sym("usart_init");
main = sym("usart_main");
if(init && main) create(Controller::Enter, 10000000);
}
}
USART::~USART() {
if(open()) close();
}
auto USART::enter() -> void {
if(init && main) {
init(
{&USART::quit, this},
@ -29,22 +53,22 @@ void USART::enter() {
while(true) step(10000000);
}
bool USART::quit() {
auto USART::quit() -> bool {
step(1);
return false;
}
void USART::usleep(unsigned milliseconds) {
auto USART::usleep(unsigned milliseconds) -> void {
step(10 * milliseconds);
}
bool USART::readable() {
auto USART::readable() -> bool {
step(1);
return txbuffer.size();
}
//SNES -> USART
uint8 USART::read() {
auto USART::read() -> uint8 {
step(1);
while(txbuffer.size() == 0) step(1);
uint8 data = txbuffer[0];
@ -52,19 +76,19 @@ uint8 USART::read() {
return data;
}
bool USART::writable() {
auto USART::writable() -> bool {
step(1);
return true;
}
//USART -> SNES
void USART::write(uint8 data) {
auto USART::write(uint8 data) -> void {
step(1);
rxbuffer.append(data ^ 0xff);
}
//clock
uint2 USART::data() {
auto USART::data() -> uint2 {
//Joypad
if(iobit()) {
if(counter >= 16) return 1;
@ -104,34 +128,10 @@ uint2 USART::data() {
}
//latch
void USART::latch(bool data) {
auto USART::latch(bool data) -> void {
if(latched == data) return;
latched = data;
counter = 0;
}
USART::USART(bool port) : Controller(port) {
latched = 0;
data1 = 0;
data2 = 0;
counter = 0;
rxlength = 0;
rxdata = 0;
txlength = 0;
txdata = 0;
string filename = {interface->path(ID::SuperFamicom), "usart.so"};
if(openAbsolute(filename)) {
init = sym("usart_init");
main = sym("usart_main");
if(init && main) create(Controller::Enter, 10000000);
}
}
USART::~USART() {
if(open()) close();
}
#endif

View File

@ -1,19 +1,19 @@
struct USART : Controller, public library {
void enter();
bool quit();
void usleep(unsigned milliseconds);
bool readable();
uint8 read();
bool writable();
void write(uint8 data);
uint2 data();
void latch(bool data);
USART(bool port);
~USART();
auto enter() -> void;
auto quit() -> bool;
auto usleep(unsigned milliseconds) -> void;
auto readable() -> bool;
auto read() -> uint8;
auto writable() -> bool;
auto write(uint8 data) -> void;
auto data() -> uint2;
auto latch(bool data) -> void;
private:
bool latched;
bool data1;

View File

@ -1,17 +1,17 @@
#ifdef DSP_CPP
void DSP::brr_decode(voice_t& v) {
auto DSP::brrDecode(Voice& v) -> void {
//state.t_brr_byte = ram[v.brr_addr + v.brr_offset] cached from previous clock cycle
int nybbles = (state.t_brr_byte << 8) + smp.apuram[(uint16)(v.brr_addr + v.brr_offset + 1)];
signed nybbles = (state._brrByte << 8) + smp.apuram[(uint16)(v.brrAddress + v.brrOffset + 1)];
const int filter = (state.t_brr_header >> 2) & 3;
const int scale = (state.t_brr_header >> 4);
const signed filter = (state._brrHeader >> 2) & 3;
const signed scale = (state._brrHeader >> 4);
//decode four samples
for(unsigned i = 0; i < 4; i++) {
for(auto n : range(4)) {
//bits 12-15 = current nybble; sign extend, then shift right to 4-bit precision
//result: s = 4-bit sign-extended sample value
int s = (int16)nybbles >> 12;
signed s = (int16)nybbles >> 12;
nybbles <<= 4; //slide nybble so that on next loop iteration, bits 12-15 = current nybble
if(scale <= 12) {
@ -22,8 +22,8 @@ void DSP::brr_decode(voice_t& v) {
}
//apply IIR filter (2 is the most commonly used)
const int p1 = v.buffer[v.buf_pos - 1];
const int p2 = v.buffer[v.buf_pos - 2] >> 1;
const signed p1 = v.buffer[v.bufferOffset - 1];
const signed p2 = v.buffer[v.bufferOffset - 2] >> 1;
switch(filter) {
case 0:
@ -55,8 +55,8 @@ void DSP::brr_decode(voice_t& v) {
//adjust and write sample
s = sclamp<16>(s);
s = (int16)(s << 1);
v.buffer.write(v.buf_pos++, s);
if(v.buf_pos >= brr_buf_size) v.buf_pos = 0;
v.buffer.write(v.bufferOffset++, s);
if(v.bufferOffset >= BrrBufferSize) v.bufferOffset = 0;
}
}

View File

@ -4,7 +4,7 @@
//all rates are evenly divisible by counter_range (0x7800, 30720, or 2048 * 5 * 3)
//note that rate[0] is a special case, which never triggers
const uint16 DSP::counter_rate[32] = {
const uint16 DSP::CounterRate[32] = {
0, 2048, 1536,
1280, 1024, 768,
640, 512, 384,
@ -22,7 +22,7 @@ const uint16 DSP::counter_rate[32] = {
//counter_offset = counter offset from zero
//counters do not appear to be aligned at zero for all rates
const uint16 DSP::counter_offset[32] = {
const uint16 DSP::CounterOffset[32] = {
0, 0, 1040,
536, 0, 1040,
536, 0, 1040,
@ -37,16 +37,16 @@ const uint16 DSP::counter_offset[32] = {
0,
};
inline void DSP::counter_tick() {
inline auto DSP::counterTick() -> void {
state.counter--;
if(state.counter < 0) state.counter = counter_range - 1;
if(state.counter < 0) state.counter = CounterRange - 1;
}
//return true if counter event should trigger
inline bool DSP::counter_poll(unsigned rate) {
inline auto DSP::counterPoll(unsigned rate) -> bool {
if(rate == 0) return false;
return (((unsigned)state.counter + counter_offset[rate]) % counter_rate[rate]) == 0;
return (((unsigned)state.counter + CounterOffset[rate]) % CounterRate[rate]) == 0;
}
#endif

View File

@ -5,8 +5,8 @@ namespace SuperFamicom {
DSP dsp;
#define REG(n) state.regs[r_##n]
#define VREG(n) state.regs[v.vidx + v_##n]
#define REG(n) state.regs[n]
#define VREG(n) state.regs[v.vidx + n]
#include "serialization.cpp"
#include "gaussian.cpp"
@ -17,277 +17,8 @@ DSP dsp;
#include "voice.cpp"
#include "echo.cpp"
/* timing */
void DSP::step(unsigned clocks) {
clock += clocks;
}
void DSP::synchronize_smp() {
if(SMP::Threaded == true) {
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(smp.thread);
} else {
while(clock >= 0) smp.enter();
}
}
void DSP::Enter() { dsp.enter(); }
void DSP::enter() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
voice_5(voice[0]);
voice_2(voice[1]);
tick();
voice_6(voice[0]);
voice_3(voice[1]);
tick();
voice_7(voice[0]);
voice_4(voice[1]);
voice_1(voice[3]);
tick();
voice_8(voice[0]);
voice_5(voice[1]);
voice_2(voice[2]);
tick();
voice_9(voice[0]);
voice_6(voice[1]);
voice_3(voice[2]);
tick();
voice_7(voice[1]);
voice_4(voice[2]);
voice_1(voice[4]);
tick();
voice_8(voice[1]);
voice_5(voice[2]);
voice_2(voice[3]);
tick();
voice_9(voice[1]);
voice_6(voice[2]);
voice_3(voice[3]);
tick();
voice_7(voice[2]);
voice_4(voice[3]);
voice_1(voice[5]);
tick();
voice_8(voice[2]);
voice_5(voice[3]);
voice_2(voice[4]);
tick();
voice_9(voice[2]);
voice_6(voice[3]);
voice_3(voice[4]);
tick();
voice_7(voice[3]);
voice_4(voice[4]);
voice_1(voice[6]);
tick();
voice_8(voice[3]);
voice_5(voice[4]);
voice_2(voice[5]);
tick();
voice_9(voice[3]);
voice_6(voice[4]);
voice_3(voice[5]);
tick();
voice_7(voice[4]);
voice_4(voice[5]);
voice_1(voice[7]);
tick();
voice_8(voice[4]);
voice_5(voice[5]);
voice_2(voice[6]);
tick();
voice_9(voice[4]);
voice_6(voice[5]);
voice_3(voice[6]);
tick();
voice_1(voice[0]);
voice_7(voice[5]);
voice_4(voice[6]);
tick();
voice_8(voice[5]);
voice_5(voice[6]);
voice_2(voice[7]);
tick();
voice_9(voice[5]);
voice_6(voice[6]);
voice_3(voice[7]);
tick();
voice_1(voice[1]);
voice_7(voice[6]);
voice_4(voice[7]);
tick();
voice_8(voice[6]);
voice_5(voice[7]);
voice_2(voice[0]);
tick();
voice_3a(voice[0]);
voice_9(voice[6]);
voice_6(voice[7]);
echo_22();
tick();
voice_7(voice[7]);
echo_23();
tick();
voice_8(voice[7]);
echo_24();
tick();
voice_3b(voice[0]);
voice_9(voice[7]);
echo_25();
tick();
echo_26();
tick();
misc_27();
echo_27();
tick();
misc_28();
echo_28();
tick();
misc_29();
echo_29();
tick();
misc_30();
voice_3c(voice[0]);
echo_30();
tick();
voice_4(voice[0]);
voice_1(voice[2]);
tick();
}
}
void DSP::tick() {
step(3 * 8);
synchronize_smp();
}
/* register interface for S-SMP $00f2,$00f3 */
bool DSP::mute() {
return state.regs[r_flg] & 0x40;
}
uint8 DSP::read(uint8 addr) {
return state.regs[addr];
}
void DSP::write(uint8 addr, uint8 data) {
state.regs[addr] = data;
if((addr & 0x0f) == v_envx) {
state.envx_buf = data;
} else if((addr & 0x0f) == v_outx) {
state.outx_buf = data;
} else if(addr == r_kon) {
state.new_kon = data;
} else if(addr == r_endx) {
//always cleared, regardless of data written
state.endx_buf = 0;
state.regs[r_endx] = 0;
}
}
/* initialization */
void DSP::power() {
memset(&state.regs, 0, sizeof state.regs);
state.echo_hist_pos = 0;
state.every_other_sample = false;
state.kon = 0;
state.noise = 0;
state.counter = 0;
state.echo_offset = 0;
state.echo_length = 0;
state.new_kon = 0;
state.endx_buf = 0;
state.envx_buf = 0;
state.outx_buf = 0;
state.t_pmon = 0;
state.t_non = 0;
state.t_eon = 0;
state.t_dir = 0;
state.t_koff = 0;
state.t_brr_next_addr = 0;
state.t_adsr0 = 0;
state.t_brr_header = 0;
state.t_brr_byte = 0;
state.t_srcn = 0;
state.t_esa = 0;
state.t_echo_disabled = 0;
state.t_dir_addr = 0;
state.t_pitch = 0;
state.t_output = 0;
state.t_looped = 0;
state.t_echo_ptr = 0;
state.t_main_out[0] = state.t_main_out[1] = 0;
state.t_echo_out[0] = state.t_echo_out[1] = 0;
state.t_echo_in[0] = state.t_echo_in[1] = 0;
for(unsigned i = 0; i < 8; i++) {
voice[i].buf_pos = 0;
voice[i].interp_pos = 0;
voice[i].brr_addr = 0;
voice[i].brr_offset = 1;
voice[i].vbit = 1 << i;
voice[i].vidx = i * 0x10;
voice[i].kon_delay = 0;
voice[i].env_mode = env_release;
voice[i].env = 0;
voice[i].t_envx_out = 0;
voice[i].hidden_env = 0;
}
}
void DSP::reset() {
create(Enter, system.apu_frequency());
REG(flg) = 0xe0;
state.noise = 0x4000;
state.echo_hist_pos = 0;
state.every_other_sample = 1;
state.echo_offset = 0;
state.counter = 0;
}
DSP::DSP() {
static_assert(sizeof(int) >= 32 / 8, "int >= 32-bits");
static_assert(sizeof(signed) >= 32 / 8, "signed >= 32-bits");
static_assert((int8)0x80 == -0x80, "8-bit sign extension");
static_assert((int16)0x8000 == -0x8000, "16-bit sign extension");
static_assert((uint16)0xffff0000 == 0, "16-bit unsigned clip");
@ -298,7 +29,272 @@ DSP::DSP() {
assert(sclamp<16>(-0x8001) == -0x8000);
}
DSP::~DSP() {
/* timing */
auto DSP::step(unsigned clocks) -> void {
clock += clocks;
}
auto DSP::synchronizeSMP() -> void {
if(SMP::Threaded == true) {
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(smp.thread);
} else {
while(clock >= 0) smp.enter();
}
}
auto DSP::Enter() -> void { dsp.enter(); }
auto DSP::enter() -> void {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
voice5(voice[0]);
voice2(voice[1]);
tick();
voice6(voice[0]);
voice3(voice[1]);
tick();
voice7(voice[0]);
voice4(voice[1]);
voice1(voice[3]);
tick();
voice8(voice[0]);
voice5(voice[1]);
voice2(voice[2]);
tick();
voice9(voice[0]);
voice6(voice[1]);
voice3(voice[2]);
tick();
voice7(voice[1]);
voice4(voice[2]);
voice1(voice[4]);
tick();
voice8(voice[1]);
voice5(voice[2]);
voice2(voice[3]);
tick();
voice9(voice[1]);
voice6(voice[2]);
voice3(voice[3]);
tick();
voice7(voice[2]);
voice4(voice[3]);
voice1(voice[5]);
tick();
voice8(voice[2]);
voice5(voice[3]);
voice2(voice[4]);
tick();
voice9(voice[2]);
voice6(voice[3]);
voice3(voice[4]);
tick();
voice7(voice[3]);
voice4(voice[4]);
voice1(voice[6]);
tick();
voice8(voice[3]);
voice5(voice[4]);
voice2(voice[5]);
tick();
voice9(voice[3]);
voice6(voice[4]);
voice3(voice[5]);
tick();
voice7(voice[4]);
voice4(voice[5]);
voice1(voice[7]);
tick();
voice8(voice[4]);
voice5(voice[5]);
voice2(voice[6]);
tick();
voice9(voice[4]);
voice6(voice[5]);
voice3(voice[6]);
tick();
voice1(voice[0]);
voice7(voice[5]);
voice4(voice[6]);
tick();
voice8(voice[5]);
voice5(voice[6]);
voice2(voice[7]);
tick();
voice9(voice[5]);
voice6(voice[6]);
voice3(voice[7]);
tick();
voice1(voice[1]);
voice7(voice[6]);
voice4(voice[7]);
tick();
voice8(voice[6]);
voice5(voice[7]);
voice2(voice[0]);
tick();
voice3a(voice[0]);
voice9(voice[6]);
voice6(voice[7]);
echo22();
tick();
voice7(voice[7]);
echo23();
tick();
voice8(voice[7]);
echo24();
tick();
voice3b(voice[0]);
voice9(voice[7]);
echo25();
tick();
echo26();
tick();
misc27();
echo27();
tick();
misc28();
echo28();
tick();
misc29();
echo29();
tick();
misc30();
voice3c(voice[0]);
echo30();
tick();
voice4(voice[0]);
voice1(voice[2]);
tick();
}
}
auto DSP::tick() -> void {
step(3 * 8);
synchronizeSMP();
}
/* register interface for S-SMP $00f2,$00f3 */
auto DSP::mute() const -> bool {
return REG(FLG) & 0x40;
}
auto DSP::read(uint8 addr) -> uint8 {
return REG(addr);
}
auto DSP::write(uint8 addr, uint8 data) -> void {
REG(addr) = data;
if((addr & 0x0f) == ENVX) {
state.envxBuffer = data;
} else if((addr & 0x0f) == OUTX) {
state.outxBuffer = data;
} else if(addr == KON) {
state.konBuffer = data;
} else if(addr == ENDX) {
//always cleared, regardless of data written
state.endxBuffer = 0;
REG(ENDX) = 0;
}
}
/* initialization */
auto DSP::power() -> void {
for(auto& r : state.regs) r = 0;
state.echoHistoryOffset = 0;
state.everyOtherSample = false;
state.kon = 0;
state.noise = 0;
state.counter = 0;
state.echoOffset = 0;
state.echoLength = 0;
state.konBuffer = 0;
state.endxBuffer = 0;
state.envxBuffer = 0;
state.outxBuffer = 0;
state._pmon = 0;
state._non = 0;
state._eon = 0;
state._dir = 0;
state._koff = 0;
state._brrNextAddress = 0;
state._adsr0 = 0;
state._brrHeader = 0;
state._brrByte = 0;
state._srcn = 0;
state._esa = 0;
state._echoDisabled = 0;
state._dirAddress = 0;
state._pitch = 0;
state._output = 0;
state._looped = 0;
state._echoPointer = 0;
state._mainOut[0] = state._mainOut[1] = 0;
state._echoOut[0] = state._echoOut[1] = 0;
state._echoIn[0] = state._echoIn[1] = 0;
for(auto n : range(8)) {
voice[n].bufferOffset = 0;
voice[n].gaussianOffset = 0;
voice[n].brrAddress = 0;
voice[n].brrOffset = 1;
voice[n].vbit = 1 << n;
voice[n].vidx = n * 0x10;
voice[n].konDelay = 0;
voice[n].envelopeMode = EnvelopeRelease;
voice[n].envelope = 0;
voice[n].hiddenEnvelope = 0;
voice[n]._envxOut = 0;
}
}
auto DSP::reset() -> void {
create(Enter, system.apu_frequency());
REG(FLG) = 0xe0;
state.noise = 0x4000;
state.echoHistoryOffset = 0;
state.everyOtherSample = 1;
state.echoOffset = 0;
state.counter = 0;
}
#undef REG

View File

@ -1,175 +1,177 @@
struct DSP : Thread {
enum : bool { Threaded = true };
alwaysinline void step(unsigned clocks);
alwaysinline void synchronize_smp();
bool mute();
uint8 read(uint8 addr);
void write(uint8 addr, uint8 data);
void enter();
void power();
void reset();
void serialize(serializer&);
DSP();
~DSP();
alwaysinline auto step(unsigned clocks) -> void;
alwaysinline auto synchronizeSMP() -> void;
auto mute() const -> bool;
auto read(uint8 addr) -> uint8;
auto write(uint8 addr, uint8 data) -> void;
auto enter() -> void;
auto power() -> void;
auto reset() -> void;
auto serialize(serializer&) -> void;
privileged:
#include "moduloarray.hpp"
#include "modulo-array.hpp"
//global registers
enum global_reg_t {
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
enum GlobalRegister : unsigned {
MVOLL = 0x0c, MVOLR = 0x1c,
EVOLL = 0x2c, EVOLR = 0x3c,
KON = 0x4c, KOFF = 0x5c,
FLG = 0x6c, ENDX = 0x7c,
EFB = 0x0d, PMON = 0x2d,
NON = 0x3d, EON = 0x4d,
DIR = 0x5d, ESA = 0x6d,
EDL = 0x7d, FIR = 0x0f, //8 coefficients at 0x0f, 0x1f, ... 0x7f
};
//voice registers
enum voice_reg_t {
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,
enum VoiceRegister : unsigned {
VOLL = 0x00, VOLR = 0x01,
PITCHL = 0x02, PITCHH = 0x03,
SRCN = 0x04, ADSR0 = 0x05,
ADSR1 = 0x06, GAIN = 0x07,
ENVX = 0x08, OUTX = 0x09,
};
//internal envelope modes
enum env_mode_t { env_release, env_attack, env_decay, env_sustain };
enum EnvelopeMode : unsigned {
EnvelopeRelease,
EnvelopeAttack,
EnvelopeDecay,
EnvelopeSustain,
};
//internal constants
enum { echo_hist_size = 8 };
enum { brr_buf_size = 12 };
enum { brr_block_size = 9 };
enum : unsigned {
EchoHistorySize = 8,
BrrBufferSize = 12,
BrrBlockSize = 9,
CounterRange = 2048 * 5 * 3, //30720 (0x7800)
};
//global state
struct state_t {
struct State {
uint8 regs[128];
moduloarray<int, echo_hist_size> echo_hist[2]; //echo history keeps most recent 8 samples
int echo_hist_pos;
ModuloArray<signed, EchoHistorySize> echoHistory[2]; //echo history keeps most recent 8 samples
signed echoHistoryOffset;
bool 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
bool everyOtherSample; //toggles every sample
signed kon; //KON value when last checked
signed noise;
signed counter;
signed echoOffset; //offset from ESA in echo buffer
signed echoLength; //number of bytes that echo_offset will stop at
//hidden registers also written to when main register is written to
int new_kon;
int endx_buf;
int envx_buf;
int outx_buf;
signed konBuffer;
signed endxBuffer;
signed envxBuffer;
signed outxBuffer;
//temporary state between clocks
//temporary state between clocks (prefixed with _)
//read once per sample
int t_pmon;
int t_non;
int t_eon;
int t_dir;
int t_koff;
signed _pmon;
signed _non;
signed _eon;
signed _dir;
signed _koff;
//read a few clocks ahead before 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_disabled;
signed _brrNextAddress;
signed _adsr0;
signed _brrHeader;
signed _brrByte;
signed _srcn;
signed _esa;
signed _echoDisabled;
//internal state that is recalculated every sample
int t_dir_addr;
int t_pitch;
int t_output;
int t_looped;
int t_echo_ptr;
signed _dirAddress;
signed _pitch;
signed _output;
signed _looped;
signed _echoPointer;
//left/right sums
int t_main_out[2];
int t_echo_out[2];
int t_echo_in [2];
signed _mainOut[2];
signed _echoOut[2];
signed _echoIn [2];
} state;
//voice state
struct voice_t {
moduloarray<int, brr_buf_size> buffer; //decoded samples
int buf_pos; //place in buffer where next samples will be decoded
int interp_pos; //relative fractional position in sample (0x1000 = 1.0)
int brr_addr; //address of current BRR block
int brr_offset; //current decoding offset in BRR block
int vbit; //bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc
int vidx; //voice channel register index: 0x00 for voice 0, 0x10 for voice 1, etc
int kon_delay; //KON delay/current setup phase
int env_mode;
int env; //current envelope level
int t_envx_out;
int hidden_env; //used by GAIN mode 7, very obscure quirk
struct Voice {
ModuloArray<signed, BrrBufferSize> buffer; //decoded samples
signed bufferOffset; //place in buffer where next samples will be decoded
signed gaussianOffset; //relative fractional position in sample (0x1000 = 1.0)
signed brrAddress; //address of current BRR block
signed brrOffset; //current decoding offset in BRR block
signed vbit; //bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc
signed vidx; //voice channel register index: 0x00 for voice 0, 0x10 for voice 1, etc
signed konDelay; //KON delay/current setup phase
signed envelopeMode;
signed envelope; //current envelope level
signed hiddenEnvelope; //used by GAIN mode 7, very obscure quirk
signed _envxOut;
} voice[8];
//gaussian
static const int16 gaussian_table[512];
int gaussian_interpolate(const voice_t& v);
static const int16 GaussianTable[512];
auto gaussianInterpolate(const Voice& v) -> signed;
//counter
enum { counter_range = 2048 * 5 * 3 }; //30720 (0x7800)
static const uint16 counter_rate[32];
static const uint16 counter_offset[32];
void counter_tick();
bool counter_poll(unsigned rate);
static const uint16 CounterRate[32];
static const uint16 CounterOffset[32];
auto counterTick() -> void;
auto counterPoll(unsigned rate) -> bool;
//envelope
void envelope_run(voice_t& v);
auto envelopeRun(Voice& v) -> void;
//brr
void brr_decode(voice_t& v);
auto brrDecode(Voice& v) -> void;
//misc
void misc_27();
void misc_28();
void misc_29();
void misc_30();
auto misc27() -> void;
auto misc28() -> void;
auto misc29() -> void;
auto misc30() -> void;
//voice
void voice_output(voice_t& v, bool channel);
void voice_1 (voice_t &v);
void voice_2 (voice_t &v);
void voice_3 (voice_t &v);
void voice_3a(voice_t &v);
void voice_3b(voice_t &v);
void voice_3c(voice_t &v);
void voice_4 (voice_t &v);
void voice_5 (voice_t &v);
void voice_6 (voice_t &v);
void voice_7 (voice_t &v);
void voice_8 (voice_t &v);
void voice_9 (voice_t &v);
auto voiceOutput(Voice& v, bool channel) -> void;
auto voice1 (Voice& v) -> void;
auto voice2 (Voice& v) -> void;
auto voice3 (Voice& v) -> void;
auto voice3a(Voice& v) -> void;
auto voice3b(Voice& v) -> void;
auto voice3c(Voice& v) -> void;
auto voice4 (Voice& v) -> void;
auto voice5 (Voice& v) -> void;
auto voice6 (Voice& v) -> void;
auto voice7 (Voice& v) -> void;
auto voice8 (Voice& v) -> void;
auto voice9 (Voice& v) -> void;
//echo
int calc_fir(int i, bool channel);
int echo_output(bool channel);
void echo_read(bool channel);
void echo_write(bool channel);
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();
auto calculateFIR(signed i, bool channel) -> signed;
auto echoOutput(bool channel) -> signed;
auto echoRead(bool channel) -> void;
auto echoWrite(bool channel) -> void;
auto echo22() -> void;
auto echo23() -> void;
auto echo24() -> void;
auto echo25() -> void;
auto echo26() -> void;
auto echo27() -> void;
auto echo28() -> void;
auto echo29() -> void;
auto echo30() -> void;
//dsp
static void Enter();
void tick();
static auto Enter() -> void;
auto tick() -> void;
};
extern DSP dsp;

View File

@ -1,106 +1,106 @@
#ifdef DSP_CPP
int DSP::calc_fir(int i, bool channel) {
int s = state.echo_hist[channel][state.echo_hist_pos + i + 1];
return (s * (int8)REG(fir + i * 0x10)) >> 6;
auto DSP::calculateFIR(signed i, bool channel) -> signed {
signed s = state.echoHistory[channel][state.echoHistoryOffset + i + 1];
return (s * (int8)REG(FIR + i * 0x10)) >> 6;
}
int DSP::echo_output(bool channel) {
int output = (int16)((state.t_main_out[channel] * (int8)REG(mvoll + channel * 0x10)) >> 7)
+ (int16)((state.t_echo_in [channel] * (int8)REG(evoll + channel * 0x10)) >> 7);
auto DSP::echoOutput(bool channel) -> signed {
signed output = (int16)((state._mainOut[channel] * (int8)REG(MVOLL + channel * 0x10)) >> 7)
+ (int16)((state._echoIn [channel] * (int8)REG(EVOLL + channel * 0x10)) >> 7);
return sclamp<16>(output);
}
void DSP::echo_read(bool channel) {
unsigned addr = state.t_echo_ptr + channel * 2;
auto DSP::echoRead(bool channel) -> void {
unsigned addr = state._echoPointer + channel * 2;
uint8 lo = smp.apuram[(uint16)(addr + 0)];
uint8 hi = smp.apuram[(uint16)(addr + 1)];
int s = (int16)((hi << 8) + lo);
state.echo_hist[channel].write(state.echo_hist_pos, s >> 1);
signed s = (int16)((hi << 8) + lo);
state.echoHistory[channel].write(state.echoHistoryOffset, s >> 1);
}
void DSP::echo_write(bool channel) {
if(!(state.t_echo_disabled & 0x20)) {
unsigned addr = state.t_echo_ptr + channel * 2;
int s = state.t_echo_out[channel];
auto DSP::echoWrite(bool channel) -> void {
if(!(state._echoDisabled & 0x20)) {
unsigned addr = state._echoPointer + channel * 2;
signed s = state._echoOut[channel];
smp.apuram[(uint16)(addr + 0)] = s;
smp.apuram[(uint16)(addr + 1)] = s >> 8;
}
state.t_echo_out[channel] = 0;
state._echoOut[channel] = 0;
}
void DSP::echo_22() {
auto DSP::echo22() -> void {
//history
state.echo_hist_pos++;
if(state.echo_hist_pos >= echo_hist_size) state.echo_hist_pos = 0;
state.echoHistoryOffset++;
if(state.echoHistoryOffset >= EchoHistorySize) state.echoHistoryOffset = 0;
state.t_echo_ptr = (uint16)((state.t_esa << 8) + state.echo_offset);
echo_read(0);
state._echoPointer = (uint16)((state._esa << 8) + state.echoOffset);
echoRead(0);
//FIR
int l = calc_fir(0, 0);
int r = calc_fir(0, 1);
signed l = calculateFIR(0, 0);
signed r = calculateFIR(0, 1);
state.t_echo_in[0] = l;
state.t_echo_in[1] = r;
state._echoIn[0] = l;
state._echoIn[1] = r;
}
void DSP::echo_23() {
int l = calc_fir(1, 0) + calc_fir(2, 0);
int r = calc_fir(1, 1) + calc_fir(2, 1);
auto DSP::echo23() -> void {
signed l = calculateFIR(1, 0) + calculateFIR(2, 0);
signed r = calculateFIR(1, 1) + calculateFIR(2, 1);
state.t_echo_in[0] += l;
state.t_echo_in[1] += r;
state._echoIn[0] += l;
state._echoIn[1] += r;
echo_read(1);
echoRead(1);
}
void DSP::echo_24() {
int l = calc_fir(3, 0) + calc_fir(4, 0) + calc_fir(5, 0);
int r = calc_fir(3, 1) + calc_fir(4, 1) + calc_fir(5, 1);
auto DSP::echo24() -> void {
signed l = calculateFIR(3, 0) + calculateFIR(4, 0) + calculateFIR(5, 0);
signed r = calculateFIR(3, 1) + calculateFIR(4, 1) + calculateFIR(5, 1);
state.t_echo_in[0] += l;
state.t_echo_in[1] += r;
state._echoIn[0] += l;
state._echoIn[1] += r;
}
void DSP::echo_25() {
int l = state.t_echo_in[0] + calc_fir(6, 0);
int r = state.t_echo_in[1] + calc_fir(6, 1);
auto DSP::echo25() -> void {
signed l = state._echoIn[0] + calculateFIR(6, 0);
signed r = state._echoIn[1] + calculateFIR(6, 1);
l = (int16)l;
r = (int16)r;
l += (int16)calc_fir(7, 0);
r += (int16)calc_fir(7, 1);
l += (int16)calculateFIR(7, 0);
r += (int16)calculateFIR(7, 1);
state.t_echo_in[0] = sclamp<16>(l) & ~1;
state.t_echo_in[1] = sclamp<16>(r) & ~1;
state._echoIn[0] = sclamp<16>(l) & ~1;
state._echoIn[1] = sclamp<16>(r) & ~1;
}
void DSP::echo_26() {
auto DSP::echo26() -> void {
//left output volumes
//(save sample for next clock so we can output both together)
state.t_main_out[0] = echo_output(0);
state._mainOut[0] = echoOutput(0);
//echo feedback
int l = state.t_echo_out[0] + (int16)((state.t_echo_in[0] * (int8)REG(efb)) >> 7);
int r = state.t_echo_out[1] + (int16)((state.t_echo_in[1] * (int8)REG(efb)) >> 7);
signed l = state._echoOut[0] + (int16)((state._echoIn[0] * (int8)REG(EFB)) >> 7);
signed r = state._echoOut[1] + (int16)((state._echoIn[1] * (int8)REG(EFB)) >> 7);
state.t_echo_out[0] = sclamp<16>(l) & ~1;
state.t_echo_out[1] = sclamp<16>(r) & ~1;
state._echoOut[0] = sclamp<16>(l) & ~1;
state._echoOut[1] = sclamp<16>(r) & ~1;
}
void DSP::echo_27() {
auto DSP::echo27() -> void {
//output
int outl = state.t_main_out[0];
int outr = echo_output(1);
state.t_main_out[0] = 0;
state.t_main_out[1] = 0;
signed outl = state._mainOut[0];
signed outr = echoOutput(1);
state._mainOut[0] = 0;
state._mainOut[1] = 0;
//TODO: global muting isn't this simple
//(turns DAC on and off or something, causing small ~37-sample pulse when first muted)
if(REG(flg) & 0x40) {
if(REG(FLG) & 0x40) {
outl = 0;
outr = 0;
}
@ -109,27 +109,27 @@ void DSP::echo_27() {
audio.sample(outl, outr);
}
void DSP::echo_28() {
state.t_echo_disabled = REG(flg);
auto DSP::echo28() -> void {
state._echoDisabled = REG(FLG);
}
void DSP::echo_29() {
state.t_esa = REG(esa);
auto DSP::echo29() -> void {
state._esa = REG(ESA);
if(!state.echo_offset) state.echo_length = (REG(edl) & 0x0f) << 11;
if(!state.echoOffset) state.echoLength = (REG(EDL) & 0x0f) << 11;
state.echo_offset += 4;
if(state.echo_offset >= state.echo_length) state.echo_offset = 0;
state.echoOffset += 4;
if(state.echoOffset >= state.echoLength) state.echoOffset = 0;
//write left echo
echo_write(0);
echoWrite(0);
state.t_echo_disabled = REG(flg);
state._echoDisabled = REG(FLG);
}
void DSP::echo_30() {
auto DSP::echo30() -> void {
//write right echo
echo_write(1);
echoWrite(1);
}
#endif

View File

@ -1,62 +1,62 @@
#ifdef DSP_CPP
void DSP::envelope_run(voice_t& v) {
int env = v.env;
auto DSP::envelopeRun(Voice& v) -> void {
signed envelope = v.envelope;
if(v.env_mode == env_release) { //60%
env -= 0x8;
if(env < 0) env = 0;
v.env = env;
if(v.envelopeMode == EnvelopeRelease) { //60%
envelope -= 0x8;
if(envelope < 0) envelope = 0;
v.envelope = envelope;
return;
}
int rate;
int env_data = VREG(adsr1);
if(state.t_adsr0 & 0x80) { //99% ADSR
if(v.env_mode >= env_decay) { //99%
env--;
env -= env >> 8;
rate = env_data & 0x1f;
if(v.env_mode == env_decay) { //1%
rate = ((state.t_adsr0 >> 3) & 0x0e) + 0x10;
signed rate;
signed envelopeData = VREG(ADSR1);
if(state._adsr0 & 0x80) { //99% ADSR
if(v.envelopeMode >= EnvelopeDecay) { //99%
envelope--;
envelope -= envelope >> 8;
rate = envelopeData & 0x1f;
if(v.envelopeMode == EnvelopeDecay) { //1%
rate = ((state._adsr0 >> 3) & 0x0e) + 0x10;
}
} else { //env_attack
rate = ((state.t_adsr0 & 0x0f) << 1) + 1;
env += rate < 31 ? 0x20 : 0x400;
rate = ((state._adsr0 & 0x0f) << 1) + 1;
envelope += rate < 31 ? 0x20 : 0x400;
}
} else { //GAIN
env_data = VREG(gain);
int mode = env_data >> 5;
envelopeData = VREG(GAIN);
signed mode = envelopeData >> 5;
if(mode < 4) { //direct
env = env_data << 4;
envelope = envelopeData << 4;
rate = 31;
} else {
rate = env_data & 0x1f;
rate = envelopeData & 0x1f;
if(mode == 4) { //4: linear decrease
env -= 0x20;
envelope -= 0x20;
} else if(mode < 6) { //5: exponential decrease
env--;
env -= env >> 8;
envelope--;
envelope -= envelope >> 8;
} else { //6, 7: linear increase
env += 0x20;
if(mode > 6 && (unsigned)v.hidden_env >= 0x600) {
env += 0x8 - 0x20; //7: two-slope linear increase
envelope += 0x20;
if(mode > 6 && (unsigned)v.hiddenEnvelope >= 0x600) {
envelope += 0x8 - 0x20; //7: two-slope linear increase
}
}
}
}
//sustain level
if((env >> 8) == (env_data >> 5) && v.env_mode == env_decay) v.env_mode = env_sustain;
v.hidden_env = env;
if((envelope >> 8) == (envelopeData >> 5) && v.envelopeMode == EnvelopeDecay) v.envelopeMode = EnvelopeSustain;
v.hiddenEnvelope = envelope;
//unsigned cast because linear decrease underflowing also triggers this
if((unsigned)env > 0x7ff) {
env = (env < 0 ? 0 : 0x7ff);
if(v.env_mode == env_attack) v.env_mode = env_decay;
if((unsigned)envelope > 0x7ff) {
envelope = (envelope < 0 ? 0 : 0x7ff);
if(v.envelopeMode == EnvelopeAttack) v.envelopeMode = EnvelopeDecay;
}
if(counter_poll(rate) == true) v.env = env;
if(counterPoll(rate)) v.envelope = envelope;
}
#endif

View File

@ -1,6 +1,6 @@
#ifdef DSP_CPP
const int16 DSP::gaussian_table[512] = {
const int16 DSP::GaussianTable[512] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,
2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5,
@ -35,19 +35,19 @@ const int16 DSP::gaussian_table[512] = {
1299, 1300, 1300, 1301, 1302, 1302, 1303, 1303, 1303, 1304, 1304, 1304, 1304, 1304, 1305, 1305,
};
int DSP::gaussian_interpolate(const voice_t& v) {
auto DSP::gaussianInterpolate(const Voice& v) -> signed {
//make pointers into gaussian table based on fractional position between samples
int offset = (v.interp_pos >> 4) & 0xff;
const int16* fwd = gaussian_table + 255 - offset;
const int16* rev = gaussian_table + offset; //mirror left half of gaussian table
signed offset = (v.gaussianOffset >> 4) & 0xff;
const int16* forward = GaussianTable + 255 - offset;
const int16* reverse = GaussianTable + offset; //mirror left half of gaussian table
offset = v.buf_pos + (v.interp_pos >> 12);
int output;
output = (fwd[ 0] * v.buffer[offset + 0]) >> 11;
output += (fwd[256] * v.buffer[offset + 1]) >> 11;
output += (rev[256] * v.buffer[offset + 2]) >> 11;
offset = v.bufferOffset + (v.gaussianOffset >> 12);
signed output;
output = (forward[ 0] * v.buffer[offset + 0]) >> 11;
output += (forward[256] * v.buffer[offset + 1]) >> 11;
output += (reverse[256] * v.buffer[offset + 2]) >> 11;
output = (int16)output;
output += (rev[ 0] * v.buffer[offset + 3]) >> 11;
output += (reverse[ 0] * v.buffer[offset + 3]) >> 11;
return sclamp<16>(output) & ~1;
}

View File

@ -1,33 +1,33 @@
#ifdef DSP_CPP
void DSP::misc_27() {
state.t_pmon = REG(pmon) & ~1; //voice 0 doesn't support PMON
auto DSP::misc27() -> void {
state._pmon = REG(PMON) & ~1; //voice 0 doesn't support PMON
}
void DSP::misc_28() {
state.t_non = REG(non);
state.t_eon = REG(eon);
state.t_dir = REG(dir);
auto DSP::misc28() -> void {
state._non = REG(NON);
state._eon = REG(EON);
state._dir = REG(DIR);
}
void DSP::misc_29() {
state.every_other_sample ^= 1;
if(state.every_other_sample) {
state.new_kon &= ~state.kon; //clears KON 63 clocks after it was last read
auto DSP::misc29() -> void {
state.everyOtherSample ^= 1;
if(state.everyOtherSample) {
state.konBuffer &= ~state.kon; //clears KON 63 clocks after it was last read
}
}
void DSP::misc_30() {
if(state.every_other_sample) {
state.kon = state.new_kon;
state.t_koff = REG(koff);
auto DSP::misc30() -> void {
if(state.everyOtherSample) {
state.kon = state.konBuffer;
state._koff = REG(KOFF);
}
counter_tick();
counterTick();
//noise
if(counter_poll(REG(flg) & 0x1f) == true) {
int feedback = (state.noise << 13) ^ (state.noise << 14);
if(counterPoll(REG(FLG) & 0x1f)) {
signed feedback = (state.noise << 13) ^ (state.noise << 14);
state.noise = (feedback & 0x4000) ^ (state.noise >> 1);
}
}

31
sfc/dsp/modulo-array.hpp Normal file
View File

@ -0,0 +1,31 @@
template<typename T, unsigned size>
struct ModuloArray {
ModuloArray() {
buffer = new T[size * 3]();
}
~ModuloArray() {
delete[] buffer;
}
inline auto operator[](signed index) const -> T {
return buffer[size + index];
}
inline auto read(signed index) const -> T {
return buffer[size + index];
}
inline auto write(unsigned index, const T value) -> void {
buffer[index] =
buffer[index + size] =
buffer[index + size + size] = value;
}
auto serialize(serializer& s) -> void {
s.array(buffer, size * 3);
}
private:
T* buffer;
};

View File

@ -1,30 +0,0 @@
template<typename T, unsigned size> struct moduloarray {
inline T operator[](int index) const {
return buffer[size + index];
}
inline T read(int index) const {
return buffer[size + index];
}
inline void write(unsigned index, const T value) {
buffer[index] =
buffer[index + size] =
buffer[index + size + size] = value;
}
void serialize(serializer& s) {
s.array(buffer, size * 3);
}
moduloarray() {
buffer = new T[size * 3]();
}
~moduloarray() {
delete[] buffer;
}
private:
T* buffer;
};

View File

@ -4,62 +4,59 @@ void DSP::serialize(serializer& s) {
Thread::serialize(s);
s.array(state.regs, 128);
state.echo_hist[0].serialize(s);
state.echo_hist[1].serialize(s);
s.integer(state.echo_hist_pos);
state.echoHistory[0].serialize(s);
state.echoHistory[1].serialize(s);
s.integer(state.echoHistoryOffset);
s.integer(state.every_other_sample);
s.integer(state.everyOtherSample);
s.integer(state.kon);
s.integer(state.noise);
s.integer(state.counter);
s.integer(state.echo_offset);
s.integer(state.echo_length);
s.integer(state.echoOffset);
s.integer(state.echoLength);
s.integer(state.new_kon);
s.integer(state.endx_buf);
s.integer(state.envx_buf);
s.integer(state.outx_buf);
s.integer(state.konBuffer);
s.integer(state.endxBuffer);
s.integer(state.envxBuffer);
s.integer(state.outxBuffer);
s.integer(state.t_pmon);
s.integer(state.t_non);
s.integer(state.t_eon);
s.integer(state.t_dir);
s.integer(state.t_koff);
s.integer(state._pmon);
s.integer(state._non);
s.integer(state._eon);
s.integer(state._dir);
s.integer(state._koff);
s.integer(state.t_brr_next_addr);
s.integer(state.t_adsr0);
s.integer(state.t_brr_header);
s.integer(state.t_brr_byte);
s.integer(state.t_srcn);
s.integer(state.t_esa);
s.integer(state.t_echo_disabled);
s.integer(state._brrNextAddress);
s.integer(state._adsr0);
s.integer(state._brrHeader);
s.integer(state._brrByte);
s.integer(state._srcn);
s.integer(state._esa);
s.integer(state._echoDisabled);
s.integer(state.t_dir_addr);
s.integer(state.t_pitch);
s.integer(state.t_output);
s.integer(state.t_looped);
s.integer(state.t_echo_ptr);
s.integer(state._dirAddress);
s.integer(state._pitch);
s.integer(state._output);
s.integer(state._looped);
s.integer(state._echoPointer);
s.integer(state.t_main_out[0]);
s.integer(state.t_main_out[1]);
s.integer(state.t_echo_out[0]);
s.integer(state.t_echo_out[1]);
s.integer(state.t_echo_in [0]);
s.integer(state.t_echo_in [1]);
s.array(state._mainOut, 2);
s.array(state._echoOut, 2);
s.array(state._echoIn, 2);
for(unsigned n = 0; n < 8; n++) {
for(auto n : range(8)) {
voice[n].buffer.serialize(s);
s.integer(voice[n].buf_pos);
s.integer(voice[n].interp_pos);
s.integer(voice[n].brr_addr);
s.integer(voice[n].brr_offset);
s.integer(voice[n].bufferOffset);
s.integer(voice[n].gaussianOffset);
s.integer(voice[n].brrAddress);
s.integer(voice[n].brrOffset);
s.integer(voice[n].vbit);
s.integer(voice[n].vidx);
s.integer(voice[n].kon_delay);
s.integer(voice[n].env_mode);
s.integer(voice[n].env);
s.integer(voice[n].t_envx_out);
s.integer(voice[n].hidden_env);
s.integer(voice[n].konDelay);
s.integer(voice[n].envelopeMode);
s.integer(voice[n].envelope);
s.integer(voice[n].hiddenEnvelope);
s.integer(voice[n]._envxOut);
}
}

View File

@ -1,174 +1,174 @@
#ifdef DSP_CPP
inline void DSP::voice_output(voice_t& v, bool channel) {
inline auto DSP::voiceOutput(Voice& v, bool channel) -> void {
//apply left/right volume
int amp = (state.t_output * (int8)VREG(voll + channel)) >> 7;
signed amp = (state._output * (int8)VREG(VOLL + channel)) >> 7;
//add to output total
state.t_main_out[channel] += amp;
state.t_main_out[channel] = sclamp<16>(state.t_main_out[channel]);
state._mainOut[channel] += amp;
state._mainOut[channel] = sclamp<16>(state._mainOut[channel]);
//optionally add to echo total
if(state.t_eon & v.vbit) {
state.t_echo_out[channel] += amp;
state.t_echo_out[channel] = sclamp<16>(state.t_echo_out[channel]);
if(state._eon & v.vbit) {
state._echoOut[channel] += amp;
state._echoOut[channel] = sclamp<16>(state._echoOut[channel]);
}
}
void DSP::voice_1(voice_t& v) {
state.t_dir_addr = (state.t_dir << 8) + (state.t_srcn << 2);
state.t_srcn = VREG(srcn);
auto DSP::voice1(Voice& v) -> void {
state._dirAddress = (state._dir << 8) + (state._srcn << 2);
state._srcn = VREG(SRCN);
}
void DSP::voice_2(voice_t& v) {
auto DSP::voice2(Voice& v) -> void {
//read sample pointer (ignored if not needed)
uint16 addr = state.t_dir_addr;
if(!v.kon_delay) addr += 2;
uint16 addr = state._dirAddress;
if(!v.konDelay) addr += 2;
uint8 lo = smp.apuram[(uint16)(addr + 0)];
uint8 hi = smp.apuram[(uint16)(addr + 1)];
state.t_brr_next_addr = ((hi << 8) + lo);
state._brrNextAddress = ((hi << 8) + lo);
state.t_adsr0 = VREG(adsr0);
state._adsr0 = VREG(ADSR0);
//read pitch, spread over two clocks
state.t_pitch = VREG(pitchl);
state._pitch = VREG(PITCHL);
}
void DSP::voice_3(voice_t& v) {
voice_3a(v);
voice_3b(v);
voice_3c(v);
auto DSP::voice3(Voice& v) -> void {
voice3a(v);
voice3b(v);
voice3c(v);
}
void DSP::voice_3a(voice_t& v) {
state.t_pitch += (VREG(pitchh) & 0x3f) << 8;
auto DSP::voice3a(Voice& v) -> void {
state._pitch += (VREG(PITCHH) & 0x3f) << 8;
}
void DSP::voice_3b(voice_t& v) {
state.t_brr_byte = smp.apuram[(uint16)(v.brr_addr + v.brr_offset)];
state.t_brr_header = smp.apuram[(uint16)(v.brr_addr)];
auto DSP::voice3b(Voice& v) -> void {
state._brrByte = smp.apuram[(uint16)(v.brrAddress + v.brrOffset)];
state._brrHeader = smp.apuram[(uint16)(v.brrAddress)];
}
void DSP::voice_3c(voice_t& v) {
auto DSP::voice3c(Voice& v) -> void {
//pitch modulation using previous voice's output
if(state.t_pmon & v.vbit) {
state.t_pitch += ((state.t_output >> 5) * state.t_pitch) >> 10;
if(state._pmon & v.vbit) {
state._pitch += ((state._output >> 5) * state._pitch) >> 10;
}
if(v.kon_delay) {
if(v.konDelay) {
//get ready to start BRR decoding on next sample
if(v.kon_delay == 5) {
v.brr_addr = state.t_brr_next_addr;
v.brr_offset = 1;
v.buf_pos = 0;
state.t_brr_header = 0; //header is ignored on this sample
if(v.konDelay == 5) {
v.brrAddress = state._brrNextAddress;
v.brrOffset = 1;
v.bufferOffset = 0;
state._brrHeader = 0; //header is ignored on this sample
}
//envelope is never run during KON
v.env = 0;
v.hidden_env = 0;
v.envelope = 0;
v.hiddenEnvelope = 0;
//disable BRR decoding until last three samples
v.interp_pos = 0;
v.kon_delay--;
if(v.kon_delay & 3) v.interp_pos = 0x4000;
v.gaussianOffset = 0;
v.konDelay--;
if(v.konDelay & 3) v.gaussianOffset = 0x4000;
//pitch is never added during KON
state.t_pitch = 0;
state._pitch = 0;
}
//gaussian interpolation
int output = gaussian_interpolate(v);
signed output = gaussianInterpolate(v);
//noise
if(state.t_non & v.vbit) {
if(state._non & v.vbit) {
output = (int16)(state.noise << 1);
}
//apply envelope
state.t_output = ((output * v.env) >> 11) & ~1;
v.t_envx_out = v.env >> 4;
state._output = ((output * v.envelope) >> 11) & ~1;
v._envxOut = v.envelope >> 4;
//immediate silence due to end of sample or soft reset
if(REG(flg) & 0x80 || (state.t_brr_header & 3) == 1) {
v.env_mode = env_release;
v.env = 0;
if(REG(FLG) & 0x80 || (state._brrHeader & 3) == 1) {
v.envelopeMode = EnvelopeRelease;
v.envelope = 0;
}
if(state.every_other_sample) {
if(state.everyOtherSample) {
//KOFF
if(state.t_koff & v.vbit) {
v.env_mode = env_release;
if(state._koff & v.vbit) {
v.envelopeMode = EnvelopeRelease;
}
//KON
if(state.kon & v.vbit) {
v.kon_delay = 5;
v.env_mode = env_attack;
v.konDelay = 5;
v.envelopeMode = EnvelopeAttack;
}
}
//run envelope for next sample
if(!v.kon_delay) envelope_run(v);
if(!v.konDelay) envelopeRun(v);
}
void DSP::voice_4(voice_t& v) {
auto DSP::voice4(Voice& v) -> void {
//decode BRR
state.t_looped = 0;
if(v.interp_pos >= 0x4000) {
brr_decode(v);
v.brr_offset += 2;
if(v.brr_offset >= 9) {
state._looped = 0;
if(v.gaussianOffset >= 0x4000) {
brrDecode(v);
v.brrOffset += 2;
if(v.brrOffset >= 9) {
//start decoding next BRR block
v.brr_addr = (uint16)(v.brr_addr + 9);
if(state.t_brr_header & 1) {
v.brr_addr = state.t_brr_next_addr;
state.t_looped = v.vbit;
v.brrAddress = (uint16)(v.brrAddress + 9);
if(state._brrHeader & 1) {
v.brrAddress = state._brrNextAddress;
state._looped = v.vbit;
}
v.brr_offset = 1;
v.brrOffset = 1;
}
}
//apply pitch
v.interp_pos = (v.interp_pos & 0x3fff) + state.t_pitch;
v.gaussianOffset = (v.gaussianOffset & 0x3fff) + state._pitch;
//keep from getting too far ahead (when using pitch modulation)
if(v.interp_pos > 0x7fff) v.interp_pos = 0x7fff;
if(v.gaussianOffset > 0x7fff) v.gaussianOffset = 0x7fff;
//output left
voice_output(v, 0);
voiceOutput(v, 0);
}
void DSP::voice_5(voice_t& v) {
auto DSP::voice5(Voice& v) -> void {
//output right
voice_output(v, 1);
voiceOutput(v, 1);
//ENDX, OUTX and ENVX won't update if you wrote to them 1-2 clocks earlier
state.endx_buf = REG(endx) | state.t_looped;
state.endxBuffer = REG(ENDX) | state._looped;
//clear bit in ENDX if KON just began
if(v.kon_delay == 5) state.endx_buf &= ~v.vbit;
if(v.konDelay == 5) state.endxBuffer &= ~v.vbit;
}
void DSP::voice_6(voice_t& v) {
state.outx_buf = state.t_output >> 8;
auto DSP::voice6(Voice& v) -> void {
state.outxBuffer = state._output >> 8;
}
void DSP::voice_7(voice_t& v) {
auto DSP::voice7(Voice& v) -> void {
//update ENDX
REG(endx) = (uint8)state.endx_buf;
state.envx_buf = v.t_envx_out;
REG(ENDX) = (uint8)state.endxBuffer;
state.envxBuffer = v._envxOut;
}
void DSP::voice_8(voice_t& v) {
auto DSP::voice8(Voice& v) -> void {
//update OUTX
VREG(outx) = (uint8)state.outx_buf;
VREG(OUTX) = (uint8)state.outxBuffer;
}
void DSP::voice_9(voice_t& v) {
auto DSP::voice9(Voice& v) -> void {
//update ENVX
VREG(envx) = (uint8)state.envx_buf;
VREG(ENVX) = (uint8)state.envxBuffer;
}
#endif

View File

@ -12,7 +12,7 @@
namespace SuperFamicom {
namespace Info {
static const string Name = "bsnes";
static const unsigned SerializerVersion = 28;
static const unsigned SerializerVersion = 29;
}
}
@ -32,7 +32,7 @@ namespace SuperFamicom {
if(thread) co_delete(thread);
}
auto create(void (*entrypoint)(), unsigned frequency) -> void {
auto create(auto (*entrypoint)() -> void, unsigned frequency) -> void {
if(thread) co_delete(thread);
thread = co_create(65536 * sizeof(void*), entrypoint);
this->frequency = frequency;