From 5fdce33b11e77dfbc6245e2003d6c3708ec9bc25 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 23 Mar 2022 01:48:45 +0900 Subject: [PATCH 1/8] Add Namco 163 Support --- CMakeLists.txt | 3 + papers/doc/4-instrument/README.md | 1 + papers/doc/4-instrument/n163.md | 14 + papers/doc/5-wave/README.md | 2 +- papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/n163.md | 25 + src/engine/dispatch.h | 13 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/n163.cpp | 642 ++++++++++++++++++++++++ src/engine/platform/n163.h | 107 ++++ src/engine/platform/sound/n163/n163.cpp | 137 +++++ src/engine/platform/sound/n163/n163.hpp | 78 +++ src/engine/playback.cpp | 57 +++ src/gui/debug.cpp | 31 ++ src/gui/gui.cpp | 90 +++- src/gui/insEdit.cpp | 26 +- 16 files changed, 1225 insertions(+), 6 deletions(-) create mode 100644 papers/doc/4-instrument/n163.md create mode 100644 papers/doc/7-systems/n163.md create mode 100644 src/engine/platform/n163.cpp create mode 100644 src/engine/platform/n163.h create mode 100644 src/engine/platform/sound/n163/n163.cpp create mode 100644 src/engine/platform/sound/n163/n163.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6116dba8..62a1452f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -273,6 +273,8 @@ src/engine/platform/sound/swan.cpp src/engine/platform/sound/k005289/k005289.cpp +src/engine/platform/sound/n163/n163.cpp + src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c @@ -320,6 +322,7 @@ src/engine/platform/lynx.cpp src/engine/platform/swan.cpp src/engine/platform/vera.cpp src/engine/platform/bubsyswsg.cpp +src/engine/platform/n163.cpp src/engine/platform/dummy.cpp ) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index ab03e836..2cf4d25a 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -27,6 +27,7 @@ depending on the instrument type, there are currently 13 different types of an i - [VERA](vera.md) - for use with Commander X16 VERA. - [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010. - [Konami SCC/Bubble System WSG](scc.md) - for use with Konami SCC and Wavetable portion in Bubble System's sound hardware. +- [Namco 163](n163.md) - for use with Namco 163. # macros diff --git a/papers/doc/4-instrument/n163.md b/papers/doc/4-instrument/n163.md new file mode 100644 index 00000000..ccf2c46e --- /dev/null +++ b/papers/doc/4-instrument/n163.md @@ -0,0 +1,14 @@ +# Namco 163 instrument editor + +Namco 163 instrument editor consists of 10 macros. + +- [Volume] - volume levels sequence +- [Arpeggio]- pitch sequence +- [Waveform pos.] - sets the waveform source address in RAM for playback (single nibble unit) +- [Waveform] - sets waveform source for playback immediately or update later +- [Waveform len.] - sets the waveform source length for playback (4 nibble unit) +- [Waveform update] - sets the waveform update trigger behavior for playback +- [Waveform to load] - sets waveform source for load to RAM immediately or later +- [Wave pos. to load] - sets address of waveform for load to RAM (single nibble unit) +- [Wave len. to load] - sets length of waveform for load to RAM (4 nibble unit) +- [Waveform load] - sets the waveform load trigger behavior diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index f6dd9b94..0668c0bf 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -2,4 +2,4 @@ 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: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS and Bubble System, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints 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, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System and N163, and 32-level height for PCE. If larger wave will be defined for these 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 5413b0b5..27e87924 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -21,5 +21,6 @@ this is a list of systems that Furnace supports, including each system's effects - [Seta/Allumer X1-010](x1_010.md) - [WonderSwan](wonderswan.md) - [Bubble System WSG](bubblesystem.md) +- [Namco 163](n163.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/n163.md b/papers/doc/7-systems/n163.md new file mode 100644 index 00000000..80a2d28d --- /dev/null +++ b/papers/doc/7-systems/n163.md @@ -0,0 +1,25 @@ +# Namco 163 + +This is Namco's one of NES mapper, with up to 8 wavetable channels. It has also 128 byte of internal RAM, both channel register and wavetables are stored here. Wavetables are variable size and freely allocable anywhere in RAM, it means it can be uses part of or merged 2 or more pre-loaded waveforms in RAM. But waveform RAM area becomes smaller as much as activating more channels; Channel register consumes 8 byte for each channels. You must avoid conflict with channel register area and waveform for avoid channel playback broken. + +It has can be outputs only single channel at clock; so it's sound quality is more crunchy as much as activating more channels. + +Furnace supports both load waveform into RAM and waveform playback simultaneously, and channel limit is dynamically changeable with effect commands. +You must load waveform to RAM first for playback or do something, its load behavior is changeable to auto-update when every waveform changes or manual update. +Both waveform playback and load command is works independently per each channel columns, (Global) commands are don't care about the channel columns for work commands and its load behavior is independent with per-channel column load commands. + +# effects + +- `10xx`: set waveform for playback. +- `11xx`: set waveform position in RAM for playback. (single nibble unit) +- `12xx`: set waveform length in RAM for playback. (04 to FC, 4 nibble unit) +- `130x`: set playback waveform update behavior. (0: off, bit 0: update now, bit 1: update when every waveform is changed) +- `14xx`: set waveform for load to RAM. +- `15xx`: set waveform position for load to RAM. (single nibble unit) +- `16xx`: set waveform length for load to RAM. (04 to FC, 4 nibble unit) +- `170x`: set waveform load behavior. (0: off, bit 0: load now, bit 1: load when every waveform is changed) +- `180x`: set channel limit (0 to 7, x + 1) +- `20xx`: (Global) set waveform for load to RAM. +- `21xx`: (Global) set waveform position for load to RAM. (single nibble unit) +- `22xx`: (Global) set waveform length for load to RAM. (04 to FC, 4 nibble unit) +- `230x`: (Global) set waveform load behavior. (0: off, bit 0: load now, bit 1: load when every waveform is changed) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 0ec3a295..272045b3 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -120,6 +120,19 @@ enum DivDispatchCmds { DIV_CMD_WS_SWEEP_TIME, DIV_CMD_WS_SWEEP_AMOUNT, + DIV_CMD_N163_WAVE_POSITION, + DIV_CMD_N163_WAVE_LENGTH, + DIV_CMD_N163_WAVE_MODE, + DIV_CMD_N163_WAVE_LOAD, + DIV_CMD_N163_WAVE_LOADPOS, + DIV_CMD_N163_WAVE_LOADLEN, + DIV_CMD_N163_WAVE_LOADMODE, + DIV_CMD_N163_CHANNEL_LIMIT, + DIV_CMD_N163_GLOBAL_WAVE_LOAD, + DIV_CMD_N163_GLOBAL_WAVE_LOADPOS, + DIV_CMD_N163_GLOBAL_WAVE_LOADLEN, + DIV_CMD_N163_GLOBAL_WAVE_LOADMODE, + DIV_ALWAYS_SET_VOLUME, DIV_CMD_MAX diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index f80ed13b..6978aa28 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -46,6 +46,7 @@ #include "platform/swan.h" #include "platform/lynx.h" #include "platform/bubsyswsg.h" +#include "platform/n163.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -275,6 +276,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_BUBSYS_WSG: dispatch=new DivPlatformBubSysWSG; break; + case DIV_SYSTEM_N163: + dispatch=new DivPlatformN163; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp new file mode 100644 index 00000000..9d22bd27 --- /dev/null +++ b/src/engine/platform/n163.cpp @@ -0,0 +1,642 @@ +/** + * 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 "n163.h" +#include "../engine.h" +#include + +#define rRead(a,v) n163->addr_w(a); n163->data_r(v); +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWriteMask(a,v,m) if (!skipRegisterWrites) {writes.emplace(a,v,m); if (dumpWrites) {addWrite(a,v);} } +#define chWrite(c,a,v) \ + if (c<=chanMax) { \ + rWrite(0x78-(c<<3)+(a&7),v) \ + } + +#define chWriteMask(c,a,v,m) \ + if (c<=chanMax) { \ + rWriteMask(0x78-(c<<3)+(a&7),v,m) \ + } + +#define CHIP_FREQBASE (15*65536) + +const char* regCheatSheetN163[]={ + "FreqL7", "40", + "AccL7", "41", + "FreqM7", "42", + "AccM7", "43", + "WavLen_FreqH7", "44", + "AccH7", "45", + "WavPos7", "46", + "Vol7", "47", + "FreqL6", "48", + "AccL6", "49", + "FreqM6", "4A", + "AccM6", "4B", + "WavLen_FreqH6", "4C", + "AccH6", "4D", + "WavPos6", "4E", + "Vol6", "4F", + "FreqL5", "50", + "AccL5", "51", + "FreqM5", "52", + "AccM5", "53", + "WavLen_FreqH5", "54", + "AccH5", "55", + "WavPos5", "56", + "Vol5", "57", + "FreqL4", "58", + "AccL4", "59", + "FreqM4", "5A", + "AccM4", "5B", + "WavLen_FreqH4", "5C", + "AccH4", "5D", + "WavPos4", "5E", + "Vol4", "5F", + "FreqL3", "60", + "AccL3", "61", + "FreqM3", "62", + "AccM3", "63", + "WavLen_FreqH3", "64", + "AccH3", "65", + "WavPos3", "66", + "Vol3", "67", + "FreqL2", "68", + "AccL2", "69", + "FreqM2", "6A", + "AccM2", "6B", + "WavLen_FreqH2", "6C", + "AccH2", "6D", + "WavPos2", "6E", + "Vol2", "6F", + "FreqL1", "70", + "AccL1", "71", + "FreqM1", "72", + "AccM1", "73", + "WavLen_FreqH1", "74", + "AccH1", "75", + "WavPos1", "76", + "Vol1", "77", + "FreqL0", "78", + "AccL0", "79", + "FreqM0", "7A", + "AccM0", "7B", + "WavLen_FreqH0", "7C", + "AccH0", "7D", + "WavPos0", "7E", + "ChanMax_Vol0", "7F", + NULL +}; + +const char** DivPlatformN163::getRegisterSheet() { + return regCheatSheetN163; +} + +const char* DivPlatformN163::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Select waveform"; + break; + case 0x11: + return "11xx: Set waveform position in RAM (single nibble unit)"; + break; + case 0x12: + return "12xx: Set waveform length in RAM (04 to FC, 4 nibble unit)"; + break; + case 0x13: + return "130x: Change waveform update mode (0: off, bit 0: update now, bit 1: update when every waveform changes)"; + break; + case 0x14: + return "14xx: Select waveform for load to RAM"; + break; + case 0x15: + return "15xx: Set waveform position for load to RAM (single nibble unit)"; + break; + case 0x16: + return "16xx: Set waveform length for load to RAM (04 to FC, 4 nibble unit)"; + break; + case 0x17: + return "170x: Change waveform load mode (0: off, bit 0: load now, bit 1: load when every waveform changes)"; + break; + case 0x18: + return "180x: Change channel limits (0 to 7, x + 1)"; + break; + case 0x20: + return "20xx: (Global) Select waveform for load to RAM"; + break; + case 0x21: + return "21xx: (Global) Set waveform position for load to RAM (single nibble unit)"; + break; + case 0x22: + return "22xx: (Global) Set waveform length for load to RAM (04 to FC, 4 nibble unit)"; + break; + case 0x23: + return "230x: (Global) Change waveform load mode (0: off, bit 0: load now, bit 1: load when every waveform changes)"; + break; + } + return NULL; +} + +void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; itick(); + int out=(n163->out()<<6)*(chanMax+1); // scale to 16 bit + if (out>32767) out=32767; + if (out<-32768) out=-32768; + bufL[i]=bufR[i]=out; + + // command queue + while (!writes.empty()) { + QueuedWrite w=writes.front(); + n163->addr_w(w.addr); + n163->data_w((n163->data_r()&~w.mask)|(w.val&w.mask)); + writes.pop(); + } + } +} + +void DivPlatformN163::updateWave(int wave, int pos, int len) { + len=MAX(0,MIN(((0x78-(chanMax<<3))<<1)-pos,len)); // avoid conflict with channel register area + DivWavetable* wt=parent->getWave(wave); + for (int i=0; imax<1 || wt->len<1) { + rWriteMask(addr>>1,0,mask); + } else { + int data=wt->data[i*wt->len/len]*15/wt->max; + if (data<0) data=0; + if (data>15) data=15; + rWriteMask(addr>>1,(addr&1)?(data<<4):(data&0xf),mask); + } + } +} + +void DivPlatformN163::updateWaveCh(int ch) { + if (ch<=chanMax) { + updateWave(chan[ch].wave,chan[ch].wavePos,chan[ch].waveLen); + if (chan[ch].active) { + chan[ch].volumeChanged=true; + } + } +} + +void DivPlatformN163::tick() { + for (int i=0; i<=chanMax; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + chan[i].outVol=(MIN(15,chan[i].std.vol)*(chan[i].vol&15))/15; + if (chan[i].outVol<0) chan[i].outVol=0; + if (chan[i].outVol>15) chan[i].outVol=15; + if (chan[i].resVol!=chan[i].outVol) { + chan[i].resVol=chan[i].outVol; + if (!isMuted[i]) { + chan[i].volumeChanged=true; + } + } + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadDuty) { + if (chan[i].wavePos!=chan[i].std.duty) { + chan[i].wavePos=chan[i].std.duty; + if (chan[i].waveMode&0x2) { + chan[i].waveUpdated=true; + } + chan[i].waveChanged=true; + } + } + if (chan[i].std.hadWave) { + if (chan[i].wave!=chan[i].std.wave) { + chan[i].wave=chan[i].std.wave; + if (chan[i].waveMode&0x2) { + chan[i].waveUpdated=true; + } + } + } + if (chan[i].std.hadEx1) { + if (chan[i].waveLen!=(chan[i].std.ex1&0xfc)) { + chan[i].waveLen=chan[i].std.ex1&0xfc; + if (chan[i].waveMode&0x2) { + chan[i].waveUpdated=true; + } + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadEx2) { + if ((chan[i].waveMode&0x1)!=(chan[i].std.ex2&0x1)) { // update waveform now + chan[i].waveMode=(chan[i].waveMode&~0x1)|(chan[i].std.ex2&0x1); + if (chan[i].waveMode&0x1) { // rising edge + chan[i].waveUpdated=true; + chan[i].waveChanged=true; + } + } + if ((chan[i].waveMode&0x2)!=(chan[i].std.ex2&0x2)) { // update when every waveform changed + chan[i].waveMode=(chan[i].waveMode&~0x2)|(chan[i].std.ex2&0x2); + if (chan[i].waveMode&0x2) { + chan[i].waveUpdated=true; + chan[i].waveChanged=true; + } + } + } + if (chan[i].std.hadEx3) { + if (chan[i].loadWave!=chan[i].std.ex3) { + chan[i].loadWave=chan[i].std.ex3; + if (chan[i].loadMode&0x2) { + updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); + } + } + } + if (chan[i].std.hadAlg) { + if (chan[i].loadPos!=chan[i].std.alg) { + chan[i].loadPos=chan[i].std.alg; + } + } + if (chan[i].std.hadFb) { + if (chan[i].loadLen!=(chan[i].std.fb&0xfc)) { + chan[i].loadLen=chan[i].std.fb&0xfc; + } + } + if (chan[i].std.hadFms) { + if ((chan[i].loadMode&0x1)!=(chan[i].std.fms&0x1)) { // load now + chan[i].loadMode=(chan[i].loadMode&~0x1)|(chan[i].std.fms&0x1); + if (chan[i].loadMode&0x1) { // rising edge + updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); + } + } + if ((chan[i].loadMode&0x2)!=(chan[i].std.fms&0x2)) { // load when every waveform changes + chan[i].loadMode=(chan[i].loadMode&~0x2)|(chan[i].std.fms&0x2); + } + } + if (chan[i].volumeChanged) { + if ((!chan[i].active) || isMuted[i]) { + chWriteMask(i,0x7,0,0xf); + } else { + chWriteMask(i,0x7,chan[i].resVol&0xf,0xf); + } + chan[i].volumeChanged=false; + } + if (chan[i].waveChanged) { + chWrite(i,0x6,chan[i].wavePos); + chan[i].freqChanged=true; + chan[i].waveChanged=false; + } + if (chan[i].waveUpdated) { + updateWaveCh(i); + if (!chan[i].keyOff) chan[i].keyOn=true; + chan[i].waveUpdated=false; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq*(chan[i].waveLen/16)*(chanMax+1),chan[i].pitch,false,0); + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff; + if (chan[i].keyOn) { + if (chan[i].wave<0) { + chan[i].wave=0; + if (chan[i].waveMode&0x2) { + updateWaveCh(i); + } + } + } + if (chan[i].keyOff && !isMuted[i]) { + chWriteMask(i,0x07,0,0xf); + } + chWrite(i,0x0,chan[i].freq&0xff); + chWrite(i,0x2,chan[i].freq>>8); + chWrite(i,0x4,((256-chan[i].waveLen)&0xfc)|((chan[i].freq>>16)&3)); + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformN163::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(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].resVol=chan[c.chan].vol; + if (!isMuted[c.chan]) { + chan[c.chan].volumeChanged=true; + } + chan[c.chan].std.init(ins); + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + //chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].std.release(); + break; + 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].insChanged=true; + 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) { + chan[c.chan].outVol=c.value; + chan[c.chan].resVol=chan[c.chan].outVol; + if (!isMuted[c.chan]) { + chan[c.chan].volumeChanged=true; + } + } + } + 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_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(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_WAVE: + chan[c.chan].wave=c.value; + if (chan[c.chan].waveMode&0x2) { + chan[c.chan].waveUpdated=true; + } + chan[c.chan].keyOn=true; + break; + case DIV_CMD_N163_WAVE_POSITION: + chan[c.chan].wavePos=c.value; + if (chan[c.chan].waveMode&0x2) { + chan[c.chan].waveUpdated=true; + } + chan[c.chan].waveChanged=true; + break; + case DIV_CMD_N163_WAVE_LENGTH: + chan[c.chan].waveLen=c.value&0xfc; + if (chan[c.chan].waveMode&0x2) { + chan[c.chan].waveUpdated=true; + } + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_N163_WAVE_MODE: + chan[c.chan].waveMode=c.value&0x3; + if (chan[c.chan].waveMode&0x3) { // update now + chan[c.chan].waveUpdated=true; + chan[c.chan].waveChanged=true; + } + break; + case DIV_CMD_N163_WAVE_LOAD: + chan[c.chan].loadWave=c.value; + if (chan[c.chan].loadMode&0x2) { // load when every waveform changes + updateWave(chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen); + } + break; + case DIV_CMD_N163_WAVE_LOADPOS: + chan[c.chan].loadPos=c.value; + break; + case DIV_CMD_N163_WAVE_LOADLEN: + chan[c.chan].loadLen=c.value&0xfc; + break; + case DIV_CMD_N163_WAVE_LOADMODE: + chan[c.chan].loadMode=c.value&0x3; + if (chan[c.chan].loadMode&0x1) { // load now + updateWave(chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen); + } + break; + case DIV_CMD_N163_GLOBAL_WAVE_LOAD: + loadWave=c.value; + if (loadMode&0x2) { // load when every waveform changes + updateWave(loadWave,loadPos,loadLen); + } + break; + case DIV_CMD_N163_GLOBAL_WAVE_LOADPOS: + loadPos=c.value; + break; + case DIV_CMD_N163_GLOBAL_WAVE_LOADLEN: + loadLen=c.value&0xfc; + break; + case DIV_CMD_N163_GLOBAL_WAVE_LOADMODE: + loadMode=c.value&0x3; + if (loadMode&0x3) { // load now + updateWave(loadWave,loadPos,loadLen); + } + break; + case DIV_CMD_N163_CHANNEL_LIMIT: + if (chanMax!=(c.value&0x7)) { + chanMax=c.value&0x7; + rWriteMask(0x7f,chanMax<<4,0x70); + forceIns(); + } + break; + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_FREQUENCY(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].keyOn=true; + } + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformN163::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + chan[ch].volumeChanged=true; +} + +void DivPlatformN163::forceIns() { + for (int i=0; i<=chanMax; i++) { + chan[i].insChanged=true; + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + chan[i].volumeChanged=true; + chan[i].waveChanged=true; + if (chan[i].waveMode&0x2) { + chan[i].waveUpdated=true; + } + } + } +} + +void DivPlatformN163::notifyWaveChange(int wave) { + for (int i=0; i<8; i++) { + if (chan[i].wave==wave) { + if (chan[i].waveMode&0x2) { + chan[i].waveUpdated=true; + } + } + } +} + +void DivPlatformN163::notifyInsChange(int ins) { + for (int i=0; i<8; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformN163::notifyInsDeletion(void* ins) { + for (int i=0; i<8; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void* DivPlatformN163::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformN163::getRegisterPool() { + for (int i=0; i<128; i++) { + regPool[i]=n163->reg(i); + } + return regPool; +} + +int DivPlatformN163::getRegisterPoolSize() { + return 128; +} + +void DivPlatformN163::reset() { + while (!writes.empty()) writes.pop(); + for (int i=0; i<8; i++) { + chan[i]=DivPlatformN163::Channel(); + } + + n163->reset(); + memset(regPool,0,128); + + n163->set_disable(false); + rWrite(0x7f,chanMax<<4); + loadWave=-1; + loadPos=0; + loadLen=0; + loadMode=0; +} + +void DivPlatformN163::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformN163::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +void DivPlatformN163::setFlags(unsigned int flags) { + switch (flags&0xf) { + case 0x0: // NTSC + rate=COLOR_NTSC/2.0; + break; + case 0x1: // PAL + rate=COLOR_PAL*3.0/8.0; + break; + case 0x2: // Dendy + rate=COLOR_PAL*2.0/5.0; + break; + } + chanMax=(flags>>4)&7; + chipClock=rate; + rate/=15; +} + +int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<8; i++) { + isMuted[i]=false; + } + n163=new n163_core(); + setFlags(flags); + + reset(); + + return 8; +} + +void DivPlatformN163::quit() { + delete n163; +} + +DivPlatformN163::~DivPlatformN163() { +} diff --git a/src/engine/platform/n163.h b/src/engine/platform/n163.h new file mode 100644 index 00000000..1aa52048 --- /dev/null +++ b/src/engine/platform/n163.h @@ -0,0 +1,107 @@ +/** + * 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 _N163_H +#define _N163_H + +#include "../dispatch.h" +#include +#include "../macroInt.h" +#include "sound/n163/n163.hpp" + +class DivPlatformN163: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + short ins, wave, wavePos, waveLen; + unsigned char waveMode; + short loadWave, loadPos, loadLen; + unsigned char loadMode; + bool active, insChanged, freqChanged, volumeChanged, waveChanged, waveUpdated, keyOn, keyOff, inPorta; + signed char vol, outVol, resVol; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + wave(-1), + wavePos(0), + waveLen(0), + waveMode(0), + loadWave(-1), + loadPos(0), + loadLen(0), + loadMode(0), + active(false), + insChanged(true), + freqChanged(false), + volumeChanged(false), + waveChanged(false), + waveUpdated(false), + keyOn(false), + keyOff(false), + inPorta(false), + vol(15), + outVol(15), + resVol(15) {} + }; + Channel chan[8]; + bool isMuted[8]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + unsigned char mask; + QueuedWrite(unsigned char a, unsigned char v, unsigned char m=~0): addr(a), val(v), mask(m) {} + }; + std::queue writes; + unsigned char chanMax; + short loadWave, loadPos, loadLen; + unsigned char loadMode; + + n163_core *n163; + unsigned char regPool[128]; + void updateWave(int wave, int pos, int len); + void updateWaveCh(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 setFlags(unsigned int flags); + void notifyWaveChange(int wave); + void notifyInsChange(int ins); + void notifyInsDeletion(void* ins); + 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(); + ~DivPlatformN163(); +}; + +#endif diff --git a/src/engine/platform/sound/n163/n163.cpp b/src/engine/platform/sound/n163/n163.cpp new file mode 100644 index 00000000..ee05bca4 --- /dev/null +++ b/src/engine/platform/sound/n163/n163.cpp @@ -0,0 +1,137 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Namco 163 Sound emulation core + + This chip is one of NES mapper with sound expansion, This one is by Namco. + + It has 1 to 8 wavetable channels, All channel registers and waveforms are stored to internal RAM. + 4 bit Waveforms are freely allocatable, and its length is variables; its can be stores many short waveforms or few long waveforms in RAM. + + But waveforms are needs to squash, reallocate to avoid conflict with channel register area, each channel register size is 8 bytes per channels. + + Sound output is time division multiplexed, it's can be captured only single channels output at once. in reason, More activated channels are less sound quality. + + Sound register layout + + Address Bit Description + 7654 3210 + + 78-7f Channel 0 + 78 xxxx xxxx Channel 0 Pitch input bit 0-7 + 79 xxxx xxxx Channel 0 Accumulator bit 0-7* + 7a xxxx xxxx Channel 0 Pitch input bit 8-15 + 7b xxxx xxxx Channel 0 Accumulator bit 8-15* + 7c xxxx xx-- Channel 0 Waveform length, 256 - (x * 4) + ---- --xx Channel 0 Pitch input bit 16-17 + 7d xxxx xxxx Channel 0 Accumulator bit 16-23* + 7e xxxx xxxx Channel 0 Waveform base offset + xxxx xxx- RAM byte (0 to 127) + ---- ---x RAM nibble + ---- ---0 Low nibble + ---- ---1 High nibble + 7f ---- xxxx Channel 0 Volume + + 7f Number of active channels + 7f -xxx ---- Number of active channels + -000 ---- Channel 0 activated + -001 ---- Channel 1 activated + -010 ---- Channel 2 activated + ... + -110 ---- Channel 6 activated + -111 ---- Channel 7 activated + + 70-77 Channel 1 (Optional if activated) + 68-6f Channel 2 (Optional if activated) + ... + 48-4f Channel 6 (Optional if activated) + 40-47 Channel 7 (Optional if activated) + + Rest of RAM area are for 4 bit Waveform and/or scratchpad. + Each waveform byte has 2 nibbles packed, fetches LSB first, MSB next. + ---- xxxx 4 bit waveform, LSB + xxxx ---- Same as above, MSB + + Waveform address: Waveform base offset + Bit 16 to 23 of Accumulator, 1 LSB of result is nibble select, 7 MSB of result is Byte address in RAM. + + Frequency formula: + Frequency: Pitch input * ((Input clock * 15 * Number of activated voices) / 65536) +*/ + +#include "n163.hpp" + +void n163_core::tick() +{ + m_out = 0; + // 0xe000-0xe7ff Disable sound bits (bit 6, bit 0 to 5 are CPU ROM Bank 0x8000-0x9fff select.) + if (m_disable) + return; + + // tick per each clock + const u32 freq = m_ram[m_voice_cycle + 0] | (u32(m_ram[m_voice_cycle + 2]) << 8) | (bitfield(m_ram[m_voice_cycle + 4], 0, 2) << 16); // 18 bit frequency + u32 accum = m_ram[m_voice_cycle + 1] | (u32(m_ram[m_voice_cycle + 3]) << 8) | ( u32(m_ram[m_voice_cycle + 5]) << 16); // 24 bit accumulator + const u16 length = 256 - (m_ram[m_voice_cycle + 4] & 0xfc); + const u8 addr = m_ram[m_voice_cycle + 6] + bitfield(accum, 16, 8); + const s16 wave = (bitfield(m_ram[bitfield(addr, 1, 7)], bitfield(addr, 0) << 2, 4) - 8); + const s16 volume = bitfield(m_ram[m_voice_cycle + 7], 0, 4); + + // accumulate address + accum = bitfield(accum + freq, 0, 24); + if (bitfield(accum, 16, 8) >= length) + accum = bitfield(accum, 0, 18); + + // writeback to register + m_ram[m_voice_cycle + 1] = bitfield(accum, 0, 8); + m_ram[m_voice_cycle + 3] = bitfield(accum, 8, 8); + m_ram[m_voice_cycle + 5] = bitfield(accum, 16, 8); + + // update voice cycle + m_voice_cycle -= 0x8; + if (m_voice_cycle < (0x78 - (bitfield(m_ram[0x7f], 4, 3) << 3))) + m_voice_cycle = 0x78; + + // output 4 bit waveform and volume, multiplexed + m_out = wave * volume; +} + +void n163_core::reset() +{ + // reset this chip + m_disable = false; + std::fill(std::begin(m_ram), std::end(m_ram), 0); + m_voice_cycle = 0x78; + m_addr_latch.reset(); + m_out = 0; +} + +// accessor +void n163_core::addr_w(u8 data) +{ + // 0xf800-0xffff Sound address, increment + m_addr_latch.addr = bitfield(data, 0, 7); + m_addr_latch.incr = bitfield(data, 7); +} + +void n163_core::data_w(u8 data, bool cpu_access) +{ + // 0x4800-0x4fff Sound data write + m_ram[m_addr_latch.addr] = data; + + // address latch increment + if (cpu_access && m_addr_latch.incr) + m_addr_latch.addr = bitfield(m_addr_latch.addr + 1, 0, 7); +} + +u8 n163_core::data_r(bool cpu_access) +{ + // 0x4800-0x4fff Sound data read + const u8 ret = m_ram[m_addr_latch.addr]; + + // address latch increment + if (cpu_access && m_addr_latch.incr) + m_addr_latch.addr = bitfield(m_addr_latch.addr + 1, 0, 7); + + return ret; +} diff --git a/src/engine/platform/sound/n163/n163.hpp b/src/engine/platform/sound/n163/n163.hpp new file mode 100644 index 00000000..b97de9ae --- /dev/null +++ b/src/engine/platform/sound/n163/n163.hpp @@ -0,0 +1,78 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Namco 163 Sound emulation core +*/ + +#include +#include + +#ifndef _VGSOUND_EMU_N163_HPP +#define _VGSOUND_EMU_N163_HPP + +#pragma once + +namespace n163 +{ + typedef unsigned char u8; + typedef unsigned short u16; + typedef unsigned int u32; + typedef signed short s16; + + // get bitfield, bitfield(input, position, len) + template T bitfield(T in, u8 pos, u8 len = 1) + { + return (in >> pos) & (len ? (T(1 << len) - 1) : 1); + } +}; + +using namespace n163; +class n163_core +{ +public: + // accessors, getters, setters + void addr_w(u8 data); + void data_w(u8 data, bool cpu_access = false); + u8 data_r(bool cpu_access = false); + + void set_disable(bool disable) { m_disable = disable; } + + // internal state + void reset(); + void tick(); + + // sound output pin + s16 out() { return m_out; } + + // register pool + u8 reg(u8 addr) { return m_ram[addr & 0x7f]; } + +private: + // Address latch + struct addr_latch_t + { + addr_latch_t() + : addr(0) + , incr(0) + { }; + + void reset() + { + addr = 0; + incr = 0; + } + + u8 addr : 7; + u8 incr : 1; + }; + + bool m_disable = false; + u8 m_ram[0x80] = {0}; // internal 128 byte RAM + u8 m_voice_cycle = 0x78; // Voice cycle for processing + addr_latch_t m_addr_latch; // address latch + s16 m_out = 0; // output +}; + +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 16f883e9..e68188a8 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -128,6 +128,18 @@ const char* cmdName[DIV_CMD_MAX]={ "WS_SWEEP_TIME", "WS_SWEEP_AMOUNT", + "N163_WAVE_POSITION", + "N163_WAVE_LENGTH", + "N163_WAVE_MODE", + "N163_WAVE_LOAD", + "N163_WAVE_LOADPOS", + "N163_WAVE_LOADLEN", + "N163_CHANNEL_LIMIT", + "N163_GLOBAL_WAVE_LOAD", + "N163_GLOBAL_WAVE_LOADPOS", + "N163_GLOBAL_WAVE_LOADLEN", + "N163_GLOBAL_WAVE_LOADMODE", + "ALWAYS_SET_VOLUME" }; @@ -248,6 +260,51 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; + case DIV_SYSTEM_N163: + switch (effect) { + case 0x10: // select instrument waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // select instrument waveform position in RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_POSITION,ch,effectVal)); + break; + case 0x12: // select instrument waveform length in RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LENGTH,ch,effectVal)); + break; + case 0x13: // change instrument waveform update mode + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_MODE,ch,effectVal)); + break; + case 0x14: // select waveform for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOAD,ch,effectVal)); + break; + case 0x15: // select waveform position for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADPOS,ch,effectVal)); + break; + case 0x16: // select waveform length for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADLEN,ch,effectVal)); + break; + case 0x17: // change waveform load mode + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADMODE,ch,effectVal)); + break; + case 0x18: // change channel limits + dispatchCmd(DivCommand(DIV_CMD_N163_CHANNEL_LIMIT,ch,effectVal)); + break; + case 0x20: // (global) select waveform for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOAD,ch,effectVal)); + break; + case 0x21: // (global) select waveform position for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADPOS,ch,effectVal)); + break; + case 0x22: // (global) select waveform length for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADLEN,ch,effectVal)); + break; + case 0x23: // (global) change waveform load mode + dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADMODE,ch,effectVal)); + break; + default: + return false; + } + break; case DIV_SYSTEM_QSOUND: switch (effect) { case 0x10: // echo feedback diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index ddbfb5b5..f5adb754 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -37,6 +37,7 @@ #include "../engine/platform/saa.h" #include "../engine/platform/amiga.h" #include "../engine/platform/x1_010.h" +#include "../engine/platform/n163.h" #include "../engine/platform/dummy.h" #define GENESIS_DEBUG \ @@ -274,6 +275,36 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::TextColored(ch->env.flag.envVinvL?colorOn:colorOff,">> EnvVinvL"); break; } + case DIV_SYSTEM_N163: { + DivPlatformN163::Channel* ch=(DivPlatformN163::Channel*)data; + ImGui::Text("> N163"); + ImGui::Text("* freq: %.4x",ch->freq); + ImGui::Text(" - base: %d",ch->baseFreq); + ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text("- note: %d",ch->note); + ImGui::Text("- wave: %d",ch->wave); + ImGui::Text("- wavepos: %d",ch->wavePos); + ImGui::Text("- wavelen: %d",ch->waveLen); + ImGui::Text("- wavemode: %d",ch->waveMode); + ImGui::Text("- loadwave: %d",ch->loadWave); + ImGui::Text("- loadpos: %d",ch->loadPos); + ImGui::Text("- loadlen: %d",ch->loadLen); + ImGui::Text("- loadmode: %d",ch->loadMode); + ImGui::Text("- ins: %d",ch->ins); + ImGui::Text("- vol: %.2x",ch->vol); + ImGui::Text("- outVol: %.2x",ch->outVol); + ImGui::Text("- resVol: %.2x",ch->resVol); + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); + ImGui::TextColored(ch->volumeChanged?colorOn:colorOff,">> VolumeChanged"); + ImGui::TextColored(ch->waveChanged?colorOn:colorOff,">> WaveChanged"); + ImGui::TextColored(ch->waveUpdated?colorOn:colorOff,">> WaveUpdated"); + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + break; + } default: ImGui::Text("Unknown system! Help!"); break; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 57b152ac..ddf9a55a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5516,6 +5516,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_SWAN); sysAddOption(DIV_SYSTEM_VERA); sysAddOption(DIV_SYSTEM_BUBSYS_WSG); + sysAddOption(DIV_SYSTEM_N163); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -5848,6 +5849,30 @@ bool FurnaceGUI::loop() { } break; } + case DIV_SYSTEM_N163: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("NTSC (1.79MHz)",(flags&15)==0)) { + e->setSysFlags(i,(flags&(~15))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (1.67MHz)",(flags&15)==1)) { + e->setSysFlags(i,(flags&(~15))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Dendy (1.77MHz)",(flags&15)==2)) { + e->setSysFlags(i,(flags&(~15))|2,restart); + updateWindowTitle(); + } + ImGui::Text("Initial channel limit:"); + int initialChannelLimit=((flags>>4)&7)+1; + if (ImGui::SliderInt("##InitialChannelLimit",&initialChannelLimit,1,8)) { + if (initialChannelLimit<1) initialChannelLimit=1; + if (initialChannelLimit>8) initialChannelLimit=8; + e->setSysFlags(i,(flags & ~(7 << 4)) | (((initialChannelLimit-1) & 7) << 4),restart); + updateWindowTitle(); + } rightClickable + break; + } case DIV_SYSTEM_GB: case DIV_SYSTEM_SWAN: case DIV_SYSTEM_VERA: @@ -5914,6 +5939,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_SWAN); sysChangeOption(i,DIV_SYSTEM_VERA); sysChangeOption(i,DIV_SYSTEM_BUBSYS_WSG); + sysChangeOption(i,DIV_SYSTEM_N163); ImGui::EndMenu(); } } @@ -7247,6 +7273,42 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3526", { + DIV_SYSTEM_OPL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3526 (drums mode)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3812", { + DIV_SYSTEM_OPL2, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3812 (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YMF262", { + DIV_SYSTEM_OPL3, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YMF262 (drums mode)", { + DIV_SYSTEM_OPL3_DRUMS, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Square"); @@ -7364,6 +7426,13 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Namco 163", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_N163, 64, 0, 112, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Mattel Intellivision", { DIV_SYSTEM_AY8910, 64, 0, 48, @@ -7408,7 +7477,7 @@ FurnaceGUI::FurnaceGUI(): )); cat.systems.push_back(FurnaceGUISysDef( "Gamate", { - DIV_SYSTEM_AY8910, 64, 0, 73, + DIV_SYSTEM_AY8910, 64, 0, 73, // Swap channel 2 and 3 for play correctly to real gamate with stereo headphone 0 } )); @@ -7439,7 +7508,6 @@ FurnaceGUI::FurnaceGUI(): 0 } )); - /* cat.systems.push_back(FurnaceGUISysDef( "Commodore 64 (6581 SID + Sound Expander)", { DIV_SYSTEM_OPL, 64, 0, 0, @@ -7467,7 +7535,7 @@ FurnaceGUI::FurnaceGUI(): DIV_SYSTEM_C64_8580, 64, 0, 1, 0 } - ));*/ + )); cat.systems.push_back(FurnaceGUISysDef( "Amiga", { DIV_SYSTEM_AMIGA, 64, 0, 0, @@ -7591,6 +7659,22 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro", { + DIV_SYSTEM_OPL2, 64, -127, 0, + DIV_SYSTEM_OPL2, 64, 127, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, -127, 0, + DIV_SYSTEM_OPL2_DRUMS, 64, 127, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "PC + Sound Blaster Pro 2", { DIV_SYSTEM_OPL3, 64, 0, 0, diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index def41aad..0afac9cc 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -133,6 +133,10 @@ const char* x1_010EnvBits[8]={ "enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL }; +const char* n163UpdateBits[8]={ + "now", "every waveform changed", NULL +}; + const char* oneBit[2]={ "on", NULL }; @@ -1596,6 +1600,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Duty"; dutyMax=63; } + if (ins->type==DIV_INS_N163) { + dutyLabel="Waveform pos."; + dutyMax=255; + } bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs); int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:63; @@ -1627,6 +1635,10 @@ void FurnaceGUI::drawInsEdit() { ex2Max=63; ex2Bit=false; } + if (ins->type==DIV_INS_N163) { + ex1Max=252; + ex2Max=2; + } if (ins->type==DIV_INS_SAA1099) ex1Max=8; if (settings.macroView==0) { // modern view @@ -1653,6 +1665,8 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope",160,ins->std.ex1MacroOpen,true,saaEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else if (ins->type==DIV_INS_X1_010) { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope Mode",160,ins->std.ex1MacroOpen,true,x1_010EnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); + } else if (ins->type==DIV_INS_N163) { + NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Waveform len.",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Duty",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } @@ -1660,6 +1674,8 @@ void FurnaceGUI::drawInsEdit() { if (ex2Max>0) { if (ins->type==DIV_INS_C64) { NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Resonance",64,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); + } else if (ins->type==DIV_INS_N163) { + NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Waveform update",64,ins->std.ex2MacroOpen,true,n163UpdateBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } else { NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",ex2Bit?64:160,ins->std.ex2MacroOpen,ex2Bit,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } @@ -1676,6 +1692,12 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,8,"fb","Noise AND Mask",96,ins->std.fbMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,8,NULL,false); NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,8,"fms","Noise OR Mask",96,ins->std.fmsMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,8,NULL,false); } + if (ins->type==DIV_INS_N163) { + NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,255,"ex3","Waveform to Load",160,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,255,NULL,false); + NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,255,"alg","Wave pos. to Load",160,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,255,NULL,false); + NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,252,"fb","Wave len. to Load",160,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,252,NULL,false); + NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,2,"fms","Waveform load",64,ins->std.fmsMacroOpen,true,n163UpdateBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,2,NULL,false); + } MACRO_END; } else { // classic view @@ -2002,7 +2024,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, PC Engine and WonderSwan.\nany other widths will be scaled during playback."); + ImGui::SetTooltip("use a width of:\n- 32 on Game Boy, PC Engine, WonderSwan and Bubble System WSG\n- 128 on X1-010\nany other widths will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); @@ -2016,7 +2038,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SameLine(); ImGui::Text("Height"); if (ImGui::IsItemHovered()) { - 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::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 envelope shape, Bubble System WSG and N163\n- 31 for PC Engine\n- 255 for X1-010 waveform\nany other heights will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); From abb5f0314394b033c9759006295d7c5e58fa56b9 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 23 Mar 2022 03:16:20 +0900 Subject: [PATCH 2/8] System docs --- papers/doc/7-systems/n163.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/n163.md b/papers/doc/7-systems/n163.md index 80a2d28d..68774801 100644 --- a/papers/doc/7-systems/n163.md +++ b/papers/doc/7-systems/n163.md @@ -1,6 +1,6 @@ # Namco 163 -This is Namco's one of NES mapper, with up to 8 wavetable channels. It has also 128 byte of internal RAM, both channel register and wavetables are stored here. Wavetables are variable size and freely allocable anywhere in RAM, it means it can be uses part of or merged 2 or more pre-loaded waveforms in RAM. But waveform RAM area becomes smaller as much as activating more channels; Channel register consumes 8 byte for each channels. You must avoid conflict with channel register area and waveform for avoid channel playback broken. +This is Namco's one of NES mapper, with up to 8 wavetable channels. It has also 128 byte of internal RAM, both channel register and wavetables are stored here. Wavetables are variable size and freely allocable anywhere in RAM, it means it can be uses part of or continuously pre-loaded waveform and/or its sequences in RAM. But waveform RAM area becomes smaller as much as activating more channels; Channel register consumes 8 byte for each channels. You must avoid conflict with channel register area and waveform for avoid channel playback broken. It has can be outputs only single channel at clock; so it's sound quality is more crunchy as much as activating more channels. From 4ba65d39067f01fa6024cc98173cde98984e6621 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 23 Mar 2022 03:17:06 +0900 Subject: [PATCH 3/8] Fix spacing --- src/engine/platform/sound/n163/n163.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/sound/n163/n163.cpp b/src/engine/platform/sound/n163/n163.cpp index ee05bca4..192189d9 100644 --- a/src/engine/platform/sound/n163/n163.cpp +++ b/src/engine/platform/sound/n163/n163.cpp @@ -30,8 +30,8 @@ 7e xxxx xxxx Channel 0 Waveform base offset xxxx xxx- RAM byte (0 to 127) ---- ---x RAM nibble - ---- ---0 Low nibble - ---- ---1 High nibble + ---- ---0 Low nibble + ---- ---1 High nibble 7f ---- xxxx Channel 0 Volume 7f Number of active channels From df8f40486dd58c9123d2d211a527c40fe750f473 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 24 Mar 2022 03:53:07 +0900 Subject: [PATCH 4/8] Fix frequency, Loading waveform, Add instrument tab for waveform initialize now for saving DivInstrumentN163 struct is... needs to compatibility breaks? --- papers/doc/4-instrument/n163.md | 11 +++++++++- src/engine/instrument.cpp | 3 +++ src/engine/instrument.h | 12 +++++++++++ src/engine/platform/n163.cpp | 36 +++++++++++++++++++++++---------- src/gui/insEdit.cpp | 29 ++++++++++++++++++++++++++ src/gui/intConst.cpp | 1 + src/gui/intConst.h | 1 + 7 files changed, 81 insertions(+), 12 deletions(-) diff --git a/papers/doc/4-instrument/n163.md b/papers/doc/4-instrument/n163.md index ccf2c46e..0bd91445 100644 --- a/papers/doc/4-instrument/n163.md +++ b/papers/doc/4-instrument/n163.md @@ -1,7 +1,16 @@ # Namco 163 instrument editor -Namco 163 instrument editor consists of 10 macros. +Namco 163 instrument editor consists of two tabs: one controlling various parameters for waveform initialize and macro tab containing 10 macros. +## N163 +- [Initial Waveform] - Determines the initial waveform for playing. +- [Initial Waveform position in RAM] - Determines the initial waveform position will be load to RAM. +- [Initial Waveform length in RAM] - Determines the initial waveform length will be load to RAM. +- [Load waveform before playback] - Determines the load initial waveform into RAM before playback. +- [Update waveforms into RAM when every waveform changes] - Determines the update every different waveform changes in playback. + + +## Macros - [Volume] - volume levels sequence - [Arpeggio]- pitch sequence - [Waveform pos.] - sets the waveform source address in RAM for playback (single nibble unit) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 785c9b19..41ccea39 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -385,6 +385,8 @@ void DivInstrument::putInsData(SafeWriter* w) { w->write(amiga.noteFreq,120*sizeof(unsigned int)); w->write(amiga.noteMap,120*sizeof(short)); } + + // N163 } DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { @@ -733,6 +735,7 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { } } + // N163 return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index ea52e6d8..a69800a1 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -385,6 +385,17 @@ struct DivInstrumentAmiga { } }; +struct DivInstrumentN163 { + int wave, wavePos, waveLen; + unsigned char waveMode; + + DivInstrumentN163(): + wave(-1), + wavePos(0), + waveLen(0), + waveMode(0) {} +}; + struct DivInstrument { String name; bool mode; @@ -394,6 +405,7 @@ struct DivInstrument { DivInstrumentGB gb; DivInstrumentC64 c64; DivInstrumentAmiga amiga; + DivInstrumentN163 n163; /** * save the instrument to a SafeWriter. diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 9d22bd27..42b55e90 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -172,10 +172,13 @@ void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len } void DivPlatformN163::updateWave(int wave, int pos, int len) { - len=MAX(0,MIN(((0x78-(chanMax<<3))<<1)-pos,len)); // avoid conflict with channel register area + len&=0xfc; // 4 nibble boundary DivWavetable* wt=parent->getWave(wave); for (int i=0; i=((0x78-(chanMax<<3))<<1)) { // avoid conflict with channel register area + break; + } unsigned char mask=(addr&1)?0xf0:0x0f; if (wt->max<1 || wt->len<1) { rWriteMask(addr>>1,0,mask); @@ -253,16 +256,16 @@ void DivPlatformN163::tick() { } } if (chan[i].std.hadEx2) { - if ((chan[i].waveMode&0x1)!=(chan[i].std.ex2&0x1)) { // update waveform now - chan[i].waveMode=(chan[i].waveMode&~0x1)|(chan[i].std.ex2&0x1); - if (chan[i].waveMode&0x1) { // rising edge + if ((chan[i].waveMode&0x2)!=(chan[i].std.ex2&0x2)) { // update when every waveform changed + chan[i].waveMode=(chan[i].waveMode&~0x2)|(chan[i].std.ex2&0x2); + if (chan[i].waveMode&0x2) { chan[i].waveUpdated=true; chan[i].waveChanged=true; } } - if ((chan[i].waveMode&0x2)!=(chan[i].std.ex2&0x2)) { // update when every waveform changed - chan[i].waveMode=(chan[i].waveMode&~0x2)|(chan[i].std.ex2&0x2); - if (chan[i].waveMode&0x2) { + if ((chan[i].waveMode&0x1)!=(chan[i].std.ex2&0x1)) { // update waveform now + chan[i].waveMode=(chan[i].waveMode&~0x1)|(chan[i].std.ex2&0x1); + if (chan[i].waveMode&0x1) { // rising edge chan[i].waveUpdated=true; chan[i].waveChanged=true; } @@ -287,15 +290,15 @@ void DivPlatformN163::tick() { } } if (chan[i].std.hadFms) { + if ((chan[i].loadMode&0x2)!=(chan[i].std.fms&0x2)) { // load when every waveform changes + chan[i].loadMode=(chan[i].loadMode&~0x2)|(chan[i].std.fms&0x2); + } if ((chan[i].loadMode&0x1)!=(chan[i].std.fms&0x1)) { // load now chan[i].loadMode=(chan[i].loadMode&~0x1)|(chan[i].std.fms&0x1); if (chan[i].loadMode&0x1) { // rising edge updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); } } - if ((chan[i].loadMode&0x2)!=(chan[i].std.fms&0x2)) { // load when every waveform changes - chan[i].loadMode=(chan[i].loadMode&~0x2)|(chan[i].std.fms&0x2); - } } if (chan[i].volumeChanged) { if ((!chan[i].active) || isMuted[i]) { @@ -316,7 +319,7 @@ void DivPlatformN163::tick() { chan[i].waveUpdated=false; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq*(chan[i].waveLen/16)*(chanMax+1),chan[i].pitch,false,0); + chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0); if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff; if (chan[i].keyOn) { @@ -344,6 +347,17 @@ int DivPlatformN163::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (chan[c.chan].insChanged) { + chan[c.chan].wave=ins->n163.wave; + chan[c.chan].wavePos=ins->n163.wavePos; + chan[c.chan].waveLen=ins->n163.waveLen; + chan[c.chan].waveMode=ins->n163.waveMode; + chan[c.chan].waveChanged=true; + if (chan[c.chan].waveMode&0x3) { + chan[c.chan].waveUpdated=true; + } + chan[c.chan].insChanged=false; + } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].freqChanged=true; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 24533c8d..b26ea534 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1522,6 +1522,35 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndTabItem(); } + if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("N163")) { + ImGui::Text("Initial waveform"); + if (ImGui::InputInt("##WAVE",&ins->n163.wave,1,10)) { PARAMETER + if (ins->n163.wave<0) ins->n163.wave=0; + if (ins->n163.wave>=e->song.waveLen) ins->n163.wave=e->song.waveLen-1; + } + ImGui::Text("Initial waveform position in RAM"); + if (ImGui::InputInt("##WAVEPOS",&ins->n163.wavePos,1,16)) { PARAMETER + if (ins->n163.wavePos<0) ins->n163.wavePos=0; + if (ins->n163.wavePos>255) ins->n163.wavePos=255; + } + ImGui::Text("Initial waveform length in RAM"); + if (ImGui::InputInt("##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER + if (ins->n163.waveLen<0) ins->n163.waveLen=0; + if (ins->n163.waveLen>252) ins->n163.waveLen=252; + ins->n163.waveLen&=0xfc; + } + + bool preLoad=ins->n163.waveMode&0x1; + if (ImGui::Checkbox("Load waveform before playback",&preLoad)) { PARAMETER + ins->n163.waveMode=(ins->n163.waveMode&~0x1)|(preLoad?0x1:0); + } + bool waveMode=ins->n163.waveMode&0x2; + if (ImGui::Checkbox("Update waveforms into RAM when every waveform changes",&waveMode)) { PARAMETER + ins->n163.waveMode=(ins->n163.waveMode&~0x2)|(waveMode?0x2:0); + } + + ImGui::EndTabItem(); + } if (ImGui::BeginTabItem("Macros")) { float asFloat[256]; int asInt[256]; diff --git a/src/gui/intConst.cpp b/src/gui/intConst.cpp index dae78ff1..9c7f53b9 100644 --- a/src/gui/intConst.cpp +++ b/src/gui/intConst.cpp @@ -29,6 +29,7 @@ const int _THIRTY_ONE=31; const int _SIXTY_FOUR=64; const int _ONE_HUNDRED=100; const int _ONE_HUNDRED_TWENTY_SEVEN=127; +const int _TWO_HUNDRED_FIFTY_FIVE=255; const int _TWO_THOUSAND_FORTY_SEVEN=2047; const int _FOUR_THOUSAND_NINETY_FIVE=4095; const int _MINUS_ONE_HUNDRED_TWENTY_SEVEN=-127; diff --git a/src/gui/intConst.h b/src/gui/intConst.h index 2618ec65..c6b13b9a 100644 --- a/src/gui/intConst.h +++ b/src/gui/intConst.h @@ -31,6 +31,7 @@ extern const int _THIRTY_ONE; extern const int _SIXTY_FOUR; extern const int _ONE_HUNDRED; extern const int _ONE_HUNDRED_TWENTY_SEVEN; +extern const int _TWO_HUNDRED_FIFTY_FIVE; extern const int _TWO_THOUSAND_FORTY_SEVEN; extern const int _FOUR_THOUSAND_NINETY_FIVE; extern const int _MINUS_ONE_HUNDRED_TWENTY_SEVEN; From 168577e4b99b008e72785d647536a2aeb0cfe372 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 24 Mar 2022 13:49:41 +0900 Subject: [PATCH 5/8] Revert preset --- src/gui/presets.cpp | 71 ++------------------------------------------- 1 file changed, 2 insertions(+), 69 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index ba747855..16d69cf7 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -88,42 +88,6 @@ void FurnaceGUI::initSystemPresets() { 0 } )); - cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YM3526", { - DIV_SYSTEM_OPL, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YM3526 (drums mode)", { - DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YM3812", { - DIV_SYSTEM_OPL2, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YM3812 (drums mode)", { - DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YMF262", { - DIV_SYSTEM_OPL3, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YMF262 (drums mode)", { - DIV_SYSTEM_OPL3_DRUMS, 64, 0, 0, - 0 - } - )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Square"); @@ -323,6 +287,7 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + /* cat.systems.push_back(FurnaceGUISysDef( "Commodore 64 (6581 SID + Sound Expander)", { DIV_SYSTEM_OPL, 64, 0, 0, @@ -350,7 +315,7 @@ void FurnaceGUI::initSystemPresets() { DIV_SYSTEM_C64_8580, 64, 0, 1, 0 } - )); + ));*/ cat.systems.push_back(FurnaceGUISysDef( "Amiga", { DIV_SYSTEM_AMIGA, 64, 0, 0, @@ -474,22 +439,6 @@ void FurnaceGUI::initSystemPresets() { 0 } )); - cat.systems.push_back(FurnaceGUISysDef( - "PC + Sound Blaster Pro", { - DIV_SYSTEM_OPL2, 64, -127, 0, - DIV_SYSTEM_OPL2, 64, 127, 0, - DIV_SYSTEM_PCSPKR, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "PC + Sound Blaster Pro (drums mode)", { - DIV_SYSTEM_OPL2_DRUMS, 64, -127, 0, - DIV_SYSTEM_OPL2_DRUMS, 64, 127, 0, - DIV_SYSTEM_PCSPKR, 64, 0, 0, - 0 - } - )); cat.systems.push_back(FurnaceGUISysDef( "PC + Sound Blaster Pro 2", { DIV_SYSTEM_OPL3, 64, 0, 0, @@ -504,15 +453,6 @@ void FurnaceGUI::initSystemPresets() { 0 } )); - cat.systems.push_back(FurnaceGUISysDef( - "PC + SAAYM", { - DIV_SYSTEM_YM2151, 64, 0, 0, // 3.58MHz or 4MHz selectable via jumper - DIV_SYSTEM_SAA1099, 64, -127, 1, // 7.16MHz or 8MHz selectable via jumper - DIV_SYSTEM_SAA1099, 64, 127, 1, // "" - DIV_SYSTEM_PCSPKR, 64, 0, 0, - 0 - } - )); cat.systems.push_back(FurnaceGUISysDef( "Sharp X1", { DIV_SYSTEM_AY8910, 64, 0, 3, @@ -601,13 +541,6 @@ void FurnaceGUI::initSystemPresets() { 0 } )); - cat.systems.push_back(FurnaceGUISysDef( - "Seta 1 + FM addon", { - DIV_SYSTEM_YM2612, 64, 0, 2, // Discrete YM3438 - DIV_SYSTEM_X1_010, 64, 0, 0, - 0 - } - )); cat.systems.push_back(FurnaceGUISysDef( "Seta 2", { DIV_SYSTEM_X1_010, 64, 0, 1, From 075f758e4dd65d7886d8e5f363cd7a05dfd49231 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 22:04:01 -0500 Subject: [PATCH 6/8] Namco 163 refinements --- papers/format.md | 9 +++++++++ src/engine/engine.h | 4 ++-- src/engine/instrument.cpp | 12 ++++++++++++ src/engine/instrument.h | 4 ++-- src/engine/platform/n163.cpp | 2 +- src/gui/insEdit.cpp | 11 ++++------- 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/papers/format.md b/papers/format.md index d61e94d4..dd4bcce2 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 73: Furnace dev73 - 72: Furnace dev72 - 71: Furnace dev71 - 70: Furnace dev70 @@ -515,6 +516,14 @@ size | description | - 480 bytes 2?? | note sample × 120 | - 240 bytes + --- | **Namco 163 data** (>=73) + 4 | initial waveform + 1 | wave position + 1 | wave length + 1 | wave mode: + | - bit 1: update on change + | - bit 0: load on playback + 1 | reserved ``` # wavetable diff --git a/src/engine/engine.h b/src/engine/engine.h index 24c3e135..0751941e 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev72" -#define DIV_ENGINE_VERSION 72 +#define DIV_VERSION "dev73" +#define DIV_ENGINE_VERSION 73 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 6556bfea..f3cf9f59 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -387,6 +387,11 @@ void DivInstrument::putInsData(SafeWriter* w) { } // N163 + w->writeI(n163.wave); + w->writeC(n163.wavePos); + w->writeC(n163.waveLen); + w->writeC(n163.waveMode); + w->writeC(0); // reserved } DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { @@ -743,6 +748,13 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { } // N163 + if (version>=73) { + n163.wave=reader.readI(); + n163.wavePos=(unsigned char)reader.readC(); + n163.waveLen=(unsigned char)reader.readC(); + n163.waveMode=(unsigned char)reader.readC(); + reader.readC(); // reserved + } return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index a69800a1..9d51670c 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -392,8 +392,8 @@ struct DivInstrumentN163 { DivInstrumentN163(): wave(-1), wavePos(0), - waveLen(0), - waveMode(0) {} + waveLen(32), + waveMode(3) {} }; struct DivInstrument { diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 42b55e90..14c2d12d 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -34,7 +34,7 @@ rWriteMask(0x78-(c<<3)+(a&7),v,m) \ } -#define CHIP_FREQBASE (15*65536) +#define CHIP_FREQBASE (15*32768) const char* regCheatSheetN163[]={ "FreqL7", "40", diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index da5b0e51..4d707f81 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2043,19 +2043,16 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndTabItem(); } - if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("N163")) { - ImGui::Text("Initial waveform"); - if (ImGui::InputInt("##WAVE",&ins->n163.wave,1,10)) { PARAMETER + if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { + if (ImGui::InputInt("Waveform##WAVE",&ins->n163.wave,1,10)) { PARAMETER if (ins->n163.wave<0) ins->n163.wave=0; if (ins->n163.wave>=e->song.waveLen) ins->n163.wave=e->song.waveLen-1; } - ImGui::Text("Initial waveform position in RAM"); - if (ImGui::InputInt("##WAVEPOS",&ins->n163.wavePos,1,16)) { PARAMETER + if (ImGui::InputInt("Offset##WAVEPOS",&ins->n163.wavePos,1,16)) { PARAMETER if (ins->n163.wavePos<0) ins->n163.wavePos=0; if (ins->n163.wavePos>255) ins->n163.wavePos=255; } - ImGui::Text("Initial waveform length in RAM"); - if (ImGui::InputInt("##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER + if (ImGui::InputInt("Length##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER if (ins->n163.waveLen<0) ins->n163.waveLen=0; if (ins->n163.waveLen>252) ins->n163.waveLen=252; ins->n163.waveLen&=0xfc; From 25b07fb4f193963650bea3d0b2fa1e78185a7d5e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 22:18:50 -0500 Subject: [PATCH 7/8] typo fixes --- papers/doc/4-instrument/vrc6.md | 4 ++-- papers/doc/7-systems/vrc6.md | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/papers/doc/4-instrument/vrc6.md b/papers/doc/4-instrument/vrc6.md index b7612c2f..a4ea64a1 100644 --- a/papers/doc/4-instrument/vrc6.md +++ b/papers/doc/4-instrument/vrc6.md @@ -3,5 +3,5 @@ VRC6 instrument editor consists of only three macros: - [Volume] - volume sequence -- [Arpeggio] - pitch sequencr -- [Duty cycle] - spicifies duty cycle for pulse wave channels +- [Arpeggio] - pitch sequence +- [Duty cycle] - specifies duty cycle for pulse wave channels diff --git a/papers/doc/7-systems/vrc6.md b/papers/doc/7-systems/vrc6.md index 7dfc2585..63bda1ab 100644 --- a/papers/doc/7-systems/vrc6.md +++ b/papers/doc/7-systems/vrc6.md @@ -1,16 +1,16 @@ # Konami VRC6 -Its one of NES mapper with sound expansion, and one of two VRCs with this feature by Konami. +the most popular expansion chip to the NES' sound system. -The chip has 2 pulse wave channel and single sawtooth channel. -volume register is 4 bit for pulse wave and 6 bit for sawtooth, but sawtooth output is corrupted when volume register value is too high. because this register is 8 bit accumulator in technically, its output is wraparoundable. +the chip has 2 pulse wave channels and one sawtooth channel. +volume register is 4 bit for pulse wave and 6 bit for sawtooth, but sawtooth output is corrupted when volume register value is too high. because this register is actually an 8 bit accumulator, its output may wrap around. -pulse wave duty cycle is 8 level, it can be ignored and it has potential for DAC at this case: volume register in this mode is DAC output and it can be PCM playback through this mode. -Furnace supports this routine for PCM playback, but it's consume a lot of CPU resource in real hardware. (even if conjunction with VRC6 integrated IRQ timer) +pulse wave duty cycle is 8-level. it can be ignored and it has potential for DAC at this case: volume register in this mode is DAC output and it can be PCM playback through this mode. +Furnace supports this routine for PCM playback, but it consumes a lot of CPU time in real hardware (even if conjunction with VRC6's integrated IRQ timer). # effects -- `12xx`: set duty cycle. (0 to 7) -- `17xx`: toggle PCM mode. +these effects only are effective in the pulse channels. -* All effects are affects at pulse channels only. \ No newline at end of file +- `12xx`: set duty cycle (0 to 7). +- `17xx`: toggle PCM mode. \ No newline at end of file From 13a88730503bca61f9923067f6b5b0deee6c8c21 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 22:30:29 -0500 Subject: [PATCH 8/8] VRC6: period tuning fixes now it is identical to the NES channels --- src/engine/platform/vrc6.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 630d5972..27cb2ffa 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -22,8 +22,6 @@ #include #include -#define CHIP_DIVIDER 1 // 16 for pulse, 14 for sawtooth - #define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } #define chWrite(c,a,v) rWrite(0x9000+(c<<12)+(a&3),v) @@ -139,6 +137,8 @@ void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len void DivPlatformVRC6::tick() { for (int i=0; i<3; i++) { + // 16 for pulse; 14 for saw + int CHIP_DIVIDER=(i==2)?14:16; chan[i].std.next(); if (chan[i].std.hadVol) { if (i==2) { // sawtooth @@ -180,9 +180,9 @@ void DivPlatformVRC6::tick() { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (i==2) { // sawtooth - chan[i].freq=parent->calcFreq(chan[i].baseFreq/14,chan[i].pitch,true)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; } else { // pulse - chan[i].freq=parent->calcFreq(chan[i].baseFreq/16,chan[i].pitch,true)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; if (chan[i].furnaceDac) { double off=1.0; if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { @@ -199,10 +199,6 @@ void DivPlatformVRC6::tick() { } if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].freq<0) chan[i].freq=0; - if (chan[i].keyOn) { - //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); - //rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); - } if (chan[i].keyOff) { chWrite(i,2,0); } else { @@ -217,6 +213,7 @@ void DivPlatformVRC6::tick() { } int DivPlatformVRC6::dispatch(DivCommand c) { + int CHIP_DIVIDER=(c.chan==2)?14:16; switch (c.cmd) { case DIV_CMD_NOTE_ON: if (c.chan!=2) { // pulse wave