From ab55a3f079dafa5b696fe7ca92d5c0b08affa066 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sat, 5 Mar 2022 16:02:01 +0700 Subject: [PATCH 01/23] 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 97f427372..bef7c5359 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 02/23] 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 caf08457f..f31213030 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 e1d97726e..540052fd3 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 b9d4b8f40..0b9ec0f4c 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 000000000..6ad0e8c97 --- /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 91fe92656..005739257 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 78aeb8cea..a42e0d061 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 000000000..c5c0bafdd --- /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 300b0033b..98a2843ea 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 52cc7177e..0bc6a2729 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 000000000..e02d63ec6 --- /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 000000000..b1b0af740 --- /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 000000000..df82376a4 --- /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 000000000..ea6466fbb --- /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 f0f43dc1a..fb3067dfe 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 3c988ceb8..fe45a4e78 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 bef7c5359..caa811481 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 fa8900380..4dd7b05ef 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 6ff30896f..6c7f06cbe 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 03/23] 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 df82376a4..4e7c63309 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 0e7a118a2..7b6f0b1e5 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 caa811481..c3388399f 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 4dd7b05ef..6f7740f91 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 93d160da5e6e5288cdfa749ffef945211985a341 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 00:24:50 -0500 Subject: [PATCH 04/23] OPLL: but it doesn't have LFOOOOOO --- src/engine/platform/opll.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 5159b3ea1..fd1d61d62 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -29,9 +29,6 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { switch (effect) { - case 0x10: - return "10xy: Setup LFO (x: enable; y: speed)"; - break; case 0x11: return "11xx: Set feedback (0 to 7)"; break; From 8f957baa3e07cbe593debfd9f532fbd96f3b027d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 01:48:48 -0500 Subject: [PATCH 05/23] dispatch: add function to notify playback stopped for the PC Speaker real driver --- src/engine/dispatch.h | 5 +++++ src/engine/engine.cpp | 3 +++ src/engine/platform/abstract.cpp | 4 ++++ src/engine/platform/pcspkr.cpp | 6 ++++++ src/engine/platform/pcspkr.h | 1 + 5 files changed, 19 insertions(+) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index e697cccbe..3c39b03b4 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -316,6 +316,11 @@ class DivDispatch { */ virtual void notifyInsDeletion(void* ins); + /** + * notify that playback stopped. + */ + virtual void notifyPlaybackStop(); + /** * force-retrigger instruments. */ diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a8b023d7d..75da62e0d 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -828,6 +828,9 @@ void DivEngine::stop() { sPreview.sample=-1; sPreview.wave=-1; sPreview.pos=0; + for (int i=0; inotifyPlaybackStop(); + } isBusy.unlock(); } diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index f61bc5be0..7b6115aef 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -97,6 +97,10 @@ void DivDispatch::notifyInsDeletion(void* ins) { } +void DivDispatch::notifyPlaybackStop() { + +} + void DivDispatch::forceIns() { } diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index f485bca8a..d8c34681e 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -344,6 +344,8 @@ void DivPlatformPCSpeaker::reset() { } #endif beepFreq(0); + } else { + beepFreq(0); } memset(regPool,0,2); @@ -365,6 +367,10 @@ void DivPlatformPCSpeaker::notifyInsDeletion(void* ins) { } } +void DivPlatformPCSpeaker::notifyPlaybackStop() { + beepFreq(0); +} + void DivPlatformPCSpeaker::poke(unsigned int addr, unsigned short val) { // ??? } diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h index 82e0f461d..17dedf247 100644 --- a/src/engine/platform/pcspkr.h +++ b/src/engine/platform/pcspkr.h @@ -82,6 +82,7 @@ class DivPlatformPCSpeaker: public DivDispatch { bool keyOffAffectsArp(int ch); void setFlags(unsigned int flags); void notifyInsDeletion(void* ins); + void notifyPlaybackStop(); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); From 165a8a4361e8dad6f0387ecf3139ae78b3eb348a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 01:54:28 -0500 Subject: [PATCH 06/23] PC speaker: register view one register :p --- src/engine/platform/pcspkr.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index d8c34681e..1ff8ae0c8 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -310,6 +310,13 @@ void* DivPlatformPCSpeaker::getChanState(int ch) { } unsigned char* DivPlatformPCSpeaker::getRegisterPool() { + if (on) { + regPool[0]=freq; + regPool[1]=freq>>8; + } else { + regPool[0]=0; + regPool[1]=0; + } return regPool; } From 119d815a165528c451d2995344d1b23ebf5855a5 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 15:44:15 +0700 Subject: [PATCH 07/23] 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 4e7c63309..1a842a227 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}; From 2453426d031ac04b4b3560b1b07b4542a009e9e0 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sat, 5 Mar 2022 16:02:01 +0700 Subject: [PATCH 08/23] 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 97f427372..bef7c5359 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 840a6fa306d654851ad9530a13b495b53e14d1e2 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sun, 6 Mar 2022 23:13:47 +0700 Subject: [PATCH 09/23] 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 caf08457f..f31213030 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 e1d97726e..540052fd3 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 b9d4b8f40..0b9ec0f4c 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 000000000..6ad0e8c97 --- /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 91fe92656..005739257 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 78aeb8cea..a42e0d061 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 000000000..c5c0bafdd --- /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 3c39b03b4..fe7c3ea34 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -109,6 +109,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 52cc7177e..0bc6a2729 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 000000000..e02d63ec6 --- /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 000000000..b1b0af740 --- /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 000000000..df82376a4 --- /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 000000000..ea6466fbb --- /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 7114a7c2a..6895fcc5d 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -251,6 +251,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 3c988ceb8..fe45a4e78 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 bef7c5359..caa811481 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 fb3e6fc31..04cf8ce49 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...")) { @@ -4979,6 +4980,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 6ff30896f..6c7f06cbe 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 25088d6032bacc0fed7053531f2cc7daaf217132 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 01:26:59 +0700 Subject: [PATCH 10/23] 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 df82376a4..4e7c63309 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 0e7a118a2..7b6f0b1e5 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 caa811481..c3388399f 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 04cf8ce49..98bbddb99 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6398,6 +6398,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 56be067af63c319c5ff5e0475e5cf558a80626f0 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 15:44:15 +0700 Subject: [PATCH 11/23] 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 4e7c63309..1a842a227 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}; From 8a924da586e9d60d85d61946251a22411269bd4c Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 18:55:25 +0700 Subject: [PATCH 12/23] Fix narrowing conversion error --- src/engine/safeWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/safeWriter.cpp b/src/engine/safeWriter.cpp index 7b6f0b1e5..f29800a4a 100644 --- a/src/engine/safeWriter.cpp +++ b/src/engine/safeWriter.cpp @@ -81,7 +81,7 @@ int SafeWriter::writeS(short val) { return write(&val,2); } int SafeWriter::writeS_BE(short val) { - unsigned char bytes[2]{(val>>8)&0xff, val&0xff}; + unsigned char bytes[2]{(unsigned char)((val>>8)&0xff), (unsigned char)(val&0xff)}; return write(bytes,2); } From b8ea64b8014a16792049ddfb98fc937582a0f649 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 19:04:20 +0700 Subject: [PATCH 13/23] Rename WS to Swan --- CMakeLists.txt | 4 +- src/engine/dispatchContainer.cpp | 4 +- .../platform/sound/{ws.cpp => swan.cpp} | 2 +- src/engine/platform/sound/{ws.h => swan.h} | 0 src/engine/platform/{ws.cpp => swan.cpp} | 46 +++++++++---------- src/engine/platform/{ws.h => swan.h} | 10 ++-- 6 files changed, 33 insertions(+), 33 deletions(-) rename src/engine/platform/sound/{ws.cpp => swan.cpp} (99%) rename src/engine/platform/sound/{ws.h => swan.h} (100%) rename src/engine/platform/{ws.cpp => swan.cpp} (91%) rename src/engine/platform/{ws.h => swan.h} (95%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f31213030..9edfc2528 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,7 +265,7 @@ src/engine/platform/sound/lynx/Mikey.cpp src/engine/platform/sound/qsound.c -src/engine/platform/sound/ws.cpp +src/engine/platform/sound/swan.cpp src/engine/platform/ym2610Interface.cpp @@ -309,7 +309,7 @@ src/engine/platform/pcspkr.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp src/engine/platform/lynx.cpp -src/engine/platform/ws.cpp +src/engine/platform/swan.cpp src/engine/platform/dummy.cpp ) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 0bc6a2729..8a597cbea 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -41,7 +41,7 @@ #include "platform/pcspkr.h" #include "platform/segapcm.h" #include "platform/qsound.h" -#include "platform/ws.h" +#include "platform/swan.h" #include "platform/dummy.h" #include "platform/lynx.h" #include "../ta-log.h" @@ -236,7 +236,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformSegaPCM; break; case DIV_SYSTEM_SWAN: - dispatch=new DivPlatformWS; + dispatch=new DivPlatformSwan; break; default: logW("this system is not supported yet! using dummy platform.\n"); diff --git a/src/engine/platform/sound/ws.cpp b/src/engine/platform/sound/swan.cpp similarity index 99% rename from src/engine/platform/sound/ws.cpp rename to src/engine/platform/sound/swan.cpp index e02d63ec6..60052f617 100644 --- a/src/engine/platform/sound/ws.cpp +++ b/src/engine/platform/sound/swan.cpp @@ -20,7 +20,7 @@ ** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "ws.h" +#include "swan.h" #include #define MK_SAMPLE_CACHE \ diff --git a/src/engine/platform/sound/ws.h b/src/engine/platform/sound/swan.h similarity index 100% rename from src/engine/platform/sound/ws.h rename to src/engine/platform/sound/swan.h diff --git a/src/engine/platform/ws.cpp b/src/engine/platform/swan.cpp similarity index 91% rename from src/engine/platform/ws.cpp rename to src/engine/platform/swan.cpp index 1a842a227..c86f2f70a 100644 --- a/src/engine/platform/ws.cpp +++ b/src/engine/platform/swan.cpp @@ -17,7 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "ws.h" +#include "swan.h" #include "../engine.h" #include @@ -46,11 +46,11 @@ const char* regCheatSheetWS[]={ NULL }; -const char** DivPlatformWS::getRegisterSheet() { +const char** DivPlatformSwan::getRegisterSheet() { return regCheatSheetWS; } -const char* DivPlatformWS::getEffectName(unsigned char effect) { +const char* DivPlatformSwan::getEffectName(unsigned char effect) { switch (effect) { case 0x10: return "10xx: Change waveform"; @@ -71,7 +71,7 @@ const char* DivPlatformWS::getEffectName(unsigned char effect) { return NULL; } -void DivPlatformWS::acquire(short* bufL, short* bufR, size_t start, size_t len) { +void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t h=start; hgetWave(chan[ch].wave); unsigned char addr=0x40+ch*16; if (wt->max<1 || wt->len<1) { @@ -125,7 +125,7 @@ void DivPlatformWS::updateWave(int ch) { } } -void DivPlatformWS::calcAndWriteOutVol(int ch, int env) { +void DivPlatformSwan::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) { @@ -138,7 +138,7 @@ void DivPlatformWS::calcAndWriteOutVol(int ch, int env) { writeOutVol(ch); } -void DivPlatformWS::writeOutVol(int ch) { +void DivPlatformSwan::writeOutVol(int ch) { unsigned char val=isMuted[ch]?0:chan[ch].outVol; if (ch==1&&pcm) { rWrite(0x14,val) @@ -147,7 +147,7 @@ void DivPlatformWS::writeOutVol(int ch) { } } -void DivPlatformWS::tick() { +void DivPlatformSwan::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(); @@ -230,7 +230,7 @@ void DivPlatformWS::tick() { rWrite(0x10,sndCtrl); } -int DivPlatformWS::dispatch(DivCommand c) { +int DivPlatformSwan::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); @@ -419,12 +419,12 @@ int DivPlatformWS::dispatch(DivCommand c) { return 1; } -void DivPlatformWS::muteChannel(int ch, bool mute) { +void DivPlatformSwan::muteChannel(int ch, bool mute) { isMuted[ch]=mute; writeOutVol(ch); } -void DivPlatformWS::forceIns() { +void DivPlatformSwan::forceIns() { for (int i=0; i<4; i++) { chan[i].insChanged=true; chan[i].freqChanged=true; @@ -433,22 +433,22 @@ void DivPlatformWS::forceIns() { } } -void* DivPlatformWS::getChanState(int ch) { +void* DivPlatformSwan::getChanState(int ch) { return &chan[ch]; } -unsigned char* DivPlatformWS::getRegisterPool() { +unsigned char* DivPlatformSwan::getRegisterPool() { // get Random from emulator regPool[0x12]=ws->SoundRead(0x92); regPool[0x13]=ws->SoundRead(0x93); return regPool; } -int DivPlatformWS::getRegisterPoolSize() { +int DivPlatformSwan::getRegisterPoolSize() { return 128; } -void DivPlatformWS::reset() { +void DivPlatformSwan::reset() { while (!writes.empty()) writes.pop(); memset(regPool,0,128); for (int i=0; i<4; i++) { @@ -474,11 +474,11 @@ void DivPlatformWS::reset() { rWrite(0x11,0x09); // enable speakers } -bool DivPlatformWS::isStereo() { +bool DivPlatformSwan::isStereo() { return true; } -void DivPlatformWS::notifyWaveChange(int wave) { +void DivPlatformSwan::notifyWaveChange(int wave) { for (int i=0; i<4; i++) { if (chan[i].wave==wave) { updateWave(i); @@ -486,21 +486,21 @@ void DivPlatformWS::notifyWaveChange(int wave) { } } -void DivPlatformWS::notifyInsDeletion(void* ins) { +void DivPlatformSwan::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) { +void DivPlatformSwan::poke(unsigned int addr, unsigned short val) { rWrite(addr,val); } -void DivPlatformWS::poke(std::vector& wlist) { +void DivPlatformSwan::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) { +int DivPlatformSwan::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; skipRegisterWrites=false; @@ -514,9 +514,9 @@ int DivPlatformWS::init(DivEngine* p, int channels, int sugRate, unsigned int fl return 4; } -void DivPlatformWS::quit() { +void DivPlatformSwan::quit() { delete ws; } -DivPlatformWS::~DivPlatformWS() { +DivPlatformSwan::~DivPlatformSwan() { } diff --git a/src/engine/platform/ws.h b/src/engine/platform/swan.h similarity index 95% rename from src/engine/platform/ws.h rename to src/engine/platform/swan.h index ea6466fbb..47470f57b 100644 --- a/src/engine/platform/ws.h +++ b/src/engine/platform/swan.h @@ -17,15 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef _WS_H -#define _WS_H +#ifndef _SWAN_H +#define _SWAN_H #include "../dispatch.h" #include "../macroInt.h" -#include "sound/ws.h" +#include "sound/swan.h" #include -class DivPlatformWS: public DivDispatch { +class DivPlatformSwan: public DivDispatch { struct Channel { int freq, baseFreq, pitch, note; unsigned char ins, pan; @@ -86,7 +86,7 @@ class DivPlatformWS: public DivDispatch { const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); - ~DivPlatformWS(); + ~DivPlatformSwan(); private: void calcAndWriteOutVol(int ch, int env); void writeOutVol(int ch); From bbaf31d0c21c2ecb50ecc90f26a6f11a5fc6167a Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 19:08:18 +0700 Subject: [PATCH 14/23] Make register view work again --- src/engine/platform/swan.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index c86f2f70a..962fbbd1e 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -97,6 +97,7 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len // the rest while (!writes.empty()) { QueuedWrite w=writes.front(); + 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(); From 7704dc0d79d1916bf59b8facfac8e0b39743556a Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 23:49:52 +0700 Subject: [PATCH 15/23] Fix volume calculation sometimes not working --- src/engine/platform/swan.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 962fbbd1e..e7cefc464 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -152,7 +152,7 @@ void DivPlatformSwan::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) { + if (chan[i].std.willVol) { int env=chan[i].std.vol; if(parent->getIns(chan[i].ins)->type==DIV_INS_AMIGA) { env=MIN(env/4,15); @@ -204,7 +204,7 @@ void DivPlatformSwan::tick() { rWrite(i*2,rVal&0xff); rWrite(i*2+1,rVal>>8); if (chan[i].keyOn) { - if (!chan[i].std.hasVol) { + if (!chan[i].std.willVol) { calcAndWriteOutVol(i,15); } if (chan[i].wave<0) { @@ -320,7 +320,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.willVol) { calcAndWriteOutVol(c.chan,15); } } @@ -392,7 +392,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { break; case DIV_CMD_PANNING: { chan[c.chan].pan=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.willVol) { calcAndWriteOutVol(c.chan,15); } break; From 27758434af1a80afe7935ab0150de30e13938830 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 17:07:00 -0500 Subject: [PATCH 16/23] update format.md to parse op count --- papers/format.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/papers/format.md b/papers/format.md index ff068d830..7bc30abe0 100644 --- a/papers/format.md +++ b/papers/format.md @@ -248,7 +248,9 @@ size | description 1 | feedback 1 | fms 1 | ams - 1 | operator count (always 4) + 1 | operator count + | - this is either 2 or 4, and is ignored on non-OPL systems. + | - always read 4 ops regardless of this value. 1 | OPLL preset (>=60) or reserved | - 0: custom | - 1-15: pre-defined patches From 03d2f87804c009d78b7c1c435b436dc41f27db33 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 17:07:29 -0500 Subject: [PATCH 17/23] OPL: some work - still does not work --- src/engine/dispatchContainer.cpp | 21 +++++ src/engine/platform/opl.cpp | 150 ++++++++++++++++++------------- src/engine/platform/opl.h | 15 ++-- 3 files changed, 114 insertions(+), 72 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 52cc7177e..0b4c9767f 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -211,8 +211,29 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformOPLL*)dispatch)->setVRC7(sys==DIV_SYSTEM_VRC7); ((DivPlatformOPLL*)dispatch)->setProperDrums(sys==DIV_SYSTEM_OPLL_DRUMS); break; + case DIV_SYSTEM_OPL: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(1,false); + break; + case DIV_SYSTEM_OPL_DRUMS: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(1,true); + break; + case DIV_SYSTEM_OPL2: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(2,false); + break; + case DIV_SYSTEM_OPL2_DRUMS: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(2,true); + break; case DIV_SYSTEM_OPL3: dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(3,false); + break; + case DIV_SYSTEM_OPL3_DRUMS: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(3,true); break; case DIV_SYSTEM_SAA1099: { int saaCore=eng->getConfInt("saaCore",0); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index ebf7dc927..50cdda389 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -44,7 +44,7 @@ const unsigned char slotsOPL2Drums[4][20]={ {N, N, N, N, N, N, N, N, N, N, N} }; -const unsigned char chanMapOPL2[20]={ +const unsigned short chanMapOPL2[20]={ 0, 1, 2, 3, 4, 5, 6, 7, 8, N, N, N, N, N, N, N, N, N, N, N }; @@ -62,12 +62,51 @@ const unsigned char slotsOPL3Drums[4][20]={ {9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N, N, N} // OP4 }; -const unsigned char chanMapOPL3[20]={ - 0, 3, 1, 4, 2, 5, 9, 12, 10, 13, 11, 14, 15, 16, 17, 6, 7, 8, N, N +const unsigned short chanMapOPL3[20]={ + 0, 3, 1, 4, 2, 5, 0x100, 0x103, 0x101, 0x104, 0x102, 0x105, 0x106, 0x107, 0x108, 6, 7, 8, N, N +}; + +const unsigned int slotMap[36]={ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, + + 0x100, 0x101, 0x102, 0x103, 0x104, 0x105, + 0x108, 0x109, 0x10a, 0x10b, 0x10c, 0x10d, + 0x110, 0x111, 0x112, 0x113, 0x114, 0x115, +}; + +const bool isOutputL[2][4][4]={ + { // 2-op + {false, true, false, false}, // 1 > 2 + { true, true, false, false}, // 1 + 2 + {false, true, false, false}, // ditto, 0 + { true, true, false, false}, // ditto, 1 + }, + { // 4-op + {false, false, false, true}, // 1 > 2 > 3 > 4 + { true, false, false, true}, // 1 + (2 > 3 > 4) + {false, true, false, true}, // (1 > 2) + (3 > 4) + { true, false, true, true} // 1 + (2 > 3) + 4 + } }; #undef N +const int orderedOpsL[4]={ + 0,2,1,3 +}; + +#define ADDR_AM_VIB_SUS_KSR_MULT 0x20 +#define ADDR_KSL_TL 0x40 +#define ADDR_AR_DR 0x60 +#define ADDR_SL_RR 0x80 +#define ADDR_WS 0xe0 + +#define ADDR_FREQ 0xa0 +#define ADDR_FREQH 0xb0 +#define ADDR_LR_FB_ALG 0xc0 + const char* DivPlatformOPL::getEffectName(unsigned char effect) { switch (effect) { case 0x10: @@ -409,38 +448,43 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (!chan[c.chan].std.willVol) { chan[c.chan].outVol=chan[c.chan].vol; } - - /* - for (int i=0; i<4; i++) { - unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; - DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isMuted[c.chan]) { - rWrite(baseAddr+ADDR_TL,127); - } else { - if (isOutput[chan[c.chan].state.alg][i]) { - if (!chan[c.chan].active || chan[c.chan].insChanged) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); - } + if (chan[c.chan].insChanged) { + int ops=(slots[3][c.chan]!=255 && ins->fm.ops==4)?4:2; + for (int i=0; i1) { + rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3)); + } } - if (chan[c.chan].insChanged) { - rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); - rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); - rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); - rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); - rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); - rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + + if (isMuted[c.chan]) { + rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)); + if (ops==4) { + rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)); + } + } else { + rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4)); + if (ops==4) { + rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4)); + } } } - if (chan[c.chan].insChanged) { - rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); - rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); - } - */ + chan[c.chan].insChanged=false; if (c.value!=DIV_NOTE_NULL) { @@ -453,19 +497,11 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_OFF: - if (c.chan==5) { - dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; break; case DIV_CMD_NOTE_OFF_ENV: - if (c.chan==5) { - dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; @@ -558,17 +594,6 @@ int DivPlatformOPL::dispatch(DivCommand c) { } break; } - case DIV_CMD_SAMPLE_MODE: { - dacMode=c.value; - rWrite(0x2b,c.value<<7); - 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_LEGATO: { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].note=c.value; @@ -724,18 +749,12 @@ void DivPlatformOPL::reset() { } lastBusy=60; - dacMode=0; - dacPeriod=0; - dacPos=0; - dacRate=0; - dacSample=-1; - sampleBank=0; lfoValue=8; + properDrums=properDrumsSys; - extMode=false; - - // LFO - immWrite(0x22,lfoValue); + if (oplType==3) { // enable OPL3 features + immWrite(0x105,1); + } delay=0; } @@ -779,20 +798,23 @@ void DivPlatformOPL::setYMFM(bool use) { useYMFM=use; } -void DivPlatformOPL::setOPLType(int type) { +void DivPlatformOPL::setOPLType(int type, bool drums) { switch (type) { case 1: case 2: slotsNonDrums=(const unsigned char**)slotsOPL2; slotsDrums=(const unsigned char**)slotsOPL2Drums; + slots=drums?slotsDrums:slotsNonDrums; chanMap=chanMapOPL2; break; case 3: slotsNonDrums=(const unsigned char**)slotsOPL3; slotsDrums=(const unsigned char**)slotsOPL3Drums; + slots=drums?slotsDrums:slotsNonDrums; chanMap=chanMapOPL3; break; } oplType=type; + properDrumsSys=drums; } void DivPlatformOPL::setFlags(unsigned int flags) { @@ -820,14 +842,18 @@ void DivPlatformOPL::setFlags(unsigned int flags) { rate=chipClock/36; }*/ - chipClock=COLOR_NTSC*4.0; - rate=chipClock/32; + if (oplType==3) { + chipClock=COLOR_NTSC*4.0; + rate=chipClock/36; + } else { + chipClock=COLOR_NTSC; + rate=chipClock/9; + } } int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; - ladder=false; skipRegisterWrites=false; for (int i=0; i<20; i++) { isMuted[i]=false; diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index ab6226a45..32913a8d6 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -67,22 +67,17 @@ class DivPlatformOPL: public DivDispatch { const unsigned char** slotsNonDrums; const unsigned char** slotsDrums; const unsigned char** slots; - const unsigned char* chanMap; + const unsigned short* chanMap; int delay, oplType; unsigned char lastBusy; unsigned char regPool[512]; - bool dacMode; - int dacPeriod; - int dacRate; - unsigned int dacPos; - int dacSample; - unsigned char sampleBank; + bool properDrums, properDrumsSys; + unsigned char lfoValue; - bool extMode, useYMFM; - bool ladder; + bool useYMFM; short oldWrites[512]; short pendingWrites[512]; @@ -107,7 +102,7 @@ class DivPlatformOPL: public DivDispatch { void muteChannel(int ch, bool mute); bool isStereo(); void setYMFM(bool use); - void setOPLType(int type); + void setOPLType(int type, bool drums); bool keyOffAffectsArp(int ch); bool keyOffAffectsPorta(int ch); void toggleRegisterDump(bool enable); From ec007b44430825ec4f89d196988af00018012297 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 18:19:25 -0500 Subject: [PATCH 18/23] OPL: more work - still not there yet --- src/engine/platform/opl.cpp | 95 +++++++++++++++++++------------------ src/gui/gui.cpp | 12 +++++ src/gui/insEdit.cpp | 17 ++++++- 3 files changed, 77 insertions(+), 47 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 50cdda389..fbcc643bb 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -30,14 +30,14 @@ // N = invalid #define N 255 -const unsigned char slotsOPL2[4][20]={ +const unsigned char slotsOPL2i[4][20]={ {0, 1, 2, 6, 7, 8, 12, 13, 14}, // OP1 {3, 4, 5, 9, 10, 11, 15, 16, 17}, // OP2 {N, N, N, N, N, N, N, N, N}, {N, N, N, N, N, N, N, N, N} }; -const unsigned char slotsOPL2Drums[4][20]={ +const unsigned char slotsOPL2Drumsi[4][20]={ {0, 1, 2, 6, 7, 8, 12, 16, 14, 17, 13}, // OP1 {3, 4, 5, 9, 10, 11, 15, N, N, N, N}, // OP2 {N, N, N, N, N, N, N, N, N, N, N}, @@ -48,14 +48,28 @@ const unsigned short chanMapOPL2[20]={ 0, 1, 2, 3, 4, 5, 6, 7, 8, N, N, N, N, N, N, N, N, N, N, N }; -const unsigned char slotsOPL3[4][20]={ +const unsigned char* slotsOPL2[4]={ + slotsOPL2i[0], + slotsOPL2i[1], + slotsOPL2i[2], + slotsOPL2i[3] +}; + +const unsigned char* slotsOPL2Drums[4]={ + slotsOPL2Drumsi[0], + slotsOPL2Drumsi[1], + slotsOPL2Drumsi[2], + slotsOPL2Drumsi[3] +}; + +const unsigned char slotsOPL3i[4][20]={ {0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 13, 14}, // OP1 {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, 15, 16, 17}, // OP2 {6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N}, // OP3 {9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N} // OP4 }; -const unsigned char slotsOPL3Drums[4][20]={ +const unsigned char slotsOPL3Drumsi[4][20]={ {0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 16, 14, 17, 13}, // OP1 {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, N, N, N, N, N}, // OP2 {6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N, N, N}, // OP3 @@ -66,6 +80,20 @@ const unsigned short chanMapOPL3[20]={ 0, 3, 1, 4, 2, 5, 0x100, 0x103, 0x101, 0x104, 0x102, 0x105, 0x106, 0x107, 0x108, 6, 7, 8, N, N }; +const unsigned char* slotsOPL3[4]={ + slotsOPL3i[0], + slotsOPL3i[1], + slotsOPL3i[2], + slotsOPL3i[3] +}; + +const unsigned char* slotsOPL3Drums[4]={ + slotsOPL3Drumsi[0], + slotsOPL3Drumsi[1], + slotsOPL3Drumsi[2], + slotsOPL3Drumsi[3] +}; + const unsigned int slotMap[36]={ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, @@ -167,18 +195,8 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ if (!writes.empty() && --delay<0) { delay=12; QueuedWrite& w=writes.front(); - if (w.addrOrVal) { - OPL3_WriteReg(&fm,0x1+((w.addr>>8)<<1),w.val); - //printf("write: %x = %.2x\n",w.addr,w.val); - lastBusy=0; - regPool[w.addr&0x1ff]=w.val; - writes.pop(); - } else { - lastBusy++; - //printf("busycounter: %d\n",lastBusy); - OPL3_WriteReg(&fm,0x0+((w.addr>>8)<<1),w.addr); - w.addrOrVal=true; - } + OPL3_WriteReg(&fm,w.addr,w.val); + writes.pop(); } OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1]; @@ -203,11 +221,10 @@ void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) } void DivPlatformOPL::tick() { - /* for (int i=0; i<20; i++) { - if (i==2 && extMode) continue; chan[i].std.next(); + /* if (chan[i].std.hadVol) { chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; for (int j=0; j<4; j++) { @@ -327,13 +344,13 @@ void DivPlatformOPL::tick() { rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } } + */ if (chan[i].keyOn || chan[i].keyOff) { - immWrite(0x28,0x00|konOffs[i]); + immWrite(chanMap[i]+ADDR_FREQH,0x00|(chan[i].freqH&31)); chan[i].keyOff=false; } } - */ for (int i=0; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { @@ -342,36 +359,23 @@ void DivPlatformOPL::tick() { } } - /* for (int i=0; i<20; i++) { if (chan[i].freqChanged) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); if (chan[i].freq>262143) chan[i].freq=262143; int freqt=toFreq(chan[i].freq); - immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); - immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); - if (chan[i].furnaceDac && dacMode) { - 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=(1280000*1.25*off)/MAX(1,chan[i].baseFreq); - if (dacRate<1) dacRate=1; - if (dumpWrites) addWrite(0xffff0001,1280000/dacRate); - } - chan[i].freqChanged=false; + chan[i].freqH=freqt>>8; + chan[i].freqL=freqt&0xff; + immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL); } if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(0x20)); chan[i].keyOn=false; + } else if (chan[i].freqChanged) { + immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5)); } + chan[i].freqChanged=false; } - */ } int DivPlatformOPL::octave(int freq) { @@ -395,6 +399,7 @@ int DivPlatformOPL::octave(int freq) { return 1; } +// TODO int DivPlatformOPL::toFreq(int freq) { if (freq>=82432) { return 0x3800|((freq>>7)&0x7ff); @@ -659,7 +664,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { return 0; break; case DIV_CMD_GET_VOLMAX: - return 127; + return 63; break; case DIV_CMD_PRE_PORTA: chan[c.chan].inPorta=c.value; @@ -801,14 +806,14 @@ void DivPlatformOPL::setYMFM(bool use) { void DivPlatformOPL::setOPLType(int type, bool drums) { switch (type) { case 1: case 2: - slotsNonDrums=(const unsigned char**)slotsOPL2; - slotsDrums=(const unsigned char**)slotsOPL2Drums; + slotsNonDrums=slotsOPL2; + slotsDrums=slotsOPL2Drums; slots=drums?slotsDrums:slotsNonDrums; chanMap=chanMapOPL2; break; case 3: - slotsNonDrums=(const unsigned char**)slotsOPL3; - slotsDrums=(const unsigned char**)slotsOPL3Drums; + slotsNonDrums=slotsOPL3; + slotsDrums=slotsOPL3Drums; slots=drums?slotsDrums:slotsNonDrums; chanMap=chanMapOPL3; break; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fb3e6fc31..06f102de1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4629,6 +4629,12 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_OPLL); sysAddOption(DIV_SYSTEM_OPLL_DRUMS); sysAddOption(DIV_SYSTEM_VRC7); + sysAddOption(DIV_SYSTEM_OPL); + sysAddOption(DIV_SYSTEM_OPL_DRUMS); + sysAddOption(DIV_SYSTEM_OPL2); + sysAddOption(DIV_SYSTEM_OPL2_DRUMS); + sysAddOption(DIV_SYSTEM_OPL3); + sysAddOption(DIV_SYSTEM_OPL3_DRUMS); sysAddOption(DIV_SYSTEM_TIA); sysAddOption(DIV_SYSTEM_SAA1099); sysAddOption(DIV_SYSTEM_AY8930); @@ -4974,6 +4980,12 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_OPLL); sysChangeOption(i,DIV_SYSTEM_OPLL_DRUMS); sysChangeOption(i,DIV_SYSTEM_VRC7); + sysChangeOption(i,DIV_SYSTEM_OPL); + sysChangeOption(i,DIV_SYSTEM_OPL_DRUMS); + sysChangeOption(i,DIV_SYSTEM_OPL2); + sysChangeOption(i,DIV_SYSTEM_OPL2_DRUMS); + sysChangeOption(i,DIV_SYSTEM_OPL3); + sysChangeOption(i,DIV_SYSTEM_OPL3_DRUMS); sysChangeOption(i,DIV_SYSTEM_TIA); sysChangeOption(i,DIV_SYSTEM_SAA1099); sysChangeOption(i,DIV_SYSTEM_AY8930); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 6ff30896f..b05f315ef 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -796,7 +796,8 @@ void FurnaceGUI::drawInsEdit() { int asInt[256]; float loopIndicator[256]; int opCount=4; - if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL) opCount=2; + if (ins->type==DIV_INS_OPLL) opCount=2; + if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2; if (ImGui::BeginTabItem("FM")) { if (ImGui::BeginTable("fmDetails",3,ImGuiTableFlags_SizingStretchSame)) { @@ -816,7 +817,19 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); break; - case DIV_INS_OPL: + case DIV_INS_OPL: { + bool fourOp=(ins->fm.ops==4); + ImGui::TableNextColumn(); + P(ImGui::SliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable + if (ImGui::Checkbox("4-op",&fourOp)) { PARAMETER + ins->fm.ops=fourOp?4:2; + } + ImGui::TableNextColumn(); + P(ImGui::SliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); rightClickable + ImGui::TableNextColumn(); + drawAlgorithm(ins->fm.alg&1,FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + break; + } case DIV_INS_OPLL: { bool dc=ins->fm.fms; bool dm=ins->fm.ams; From cdd45bb18c3e5d0842bdd6c87f8469f3a45bd1ef Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 22:28:20 -0500 Subject: [PATCH 19/23] allow detune 4 --- src/engine/platform/arcade.cpp | 2 +- src/engine/platform/genesisshared.h | 4 ++-- src/engine/platform/ym2610shared.h | 2 +- src/gui/insEdit.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 1af99eb75..0f13be178 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -42,7 +42,7 @@ static bool isOutput[8][4]={ {true ,true ,true ,true}, }; static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,0 + 7,6,5,0,1,2,3,4 }; static int orderedOps[4]={ diff --git a/src/engine/platform/genesisshared.h b/src/engine/platform/genesisshared.h index 22e4cd826..d2402a60d 100644 --- a/src/engine/platform/genesisshared.h +++ b/src/engine/platform/genesisshared.h @@ -35,7 +35,7 @@ static bool isOutput[8][4]={ {true ,true ,true ,true}, }; static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,0 + 7,6,5,0,1,2,3,4 }; static int orderedOps[4]={ @@ -45,4 +45,4 @@ static int orderedOps[4]={ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } -#include "fmshared_OPN.h" \ No newline at end of file +#include "fmshared_OPN.h" diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index c41d5520a..8d8847c2e 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -32,7 +32,7 @@ static bool isOutput[8][4]={ {true ,true ,true ,true}, }; static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,0 + 7,6,5,0,1,2,3,4 }; static int orderedOps[4]={ diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index b05f315ef..dba0b1bae 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1077,7 +1077,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::SliderInt("##DT",&detune,-3,3)) { PARAMETER + if (ImGui::SliderInt("##DT",&detune,-3,4)) { PARAMETER op.dt=detune+3; } rightClickable ImGui::TableNextColumn(); From 36db137e8f6d630d73fbf3d108ff5bc0649f5e2a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 22:28:33 -0500 Subject: [PATCH 20/23] OPL: absolute mess up now it kinda works --- src/engine/platform/opl.cpp | 50 +++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index fbcc643bb..ec8f03b34 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -196,6 +196,7 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ delay=12; QueuedWrite& w=writes.front(); OPL3_WriteReg(&fm,w.addr,w.val); + regPool[w.addr&511]=w.val; writes.pop(); } @@ -378,20 +379,22 @@ void DivPlatformOPL::tick() { } } +#define OPLL_C_NUM 686 + int DivPlatformOPL::octave(int freq) { - if (freq>=82432) { + if (freq>=OPLL_C_NUM*64) { return 128; - } else if (freq>=41216) { + } else if (freq>=OPLL_C_NUM*32) { return 64; - } else if (freq>=20608) { + } else if (freq>=OPLL_C_NUM*16) { return 32; - } else if (freq>=10304) { + } else if (freq>=OPLL_C_NUM*8) { return 16; - } else if (freq>=5152) { + } else if (freq>=OPLL_C_NUM*4) { return 8; - } else if (freq>=2576) { + } else if (freq>=OPLL_C_NUM*2) { return 4; - } else if (freq>=1288) { + } else if (freq>=OPLL_C_NUM) { return 2; } else { return 1; @@ -399,24 +402,23 @@ int DivPlatformOPL::octave(int freq) { return 1; } -// TODO int DivPlatformOPL::toFreq(int freq) { - if (freq>=82432) { - return 0x3800|((freq>>7)&0x7ff); - } else if (freq>=41216) { - return 0x3000|((freq>>6)&0x7ff); - } else if (freq>=20608) { - return 0x2800|((freq>>5)&0x7ff); - } else if (freq>=10304) { - return 0x2000|((freq>>4)&0x7ff); - } else if (freq>=5152) { - return 0x1800|((freq>>3)&0x7ff); - } else if (freq>=2576) { - return 0x1000|((freq>>2)&0x7ff); - } else if (freq>=1288) { - return 0x800|((freq>>1)&0x7ff); + if (freq>=OPLL_C_NUM*64) { + return 0x1c00|((freq>>7)&0x3ff); + } else if (freq>=OPLL_C_NUM*32) { + return 0x1800|((freq>>6)&0x3ff); + } else if (freq>=OPLL_C_NUM*16) { + return 0x1400|((freq>>5)&0x3ff); + } else if (freq>=OPLL_C_NUM*8) { + return 0x1000|((freq>>4)&0x3ff); + } else if (freq>=OPLL_C_NUM*4) { + return 0xc00|((freq>>3)&0x3ff); + } else if (freq>=OPLL_C_NUM*2) { + return 0x800|((freq>>2)&0x3ff); + } else if (freq>=OPLL_C_NUM) { + return 0x400|((freq>>1)&0x3ff); } else { - return freq&0x7ff; + return freq&0x3ff; } } @@ -456,7 +458,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { int ops=(slots[3][c.chan]!=255 && ins->fm.ops==4)?4:2; for (int i=0; i Date: Mon, 7 Mar 2022 22:52:32 -0500 Subject: [PATCH 21/23] OPL: it's coming together --- src/engine/instrument.cpp | 2 +- src/engine/platform/opl.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index bdf028905..b22fdced2 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -39,7 +39,7 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(fm.fb); w->writeC(fm.fms); w->writeC(fm.ams); - w->writeC(4); // operator count; always 4 + w->writeC(fm.ops); w->writeC(fm.opllPreset); w->writeC(0); // reserved w->writeC(0); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index ec8f03b34..2c005ee90 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -25,7 +25,7 @@ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } -#define CHIP_FREQBASE 4720272 +#define CHIP_FREQBASE 9440540 // N = invalid #define N 255 @@ -193,7 +193,7 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ for (size_t h=start; hcalcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); - if (chan[i].freq>262143) chan[i].freq=262143; + if (chan[i].freq>131071) chan[i].freq=131071; int freqt=toFreq(chan[i].freq); chan[i].freqH=freqt>>8; chan[i].freqL=freqt&0xff; @@ -851,10 +851,10 @@ void DivPlatformOPL::setFlags(unsigned int flags) { if (oplType==3) { chipClock=COLOR_NTSC*4.0; - rate=chipClock/36; + rate=chipClock/288; } else { chipClock=COLOR_NTSC; - rate=chipClock/9; + rate=chipClock/72; } } From 09655f7d57e3233908b36228b73d6613f233ae8e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 23:09:42 -0500 Subject: [PATCH 22/23] WonderSwan: fix build --- src/engine/platform/swan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index e7cefc464..8fdca1d40 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -222,7 +222,7 @@ void DivPlatformSwan::tick() { if (chan[3].std.hadDuty) { noise=chan[3].std.duty; if (noise>0) { - rWrite(0x0e,(noise-1)&0x07|0x18); + rWrite(0x0e,((noise-1)&0x07)|0x18); sndCtrl|=0x80; } else { sndCtrl&=~0x80; @@ -378,7 +378,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { case DIV_CMD_STD_NOISE_MODE: if (c.chan==3) { noise=c.value&0xff; - if (noise>0) rWrite(0x0e,(noise-1)&0x07|0x18); + if (noise>0) rWrite(0x0e,((noise-1)&0x07)|0x18); } break; case DIV_CMD_SAMPLE_MODE: From 2d922d5e0981e5fbce8a052f076ca8bf0f17ec40 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 23:11:14 -0500 Subject: [PATCH 23/23] GUI: attribution --- README.md | 1 - src/gui/gui.cpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 540052fd3..e1d97726e 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,6 @@ 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/src/gui/gui.cpp b/src/gui/gui.cpp index 7245f2f30..9cdc1cefe 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1551,6 +1551,7 @@ const char* aboutLine[]={ "", "-- program --", "tildearrow", + "akumanatt", "cam900", "laoo", "superctr",