From ab55a3f079dafa5b696fe7ca92d5c0b08affa066 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sat, 5 Mar 2022 16:02:01 +0700 Subject: [PATCH 1/4] Turn second chip checks into variables in vgmOps --- src/engine/vgmOps.cpp | 146 ++++++++++++++++++++++-------------------- 1 file changed, 75 insertions(+), 71 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 97f42737..bef7c535 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -25,119 +25,123 @@ constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool isSecond) { + unsigned char baseAddr1=isSecond?0xa0:0x50; + unsigned char baseAddr2=isSecond?0x80:0; + unsigned short baseAddr2S=isSecond?0x8000:0; + unsigned char smsAddr=isSecond?0x30:0x50; if (write.addr==0xffffffff) { // Furnace fake reset switch (sys) { case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: for (int i=0; i<3; i++) { // set SL and RR to highest - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x80+i); w->writeC(0xff); - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x84+i); w->writeC(0xff); - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x88+i); w->writeC(0xff); - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x8c+i); w->writeC(0xff); - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(0x80+i); w->writeC(0xff); - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(0x84+i); w->writeC(0xff); - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(0x88+i); w->writeC(0xff); - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(0x8c+i); w->writeC(0xff); } for (int i=0; i<3; i++) { // note off - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x28); w->writeC(i); - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x28); w->writeC(4+i); } - w->writeC(isSecond?0xa2:0x52); // disable DAC + w->writeC(2|baseAddr1); // disable DAC w->writeC(0x2b); w->writeC(0); break; case DIV_SYSTEM_SMS: for (int i=0; i<4; i++) { - w->writeC(isSecond?0x30:0x50); + w->writeC(smsAddr); w->writeC(0x90|(i<<5)|15); } break; case DIV_SYSTEM_GB: // square 1 w->writeC(0xb3); - w->writeC(isSecond?0x82:2); + w->writeC(2|baseAddr2); w->writeC(0); w->writeC(0xb3); - w->writeC(isSecond?0x84:4); + w->writeC(4|baseAddr2); w->writeC(0x80); // square 2 w->writeC(0xb3); - w->writeC(isSecond?0x87:7); + w->writeC(7|baseAddr2); w->writeC(0); w->writeC(0xb3); - w->writeC(isSecond?0x89:9); + w->writeC(9|baseAddr2); w->writeC(0x80); // wave w->writeC(0xb3); - w->writeC(isSecond?0x8c:0x0c); + w->writeC(0x0c|baseAddr2); w->writeC(0); w->writeC(0xb3); - w->writeC(isSecond?0x8e:0x0e); + w->writeC(0x0e|baseAddr2); w->writeC(0x80); // noise w->writeC(0xb3); - w->writeC(isSecond?0x91:0x11); + w->writeC(0x11|baseAddr2); w->writeC(0); w->writeC(0xb3); - w->writeC(isSecond?0x93:0x13); + w->writeC(0x13|baseAddr2); w->writeC(0x80); break; case DIV_SYSTEM_PCE: for (int i=0; i<6; i++) { w->writeC(0xb9); - w->writeC(isSecond?0x80:0); + w->writeC(0|baseAddr2); w->writeC(i); w->writeC(0xb9); - w->writeC(isSecond?0x84:4); + w->writeC(4|baseAddr2); w->writeC(0); } break; case DIV_SYSTEM_NES: w->writeC(0xb4); - w->writeC(isSecond?0x95:0x15); + w->writeC(0x15|baseAddr2); w->writeC(0); break; case DIV_SYSTEM_YM2151: for (int i=0; i<8; i++) { - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0xe0+i); w->writeC(0xff); - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0xe8+i); w->writeC(0xff); - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0xf0+i); w->writeC(0xff); - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0xf8+i); w->writeC(0xff); - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0x08); w->writeC(i); } @@ -146,7 +150,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_SEGAPCM_COMPAT: for (int i=0; i<16; i++) { w->writeC(0xc0); - w->writeS((isSecond?0x8086:0x86)+(i<<3)); + w->writeS((0x86|baseAddr2S)+(i<<3)); w->writeC(3); } break; @@ -157,60 +161,60 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B_EXT: for (int i=0; i<2; i++) { // set SL and RR to highest - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x81+i); w->writeC(0xff); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x85+i); w->writeC(0xff); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x89+i); w->writeC(0xff); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x8d+i); w->writeC(0xff); - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0x81+i); w->writeC(0xff); - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0x85+i); w->writeC(0xff); - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0x89+i); w->writeC(0xff); - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0x8d+i); w->writeC(0xff); } for (int i=0; i<2; i++) { // note off - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x28); w->writeC(1+i); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x28); w->writeC(5+i); } // reset AY - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(7); w->writeC(0x3f); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(8); w->writeC(0); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(9); w->writeC(0); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(10); w->writeC(0); // reset sample - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0); w->writeC(0xbf); break; @@ -218,56 +222,56 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: for (int i=0; i<9; i++) { - w->writeC(isSecond?0xa1:0x51); + w->writeC(1|baseAddr1); w->writeC(0x20+i); w->writeC(0); - w->writeC(isSecond?0xa1:0x51); + w->writeC(1|baseAddr1); w->writeC(0x30+i); w->writeC(0); - w->writeC(isSecond?0xa1:0x51); + w->writeC(1|baseAddr1); w->writeC(0x10+i); w->writeC(0); } break; case DIV_SYSTEM_AY8910: w->writeC(0xa0); - w->writeC(isSecond?0x87:7); + w->writeC(7|baseAddr2); w->writeC(0x3f); w->writeC(0xa0); - w->writeC(isSecond?0x88:8); + w->writeC(8|baseAddr2); w->writeC(0); w->writeC(0xa0); - w->writeC(isSecond?0x89:9); + w->writeC(9|baseAddr2); w->writeC(0); w->writeC(0xa0); - w->writeC(isSecond?0x8a:10); + w->writeC(10|baseAddr2); w->writeC(0); break; case DIV_SYSTEM_AY8930: w->writeC(0xa0); - w->writeC(isSecond?0x8d:0x0d); + w->writeC(0x0d|baseAddr2); w->writeC(0); w->writeC(0xa0); - w->writeC(isSecond?0x8d:0x0d); + w->writeC(0x0d|baseAddr2); w->writeC(0xa0); break; case DIV_SYSTEM_SAA1099: w->writeC(0xbd); - w->writeC(isSecond?0x9c:0x1c); + w->writeC(0x1c|baseAddr2); w->writeC(0x02); w->writeC(0xbd); - w->writeC(isSecond?0x94:0x14); + w->writeC(0x14|baseAddr2); w->writeC(0); w->writeC(0xbd); - w->writeC(isSecond?0x95:0x15); + w->writeC(0x15|baseAddr2); w->writeC(0); for (int i=0; i<6; i++) { w->writeC(0xbd); - w->writeC((isSecond?0x80:0)+i); + w->writeC((0|baseAddr2)+i); w->writeC(0); } break; @@ -346,49 +350,49 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_YM2612_EXT: switch (write.addr>>8) { case 0: // port 0 - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case 1: // port 1 - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case 2: // PSG - w->writeC(isSecond?0x30:0x50); + w->writeC(smsAddr); w->writeC(write.val); break; } break; case DIV_SYSTEM_SMS: - w->writeC(isSecond?0x30:0x50); + w->writeC(smsAddr); w->writeC(write.val); break; case DIV_SYSTEM_GB: w->writeC(0xb3); - w->writeC((isSecond?0x80:0)|((write.addr-16)&0xff)); + w->writeC(baseAddr2|((write.addr-16)&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_PCE: w->writeC(0xb9); - w->writeC((isSecond?0x80:0)|(write.addr&0xff)); + w->writeC(baseAddr2|(write.addr&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_NES: w->writeC(0xb4); - w->writeC((isSecond?0x80:0)|(write.addr&0xff)); + w->writeC(baseAddr2|(write.addr&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_YM2151: - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: w->writeC(0xc0); - w->writeS((isSecond?0x8000:0)|(write.addr&0xffff)); + w->writeS(baseAddr2S|(write.addr&0xffff)); w->writeC(write.val); break; case DIV_SYSTEM_YM2610: @@ -399,12 +403,12 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_YM2610B_EXT: switch (write.addr>>8) { case 0: // port 0 - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case 1: // port 1 - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; @@ -413,19 +417,19 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: - w->writeC(isSecond?0xa1:0x51); + w->writeC(1|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: w->writeC(0xa0); - w->writeC((isSecond?0x80:0)|(write.addr&0xff)); + w->writeC(baseAddr2|(write.addr&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_SAA1099: w->writeC(0xbd); - w->writeC((isSecond?0x80:0)|(write.addr&0xff)); + w->writeC(baseAddr2|(write.addr&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_LYNX: From df7ac3e0732644aabec12bc80ffff03014383543 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sun, 6 Mar 2022 23:13:47 +0700 Subject: [PATCH 2/4] Add WonderSwan support --- CMakeLists.txt | 5 +- README.md | 1 + papers/doc/4-instrument/README.md | 3 +- papers/doc/4-instrument/wonderswan.md | 8 + papers/doc/5-wave/README.md | 4 +- papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/wonderswan.md | 20 + src/engine/dispatch.h | 3 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/sound/ws.cpp | 412 ++++++++++++++++++++ src/engine/platform/sound/ws.h | 82 ++++ src/engine/platform/ws.cpp | 522 ++++++++++++++++++++++++++ src/engine/platform/ws.h | 95 +++++ src/engine/playback.cpp | 21 ++ src/engine/sysDef.cpp | 1 + src/engine/vgmOps.cpp | 45 +++ src/gui/gui.cpp | 2 + src/gui/insEdit.cpp | 8 +- 18 files changed, 1231 insertions(+), 6 deletions(-) create mode 100644 papers/doc/4-instrument/wonderswan.md create mode 100644 papers/doc/7-systems/wonderswan.md create mode 100644 src/engine/platform/sound/ws.cpp create mode 100644 src/engine/platform/sound/ws.h create mode 100644 src/engine/platform/ws.cpp create mode 100644 src/engine/platform/ws.h diff --git a/CMakeLists.txt b/CMakeLists.txt index caf08457..f3121303 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,8 @@ src/engine/platform/sound/lynx/Mikey.cpp src/engine/platform/sound/qsound.c +src/engine/platform/sound/ws.cpp + src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c @@ -306,8 +308,9 @@ src/engine/platform/amiga.cpp src/engine/platform/pcspkr.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp -src/engine/platform/dummy.cpp src/engine/platform/lynx.cpp +src/engine/platform/ws.cpp +src/engine/platform/dummy.cpp ) if (WIN32) diff --git a/README.md b/README.md index e1d97726..540052fd 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - Philips SAA1099 - Amiga - TIA (Atari 2600/7800) + - WonderSwan - multiple sound chips in a single song! - clean-room design (guesswork and ABX tests only, no decompilation involved) - bug/quirk implementation for increased playback accuracy diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index b9d4b8f4..0b9ec0f4 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -10,12 +10,13 @@ double-click to open the instrument editor. every instrument can be renamed and have its type changed. -depending on the instrument type, there are currently 10 different types of an instrument editor: +depending on the instrument type, there are currently 12 different types of an instrument editor: - [FM synthesis](fm.md) - for use with YM2612, YM2151 and FM block portion of YM2610. - [Standard](standard.md) - for use with NES and Sega Master System's PSG sound source and its derivatives. - [Game Boy](game-boy.md) - for use with Game Boy APU. - [PC Engine/TurboGrafx-16](pce.md) - for use with PC Engine's wavetable synthesizer. +- [WonderSwan](wonderswan.md) - for use with WonderSwan's wavetable synthesizer. - [AY8930](8930.md) - for use with Microchip AY8930 E-PSG sound source. - [Commodore 64](c64.md) - for use with Commodore 64 SID. - [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source. diff --git a/papers/doc/4-instrument/wonderswan.md b/papers/doc/4-instrument/wonderswan.md new file mode 100644 index 00000000..6ad0e8c9 --- /dev/null +++ b/papers/doc/4-instrument/wonderswan.md @@ -0,0 +1,8 @@ +# WonderSwan instrument editor + +WS instrument editor consists of only four macros, similar to PCE but with different volume and noise range: + +- [Volume] - volume sequence +- [Arpeggio] - pitch sequencr +- [Noise] - noise LFSR tap sequence +- [Waveform] - spicifies wavetables sequence diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index 91fe9265..00573925 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -1,5 +1,5 @@ # wavetable editor -Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.5, wavetable editor affects PC Engine and channel 3 of Game Boy. +Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. -Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: both Game Boy and PCE can handle max 32 byte waveforms as of now, width 16-level height for GB and 32-level height for PCE. If larger wave will be defined for these two systems, it will be squashed to fit in the constrains of the system. +Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms as of now, with 16-level height for GB and WS, and 32-level height for PCE. If larger wave will be defined for these two systems, it will be squashed to fit within the constraints of the system. diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 78aeb8ce..a42e0d06 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -18,5 +18,6 @@ this is a list of systems that Furnace supports, including each system's effects - [Atari 2600](tia.md) - [Philips SAA1099](saa1099.md) - [Microchip AY8930](ay8930.md) +- [WonderSwan](wonderswan.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all. diff --git a/papers/doc/7-systems/wonderswan.md b/papers/doc/7-systems/wonderswan.md new file mode 100644 index 00000000..c5c0bafd --- /dev/null +++ b/papers/doc/7-systems/wonderswan.md @@ -0,0 +1,20 @@ +# WonderSwan + +A handheld console released only in Japan by Bandai. Designed by the same +people behind Game Boy and Virtual Boy, it has lots of similar elements from +those two systems in the sound department. + +It has 4 wavetable channels, one channel could play PCM, the other has hardware +sweep and the other could play noise. + +# effects + +- `10xx`: change wave. +- `11xx`: setup noise mode (channel 4 only). + - 0: disable. + - 1-8: enable and set tap preset. +- `12xx`: setup sweep period (channel 3 only). + - 0: disable. + - 1-32: enable and set period. +- `13xx`: setup sweep amount (channel 3 only). +- `17xx`: toggle PCM mode (channel 2 only). diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 300b0033..98a2843e 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -103,6 +103,9 @@ enum DivDispatchCmds { DIV_CMD_QSOUND_ECHO_DELAY, DIV_CMD_QSOUND_ECHO_LEVEL, + DIV_CMD_WS_SWEEP_TIME, + DIV_CMD_WS_SWEEP_AMOUNT, + DIV_ALWAYS_SET_VOLUME, DIV_CMD_MAX diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 52cc7177..0bc6a272 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -41,6 +41,7 @@ #include "platform/pcspkr.h" #include "platform/segapcm.h" #include "platform/qsound.h" +#include "platform/ws.h" #include "platform/dummy.h" #include "platform/lynx.h" #include "../ta-log.h" @@ -234,6 +235,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SEGAPCM_COMPAT: dispatch=new DivPlatformSegaPCM; break; + case DIV_SYSTEM_SWAN: + dispatch=new DivPlatformWS; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/sound/ws.cpp b/src/engine/platform/sound/ws.cpp new file mode 100644 index 00000000..e02d63ec --- /dev/null +++ b/src/engine/platform/sound/ws.cpp @@ -0,0 +1,412 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* sound.cpp - WonderSwan Sound Emulation +** Copyright (C) 2007-2017 Mednafen Team +** Copyright (C) 2016 Alex 'trap15' Marshall - http://daifukkat.su/ +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "ws.h" +#include + +#define MK_SAMPLE_CACHE \ + { \ + int sample; \ + sample = (((wsRAM[(/*(SampleRAMPos << 6) + */(sample_pos[ch] >> 1) + (ch << 4)) ] >> ((sample_pos[ch] & 1) ? 4 : 0)) & 0x0F)); \ + sample_cache[ch][0] = sample * ((volume[ch] >> 4) & 0x0F); \ + sample_cache[ch][1] = sample * ((volume[ch] >> 0) & 0x0F); \ + } + +#define MK_SAMPLE_CACHE_NOISE \ + { \ + int sample; \ + sample = ((nreg & 1) ? 0xF : 0x0); \ + sample_cache[ch][0] = sample * ((volume[ch] >> 4) & 0x0F); \ + sample_cache[ch][1] = sample * ((volume[ch] >> 0) & 0x0F); \ + } + +#define MK_SAMPLE_CACHE_VOICE \ + { \ + int sample, half; \ + sample = volume[ch]; \ + half = sample >> 1; \ + sample_cache[ch][0] = (voice_volume & 4) ? sample : (voice_volume & 8) ? half : 0; \ + sample_cache[ch][1] = (voice_volume & 1) ? sample : (voice_volume & 2) ? half : 0; \ + } + + +#define SYNCSAMPLE(wt) /* \ + { \ + int32_t left = sample_cache[ch][0], right = sample_cache[ch][1]; \ + WaveSynth.offset_inline(wt, left - last_val[ch][0], sbuf[0]); \ + WaveSynth.offset_inline(wt, right - last_val[ch][1], sbuf[1]); \ + last_val[ch][0] = left; \ + last_val[ch][1] = right; \ + } */ + +#define SYNCSAMPLE_NOISE(wt) SYNCSAMPLE(wt) + +void WSwan::SoundUpdate(uint32_t v30mz_timestamp) +{ + int32_t run_time; + + //printf("%d\n", v30mz_timestamp); + //printf("%02x %02x\n", control, noise_control); + run_time = v30mz_timestamp - last_ts; + + for(int y = 0; y < 2; y++) + sbuf[y] = 0; + + for(unsigned int ch = 0; ch < 4; ch++) + { + // Channel is disabled? + if(!(control & (1 << ch))) + continue; + + if(ch == 1 && (control & 0x20)) // Direct D/A mode? + { + MK_SAMPLE_CACHE_VOICE; + SYNCSAMPLE(v30mz_timestamp); + } + else if(ch == 2 && (control & 0x40) && sweep_value) // Sweep + { + uint32_t tmp_pt = 2048 - period[ch]; + uint32_t meow_timestamp = v30mz_timestamp - run_time; + uint32_t tmp_run_time = run_time; + + while(tmp_run_time) + { + int32_t sub_run_time = tmp_run_time; + + if(sub_run_time > sweep_8192_divider) + sub_run_time = sweep_8192_divider; + + sweep_8192_divider -= sub_run_time; + if(sweep_8192_divider <= 0) + { + sweep_8192_divider += 8192; + sweep_counter--; + if(sweep_counter <= 0) + { + sweep_counter = sweep_step + 1; + period[ch] = (period[ch] + (int8_t)sweep_value) & 0x7FF; + } + } + + meow_timestamp += sub_run_time; + if(tmp_pt > 4) + { + period_counter[ch] -= sub_run_time; + while(period_counter[ch] <= 0) + { + sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F; + + MK_SAMPLE_CACHE; + SYNCSAMPLE(meow_timestamp + period_counter[ch]); + period_counter[ch] += tmp_pt; + } + } + tmp_run_time -= sub_run_time; + } + } + else if(ch == 3 && (control & 0x80) && (noise_control & 0x10)) // Noise + { + uint32_t tmp_pt = 2048 - period[ch]; + + period_counter[ch] -= run_time; + while(period_counter[ch] <= 0) + { + static const uint8_t stab[8] = { 14, 10, 13, 4, 8, 6, 9, 11 }; + + nreg = ((nreg << 1) | ((1 ^ (nreg >> 7) ^ (nreg >> stab[noise_control & 0x7])) & 1)) & 0x7FFF; + + if(control & 0x80) + { + MK_SAMPLE_CACHE_NOISE; + SYNCSAMPLE_NOISE(v30mz_timestamp + period_counter[ch]); + } + else if(tmp_pt > 4) + { + sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F; + MK_SAMPLE_CACHE; + SYNCSAMPLE(v30mz_timestamp + period_counter[ch]); + } + period_counter[ch] += tmp_pt; + } + } + else + { + uint32_t tmp_pt = 2048 - period[ch]; + + if(tmp_pt > 4) + { + period_counter[ch] -= run_time; + while(period_counter[ch] <= 0) + { + sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F; + + MK_SAMPLE_CACHE; + SYNCSAMPLE(v30mz_timestamp + period_counter[ch]); // - period_counter[ch]); + period_counter[ch] += tmp_pt; + } + } + } + sbuf[0] += sample_cache[ch][0]; + sbuf[1] += sample_cache[ch][1]; + } + + if(HVoiceCtrl & 0x80) + { + int16_t sample = (uint8_t)HyperVoice; + + switch(HVoiceCtrl & 0xC) + { + case 0x0: sample = (uint16_t)sample << (8 - (HVoiceCtrl & 3)); break; + case 0x4: sample = (uint16_t)(sample | -0x100) << (8 - (HVoiceCtrl & 3)); break; + case 0x8: sample = (uint16_t)((int8_t)sample) << (8 - (HVoiceCtrl & 3)); break; + case 0xC: sample = (uint16_t)sample << 8; break; + } + // bring back to 11bit, keeping signedness + sample >>= 5; + + int32_t left, right; + left = (HVoiceChanCtrl & 0x40) ? sample : 0; + right = (HVoiceChanCtrl & 0x20) ? sample : 0; + + // WaveSynth.offset_inline(v30mz_timestamp, left - last_hv_val[0], sbuf[0]); + // WaveSynth.offset_inline(v30mz_timestamp, right - last_hv_val[1], sbuf[1]); + // last_hv_val[0] = left; + // last_hv_val[1] = right; + sbuf[0] += left; + sbuf[1] += right; + } + last_ts = v30mz_timestamp; +} + +void WSwan::SoundWrite(uint32_t A, uint8_t V) +{ + if(A >= 0x80 && A <= 0x87) + { + int ch = (A - 0x80) >> 1; + + if(A & 1) + period[ch] = (period[ch] & 0x00FF) | ((V & 0x07) << 8); + else + period[ch] = (period[ch] & 0x0700) | ((V & 0xFF) << 0); + + //printf("Period %d: 0x%04x --- %f\n", ch, period[ch], 3072000.0 / (2048 - period[ch])); + } + else if(A >= 0x88 && A <= 0x8B) + { + volume[A - 0x88] = V; + } + else if(A == 0x8C) + sweep_value = V; + else if(A == 0x8D) + { + sweep_step = V; + sweep_counter = sweep_step + 1; + sweep_8192_divider = 8192; + } + else if(A == 0x8E) + { + //printf("NOISECONTROL: %02x\n", V); + if(V & 0x8) + nreg = 0; + + noise_control = V & 0x17; + } + else if(A == 0x90) + { + for(int n = 0; n < 4; n++) + { + if(!(control & (1 << n)) && (V & (1 << n))) + { + period_counter[n] = 1; + sample_pos[n] = 0x1F; + } + } + control = V; + //printf("Sound Control: %02x\n", V); + } + else if(A == 0x91) + { + output_control = V & 0xF; + //printf("%02x, %02x\n", V, (V >> 1) & 3); + } + else if(A == 0x92) + nreg = (nreg & 0xFF00) | (V << 0); + else if(A == 0x93) + nreg = (nreg & 0x00FF) | ((V & 0x7F) << 8); + else if(A == 0x94) + { + voice_volume = V & 0xF; + //printf("%02x\n", V); + } + else switch(A) + { + case 0x6A: HVoiceCtrl = V; break; + case 0x6B: HVoiceChanCtrl = V & 0x6F; break; + case 0x8F: SampleRAMPos = V; break; + case 0x95: HyperVoice = V; break; // Pick a port, any port?! + //default: printf("%04x:%02x\n", A, V); break; + } +} + +uint8_t WSwan::SoundRead(uint32_t A) +{ + if(A >= 0x80 && A <= 0x87) + { + int ch = (A - 0x80) >> 1; + + if(A & 1) + return(period[ch] >> 8); + else + return(period[ch]); + } + else if(A >= 0x88 && A <= 0x8B) + return(volume[A - 0x88]); + else switch(A) + { + default: /*printf("SoundRead: %04x\n", A);*/ return(0); + case 0x6A: return(HVoiceCtrl); + case 0x6B: return(HVoiceChanCtrl); + case 0x8C: return(sweep_value); + case 0x8D: return(sweep_step); + case 0x8E: return(noise_control); + case 0x8F: return(SampleRAMPos); + case 0x90: return(control); + case 0x91: return(output_control | 0x80); + case 0x92: return((nreg >> 0) & 0xFF); + case 0x93: return((nreg >> 8) & 0xFF); + case 0x94: return(voice_volume); + } +} + +void WSwan::RAMWrite(uint32_t A, uint8_t V) +{ + wsRAM[A & 0x3F] = V; +} + +int32_t WSwan::SoundFlush(int16_t *SoundBuf, const int32_t MaxSoundFrames) +{ + int32_t FrameCount = 0; + + if(SoundBuf) + { + for(int y = 0; y < 2; y++) + { + // sbuf[y]->end_frame(v30mz_timestamp); + // FrameCount = sbuf[y]->read_samples(SoundBuf + y, MaxSoundFrames, true); + int32_t left = sbuf[0]; + int32_t right = sbuf[1]; + if (left >= 0x400) left = 0x3FF; + else if (left < -0x400) left = -0x400; + if (right >= 0x400) left = 0x3FF; + else if (right < -0x400) left = -0x400; + SoundBuf[0] = (int16_t)left << 5; + SoundBuf[1] = (int16_t)right << 5; + } + } + + last_ts = 0; + + return(FrameCount); +} + +// Call before wsRAM is updated +// void WSwan::SoundCheckRAMWrite(uint32_t A) +// { +// if((A >> 6) == SampleRAMPos) +// SoundUpdate(); +// } + +// static void RedoVolume(void) +// { +// WaveSynth.volume(2.5); +// } + +// void WSwan::SoundInit(void) +// { +// for(int i = 0; i < 2; i++) +// { +// sbuf[i] = new Blip_Buffer(); + +// sbuf[i]->set_sample_rate(0 ? 0 : 44100, 60); +// sbuf[i]->clock_rate((long)(3072000)); +// sbuf[i]->bass_freq(20); +// } + +// RedoVolume(); +// } + +// void WSwan::SoundKill(void) +// { +// for(int i = 0; i < 2; i++) +// { +// if(sbuf[i]) +// { +// delete sbuf[i]; +// sbuf[i] = NULL; +// } +// } + +// } + +// bool WSwan::SetSoundRate(uint32_t rate) +// { +// for(int i = 0; i < 2; i++) +// sbuf[i]->set_sample_rate(rate?rate:44100, 60); + +// return(true); +// } + +void WSwan::SoundReset(void) +{ + memset(period, 0, sizeof(period)); + memset(volume, 0, sizeof(volume)); + voice_volume = 0; + sweep_step = 0; + sweep_value = 0; + noise_control = 0; + control = 0; + output_control = 0; + + sweep_8192_divider = 8192; + sweep_counter = 1; + SampleRAMPos = 0; + + for(unsigned ch = 0; ch < 4; ch++) + period_counter[ch] = 1; + + memset(sample_pos, 0, sizeof(sample_pos)); + nreg = 0; + + memset(sample_cache, 0, sizeof(sample_cache)); + // memset(last_val, 0, sizeof(last_val)); + last_v_val = 0; + + HyperVoice = 0; + last_hv_val[0] = last_hv_val[1] = 0; + HVoiceCtrl = 0; + HVoiceChanCtrl = 0; + + for(int y = 0; y < 2; y++) + // sbuf[y]->clear(); + sbuf[y] = 0; + last_ts = 0; +} diff --git a/src/engine/platform/sound/ws.h b/src/engine/platform/sound/ws.h new file mode 100644 index 00000000..b1b0af74 --- /dev/null +++ b/src/engine/platform/sound/ws.h @@ -0,0 +1,82 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* sound.h - WonderSwan Sound Emulation +** Copyright (C) 2007-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef __WSWAN_SOUND_H +#define __WSWAN_SOUND_H + +#include + +class WSwan +{ +public: + int32_t SoundFlush(int16_t *SoundBuf, const int32_t MaxSoundFrames); + + // void SoundInit(void); + // void SoundKill(void); + // void SetSoundMultiplier(double multiplier); + // bool SetSoundRate(uint32_t rate); + + void SoundWrite(uint32_t, uint8_t); + uint8_t SoundRead(uint32_t); + void SoundReset(void); + // void SoundCheckRAMWrite(uint32_t A); + + void SoundUpdate(uint32_t); + void RAMWrite(uint32_t, uint8_t); + +private: + // Blip_Synth WaveSynth; + + // Blip_Buffer *sbuf[2] = { NULL }; + int32_t sbuf[2]; + + uint16_t period[4]; + uint8_t volume[4]; // left volume in upper 4 bits, right in lower 4 bits + uint8_t voice_volume; + + uint8_t sweep_step, sweep_value; + uint8_t noise_control; + uint8_t control; + uint8_t output_control; + + int32_t sweep_8192_divider; + uint8_t sweep_counter; + uint8_t SampleRAMPos; + + int32_t sample_cache[4][2]; + + int32_t last_v_val; + + uint8_t HyperVoice; + int32_t last_hv_val[2]; + uint8_t HVoiceCtrl, HVoiceChanCtrl; + + int32_t period_counter[4]; + // int32_t last_val[4][2]; // Last outputted value, l&r + uint8_t sample_pos[4]; + uint16_t nreg; + uint32_t last_ts; + + uint8_t wsRAM[64]; + int16_t sBuf[2]; +}; + +#endif diff --git a/src/engine/platform/ws.cpp b/src/engine/platform/ws.cpp new file mode 100644 index 00000000..df82376a --- /dev/null +++ b/src/engine/platform/ws.cpp @@ -0,0 +1,522 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ws.h" +#include "../engine.h" +#include + +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);}} + +#define CHIP_DIVIDER 32 + +const char* regCheatSheetWS[]={ + "CH1_Pitch", "00", + "CH2_Pitch", "02", + "CH3_Pitch", "04", + "CH4_Pitch", "06", + "CH1_Vol", "08", + "CH2_Vol", "09", + "CH3_Vol", "0A", + "CH4_Vol", "0B", + "Sweep_Value", "0C", + "Sweep_Time", "0D", + "Noise", "0E", + "Wave_Base", "0F", + "Ctrl", "10", + "Output", "11", + "Random", "12", + "Voice_Ctrl", "14", + "Wave_Mem", "40", + NULL +}; + +const char** DivPlatformWS::getRegisterSheet() { + return regCheatSheetWS; +} + +const char* DivPlatformWS::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + case 0x11: + return "11xx: Setup noise mode (0: disabled; 1-8: enabled/tap)"; + break; + case 0x12: + return "12xx: Setup sweep period (0: disabled; 1-20: enabled/period)"; + break; + case 0x13: + return "13xx: Set sweep amount"; + break; + case 0x17: + return "17xx: Toggle PCM mode"; + break; + } + return NULL; +} + +void DivPlatformWS::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; hrate) { + DivSample* s=parent->getSample(dacSample); + if (s->samples<=0) { + dacSample=-1; + continue; + } + rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80); + if (dacPos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + dacPos=s->loopStart; + } else { + dacSample=-1; + } + } + dacPeriod-=rate; + } + } + + // the rest + while (!writes.empty()) { + QueuedWrite w=writes.front(); + if (regPool[w.addr]!=w.val) { + if (w.addr<0x40) ws->SoundWrite(w.addr|0x80,w.val); + else ws->RAMWrite(w.addr&0x3f,w.val); + regPool[w.addr]=w.val; + } + writes.pop(); + } + int16_t samp[2]{0, 0}; + ws->SoundUpdate(16); + ws->SoundFlush(samp, 1); + bufL[h]=samp[0]; + bufR[h]=samp[1]; + } +} + +void DivPlatformWS::updateWave(int ch) { + DivWavetable* wt=parent->getWave(chan[ch].wave); + unsigned char addr=0x40+ch*16; + if (wt->max<1 || wt->len<1) { + for (int i=0; i<16; i++) { + rWrite(addr+i,0); + } + } else { + for (int i=0; i<16; i++) { + unsigned char nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max; + unsigned char nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max; + rWrite(addr+i,nibble1|(nibble2<<4)); + } + } +} + +void DivPlatformWS::calcAndWriteOutVol(int ch, int env) { + int vl=chan[ch].vol*((chan[ch].pan>>4)&0x0f)*env/225; + int vr=chan[ch].vol*(chan[ch].pan&0x0f)*env/225; + if (ch==1&&pcm) { + vl=(vl>0)?((vl>7)?3:2):0; + vr=(vr>0)?((vr>7)?3:2):0; + chan[1].outVol=vr|(vl<<2); + } else { + chan[ch].outVol=vr|(vl<<4); + } + writeOutVol(ch); +} + +void DivPlatformWS::writeOutVol(int ch) { + unsigned char val=isMuted[ch]?0:chan[ch].outVol; + if (ch==1&&pcm) { + rWrite(0x14,val) + } else { + rWrite(0x08+ch,val); + } +} + +void DivPlatformWS::tick() { + unsigned char sndCtrl=(pcm?0x20:0)|(sweep?0x40:0)|((noise>0)?0x80:0); + for (int i=0; i<4; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + int env=chan[i].std.vol; + if(parent->getIns(chan[i].ins)->type==DIV_INS_AMIGA) { + env=MIN(env/4,15); + } + calcAndWriteOutVol(i,env); + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadWave && !(i==1 && pcm)) { + if (chan[i].wave!=chan[i].std.wave) { + chan[i].wave=chan[i].std.wave; + updateWave(i); + } + } + if (chan[i].active) { + sndCtrl|=(1<calcFreq(chan[i].baseFreq,chan[i].pitch,true); + if (i==1 && furnaceDac) { + double off=1.0; + if (dacSample>=0 && dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(dacSample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } + dacRate=((double)chipClock/2)/MAX(1,off*chan[i].freq); + if (dumpWrites) addWrite(0xffff0001,dacRate); + } + if (chan[i].freq>2048) chan[i].freq=2048; + if (chan[i].freq<1) chan[i].freq=1; + int rVal=2048-chan[i].freq; + rWrite(i*2,rVal&0xff); + rWrite(i*2+1,rVal>>8); + if (chan[i].keyOn) { + if (!chan[i].std.hasVol) { + calcAndWriteOutVol(i,15); + } + if (chan[i].wave<0) { + chan[i].wave=0; + updateWave(i); + } + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + chan[i].keyOff=false; + } + chan[i].freqChanged=false; + } + } + if (chan[3].std.hadDuty) { + noise=chan[3].std.duty; + if (noise>0) { + rWrite(0x0e,(noise-1)&0x07|0x18); + sndCtrl|=0x80; + } else { + sndCtrl&=~0x80; + } + } + rWrite(0x10,sndCtrl); +} + +int DivPlatformWS::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (c.chan==1 && ins->type==DIV_INS_AMIGA) { + pcm=true; + } else if (furnaceDac) { + pcm=false; + } + if (c.chan==1 && pcm) { + if (skipRegisterWrites) break; + dacPos=0; + dacPeriod=0; + if (ins->type==DIV_INS_AMIGA) { + dacSample=ins->amiga.initSample; + if (dacSample<0 || dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) { + addWrite(0xffff0000,dacSample); + } + } + if (c.value!=DIV_NOTE_NULL) { + chan[1].baseFreq=NOTE_PERIODIC(c.value); + chan[1].freqChanged=true; + chan[1].note=c.value; + } + chan[1].active=true; + chan[1].keyOn=true; + chan[1].std.init(ins); + furnaceDac=true; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[1].note=c.value; + } + dacSample=12*sampleBank+chan[1].note%12; + if (dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000,dacSample); + } + dacRate=parent->getSample(dacSample)->rate; + if (dumpWrites) { + addWrite(0xffff0001,dacRate); + } + chan[1].active=true; + chan[1].keyOn=true; + furnaceDac=false; + } + break; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].std.init(ins); + break; + } + case DIV_CMD_NOTE_OFF: + if (c.chan==1&&pcm) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + pcm=false; + } + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + calcAndWriteOutVol(c.chan,15); + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + chan[c.chan].wave=c.value; + updateWave(c.chan); + chan[c.chan].keyOn=true; + break; + case DIV_CMD_WS_SWEEP_TIME: + if (c.chan==2) { + if (c.value==0) { + sweep=false; + } else { + sweep=true; + rWrite(0x0d,(c.value-1)&0xff); + } + } + break; + case DIV_CMD_WS_SWEEP_AMOUNT: + if (c.chan==2) { + rWrite(0x0c,c.value&0xff); + } + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + if (c.chan==3) { + noise=c.value&0xff; + if (noise>0) rWrite(0x0e,(noise-1)&0x07|0x18); + } + break; + case DIV_CMD_SAMPLE_MODE: + if (c.chan==1) pcm=c.value; + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_PANNING: { + chan[c.chan].pan=c.value; + if (!chan[c.chan].std.hasVol) { + calcAndWriteOutVol(c.chan,15); + } + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + default: + break; + } + return 1; +} + +void DivPlatformWS::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + writeOutVol(ch); +} + +void DivPlatformWS::forceIns() { + for (int i=0; i<4; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + updateWave(i); + writeOutVol(i); + } +} + +void* DivPlatformWS::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformWS::getRegisterPool() { + // get Random from emulator + regPool[0x12]=ws->SoundRead(0x92); + regPool[0x13]=ws->SoundRead(0x93); + return regPool; +} + +int DivPlatformWS::getRegisterPoolSize() { + return 128; +} + +void DivPlatformWS::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,128); + for (int i=0; i<4; i++) { + chan[i]=Channel(); + chan[i].vol=15; + chan[i].pan=0xff; + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + ws->SoundReset(); + pcm=false; + sweep=false; + furnaceDac=false; + noise=0; + dacPeriod=0; + dacRate=0; + dacPos=0; + dacSample=-1; + sampleBank=0; + rWrite(0x0f,0x00); // wave table at 0x0000 + rWrite(0x11,0x09); // enable speakers +} + +bool DivPlatformWS::isStereo() { + return true; +} + +void DivPlatformWS::notifyWaveChange(int wave) { + for (int i=0; i<4; i++) { + if (chan[i].wave==wave) { + updateWave(i); + } + } +} + +void DivPlatformWS::notifyInsDeletion(void* ins) { + for (int i=0; i<4; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformWS::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformWS::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformWS::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + chipClock=3072000; + rate=chipClock/16; // = 192000kHz, should be enough + for (int i=0; i<4; i++) { + isMuted[i]=false; + } + ws=new WSwan(); + reset(); + return 4; +} + +void DivPlatformWS::quit() { + delete ws; +} + +DivPlatformWS::~DivPlatformWS() { +} diff --git a/src/engine/platform/ws.h b/src/engine/platform/ws.h new file mode 100644 index 00000000..ea6466fb --- /dev/null +++ b/src/engine/platform/ws.h @@ -0,0 +1,95 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _WS_H +#define _WS_H + +#include "../dispatch.h" +#include "../macroInt.h" +#include "sound/ws.h" +#include + +class DivPlatformWS: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + unsigned char ins, pan; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; + int vol, outVol, wave; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + pan(255), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + vol(15), + outVol(15), + wave(-1) {} + }; + Channel chan[4]; + bool isMuted[4]; + bool pcm, sweep, furnaceDac; + unsigned char sampleBank, noise; + int dacPeriod, dacRate; + unsigned int dacPos; + int dacSample; + + unsigned char regPool[0x80]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + WSwan* ws; + void updateWave(int ch); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + bool isStereo(); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformWS(); + private: + void calcAndWriteOutVol(int ch, int env); + void writeOutVol(int ch); +}; + +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index f0f43dc1..fb3067df 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -250,6 +250,27 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe break; } break; + case DIV_SYSTEM_SWAN: + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // noise mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + case 0x12: // sweep period + dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_TIME,ch,effectVal)); + break; + case 0x13: // sweep amount + dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_AMOUNT,ch,effectVal)); + break; + case 0x17: // PCM enable + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); + break; + default: + return false; + } + break; default: return false; } diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3c988ceb..fe45a4e7 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1616,6 +1616,7 @@ bool DivEngine::isVGMExportable(DivSystem which) { case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: + case DIV_SYSTEM_SWAN: return true; default: return false; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index bef7c535..caa81148 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -443,6 +443,18 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(write.val&0xff); w->writeC(write.addr&0xff); break; + case DIV_SYSTEM_SWAN: + if ((write.addr&0x7f)<0x40) { + w->writeC(0xbc); + w->writeC(baseAddr2|(write.addr&0x3f)); + w->writeC(write.val&0xff); + } else { + // (Wave) RAM write + w->writeC(0xc6); + w->writeS(baseAddr2S|(write.addr&0x3f)); + w->writeC(write.val&0xff); + } + break; default: logW("write not handled!\n"); break; @@ -746,6 +758,21 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { addWarning("dual QSound is not supported by the VGM format"); } break; + case DIV_SYSTEM_SWAN: + if (!hasSwan) { + hasSwan=disCont[i].dispatch->chipClock; + willExport[i]=true; + // funny enough, VGM doesn't have support for WSC's sound DMA by design + // so DAC stream it goes + // since WS has the same PCM format as YM2612 DAC, I can just reuse this flag + writeDACSamples=true; + } else if (!(hasSwan&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasSwan|=0x40000000; + howManyChips++; + } + break; default: break; } @@ -1031,6 +1058,24 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { streamID++; } break; + case DIV_SYSTEM_SWAN: + w->writeC(0x90); + w->writeC(streamID); + w->writeC(33); + w->writeC(0); // port + w->writeC(isSecond[i]?0x89:0x09); // DAC + + w->writeC(0x91); + w->writeC(streamID); + w->writeC(0); + w->writeC(1); + w->writeC(0); + + w->writeC(0x92); + w->writeC(streamID); + w->writeI(24000); // default + streamID++; + break; default: break; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fa890038..4dd7b05e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4634,6 +4634,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_AY8930); sysAddOption(DIV_SYSTEM_LYNX); sysAddOption(DIV_SYSTEM_QSOUND); + sysAddOption(DIV_SYSTEM_SWAN); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -4971,6 +4972,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_AY8930); sysChangeOption(i,DIV_SYSTEM_LYNX); sysChangeOption(i,DIV_SYSTEM_QSOUND); + sysChangeOption(i,DIV_SYSTEM_SWAN); ImGui::EndMenu(); } } diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 6ff30896..6c7f06cb 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1358,6 +1358,10 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_PCE) { dutyMax=1; } + if (ins->type==DIV_INS_SWAN) { + dutyLabel="Noise"; + dutyMax=8; + } if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) { dutyMax=0; } @@ -1777,7 +1781,7 @@ void FurnaceGUI::drawWaveEdit() { DivWavetable* wave=e->song.wave[curWave]; ImGui::Text("Width"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of 32 on Game Boy and PC Engine.\nany other widths will be scaled during playback."); + ImGui::SetTooltip("use a width of 32 on Game Boy, PC Engine and WonderSwan.\nany other widths will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); @@ -1791,7 +1795,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SameLine(); ImGui::Text("Height"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy\n- 31 for PC Engine\nany other heights will be scaled during playback."); + ImGui::SetTooltip("use a height of:\n- 15 for Game Boy and WonderSwan\n- 31 for PC Engine\nany other heights will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); From 8f31c4b49fd0ec2da40ec81900b36575c5387abf Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 01:26:59 +0700 Subject: [PATCH 3/4] Fix playback and VGM export --- src/engine/platform/ws.cpp | 103 +++++++++++++++++++------------------ src/engine/safeWriter.cpp | 4 ++ src/engine/vgmOps.cpp | 6 +-- src/gui/gui.cpp | 6 +++ 4 files changed, 66 insertions(+), 53 deletions(-) diff --git a/src/engine/platform/ws.cpp b/src/engine/platform/ws.cpp index df82376a..4e7c6330 100644 --- a/src/engine/platform/ws.cpp +++ b/src/engine/platform/ws.cpp @@ -187,7 +187,7 @@ void DivPlatformWS::tick() { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); - if (i==1 && furnaceDac) { + if (i==1 && pcm && furnaceDac) { double off=1.0; if (dacSample>=0 && dacSamplesong.sampleLen) { DivSample* s=parent->getSample(dacSample); @@ -237,56 +237,58 @@ int DivPlatformWS::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); - if (c.chan==1 && ins->type==DIV_INS_AMIGA) { - pcm=true; - } else if (furnaceDac) { - pcm=false; - } - if (c.chan==1 && pcm) { - if (skipRegisterWrites) break; - dacPos=0; - dacPeriod=0; + if (c.chan==1) { if (ins->type==DIV_INS_AMIGA) { - dacSample=ins->amiga.initSample; - if (dacSample<0 || dacSample>=parent->song.sampleLen) { - dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); - break; - } else { - if (dumpWrites) { - addWrite(0xffff0000,dacSample); - } - } - if (c.value!=DIV_NOTE_NULL) { - chan[1].baseFreq=NOTE_PERIODIC(c.value); - chan[1].freqChanged=true; - chan[1].note=c.value; - } - chan[1].active=true; - chan[1].keyOn=true; - chan[1].std.init(ins); - furnaceDac=true; - } else { - if (c.value!=DIV_NOTE_NULL) { - chan[1].note=c.value; - } - dacSample=12*sampleBank+chan[1].note%12; - if (dacSample>=parent->song.sampleLen) { - dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); - break; - } else { - if (dumpWrites) addWrite(0xffff0000,dacSample); - } - dacRate=parent->getSample(dacSample)->rate; - if (dumpWrites) { - addWrite(0xffff0001,dacRate); - } - chan[1].active=true; - chan[1].keyOn=true; - furnaceDac=false; + pcm=true; + } else if (furnaceDac) { + pcm=false; + } + if (pcm) { + if (skipRegisterWrites) break; + dacPos=0; + dacPeriod=0; + if (ins->type==DIV_INS_AMIGA) { + dacSample=ins->amiga.initSample; + if (dacSample<0 || dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) { + addWrite(0xffff0000,dacSample); + } + } + if (c.value!=DIV_NOTE_NULL) { + chan[1].baseFreq=NOTE_PERIODIC(c.value); + chan[1].freqChanged=true; + chan[1].note=c.value; + } + chan[1].active=true; + chan[1].keyOn=true; + chan[1].std.init(ins); + furnaceDac=true; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[1].note=c.value; + } + dacSample=12*sampleBank+chan[1].note%12; + if (dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000,dacSample); + } + dacRate=parent->getSample(dacSample)->rate; + if (dumpWrites) { + addWrite(0xffff0001,dacRate); + } + chan[1].active=true; + chan[1].keyOn=true; + furnaceDac=false; + } + break; } - break; } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); @@ -412,7 +414,7 @@ int DivPlatformWS::dispatch(DivCommand c) { return 15; break; case DIV_ALWAYS_SET_VOLUME: - return 0; + return 1; break; default: break; @@ -456,6 +458,7 @@ void DivPlatformWS::reset() { chan[i]=Channel(); chan[i].vol=15; chan[i].pan=0xff; + rWrite(0x08+i,0xff); } if (dumpWrites) { addWrite(0xffffffff,0); diff --git a/src/engine/safeWriter.cpp b/src/engine/safeWriter.cpp index 0e7a118a..7b6f0b1e 100644 --- a/src/engine/safeWriter.cpp +++ b/src/engine/safeWriter.cpp @@ -80,6 +80,10 @@ int SafeWriter::writeC(signed char val) { int SafeWriter::writeS(short val) { return write(&val,2); } +int SafeWriter::writeS_BE(short val) { + unsigned char bytes[2]{(val>>8)&0xff, val&0xff}; + return write(bytes,2); +} int SafeWriter::writeI(int val) { return write(&val,4); diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index caa81148..c3388399 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -451,7 +451,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write } else { // (Wave) RAM write w->writeC(0xc6); - w->writeS(baseAddr2S|(write.addr&0x3f)); + w->writeS_BE(baseAddr2S|(write.addr&0x3f)); w->writeC(write.val&0xff); } break; @@ -1061,9 +1061,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { case DIV_SYSTEM_SWAN: w->writeC(0x90); w->writeC(streamID); - w->writeC(33); + w->writeC(isSecond[i]?0xa1:0x21); w->writeC(0); // port - w->writeC(isSecond[i]?0x89:0x09); // DAC + w->writeC(0x09); // DAC w->writeC(0x91); w->writeC(streamID); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4dd7b05e..6f7740f9 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6390,6 +6390,12 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "WonderSwan", { + DIV_SYSTEM_SWAN, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Computers"); From 119d815a165528c451d2995344d1b23ebf5855a5 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 15:44:15 +0700 Subject: [PATCH 4/4] No need to de-duplicate writes here --- src/engine/platform/ws.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/ws.cpp b/src/engine/platform/ws.cpp index 4e7c6330..1a842a22 100644 --- a/src/engine/platform/ws.cpp +++ b/src/engine/platform/ws.cpp @@ -97,11 +97,8 @@ void DivPlatformWS::acquire(short* bufL, short* bufR, size_t start, size_t len) // the rest while (!writes.empty()) { QueuedWrite w=writes.front(); - if (regPool[w.addr]!=w.val) { - if (w.addr<0x40) ws->SoundWrite(w.addr|0x80,w.val); - else ws->RAMWrite(w.addr&0x3f,w.val); - regPool[w.addr]=w.val; - } + if (w.addr<0x40) ws->SoundWrite(w.addr|0x80,w.val); + else ws->RAMWrite(w.addr&0x3f,w.val); writes.pop(); } int16_t samp[2]{0, 0};