mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
b0e862613b
commit
8476a12deb
|
@ -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/";
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
542
sfc/dsp/dsp.cpp
542
sfc/dsp/dsp.cpp
|
@ -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
|
||||
|
|
254
sfc/dsp/dsp.hpp
254
sfc/dsp/dsp.hpp
|
@ -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;
|
||||
|
|
130
sfc/dsp/echo.cpp
130
sfc/dsp/echo.cpp
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue